U+E000 私用領域

しがない緑茶好きのメモ的なの。

strippedなバイナリをgdbで動的解析する

まえがき

ゲームも小説も飽きてしまったので、CTFのpwnに本腰入れようと昨日の夜からぶっ通しで色々やっていました。
去年の暮れ頃に開催されたCTFの問題やらに手を出して、pwnable.xyzの存在を知ったので、早速取り組む事に。
しかしながら一問目のWelcome問題からつまづいてしまったので、その時の簡易的なメモ。

つまづいた点

pwnable.xyzのwelcome問題を静的解析してみると、mallocでヒープ領域に値を動的確保している事がわかったのでgdbでヒープ領域の変遷を観察しようと考える。
しかし、実際にgdbに渡してみると...

$ gdb -q ./challenge
Reading symbols from ./challenge...
(No debugging symbols found in ./challenge)
gdb-peda$ b main
Function "main" not defined.
gdb-peda$ start
No unwaited-for children left.
Display various information of current execution context
Usage:
    context [reg,code,stack,all] [code/stack length]

gdb-peda$ info func
All defined functions:

Non-debugging symbols:
0x00005555555548b0  puts@plt
0x00005555555548b8  write@plt
0x00005555555548c0  __stack_chk_fail@plt
0x00005555555548c8  system@plt
0x00005555555548d0  alarm@plt
0x00005555555548d8  read@plt
0x00005555555548e0  signal@plt
0x00005555555548e8  malloc@plt
0x00005555555548f0  __printf_chk@plt
0x00005555555548f8  setvbuf@plt
0x0000555555554900  __isoc99_scanf@plt
0x0000555555554908  exit@plt
0x0000555555554910  __cxa_finalize@plt

何故かmain関数は見つからないし、当然ながらブレークポイントも貼れない...

なんでダメだったのか

A. バイナリがstippedだったから
fileコマンドでバイナリを見てみると、

$ file ./challenge
./challenge: ELF 64-bit LSB shared object, x86-64, version 1 (SYSV), dynamically linked, interpreter /lib64/ld-linux-x86-64.so.2, for GNU/Linux 2.6.32, BuildID[sha1]=1c86fc6fe1662d8037294b634c1cd0011bb304cb, stripped

よくあるx86-64向けのELFで、動的リンク。
しかし、最後の方にstrippedと表示されている事がわかる。
これについて、かの有名なハリネズミ本で読んだ記憶がうっすらとあり、ページをめくり探してみると以下のような記述があったので引用させていただく。

3つめとの差分として、strippedという単語が末尾に見えると思います。
これは、シンボル情報が削除されていることを示します。
シンボル情報とは、関数名やアドレスなどを示したものです。
これは、stripコマンドで実現されます。

バイナリ内にmain関数に該当する処理は存在しているが関数名とアドレスに関する情報が無い為、gdbはどこがmain関数なのかわからなかった様子。
これで、ブレークポイントが貼れず、関数情報を表示させてもmain関数が見当たらなかった理由がわかった。

解決策

結論から言ってしまうと、バイナリを一回適当に走らせて、その後__libc_start_mainブレークポイントを貼り、その時の第一引数を見ればmain関数のアドレスがわかるので動的解析の糸口になる。
まず、一回適当に走らせる事で動的にglibcがリンクする。
これによって__libc_start_mainをgdb上で検出できるようになる。
実際に見てみると、

$ gdb -q ./challeng
Reading symbols from ./challenge...
(No debugging symbols found in ./challenge)
gdb-peda$ run
Starting program: /home/t3mp/CTF/pwnable.xyz/image/challenge/challenge 
Welcome.
Leak: 0x7ffff7d7c010
Length of your message: 1 # 適当に入力してプログラムを終了させる
Enter your message: 1
[Inferior 1 (process 6176) exited normally] # プログラム終了
Warning: not running
gdb-peda$ 
gdb-peda$ info func
(出力が多すぎるので一部省略)
0x00007ffff7de2550  calloc@plt
0x00007ffff7de2560  *ABS*+0xa36c0@plt
0x00007ffff7de2570  *ABS*+0xa22d0@plt
0x00007ffff7de2580  *ABS*+0xa2890@plt
0x00007ffff7de2590  *ABS*+0xa3590@plt
0x00007ffff7de25a0  *ABS*+0xa3760@plt
0x00007ffff7de25b0  *ABS*+0xbf9a0@plt
0x00007ffff7de25c0  *ABS*+0xbfe60@plt
0x00007ffff7de25d0  *ABS*+0xa4dd0@plt
0x00007ffff7de25e0  *ABS*+0xa2960@plt
0x00007ffff7de25f0  *ABS*+0xa2220@plt
0x00007ffff7de2600  *ABS*+0xa3930@plt
0x00007ffff7de2610  *ABS*+0xa2900@plt
0x00007ffff7de2620  *ABS*+0xa3600@plt
gdb-peda$ info symbol __libc_start_main
__libc_start_main in section .text of /lib/x86_64-linux-gnu/libc.so.6

といった感じで、glibc上の__libc_start_mainを検出できている。
__libc_start_mainは関数名から察せるように、main関数を呼び出す処理を行っている。
実際にglibcソースコードを探してみると、/csu/libc-start.cに、

