よもぎのメモ帳

備忘録的な感じで技術的なことをストックしていきます。

CTFの勉強会の振り返り part1

策略本の勉強会 part1

f:id:y0m0g1:20180620165201p:plain
社畜ちゃん台詞メーカーより

この勉強会では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.outgdbで弄ることで、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限にやってます。

勉強会の名前をつけられるといいのかな

*1:筆者はgcc version 5.4.0 20160609 (Ubuntu 5.4.0-6ubuntu1~16.04.9), Ubuntu 16.0.4 64bit(WSL, Bash on Ubuntu on Windows) といった環境です