LoginSignup
3
0

More than 1 year has passed since last update.

初心者がpwndbg (gdb)を学ぶ

Last updated at Posted at 2022-12-07

pwndbgとは、gdbを便利にしてくれる拡張スクリプトです。
gdbとは、主にgccでコンパイルしたプログラムをデバッグするツールです(ざっくり)。
pwndbgはCTFerがよく使っているイメージがある。

この記事では、これ以上ない程単純なreversingの問題を、勉強のためにあえてpwndbg縛りで解きます

リバースエンジニアリングは、自作やCTFなど許可されたファイル以外にはしないようにしましょう(そんなレベルの記事ではないですが、念のため)。

環境

WSL2 (win10)
Ubuntu 20.04.4 LTS

pwndbgはこれ。
https://github.com/pwndbg/pwndbg
検索する時dbgとgdbを間違えやすいので注意(1敗)

gdbとコマンドをうつことで、pwndbgを開ける。
(pwndbgの文字が出ていないならインストールに失敗してます)
動作が確認出来たら、一旦qで閉じます。

ターゲット

ターゲットはこちら。

tar1.c
#include <stdio.h>
void main(){
    int a,b,c;
    a=2;
    b=3;
    c = a * b;
    if (c == 20){
        printf("C is %d\n",c);
        printf("Awesome!\n");
    }
    else{
        printf("C is %d\n",c);
    }
}
user@WSL$ gcc tar1.c -o tar1
user@WSL$ ./tar1
C is 6

当然cは6になり、Awesome!とは表示されない。ので、Awesomeを出させます。
なおCTFなどではこんなソースコードは与えられません。pwndbgを使う前にGhidraなどの解析ソフトでデコンパイルし、到達したい場所のある程度の目星を付ける必要があります。

pwndbgで開いてみる。

user@WSL$ gdb -q tar1
pwndbg> start

pwndbgが色々ログを出してくれます。
startコマンドでは、mainやinitなどの丁度いい所で処理を一旦ストップしてくれます (便利!)
ちゃんと説明すると、丁度いい所にブレークポイントを貼っています。

pwndbg> start
Temporary breakpoint 1 at 0x1169

Temporary breakpoint 1, 0x0000555555555169 in main ()
LEGEND: STACK | HEAP | CODE | DATA | RWX | RODATA
─────────────────────────────────[ REGISTERS / show-flags off / show-compact-regs off ]─────────────────────────────────
*RAX  0x555555555169 (main) ◂— endbr64
*RBX  0x5555555551d0 (__libc_csu_init) ◂— endbr64
*RCX  0x5555555551d0 (__libc_csu_init) ◂— endbr64
*RDX  0x7fffffffdd18 —▸ 0x7fffffffdf5c ◂— 'SHELL=/bin/bash'
*RDI  0x1
*RSI  0x7fffffffdd08 —▸ 0x7fffffffdf3f ◂— '/{path}/tar1'
 R8   0x0
*R9   0x7ffff7fe0d60 (_dl_fini) ◂— endbr64
*R10  0x7
*R11  0x2
*R12  0x555555555080 (_start) ◂— endbr64
*R13  0x7fffffffdd00 ◂— 0x1
 R14  0x0
 R15  0x0
 RBP  0x0
*RSP  0x7fffffffdc18 —▸ 0x7ffff7deb083 (__libc_start_main+243) ◂— mov edi, eax
*RIP  0x555555555169 (main) ◂— endbr64
──────────────────────────────────────────[ DISASM / x86-64 / set emulate on ]──────────────────────────────────────────
 ► 0x555555555169 <main>       endbr64
   0x55555555516d <main+4>     push   rbp
   0x55555555516e <main+5>     mov    rbp, rsp
   0x555555555171 <main+8>     sub    rsp, 0x10
   0x555555555175 <main+12>    mov    dword ptr [rbp - 0xc], 2
   0x55555555517c <main+19>    mov    dword ptr [rbp - 8], 3
   0x555555555183 <main+26>    mov    eax, dword ptr [rbp - 0xc]
   0x555555555186 <main+29>    imul   eax, dword ptr [rbp - 8]
   0x55555555518a <main+33>    mov    dword ptr [rbp - 4], eax
   0x55555555518d <main+36>    cmp    dword ptr [rbp - 4], 0x14
   0x555555555191 <main+40>    jne    main+78                <main+78>
