CTFの勉強会の振り返り part1
策略本の勉強会 part1
この勉強会ではHacking:美しき策謀 第2版 ――脆弱性攻撃の理論と実際を参考書として、輪講(のようなもの)をしていきます
オライリー・ジャパンの公式サイトから配布されているサンプルコードを用いてやっていきました。
gdbと触れ合う
初回(2018/06/13)ではgdbとふれあいました
コンパイル
0x200\firstprog.c
をコンパイル
$gcc -O0 -g3 firstprog.c
-O0
で最適化なしでのコンパイル->逆アセンブルしたときにわかりやすい形に。
-g3
でデバッガレベルを3にしてデバッグしやすいようにしました。
firstprog.c
の内容は次のとおりです
#include <stdio.h> int main() { int i; for (i = 0; i < 10; i++) // 10回繰り返す { printf("Hello, world!\n"); // 文字列を出力する } return 0; // プログラムが問題なく終了したことをOSに知らせる }
簡単なプログラムですね。コンパイルして出力されるa.out
をgdbで弄ることで、gdbに慣れていきました
gdbの起動
まず、gdbはデフォルトでAT&T記法なのでintel記法にするべく、.gdbinit
をいじる
$echo "set disassembly-flavor intel" > ~/.gdbinit $cat ~/.gdbinit set disassembly-flavor intel
次にgdbの起動
$gdb -q a.out Reading symbols from a.out...done. (gdb)
-q
オプションで最初に表示される文章がなくなるんですね(知らなかった)。
とりあえず走らせてみる
(gdb) r Starting program: /mnt/(略)/Hacking/0x200/a.out Hello, world! Hello, world! Hello, world! Hello, world! Hello, world! Hello, world! Hello, world! Hello, world! Hello, world! Hello, world! [Inferior 1 (process 113) exited normally]
予想通りHello, world!を10回表示するプログラムでした。
逆アセンブルとブレークポイントの設定
次に、main関数を逆アセンブルしてみる
(gdb) disass main Dump of assembler code for function main: 0x0000000000400526 <+0>: push rbp 0x0000000000400527 <+1>: mov rbp,rsp 0x000000000040052a <+4>: sub rsp,0x10 0x000000000040052e <+8>: mov DWORD PTR [rbp-0x4],0x0 0x0000000000400535 <+15>: jmp 0x400545 <main+31> 0x0000000000400537 <+17>: mov edi,0x4005e4 0x000000000040053c <+22>: call 0x400400 <puts@plt> 0x0000000000400541 <+27>: add DWORD PTR [rbp-0x4],0x1 0x0000000000400545 <+31>: cmp DWORD PTR [rbp-0x4],0x9 0x0000000000400549 <+35>: jle 0x400537 <main+17> 0x000000000040054b <+37>: mov eax,0x0 0x0000000000400550 <+42>: leave 0x0000000000400551 <+43>: ret End of assembler dump.
なかでも、
mov edi,0x4005e4 call 0x400400 <puts@plt> add DWORD PTR [rbp-0x4],0x1 cmp DWORD PTR [rbp-0x4],0x9 jle 0x400537 <main+17>
この部分でfor文が回っているのがわかると思います。
- つまり、mov edi,0x4005e4
で引数に"Hello, world!"へのポインタをセットしてputs関数を呼び出す
- add DWORD PTR [rbp-0x4],0x1
でループ変数iに1を足している
- cmp DWORD PTR [rbp-0x4],0x9
でループ変数と9を比較
- もしも9以下だったら、jle 0x400537 <main+17>
でループを回す。jleはJump if Less than or Equal toの意味
最後の3行はreturn 0;
のセット(mov eax, 0x0
)、スタックの開放(leave
)、関数から去る(ret
)みたいです。
ちなみに逆コンパイルの結果はgccのバージョンにもよるみたいです*1
(gdb) l 1 #include <stdio.h> 2 3 int main() 4 { 5 int i; 6 for (i = 0; i < 10; i++) // 10回繰り返す 7 { 8 printf("Hello, world!\n"); // 文字列を出力する 9 } 10 return 0; // プログラムが問題なく終了したことをOSに知らせる (gdb) 11 }
list
あるいはl
でもとのソースコードが表示されます。-g3
オプションつけたからでしょうか。
いよいよブレークポイントを設定します。 listでソースコードが表示されているように、実行ファイル内にソースコードの情報が含まれているので以下のような設定が可能です。
(gdb) b 8 Breakpoint 1 at 0x400537: file firstprog.c, line 8. (gdb) r Starting program: hoge/0x200/a.out Breakpoint 1, main () at firstprog.c:8 8 printf("Hello, world!\n"); // 文字列を出力する (gdb) disass main Dump of assembler code for function main: 0x0000000000400526 <+0>: push rbp 0x0000000000400527 <+1>: mov rbp,rsp 0x000000000040052a <+4>: sub rsp,0x10 0x000000000040052e <+8>: mov DWORD PTR [rbp-0x4],0x0 0x0000000000400535 <+15>: jmp 0x400545 <main+31> => 0x0000000000400537 <+17>: mov edi,0x4005e4 0x000000000040053c <+22>: call 0x400400 <puts@plt> 0x0000000000400541 <+27>: add DWORD PTR [rbp-0x4],0x1 0x0000000000400545 <+31>: cmp DWORD PTR [rbp-0x4],0x9 0x0000000000400549 <+35>: jle 0x400537 <main+17> 0x000000000040054b <+37>: mov eax,0x0 0x0000000000400550 <+42>: leave 0x0000000000400551 <+43>: ret End of assembler dump.
ブレークポイントが設定されていることと、そこで止まったことがわかりました。 次の命令に移ってみましょう
(gdb) ni 0x000000000040053c 8 printf("Hello, world!\n"); // 文字列を出力する (gdb) disass main Dump of assembler code for function main: 0x0000000000400526 <+0>: push rbp 0x0000000000400527 <+1>: mov rbp,rsp 0x000000000040052a <+4>: sub rsp,0x10 0x000000000040052e <+8>: mov DWORD PTR [rbp-0x4],0x0 0x0000000000400535 <+15>: jmp 0x400545 <main+31> 0x0000000000400537 <+17>: mov edi,0x4005e4 => 0x000000000040053c <+22>: call 0x400400 <puts@plt> 0x0000000000400541 <+27>: add DWORD PTR [rbp-0x4],0x1 0x0000000000400545 <+31>: cmp DWORD PTR [rbp-0x4],0x9 0x0000000000400549 <+35>: jle 0x400537 <main+17> 0x000000000040054b <+37>: mov eax,0x0 0x0000000000400550 <+42>: leave 0x0000000000400551 <+43>: ret End of assembler dump.
現在の位置が一つ次の行に移りました
ni
はnext Indicatorの略みたいです
レジスタを覗く
(gdb) i r edi edi 0x4005e4 4195812
さて、文字列へのポインタが入っているか確認すると……ちゃんとmov
で代入した値が入ってますね。
i r
はinfo registerの略みたいです。引数なしだと、すべてのレジスタの情報がでます。
さてさて、ちゃんと文字は入っているでしょうか
(gdb) x /s $edi 0x4005e4: "Hello, world!"
入っていました!x
はexamineの略だそうです
examineコマンドと触れ合う
xのあとにオプションを設定することでいろいろな形式で表せます。 オプションとしては - いくつ表示するか(一つのときには省略可) - どのように表示するか - 16進数のときにはどのブロックを単位として読み出すか
リトルインディアン方式なので、ブロック読み出しで人の目には変わってきますね。
以下は遊んでみた結果
(gdb) x /s $edi 0x4005e4: "Hello, world!" (gdb) x /b $edi 0x4005e4: "Hello, world!" (gdb) x /x $edi 0x4005e4: 0x48 (gdb) x /h $edi 0x4005e4: 0x6548 (gdb) x /w $edi 0x4005e4: 0x6c6c6548 (gdb) x /g $edi 0x4005e4: 0x77202c6f6c6c6548 (gdb) x /xb $edi 0x4005e4: 0x48 (gdb) x /2xb $edi 0x4005e4: 0x48 0x65 (gdb) x /3xb $edi 0x4005e4: 0x48 0x65 0x6c (gdb) x /3xh $edi 0x4005e4: 0x6548 0x6c6c 0x2c6f (gdb) x /3xw $edi 0x4005e4: 0x6c6c6548 0x77202c6f 0x646c726f (gdb) x /u $edi 0x4005e4: 1819043144 (gdb) x /o $edi 0x4005e4: 015433062510 (gdb) x /t $edi 0x4005e4: 01101100011011000110010101001000 (gdb) x /10c $edi 0x4005e4: 72 'H' 101 'e' 108 'l' 108 'l' 111 'o' 44 ',' 32 ' ' 119 'w' 0x4005ec: 111 'o' 114 'r'
まとめ
覚えておきたいコマンドはこんな感じ(あくまでも自分用)
コマンド | 説明 |
---|---|
r |
実行 |
disass 関数名 |
関数を逆アセンブル |
l |
ソースコード表示 |
ni |
次の命令 |
i r レジスタ名 |
レジスタの値の表示 |
x /s $レジスタ名 |
レジスタが示すポインタの文字列表示 |
x /xb $レジスタ名 |
1バイト分16進数表示 |
x /2xh $レジスタ名 |
2バイト分16進数表示×2 |
x /3xw $レジスタ名 |
4バイト分16進数表示×3 |
x /4xg $レジスタ名 |
8バイト分16進数表示×4 |
x /10c $レジスタ名 |
char型で10文字分表示 |
おわりに
gdbを初めてちゃんとやった気がします。今回講師役(?)だったまさお君に感謝 :pray:
ぼちぼち勉強会をやっているので、気になる学科民いたらぜひー水曜の4限にやってます。
勉強会の名前をつけられるといいのかな