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氏の以下の動画が大変参考になった。
というわけなので、pwnable.xyz頑張ります...