Exgdbとは
SecHack365という一年を通して行われるセキュリティハッカソンで私が開発したgdb拡張です。
リポジトリ:https://github.com/miyagaw61/exgdb
詳細はリポジトリのREADME.mdをご参照ください。
環境
Ubuntu 16.04 LTS
まずはgdbを使ってみる
次に、gdb-pedaを使ってみる
そしてExgdb
Install
/path/toは任意のディレクトリを表します。
$ git clone https://github.com/miyagaw61/exgdb.git /path/to/exgdb
$ cd /path/to/exgdb
$ ./install.sh
[EXECUTED] echo "export EXGDBPATH=/path/to/exgdb" | sudo tee -a ~/.bashrc
export EXGDBPATH=/path/to/exgdb
[EXECUTED] echo "export PATH=$PATH:$EXGDBPATH/bin" | sudo tee -a ~/.bashrc
export PATH=\$PATH:\$EXGDBPATH/bin
[EXECUTED] echo "source /path/to/exgdb/gdbinit.py" | sudo tee -a ~/.gdbinit
source /path/to/exgdb/gdbinit.py
#################################################
[!] Please execute this command: source ~/.bashrc
#################################################
[INFO] You can use exgdbctl command after executing above command.
$ source ~/.bashrc
$ exgdbctl list
exgdb: enabled
peda: not installed
Pwngdb: not installed
gdb-dashboard: not installed
$ exgdbctl install peda
$ exgdbctl install Pwngdb
$ exgdbctl list
exgdb: enabled
peda: enabled
Pwngdb: enabled
gdb-dashboard: not installed
実際に使ってみる
gdbやgdb-pedaで使用したバイナリを使って解析してみます。
$ 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-peda$
startしてみます。
gdb-peda$ start
[----------------------------------registers-----------------------------------]
RAX: 0x400596 (<main>: push rbp)
RBX: 0x0
RCX: 0x0
RDX: 0x7fffffffe0b8 --> 0x7fffffffe38e ("XDG_SESSION_ID=9152")
RSI: 0x7fffffffe0a8 --> 0x7fffffffe375 ("/home/miyagaw61/tmp/test")
RDI: 0x1
RBP: 0x7fffffffdfc0 --> 0x4005f0 (<__libc_csu_init>: push r15)
RSP: 0x7fffffffdfc0 --> 0x4005f0 (<__libc_csu_init>: push r15)
RIP: 0x40059a (<main+4>: sub rsp,0x30)
R8 : 0x400660 (<__libc_csu_fini>: repz ret)
R9 : 0x7ffff7de7ab0 (<_dl_fini>: push rbp)
R10: 0x846
R11: 0x7ffff7a2d740 (<__libc_start_main>: push r14)
R12: 0x4004a0 (<_start>: xor ebp,ebp)
R13: 0x7fffffffe0a0 --> 0x1
R14: 0x0
R15: 0x0
EFLAGS: 0x246 (carry PARITY adjust ZERO sign trap INTERRUPT direction overflow)
[-------------------------------------code-------------------------------------]
0x400591 <frame_dummy+33>: jmp 0x400510 <register_tm_clones>
0x400596 <main>: push rbp
0x400597 <main+1>: mov rbp,rsp
=> 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
[------------------------------------stack-------------------------------------]
0000| 0x7fffffffdfc0 --> 0x4005f0 (<__libc_csu_init>: push r15)
0008| 0x7fffffffdfc8 --> 0x7ffff7a2d830 (<__libc_start_main+240>: mov edi,eax)
0016| 0x7fffffffdfd0 --> 0x0
0024| 0x7fffffffdfd8 --> 0x7fffffffe0a8 --> 0x7fffffffe375 ("/home/miyagaw61/tmp/test")
0032| 0x7fffffffdfe0 --> 0x1f7ffcca0
0040| 0x7fffffffdfe8 --> 0x400596 (<main>: push rbp)
0048| 0x7fffffffdff0 --> 0x0
0056| 0x7fffffffdff8 --> 0x8fa409088f82687e
[------------------------------------------------------------------------------]
Legend: code, data, rodata, value
Temporary breakpoint 2, 0x000000000040059a in main ()
表示は何も変わりませんね。
では、exgdbで追加された新機能を触ってみましょう。
allstackコマンドを実行してみましょう。
gdb-peda$ allstack
0000| 0x7fffffffdfc0 --> 0x4005f0 (<__libc_csu_init>: push r15)
一行だけ表示されました。
このコマンドは、現在のスタックフレーム(rspからrbpまで)を表示するコマンドです。
スタックだけを見たいなら、このコマンドを使った方が便利な時があります。
nextnow 10
は、現在のプログラムカウンタが指しているアドレスから10個分の命令を表示します。
挙動としては、x/10i $rip
に色が付いたものになります。nn
という省略記法も使えます。
また、prevnow
を使うと、プログラムカウンタ以前の命令を表示します。
gdb-peda$ nextnow 10
=> 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
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
上から三つめの0x4005a7番地を始点として10個の命令列を見たい場合は、nextpd 0x4005a7 10
というコマンドを実行します。x/10i 0x4005a7
に色がつきます。省略記法はnpd
です。prevpd (ppd)
もあります。
gdb-peda$ code 0x4005a7 10
=> 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
第二引数を1にすると0x4005a7番地の命令だけを見れます。
gdb-peda$ npd 0x4005da 1
=> 0x4005da <main+68>: mov rdx,QWORD PTR [rbp-0x8]
"rbp-0x8"という文字列があることがわかると思います。
この"rbp-0x8"という文字列を含む命令をもつアドレスまで進んでみましょう。
gdb-peda$ nuntil rbp-0x8
[----------------------------------registers-----------------------------------]
RAX: 0x4d9fbbbfcb981a00
RBX: 0x0
RCX: 0x0
RDX: 0x7fffffffe0b8 --> 0x7fffffffe38e ("XDG_SESSION_ID=9152")
RSI: 0x7fffffffe0a8 --> 0x7fffffffe375 ("/home/miyagaw61/tmp/test")
RDI: 0x1
RBP: 0x7fffffffdfc0 --> 0x4005f0 (<__libc_csu_init>: push r15)
RSP: 0x7fffffffdf90 --> 0x0
RIP: 0x4005a7 (<main+17>: mov QWORD PTR [rbp-0x8],rax)
R8 : 0x400660 (<__libc_csu_fini>: repz ret)
R9 : 0x7ffff7de7ab0 (<_dl_fini>: push rbp)
R10: 0x846
R11: 0x7ffff7a2d740 (<__libc_start_main>: push r14)
R12: 0x4004a0 (<_start>: xor ebp,ebp)
R13: 0x7fffffffe0a0 --> 0x1
R14: 0x0
R15: 0x0
EFLAGS: 0x206 (carry PARITY adjust zero sign trap INTERRUPT direction overflow)
[-------------------------------------code-------------------------------------]
0x400597 <main+1>: mov rbp,rsp
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
0x4005b4 <main+30>: movabs rax,0x67202c6f6c6c6548
0x4005be <main+40>: mov QWORD PTR [rbp-0x20],rax
[------------------------------------stack-------------------------------------]
0000| 0x7fffffffdf90 --> 0x0
0008| 0x7fffffffdf98 --> 0x0
0016| 0x7fffffffdfa0 --> 0x4005f0 (<__libc_csu_init>: push r15)
0024| 0x7fffffffdfa8 --> 0x4004a0 (<_start>: xor ebp,ebp)
0032| 0x7fffffffdfb0 --> 0x7fffffffe0a0 --> 0x1
0040| 0x7fffffffdfb8 --> 0x0
0048| 0x7fffffffdfc0 --> 0x4005f0 (<__libc_csu_init>: push r15)
0056| 0x7fffffffdfc8 --> 0x7ffff7a2d830 (<__libc_start_main+240>: mov edi,eax)
[------------------------------------------------------------------------------]
Legend: code, data, rodata, value
0x00000000004005a7 in main ()
0x4005a7 <main+17>: mov QWORD PTR [rbp-0x8],rax
で止まったと思います。
このように、nuntil
コマンドでは、引数に与えた文字列を命令に含むアドレスまで自動で進むことができます。
ちなみに、正規表現も使えます。
次に、今の命令の中にある[rbp-0x8]
の中身を見てみましょう。
gdb-peda内蔵の機能では、tel $rbp-0x8
というコマンドで可能です。
gdb-peda$ tel $rbp-0x8
0000| 0x7fffffffdfb8 --> 0x0
Exgdbには、現在のプログラムカウンタが指しているアドレス中の命令に含まれているデータを解析し、自動で中身を表示してくれるinfonow
コマンドが存在します。
gdb-peda$ infonow
RAX: 0xa06857ec28cd0e00
RBP: 0x7fffffffdfc0 --> 0x4005f0 (<__libc_csu_init>: push r15)
[rbp-0x8]: 0x7fffffffdfb8 --> 0x0
contextmode infonow,reg,code,stack
とやると、ステップする度にinfonowが上部に表示されるようになります。
memtrace
も便利です。その命令で行われるメモリをトレースします。
contextmode
の引数として指定する場合、memtrace
とinfonow
を合わせてmemtinow
と指定することができます。
「gdb-peda超入門」の記事でも紹介しましたが、gdb-peda内蔵の機能で、関数の中身を全てダンプするpdisass
というコマンドが存在します。
pdisass main
を実行し、main関数の中身を全て見てみましょう。
gdb-peda$ pdisass main
Dump of assembler code for function main:
0x0000000000400596 <+0>: push rbp
0x0000000000400597 <+1>: mov rbp,rsp
0x000000000040059a <+4>: sub rsp,0x30
0x000000000040059e <+8>: mov rax,QWORD PTR fs:0x28
=> 0x00000000004005a7 <+17>: mov QWORD PTR [rbp-0x8],rax
0x00000000004005ab <+21>: xor eax,eax
0x00000000004005ad <+23>: mov DWORD PTR [rbp-0x24],0xa
0x00000000004005b4 <+30>: movabs rax,0x45202c6f6c6c6548
0x00000000004005be <+40>: mov QWORD PTR [rbp-0x20],rax
0x00000000004005c2 <+44>: mov DWORD PTR [rbp-0x18],0x62646778
0x00000000004005c9 <+51>: mov WORD PTR [rbp-0x14],0x21
0x00000000004005cf <+57>: lea rax,[rbp-0x20]
0x00000000004005d3 <+61>: mov rdi,rax
0x00000000004005d6 <+64>: call 0x400460 <puts@plt>
0x00000000004005db <+69>: mov eax,0x0
0x00000000004005e0 <+74>: mov rdx,QWORD PTR [rbp-0x8]
0x00000000004005e4 <+78>: xor rdx,QWORD PTR fs:0x28
0x00000000004005ed <+87>: je 0x4005f4 <main+94>
0x00000000004005ef <+89>: call 0x400470 <__stack_chk_fail@plt>
0x00000000004005f4 <+94>: leave
0x00000000004005f5 <+95>: ret
End of assembler dump.
ここから、「callを呼んでいるところだけを抽出したい」とします。
そういう時は、grep
コマンドが便利です。
第一引数に「何かを表示するコマンド」、第二引数に「正規表現」です。
gdb-peda$ grep "pdisass main" call
0x00000000004005d0 <+58>: call 0x400460 <puts@plt>
0x00000000004005e9 <+83>: call 0x400470 <__stack_chk_fail@plt>
少し前にnuntil
という正規表現にマッチしたアドレスまで進むというコマンドを紹介したと思います。
あれは内部でnexti
を使用しているのですが、代わりにnextcall
を使用することで高速化したnextcalluntil
コマンドも存在します。
gdb-peda$ nextcalluntil puts
[----------------------------------registers-----------------------------------]
RAX: 0x7fffffffdfa0 ("Hello, gdb!")
RBX: 0x0
RCX: 0x0
RDX: 0x7fffffffe0b8 --> 0x7fffffffe38e ("XDG_SESSION_ID=9152")
RSI: 0x7fffffffe0a8 --> 0x7fffffffe375 ("/home/miyagaw61/tmp/test")
RDI: 0x7fffffffdfa0 ("Hello, gdb!")
RBP: 0x7fffffffdfc0 --> 0x4005f0 (<__libc_csu_init>: push r15)
RSP: 0x7fffffffdf90 --> 0x0
RIP: 0x4005d0 (<main+58>: call 0x400460 <puts@plt>)
R8 : 0x400660 (<__libc_csu_fini>: repz ret)
R9 : 0x7ffff7de7ab0 (<_dl_fini>: push rbp)
R10: 0x846
R11: 0x7ffff7a2d740 (<__libc_start_main>: push r14)
R12: 0x4004a0 (<_start>: xor ebp,ebp)
R13: 0x7fffffffe0a0 --> 0x1
R14: 0x0
R15: 0x0
EFLAGS: 0x246 (carry PARITY adjust ZERO sign trap INTERRUPT direction overflow)
[-------------------------------------code-------------------------------------]
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>
Guessed arguments:
arg[0]: 0x7fffffffdfa0 ("Hello, gdb!")
[------------------------------------stack-------------------------------------]
0000| 0x7fffffffdf90 --> 0x0
0008| 0x7fffffffdf98 --> 0xa00000000 ('')
0016| 0x7fffffffdfa0 ("Hello, gdb!")
0024| 0x7fffffffdfa8 --> 0x216264 ('db!')
0032| 0x7fffffffdfb0 --> 0x7fffffffe0a0 --> 0x1
0040| 0x7fffffffdfb8 --> 0x9d1304f00b2f0b00
0048| 0x7fffffffdfc0 --> 0x4005f0 (<__libc_csu_init>: push r15)
0056| 0x7fffffffdfc8 --> 0x7ffff7a2d830 (<__libc_start_main+240>: mov edi,eax)
[------------------------------------------------------------------------------]
Legend: code, data, rodata, value
正しくputs関数で止まることが確認できました。
nextcalluntil "(malloc|free)"
などと書くと、エンターを押すたびにmallocかfreeで止まる(超高速)、みたいなことが可能になります。
あとは、ヒープ関連のコマンドを紹介したいのですが、ヒープは少しばかり難しいので、今回は紹介しないでおきます。
いくつかチャンクをalloc/freeした状態で、showchunkheaders
, showchunkheader <addr>
, showchunks
, showchunk <addr>
などのコマンドを試してみてください。
おわり
以上でExgdbの基礎は終わりです。
このように、Exgdbでは、動的解析を効率化する様々なコマンドを用意しています。
日常でバイナリの動的解析をする中で、「このような機能が欲しい」と感じたら、ぜひ教えていただけると幸いです。
enjoy your exgdb life!