Monday, February 18, 2013

GITS'13 - back2skool Writeup

This past weekend I competed in GhostInTheShellcode Capture-The-Flag for the PwningYeti team. This is my write-up for the back2skool challenge. Due to time constraints and other responsibilities I was not able to finish the challenge before the CTF ended. These are my results running the executable on my own box. It should have worked on the actual service but computers are hard ;).

== Identifying The Vulnerability ==
The challenge consisted of a 32-bit ELF executable that listens on port 31337. First I connected to the service with netcat and was presented with a textual interface:
    __  ___      __  __   _____
   /  |/  /___ _/ /_/ /_ / ___/___  ______   __ v0.01
  / /|_/ / __ `/ __/ __ \\__ \/ _ \/ ___/ | / /
 / /  / / /_/ / /_/ / / /__/ /  __/ /   | |/ /
/_/  /_/\__,_/\__/_/ /_/____/\___/_/    |___/
===============================================
Welcome to MathServ! The one-stop shop for all your arithmetic needs.
This program was written by a team of fresh CS graduates using only the most
agile of spiraling waterfall development methods, so rest assured there are
no bugs here!

Your current workspace is comprised of a 10-element table initialized as:
{ 0, 1, 2, 3, 4, 5, 6, 7, 8, 9 }

Commands:
 read Read value from given index in table
 write Write value to given index in table
 func1 Change operation to addition
 func2 Change operation to multiplication
 math Perform math operation on table
 exit Quit and disconnect
From here I started to play around with the different commands, especially read and write. It didn't take very long to find the vulnerability:
read
Input position to read from:
1
Value at position 1: 1
read   
Input position to read from:
50
Value at position 50: 0
read
Input position to read from:
-1
Value at position -1: 0
write  
Input position to write to:
1
Input numeric value to write:
1
Value at position: 1: 1
write
Input position to write to:
50
Table index too large!
write
Input position to write to:
-1
Input numeric value to write:
5
Value at position -1: 5
We were told that we had a 10 element table but were able to read outside the bounds of the table and were also able to write outside the bounds of the table using negative numbers. At this point I guessed this was a signed comparison vulnerability. Further reverse engineering would confirm that this was in fact the result of a signed comparison when it should have been unsigned:
.text:080491F2                 lea     eax, [ebp+userData]
.text:080491F5                 mov     [esp], eax      ; nptr
.text:080491F8                 call    _atoi
.text:080491FD                 mov     dword ptr [ebp+index], eax
.text:08049200                 cmp     dword ptr [ebp+index], 9
.text:08049204                 jle     short loc_8049225
... snip ...
.text:08049266                 lea     eax, [ebp+userData]
.text:08049269                 mov     [esp], eax      ; nptr
.text:0804926C                 call    _atoi
.text:08049271                 mov     [ebp+value], eax
.text:08049274                 mov     eax, ds:(values_ptr - 804BF54h)[ebx]
.text:0804927A                 mov     edx, dword ptr [ebp+index]
.text:0804927D                 mov     ecx, [ebp+value]
.text:08049280                 mov     [eax+edx*4], ecx
Using this vulnerability we can write data of our choice to a nearly arbitrary address.

== Getting initial execution ==
Now that I had identified the vulnerability I just needed to find something to overwrite to get code execution. I did find it interesting that this executable was build with position independent code (-fPIC) but was not built as a position independent executable (-fPIE). You can see from the above assembly that IDA Pro shows the full address for each instruction instead of the offset, which it does for position independent executables. You could also check this using any number of scripts available on the internet. For us this means that the binary will not take advantage of ASLR. I assumed that the remote host this service was running on supported DEP.

Initially I tried overwriting pointers in the Global Offset Table (.got) which is listed as read/write in the section header but I found that after the executable was loaded the permissions were changed to read-only.

After a little more playing around with the executable I found that the user could choose between two functions that would perform a mathematical operation on the data (addition or multiplication). Then whenever the user issued the math command it would execute the chosen function. Looking at the math_doit function I found that this was implemented using a function pointer stored in global data. The only check on this value was to ensure it wasn't zero before calling the specified address. This means that we only need to overwrite the function pointer with an address of our choosing and send the math command to gain code execution.

Looking at the disassembly above you can see that the user specified index must be less than 9 and is multiplied by 4 to then added to the base address of the values array. To overwrite the math pointer field we need to supply an index which is negative and when multiplied by 4 will overflow a 32-bit register, resulting in our chosen offset from the values array. This is the calculation we will use to determine the index to read/write:
index = ((address - 0x804c040) / 4) | 0x80000000

Lets try it (index = -2147483634; value = 1094795585):
(gdb) c
Continuing.

Program received signal SIGSEGV, Segmentation fault.
0x41414141 in ?? ()
(gdb) i r
eax            0x804c040 134529088
ecx            0xffb7b400 -4738048
edx            0x41414141 1094795585
ebx            0x804bf54 134528852
esp            0xffb7b3bc 0xffb7b3bc
ebp            0xffb7b3e8 0xffb7b3e8
esi            0xffb7b456 -4737962
edi            0x8049ae0 134519520
eip            0x41414141 0x41414141
eflags         0x10206 [ PF IF RF ]
cs             0x23 35
ss             0x2b 43
ds             0x2b 43
es             0x2b 43
fs             0x0 0
gs             0x63 99

== Defeating DEP ==
At this point we can direct execution to any address we choose but we still need to defeat DEP. After spending an hour looking through the gadgets in the binary it was suggested to me that I use gadgets from libc. But I likely didn't have the same libc that was on the target, so they suggested that I dump libc from memory. This seemed like an interesting idea and was something that I had never done before so I wrote some Python to dump the libc .text section. I started by reading the address of the send function from the .got table and then read the first 4 bytes from each page, decrementing the page address until I found the first page with the ELF magic value (\x7fELF). Then it read 0x800 bytes before disconnecting and re-connecting to read another 0x800 bytes, until the entire .text section was dumped to a file. I was never able to dump the entire remote libc because my connection kept getting dropped.

Once I had the bytes from libc I uploaded them to ropshell.com specifying "RAW binary" (otherwise ropshell.com will try to analyze it as a whole ELF file and that will fail). Ropshell.com found 17983 gadgets, one of which was "xchg esp, eax; ret" which is perfect since when the math function pointer is called eax points to the beginning of the values array.

At this point we could try and find all the rop gadgets that would allow us to read the key from disk or we could just call mprotect to change the permissions of the .data section to read/write/execute. I choose to call mprotect. I found the address of mprotect by looking at the mprotect function in my local libc (because it had symbols) and then I used IDA Pro to search for the same bytes in the dumped libc data. This gave me the offset for mprotect in the remote libc. Then I wrote the following rop into the values array:
0x4d346b40 # mprotect (for my local libc)
0x804c000 # return
0x804c000 # addr
0x1   # length
0x07  # protections - rwx

== Grab The Goods And Run ==
At this point all you need to do is write some moderately small shellcode (there are only 64 bytes from the start of the .data section to the beginning of the values array) to the beginning of the .data section (0x804c000). I wrote some simple shellcode that called open, read, and write back to the open socket. You could guess the socket file descriptor or you could read it from memory (it's the 4 bytes before the math function pointer).

== Wrapping It Up ==
Like I mentioned before, I didn't actually solve the challenge during the CTF so I don't have the key. But I believe that everything would have worked on the remote service since it worked on an unmodified binary running on Fedora 15.

Thanks for taking the time to read this long blog post. Now get out there and solve some CTF challenges!