Malleus CTF Pwnを読んだ
前々からCTF(特にpwn)に興味があったので、Malleus CTF Pwnを読みました。
ページ数もそこまで多くないしさらっと読めるかなと思って買ったのですが、CTFやpwnに関する事前知識ゼロだったので、結局時間をかけてじっくり読むことになりました。その分得るものがたくさんあって、読み応えがあってよかったかなと。あとやっぱりスタックやらmallocやらその辺の仕組みを知っていくのは楽しい。
メモした内容を忘れないように残しておきます。
(最近商業版?も出たらしい)
解題pwnable セキュリティコンテストに挑戦しよう! (技術の泉シリーズ(NextPublishing)) | 草野 一彦 | 工学 | Kindleストア | Amazon
第1章 準備
ツールの説明とか。
使っているPCにそもそもDockerを入れてなかったので入れた。
https://docs.docker.com/engine/install/ubuntu/
第2章 login1, 第3章 login2
スタックバッファオーバーフローについて。このへんはすんなり読めた。
第4章 login3
このあたりから理解に時間がかかるようになってきた。
One-gadget RCEの存在を初めて知った。本来どういう用途で使われているか知らないので、狙われるだけでは?と思ったけど何のための機能なんだろう。
関数のアドレスを表示してランダム化されていること (ASLR) を確認するところで、以下のコードで関数のアドレスを確認していた。
printf("printf: %p\n", printf);
ふとアセンブリはどうなっているのだろうと気になって確認したところ、以下のようになっていた。これだと printf
の GOT のスロットのアドレス (0x403fe8) が出力されるのでは?と思ったが、よくよく見ると mov
が使われているので、ちゃんとGOTのスロットのアドレスに格納されている値、つまり printf
のアドレスが出力される。
mov rax,QWORD PTR [rip+0x2eb7] # 403fe8 <printf@GLIBC_2.2.5>
ちなみに以下だと lea
が使われる。コンパイラよくできてる。
int a; printf("%p\n", &a);
あとはそもそもPythonの文字列とかバイトオブジェクトとかがわかってなかったので、以下を読んだりした。
第5章 rot13
Format String Attack
最初攻撃スクリプトが何をしているか全然わからなかったけど、丁寧に追っていくことでなんとか理解。
printfで書き込み (%n
) もできるというのは初耳。
第6章 birdcage
ここからヒープ。
第7章 strstr
mallocががっつりでてきた。ちょっとついていけなくなってしまったのでmalloc動画をまずは見ることにした。
ヒープわからんってなったので例のmalloc動画を履修した
— penguing27 (@penguing27) 2020年12月30日
これもわかりやすい。ここまで詳細な文書をまとめることに脱帽。
malloc(3)のメモリ管理構造
あとはソースも適当に見ながら読み進めた。
libc-2.27.so内の main_arena
のアドレスを逆アセンブル結果を見て特定するところがよくわからなかった。アセンブリ読めればわかるということかな。
第8章 strstrstr
本と同じようにPwngdbのparseheapとかheapinfoとかが使いたくていろいろ奮闘した。
まずは手元の環境 (Ubuntu 20.04.1 LTS) で配布されたlibc-2.27.soを使うようにpatchelfで実行ファイルにパッチを当ててgdbを動かしてみた。シェルは取れたけど、libc6-dbgがないよと怒られてparseheapはできなかった。
gdb-peda$ parseheap Can not get libc version Cannot get main_arena's symbol address. Make sure you install libc debug file (libc6-dbg & libc6-dbg:i386 for debian package). can't find heap info gdb-peda$ heapinfo Can not get libc version Cannot get main_arena's symbol address. Make sure you install libc debug file (libc6-dbg & libc6-dbg:i386 for debian package). Can't find heap info
libc6-dbgの存在を知らなかったけど、調べてみるとlibcのデバッグ情報をlibcとは別出しで持っているパッケージとのこと。これがないとparseheapとかはできないらしい。
https://github.com/scwuaptx/Pwngdb#heapinfo
ということで、libc-2.27.soのlibc6-dbgも入れられるように、Ubuntu 18.04で試してみることにした。apt install
でlibc6, libc-dbgを入れていざ試してみると、今度はシェルが取れない。。たぶんすでにlibc6にパッチがあたっているからだと思われる。
$ apt list -a libc6 Listing... Done libc6/bionic-updates,now 2.27-3ubuntu1.4 amd64 [installed] libc6/bionic-security 2.27-3ubuntu1.2 amd64 libc6/bionic 2.27-3ubuntu1 amd64
今度は一番古いバージョンのlibc6にダウングレードしてみたところ、配布されているlibc-2.27.soと全く同じものがインストールされた。
$ sudo apt install libc6=2.27-3ubuntu1 libc-dev-bin=2.27-3ubuntu1 libc6-dev=2.27-3ubuntu1 libc6-dbg=2.27-3ubuntu1
$ cmp /path/to/libc-2.27.so /lib/x86_64-linux-gnu/libc-2.27.so $
配布されたソースコードを -g
付きでコンパイル、止めたいところでbreakしてparseheapすることで、ようやく本と同じ結果が得られた。
第9章 freefree
House of Orange
ここもparseheap、heapinfoでヒープの状態を確認しながら攻撃スクリプトの挙動を追っていった。
第10章 freefree++
File stream oriented programming
第11章 writefree
House of Corrosion
fastbinにチャンクを格納する際のサイズチェックに引っかからないようにするために、各サイズで一旦確保し解放することでヒープ中の次のチャンクのsizeにあたる位置にサイズを書き込んでおく、というところがよくわからず小一時間悩んだ。なぜ一旦確保し解放することで次のチャンクのsizeに書き込めるのか。
元記事だとこのあたり。
This is achieved by allocating then freeing a chunk so that the top chunk size is written to that location.
けど↑にも書いてあるとおり、次のチャンク = top ということに気づいて納得。一旦確保することでtopの位置が変わり、それに伴いtopのサイズも変わるから、正常なサイズが書き込まれるということ。