Help us understand the problem. What is going on with this article?

gdb超入門

More than 1 year has passed since last update.

gdbとは

実行可能ファイル(バイナリ)を動的に(実際に動かして)解析するツールです。
実行可能ファイルの中身は機械語の羅列になっており、それらを一命令ずつステップ実行していくことができます。

環境

Ubuntu 16.04 LTS

とりあえずインストール

$ sudo apt update -y
$ sudo apt install gdb

対象バイナリを用意。

$ vim test.c
test.c
#include <stdio.h>

int main(void) {
    int hoge = 10;
    char fuga[] = "Hello, gdb!";
    printf("%s\n", fuga);
}
$ make test
$ ./test
Hello, gdb!

gdbを使ってみる

$ gdb test
GNU gdb (Ubuntu 7.11.1-0ubuntu1~16.5) 7.11.1
Copyright (C) 2016 Free Software Foundation, Inc.
License GPLv3+: GNU GPL version 3 or later <http://gnu.org/licenses/gpl.html>
This is free software: you are free to change and redistribute it.
There is NO WARRANTY, to the extent permitted by law.  Type "show copying"
and "show warranty" for details.
This GDB was configured as "x86_64-linux-gnu".
Type "show configuration" for configuration details.
For bug reporting instructions, please see:
<http://www.gnu.org/software/gdb/bugs/>.
Find the GDB manual and other documentation resources online at:
<http://www.gnu.org/software/gdb/documentation/>.
For help, type "help".
Type "apropos word" to search for commands related to "word"...
Reading symbols from test...(no debugging symbols found)...done.
(gdb)

gdbプロンプトに入りました。以降はgdbプロンプトでのコマンドになります。

まずは、main関数の中身を見てみましょう。

(gdb) disass main
Dump of assembler code for function main:
   0x0000000000400596 <+0>:     push   %rbp
   0x0000000000400597 <+1>:     mov    %rsp,%rbp
   0x000000000040059a <+4>:     sub    $0x30,%rsp
   0x000000000040059e <+8>:     mov    %fs:0x28,%rax
   0x00000000004005a7 <+17>:    mov    %rax,-0x8(%rbp)
   0x00000000004005ab <+21>:    xor    %eax,%eax
   0x00000000004005ad <+23>:    movl   $0xa,-0x24(%rbp)
   0x00000000004005b4 <+30>:    movabs $0x67202c6f6c6c6548,%rax
   0x00000000004005be <+40>:    mov    %rax,-0x20(%rbp)
   0x00000000004005c2 <+44>:    movl   $0x216264,-0x18(%rbp)
   0x00000000004005c9 <+51>:    lea    -0x20(%rbp),%rax
   0x00000000004005cd <+55>:    mov    %rax,%rdi
   0x00000000004005d0 <+58>:    callq  0x400460 <puts@plt>
   0x00000000004005d5 <+63>:    mov    $0x0,%eax
   0x00000000004005da <+68>:    mov    -0x8(%rbp),%rdx
   0x00000000004005de <+72>:    xor    %fs:0x28,%rdx
   0x00000000004005e7 <+81>:    je     0x4005ee <main+88>
   0x00000000004005e9 <+83>:    callq  0x400470 <__stack_chk_fail@plt>
   0x00000000004005ee <+88>:    leaveq
   0x00000000004005ef <+89>:    retq
End of assembler dump.

startコマンドでプログラムを開始します。

(gdb) start
Temporary breakpoint 1 at 0x40059a
Starting program: /home/miyagaw61/tmp/test

Temporary breakpoint 1, 0x000000000040059a in main ()

main関数内のどこかのbreakponitで止まりました(詳細は省きますが、ここではエントリーポイントと呼ばれている部分で止まっています)。
breakpointというのはプログラムの実行を止めるアドレスのことです。
次にCPUが実行するであろう命令を見てみましょう。

(gdb) x/5i $rip
=> 0x40059a <main+4>:   sub    $0x30,%rsp
   0x40059e <main+8>:   mov    %fs:0x28,%rax
   0x4005a7 <main+17>:  mov    %rax,-0x8(%rbp)
   0x4005ab <main+21>:  xor    %eax,%eax
   0x4005ad <main+23>:  movl   $0xa,-0x24(%rbp)

xというのが、メモリの中を表示するコマンドで、その後スラッシュで区切り、「表示する個数」「表示するフォーマット」を指定します。そして、引数に開始アドレスを指定します。
ここでは、RIPレジスタという、プログラムカウンタの役割をしているレジスタに格納されているアドレスを開始アドレスとして、i(instructionの略で、命令を表すフォーマット指定子)フォーマットで5つ分表示します。

ここで表示されているのが、アセンブリ言語です。機械語と一対一になっています。
アセンブリには表記形式がいくつあり、宗教上の理由で私はintel表記しか読めないため、intel表記に変更します。

(gdb) set disassembly-flavor intel
(gdb) x/5i $rip
=> 0x40059a <main+4>:   sub    rsp,0x30
   0x40059e <main+8>:   mov    rax,QWORD PTR fs:0x28
   0x4005a7 <main+17>:  mov    QWORD PTR [rbp-0x8],rax
   0x4005ab <main+21>:  xor    eax,eax
   0x4005ad <main+23>:  mov    DWORD PTR [rbp-0x24],0xa

