== 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
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
.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
== 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!