ゾンビ狩りクラブ

Linux, Server, Network, Security 関連などをゆるーくテキトーに載せてます

[x64] GDBを用いたリバースエンジニアリング 入門

Index

1. はじめに

今回は、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

2. 使ってみる

以下のプログラムを使用して例を示す。

// 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

書籍

実践 デバッグ技法 ―GDB、DDD、Eclipseによるデバッギング

実践 デバッグ技法 ―GDB、DDD、Eclipseによるデバッギング

GDBハンドブック

GDBハンドブック