Djo (@shell_storm) has already published a write-up (english) on Nibbles' blog, as well as @hellman on Leet More's blog, and Agix (@Agixid) on shell-storm. Just like hashcalc1 or hashcalc2, there was no NX. However, ASLR was enabled. Djo and hellman both used a big nopsled + brute-force to circumvent ASLR, Agix used a nice ret2ret, while I chose ROP to mmap rwx. Good thing is that it would also have worked if NX had been effectively enabled.
Not familiar with ROP? Have a look at the references posted on this exploit writing tutorial by @corelanc0d3r.
Context
19 - Another small bug - 250 pts Description Category: pwnables This time, let's attack /opt/pctf/z2/exploitme. ssh z2_16@a5.amalgamated.biz # Q7044oQfwTHFIf8x92VtcQ75
Analysis
Reversing the binary tells you that:- it takes a number as first argument, converted using strtoul
- it reads input on stdin with fgets_unlocked, size being the previous number
- input is stored in a stack buffer
Let's try quickly with gdb:
gdb$ r 2048 < <(python -c 'from struct import pack; print "A"*532+pack("<I",0xdeadbeef)') AAAAA[...] Program received signal SIGSEGV, Segmentation fault. 0xdeadbeef in ?? ()Win!
But there was a check?
Well, you may argue it should not work because there is a check on size >= 512. However, if you read attentively, it only exits if log_error succeeds.log_error fails if it cannot open (or create since it's "a" mode) the log file. But hold on:
z2_16@a5:~$ ls -l /home/z2/logs/ ls: cannot access /home/z2/logs/: No such file or directoryNot sure if it was intended :) but it explains why the previous check on user-supplied size parameter does not stop the program.
It the file existed and was readable, then fopen call would not have failed. To make it fail, we could have used setrlimit with RLIMIT_NOFILE (or simply bash's ulimit -n) to restrict the maximum number of opened file descriptors. For the notice, it also explains why the binary is statically compiled (see just below). Indeed, if dynamically linked, the loader would fail loading libraries, and exploit writer surely did not want that. ;)
Exploitation
Interestingly, the binary is statically compiled:$ file exploitme exploitme: ELF 32-bit LSB executable, Intel 80386, version 1 (SYSV), statically linked, not strippedIt means that libc functions are present in the binary, like... mmap!
How good is that? Well, we can simply return to mmap asking for an rwx area, copy a shellcode in it, and return to it! Straightforward ROP, similar to my Shmoocon's CTF warm-up alternative solution (exploit).
I used a bunch of gadgets to copy the shellcode byte per byte with a wrapper since we were not limited in payload size. One could use alternatively use recv to directly receive a shellcode from stdin or else.
Full exploit here: colored syntax or plain .py
Just run it with cat to keep stdin opened:
$ { python exploit.py; cat; } | /opt/pctf/z2/exploitme 1300 AAAAA[...] id uid=2015(z2_16) gid=1001(z2users) egid=1003(z2key) groups=1001(z2users)ROP is fun!
Wow nice!
ReplyDeleteI think there also another way, to use int 0x80 gadget and do execve or smth
W00t !
ReplyDeleteNice sploit!
it is a masterpiece :)
Agix (@Agixid), has published another method for "Another Small Bug" (http://howto.shell-storm.org/files/howto-12-en.php) (ret2ret)
But, Stalker you're right, the ROP is very fun. good game :)
Thanks for the link, added! Indeed, nice ret2ret by Agix :)
ReplyDeleteAwesome +) We made it harder...
ReplyDelete