LIBC_START_MAIN (int (*main) (int, char **, char ** MAIN_AUXVEC_DECL),<省略>

libc-start.c - csu/libc-start.c - Glibc source code (glibc-2.33.9000) - Bootlin

と書かれており、第一引数にmain関数を渡している事が確認できる。
x86-64の関数呼び出しでは、第一引数がRDIレジスタに設定されているので、__libc_start_mainが呼び出された直後のRDIレジスタを見れば、main関数のアドレスがわかると思われる。
実際に確認してみる。

gdb-peda$ b __libc_start_main
Breakpoint 1 at 0x7ffff7de3fc0: file ../csu/libc-start.c, line 137.
gdb-peda$ run
Starting program: /home/t3mp/CTF/pwnable.xyz/image/challenge/challenge 
[----------------------------------registers-----------------------------------]
RAX: 0x1c 
RBX: 0x0 
RCX: 0x555555554ba0 (push   r15)
RDX: 0x7fffffffdda8 --> 0x7fffffffe14f ("/home/t3mp/CTF/pwnable.xyz/image/challenge/challenge")
RSI: 0x1 
RDI: 0x555555554920 (push   rbp)
RBP: 0x0 
RSP: 0x7fffffffdd88 --> 0x555555554a3a (hlt)
RIP: 0x7ffff7de3fc0 (<__libc_start_main>: endbr64)
R8 : 0x555555554c10 (repz ret)
R9 : 0x7ffff7fe0d50 (endbr64)
R10: 0x7ffff7ffcf68 --> 0x6ffffff0 
R11: 0x202 
R12: 0x555555554a10 (xor    ebp,ebp)
R13: 0x7fffffffdda0 --> 0x1 
R14: 0x0 
R15: 0x0
EFLAGS: 0x206 (carry PARITY adjust zero sign trap INTERRUPT direction overflow)
[-------------------------------------code-------------------------------------]
   0x7ffff7de3fb4 <_init+132>:    mov    rsi,QWORD PTR [rsp]
   0x7ffff7de3fb8 <_init+136>:    jmp    0x7ffff7de3f57 <_init+39>
   0x7ffff7de3fba:  nop    WORD PTR [rax+rax*1+0x0]
=> 0x7ffff7de3fc0 <__libc_start_main>: endbr64 
   0x7ffff7de3fc4 <__libc_start_main+4>:  push   r15
   0x7ffff7de3fc6 <__libc_start_main+6>:  xor    eax,eax
   0x7ffff7de3fc8 <__libc_start_main+8>:  push   r14
   0x7ffff7de3fca <__libc_start_main+10>: push   r13
[------------------------------------stack-------------------------------------]
0000| 0x7fffffffdd88 --> 0x555555554a3a (hlt)
0008| 0x7fffffffdd90 --> 0x7fffffffdd98 --> 0x1c 
0016| 0x7fffffffdd98 --> 0x1c 
0024| 0x7fffffffdda0 --> 0x1 
0032| 0x7fffffffdda8 --> 0x7fffffffe14f ("/home/t3mp/CTF/pwnable.xyz/image/challenge/challenge")
0040| 0x7fffffffddb0 --> 0x0 
0048| 0x7fffffffddb8 --> 0x7fffffffe184 ("SHELL=/bin/bash")
0056| 0x7fffffffddc0 --> 0x7fffffffe194 ("SESSION_MANAGER=local/yame-cha:@/tmp/.ICE-unix/1501,unix/yame-cha:/tmp/.ICE-unix/1501")
[------------------------------------------------------------------------------]
Legend: code, data, rodata, value

Breakpoint 1, __libc_start_main (main=0x555555554920, argc=0x1, argv=0x7fffffffdda8, init=0x555555554ba0, fini=0x555555554c10, rtld_fini=0x7ffff7fe0d50, stack_end=0x7fffffffdd98) at ../csu/libc-start.c:137
gdb-peda$ i r rdi
rdi            0x555555554920      0x555555554920
gdb-peda$ x/20i 0x555555554920
   0x555555554920:  push   rbp
   0x555555554921:  push   rbx
   0x555555554922:  sub    rsp,0x18
   0x555555554926:  mov    rax,QWORD PTR fs:0x28
   0x55555555492f:  mov    QWORD PTR [rsp+0x8],rax
   0x555555554934:  xor    eax,eax
   0x555555554936:  call   0x555555554b4e
   0x55555555493b:  lea    rdi,[rip+0x2e2]        # 0x555555554c24
   0x555555554942:  call   0x5555555548b0 <puts@plt>
   0x555555554947:  mov    edi,0x40000
   0x55555555494c:  call   0x5555555548e8 <malloc@plt>
   0x555555554951:  lea    rsi,[rip+0x2d5]        # 0x555555554c2d
   0x555555554958:  mov    rdx,rax
   0x55555555495b:  mov    rbx,rax
   0x55555555495e:  mov    QWORD PTR [rax],0x1
   0x555555554965:  mov    edi,0x1
   0x55555555496a:  xor    eax,eax
   0x55555555496c:  call   0x5555555548f0 <__printf_chk@plt>
   0x555555554971:  lea    rsi,[rip+0x2bf]        # 0x555555554c37
   0x555555554978:  mov    edi,0x1

関数のエピローグが確認できる上、動作を見てみてみるとmain関数に該当する処理である事がわかる。
main関数がわかったので、動的解析を進めることができそうだ。
ちなみに、静的解析の場合はLiveOverflow氏の以下の動画が大変参考になった。

www.youtube.com

というわけなので、pwnable.xyz頑張ります...