sub rsp,0x30であれば, rspレジスタを0x30だけ減算します。
アセンブリ言語は一つ一つの命令は非常に簡単です。
わからない命令があれば、このサイトで調べてみましょう。
mov命令の意味が知りたければ「x86 mov」とかでggるとすぐにこのサイトが出てくるはずです。

一つ命令を進めてみましょう。

(gdb) nexti
0x000000000040059e in main ()
(gdb) x/5i $rip
=> 0x40059e <main+8>:   mov    rax,QWORD PTR fs:0x28
   0x4005a7 <main+17>:  mov    QWORD PTR [rbp-0x8],rax
   0x4005ab <main+21>:  xor    eax,eax
   0x4005ad <main+23>:  mov    DWORD PTR [rbp-0x24],0xa
   0x4005b4 <main+30>:  movabs rax,0x67202c6f6c6c6548

一つ進んだことがわかると思います。

20個の命令を表示してみます。

(gdb) x/20i $rip
=> 0x40059e <main+8>:   mov    rax,QWORD PTR fs:0x28
   0x4005a7 <main+17>:  mov    QWORD PTR [rbp-0x8],rax
   0x4005ab <main+21>:  xor    eax,eax
   0x4005ad <main+23>:  mov    DWORD PTR [rbp-0x24],0xa
   0x4005b4 <main+30>:  movabs rax,0x67202c6f6c6c6548
   0x4005be <main+40>:  mov    QWORD PTR [rbp-0x20],rax
   0x4005c2 <main+44>:  mov    DWORD PTR [rbp-0x18],0x216264
   0x4005c9 <main+51>:  lea    rax,[rbp-0x20]
   0x4005cd <main+55>:  mov    rdi,rax
   0x4005d0 <main+58>:  call   0x400460 <puts@plt>
   0x4005d5 <main+63>:  mov    eax,0x0
   0x4005da <main+68>:  mov    rdx,QWORD PTR [rbp-0x8]
   0x4005de <main+72>:  xor    rdx,QWORD PTR fs:0x28
   0x4005e7 <main+81>:  je     0x4005ee <main+88>
   0x4005e9 <main+83>:  call   0x400470 <__stack_chk_fail@plt>
   0x4005ee <main+88>:  leave
   0x4005ef <main+89>:  ret
   0x4005f0 <__libc_csu_init>:  push   r15
   0x4005f2 <__libc_csu_init+2>:        push   r14
   0x4005f4 <__libc_csu_init+4>:        mov    r15d,edi

puts関数が見えると思います(0x4005d0 <main+58>: call 0x400460 <puts@plt>)。
puts関数が呼ばれるのは0x4005d0らしいので、そこにbreakpointを仕掛けます。

(gdb) break *0x4005d0
Breakpoint 2 at 0x4005d0

そこまで一気に進みます。

(gdb) continue
Continuing.

Breakpoint 2, 0x00000000004005d0 in main ()

実際にputs関数を呼ぶところで処理が止まったことを確認しましょう。

(gdb) x/5i $rip
=> 0x4005d0 <main+58>:  call   0x400460 <puts@plt>
   0x4005d5 <main+63>:  mov    eax,0x0
   0x4005da <main+68>:  mov    rdx,QWORD PTR [rbp-0x8]
   0x4005de <main+72>:  xor    rdx,QWORD PTR fs:0x28
   0x4005e7 <main+81>:  je     0x4005ee <main+88>

RBPレジスタに格納されているアドレスよりも8*4バイト上位のアドレスにあるデータを見てみましょう。

(gdb) x/s $rbp-8*4
0x7fffffffdfa0: "Hello, gdb!"

この値が引数として使われます。
一つ命令を進めてみましょう。

(gdb) nexti
Hello, gdb!
0x00000000004005d5 in main ()

引数の値が出力されたことが分かると思います。

breakpointを仕掛けずに最後まで走らせてみます。

(gdb) continue
Continuing.
[Inferior 1 (process 26356) exited normally]

終了しました。

breakpointの一覧を見てみます。

(gdb) info break
Num     Type           Disp Enb Address            What
2       breakpoint     keep y   0x00000000004005d0 <main+58>
        breakpoint already hit 1 time

breakpointを削除します。

(gdb) delete 2
(gdb) info break
No breakpoints or watchpoints.

最初から再開してみます。

(gdb) run
Starting program: /home/miyagaw61/tmp/test
Hello, gdb!
[Inferior 1 (process 12759) exited normally]

一度も止まらずに最初から最後まで走りました。

qコマンドでgdbを終了します。

(gdb) q
$

おわり

gdbの基本は以上となります。
enjoy your gdb life!

miyase256
SoftwareDvelopment, LinuxKernelReading, MalwareAnalysis, Exploit, AtCoder / seccamp'17'18, SecHack365'18, GlobalCybersecurityCamp'18
https://miyase256.github.io
ipfactory
情報科学専門学校 情報技術サークル「IPFactory」のOrganizationです。それぞれのアウトプット活動を促進するために発足されました。
https://twitter.com/_ipfactory_
Why not register and get more from Qiita?
  1. We will deliver articles that match you
    By following users and tags, you can catch up information on technical fields that you are interested in as a whole
  2. you can read useful information later efficiently
    By "stocking" the articles you like, you can search right away