───────────────────────────────────────────────────────[ STACK ]────────────────────────────────────────────────────────
00:0000│ rsp 0x7fffffffdc18 —▸ 0x7ffff7deb083 (__libc_start_main+243) ◂— mov edi, eax
01:0008│     0x7fffffffdc20 —▸ 0x7ffff7ffc620 (_rtld_global_ro) ◂— 0x50a6600000000
02:0010│     0x7fffffffdc28 —▸ 0x7fffffffdd08 —▸ 0x7fffffffdf3f ◂— '/{path}/tar1'
03:0018│     0x7fffffffdc30 ◂— 0x100000000
04:0020│     0x7fffffffdc38 —▸ 0x555555555169 (main) ◂— endbr64
05:0028│     0x7fffffffdc40 —▸ 0x5555555551d0 (__libc_csu_init) ◂— endbr64
06:0030│     0x7fffffffdc48 ◂— 0xaf2eec1db698b8c3
07:0038│     0x7fffffffdc50 —▸ 0x555555555080 (_start) ◂— endbr64
─────────────────────────────────────────────────────[ BACKTRACE ]──────────────────────────────────────────────────────
 ► f 0   0x555555555169 main
   f 1   0x7ffff7deb083 __libc_start_main+243
────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────
pwndbg>

出ているのはスタックの中身やアセンブリ言語など。今見るのはここ!
image.png

目的のif文はアセンブリ言語でcmpなので、直前まで移動する。

pwndbg> n

をするごとに1行ずつ処理されるので、nを連打。
image.png
cmpの直前まで来た。
ここで、

pwndbg> i r

を入力すると
image.png
現在のレジスタの情報が見れます。便利!
先ほどのアセンブリ言語からeax0x14を比較して分岐していることが分かるので、レジスタrax0x14であれば望み通りの分岐に行ってくれる。

pwndbg> set $rax=0x14

を入力し、もう一度i rで確認する。
image.png
レジスタの中身の書き換えに成功!

pwndbg> c

でそのまま最後まで一気に実行。
image.png
無事(?)、6となるはずのcが20に書き換えられ、出力されるはずのないコードが出力されました!

......

ところで、レジスタの中身を書き換えられるのなら、比較した結果のTrueかFalseを直接書き換えればいいと思いませんか?
image.png
最初から新しい状態でstartし、cmpの後まで来ました。この状態で、レジスタの中身は次の通りです。
image.png
直前のcmpの結果はeflagsに格納されています。eflagsは各ビットがフラグとして扱われているレジスタです。
計算機の勉強をしたことがある人なら、桁上がりでCF(キャリーフラグ)がセットされる、などと聞いたことがあるのでは。

重要なのはZF(ゼロフラグ)です。これは6番目のフラグです。
直前の操作の結果が0になったときだけセットされます。
cmp命令の中身は実質引き算で、差が0であれば等しいとされるのです。

というわけで、eflagsの6番目を書き換えてZFを1にしてしまいましょう。

pwndbg> set $eflags |= (1<<6)

image.png
無事ZFが1になりました。
cで最後まで行きます。
image.png
cが20ではなく6のままなのに、if(c==20)を通過してしまいました。

おわり

とても便利……
普通にデバッガとして使う場合でも超便利。

このツール、まだまだ色んなことができます。
(ブレークポイントを使わないgdb記事があっていいのか)
プログラムの任意の処理に対応するアドレスを特定すれば、そこにブレークポイントを貼って処理を開始することでブレークポイントで停止し、レジスタの中身などを確認することができます(効率的!)

おわり

3
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
3
0