はじめに
今回は、gdbを使用してc言語で書かれたプログラムを解析してみる。
gdbとはデバッガあり、プログラムを任意の位置で停止させたり、値の書き換えなどをすることができる。
基本的なアセンブリに関しては、以下の記事を参考にしたい。
x64 アセンブリ 入門
gdbのコマンドに関しては、以下の記事を参考にしたい。
gdb 主なコマンド一覧
また、今回の実行環境は以下にである。
% uname -a
Linux ubuntu 4.10.0-37-generic #41~16.04.1-Ubuntu SMP Fri Oct 6 22:42:59 UTC 2017 x86_64 x86_64 x86_64 GNU/Linux
% lsb_release -a
No LSB modules are available.
Distributor ID: Ubuntu
Description: Ubuntu 16.04.3 LTS
Release: 16.04
Codename: xenial
使ってみる
以下のプログラムを使用して例を示す。
// test.c
#include <stdio.h>
int main() {
printf("Hello world!\n");
return 0;
}
コンパイル&実行してみる
% gcc test.c
% ./a.out
Hello world!
期待通り ”Hello world!” が出力された。
このプログラム例にgdbを使用してみる。
以下のコマンドを実行しgdbを開始する。
% gdb ./a.out
GNU gdb (Ubuntu 7.11-0ubuntu1) 7.11
Copyright (C) 2016 Free Software Foundation, Inc.
...
Reading symbols from ./a.out...(no debugging symbols found)...done.
(gdb)
デフォルトではgdbを起動したときにバナーが出てしまい多少邪魔である。
バナーを非表示にするには q オプションを使用すればよい。
% gdb -q ./a.out
Reading symbols from ./a.out...(no debugging symbols found)...done.
(gdb)
まず最初にdisassembleコマンドを使用してmain関数を逆アセンブルする。
このコマンドはdisasと省略することができる。
(gdb) disassemble main
Dump of assembler code for function main:
0x0000000000400526 <+0>: push %rbp
0x0000000000400527 <+1>: mov %rsp,%rbp
0x000000000040052a <+4>: mov $0x4005c4,%edi
0x000000000040052f <+9>: callq 0x400400 <puts@plt>
0x0000000000400534 <+14>: mov $0x0,%eax
0x0000000000400539 <+19>: pop %rbp
0x000000000040053a <+20>: retq
End of assembler dump.
アセンブリの記法には、Intel記法とAT&T記法があるが、gdbはデフォルトではAT&T記法である。
Intel記法にするには、”set disassembly-flavor” コマンドを実行すれば良い。
(gdb) set disassembly-flavor intel
(gdb) disas main
Dump of assembler code for function main:
0x0000000000400526 <+0>: push rbp
0x0000000000400527 <+1>: mov rbp,rsp
0x000000000040052a <+4>: mov edi,0x4005c4
0x000000000040052f <+9>: call 0x400400 <puts@plt>
0x0000000000400534 <+14>: mov eax,0x0
0x0000000000400539 <+19>: pop rbp
0x000000000040053a <+20>: ret
End of assembler dump.
AT&T記法に戻したい場合は、上記の ”intel” のところを ”att” にすればよい。
また、これでは gdb を終了したときに設定が消えてしまう。
永続的にIntel記法にするには、以下のように ".gdbinit" に設定を書き込む必要がある。
% echo "set disassembly-flavor intel" > ~/.gdbinit
デバッガの最大の利点は、プログラムを任意の場所で停止させられることだが、停止させるにはブレークポイントというものを設定する必要がある。
このブレークポイントは、breakコマンドで設定できる。
(gdb) break main
Breakpoint 1 at 0x40052a
breakコマンドは、引数に関数名やアドレスを指定することで、その場所にブレークポイントを設定することができる。
上記のコマンドは以下を実行するのと同じである。
(gdb) break *0x40052a
設定したブレークポイントの一覧を表示するには、以下のコマンドを入力する。
(gdb) info breakpoints
Num Type Disp Enb Address What
1 breakpoint keep y 0x000000000040052a <main+4>
ブレークポイントを設定したので、プログラムを実行してみる。
(gdb) run
Starting program: /home/r00t/work/a.out
Breakpoint 1, 0x000000000040052a in main ()
(gdb) disas main
Dump of assembler code for function main:
0x0000000000400526 <+0>: push rbp
0x0000000000400527 <+1>: mov rbp,rsp
=> 0x000000000040052a <+4>: mov edi,0x4005c4
0x000000000040052f <+9>: call 0x400400 <puts@plt>
0x0000000000400534 <+14>: mov eax,0x0
0x0000000000400539 <+19>: pop rbp
0x000000000040053a <+20>: ret
End of assembler dump.
ここで disassemble コマンドを実行すると、
次に実行される命令の場所に ⇒ が表示される。
命令を一つ実行するには nexti コマンドを使用する。
(gdb) nexti
0x000000000040052f in main ()
(gdb) disas main
Dump of assembler code for function main:
0x0000000000400526 <+0>: push rbp
0x0000000000400527 <+1>: mov rbp,rsp
0x000000000040052a <+4>: mov edi,0x4005c4
=> 0x000000000040052f <+9>: call 0x400400 <puts@plt>
0x0000000000400534 <+14>: mov eax,0x0
0x0000000000400539 <+19>: pop rbp
0x000000000040053a <+20>: ret
End of assembler dump.
今実行された命令は、”mov edi,0x4005c4” であり、次に実行される命令は ”call 0x400400 puts@plt” である。
今実行された命令は、edi レジスタに 0x4005c4 というアドレスを格納するという意味である。
では、0x4005c4というアドレスには何が入っているのだろうか。
gdbを使用して調べてみる。
アドレスの中身を表示するにはexamineコマンドが使用できる。
(gdb) x/x $edi
0x4005c4: 0x6c6c6548
先ほどは4バイトの16進数が表示されたが、以下では8バイトの16進数を表示させている。
(gdb) x/gx $edi
0x4005c4: 0x6f77206f6c6c6548
次に4バイトの16進数を3セット表示してみる。
(gdb) x/3wx $edi
0x4005c4: 0x6c6c6548 0x6f77206f 0x21646c72
最後に1バイトの16進数を12セット表示してみる。
(gdb) x/12bx $edi
0x4005c4: 0x48 0x65 0x6c 0x6c 0x6f 0x20 0x77 0x6f
0x4005cc: 0x72 0x6c 0x64 0x21
表示するバイト数によって値が入れ替わっているが、これはバイトオーダーがリトルエンディアンであるからである。
また、上記の値は、すべてascii文字の印字可能な範囲にある。
asciiコード表を確認するには、”man ascii” コマンドを打てばよい。
% man ascii | cat
ASCII(7) Linux Programmer's Manual ASCII(7)
...
Oct Dec Hex Char Oct Dec Hex Char
────────────────────────────────────────────────────────────────────────
000 0 00 NUL '\0' (null character) 100 64 40 @
001 1 01 SOH (start of heading) 101 65 41 A
002 2 02 STX (start of text) 102 66 42 B
003 3 03 ETX (end of text) 103 67 43 C
004 4 04 EOT (end of transmission) 104 68 44 D
005 5 05 ENQ (enquiry) 105 69 45 E
006 6 06 ACK (acknowledge) 106 70 46 F
007 7 07 BEL '\a' (bell) 107 71 47 G
010 8 08 BS '\b' (backspace) 110 72 48 H
011 9 09 HT '\t' (horizontal tab) 111 73 49 I
012 10 0A LF '\n' (new line) 112 74 4A J
013 11 0B VT '\v' (vertical tab) 113 75 4B K
014 12 0C FF '\f' (form feed) 114 76 4C L
015 13 0D CR '\r' (carriage ret) 115 77 4D M
016 14 0E SO (shift out) 116 78 4E N
017 15 0F SI (shift in) 117 79 4F O
020 16 10 DLE (data link escape) 120 80 50 P
021 17 11 DC1 (device control 1) 121 81 51 Q
022 18 12 DC2 (device control 2) 122 82 52 R
023 19 13 DC3 (device control 3) 123 83 53 S
024 20 14 DC4 (device control 4) 124 84 54 T
025 21 15 NAK (negative ack.) 125 85 55 U
026 22 16 SYN (synchronous idle) 126 86 56 V
027 23 17 ETB (end of trans. blk) 127 87 57 W
030 24 18 CAN (cancel) 130 88 58 X
031 25 19 EM (end of medium) 131 89 59 Y
032 26 1A SUB (substitute) 132 90 5A Z
033 27 1B ESC (escape) 133 91 5B [
034 28 1C FS (file separator) 134 92 5C \ '\\'
035 29 1D GS (group separator) 135 93 5D ]
036 30 1E RS (record separator) 136 94 5E ^
037 31 1F US (unit separator) 137 95 5F _
040 32 20 SPACE 140 96 60 `
041 33 21 ! 141 97 61 a
042 34 22 " 142 98 62 b
043 35 23 # 143 99 63 c
044 36 24 $ 144 100 64 d
045 37 25 % 145 101 65 e
046 38 26 & 146 102 66 f
047 39 27 ' 147 103 67 g
050 40 28 ( 150 104 68 h
051 41 29 ) 151 105 69 i
052 42 2A * 152 106 6A j
053 43 2B + 153 107 6B k
054 44 2C , 154 108 6C l
055 45 2D - 155 109 6D m
056 46 2E . 156 110 6E n
057 47 2F / 157 111 6F o
060 48 30 0 160 112 70 p
061 49 31 1 161 113 71 q
062 50 32 2 162 114 72 r
063 51 33 3 163 115 73 s
064 52 34 4 164 116 74 t
065 53 35 5 165 117 75 u
066 54 36 6 166 118 76 v
067 55 37 7 167 119 77 w
070 56 38 8 170 120 78 x
071 57 39 9 171 121 79 y
072 58 3A : 172 122 7A z
073 59 3B ; 173 123 7B {
074 60 3C < 174 124 7C |
075 61 3D = 175 125 7D }
076 62 3E > 176 126 7E ~
077 63 3F ? 177 127 7F DEL
この表を元に先ほど表示された値、”0x48,0x65,0x6c”を見ていくと、”Hel”となることがわかる。
そうである。これをすべて見ていくと ”Hello world!” になる。
しかし、gdbには便利な機能があり、examineコマンドを使用すれば、このようにいちいちasciiコード表と対応する文字を探さなくてもよい。
(gdb) x/s $edi
0x4005c4: "Hello world!"
x64(x86-64)では、関数の引数は第一引数から以下のレジスタに保存し、関数をcallすることで引数を渡しているのでこのようになる。
rdi, rsi, rdx, rcx, r8, r9
プログラムを続行すると ”Hello world!” が出力され終了する。
(gdb) continue
Continuing.
Hello world!
[Inferior 1 (process 5289) exited normally]
(gdb) quit
0件のコメント