バイナリアン入門 第一回(x64, Linux)
バイナリの講義で、実行ファイルの静的解析を学習したのだが「こんな感じでやるのかー」ぐらいしか理解できなかったため、アセンブリの読解とバイナリ解析の手順に関して、簡単なプログラムから解析を行い、順々にバイナリの解析力をあげていこうと思う。そのための第一歩としてまずは、お馴染みの hello world.
を表示するプログラムを解析してみる。
下準備
C言語で以下のようなソースを用意
#include <stdio.h> int main(void) { printf("hello world.\n"); return 0; }
コンパイルする
# gcc hello.c
出力結果の確認
# ./a.out hello world.
解析
下準備はできたのでここから解析手順について説明。自分で用意しているバイナリのため本来調査の必要はないのだが、対象のファイルが何か全くわからない状態を想定して、解析を行ってみる。
表層解析
まずは表層解析としゃれこんで、対象のファイルをデータとして見たとき、どのようなファイルなのかを確認する。
ファイル形式の確認
# file a.out a.out: ELF 64-bit LSB shared object, x86-64, version 1 (SYSV), dynamically linked, interpreter /lib64/ld-linux-x86-64.so.2, for GNU/Linux 3.2.0, BuildID[sha1]=f5cceda86933a497d3c81a1ecf87457f753597fe, not stripped
ELF 64-bit
からLinuxの実行ファイルだということが判明。
どのような文字列が含まれてるのか確認
文字列としてどのような値が含まれているかも、一応確認してみる。
# strings a.out /lib64/ld-linux-x86-64.so.2 libc.so.6 puts __cxa_finalize __libc_start_main GLIBC_2.2.5 _ITM_deregisterTMCloneTable __gmon_start__ _ITM_registerTMCloneTable AWAVI AUATL []A\A]A^A_ hello world.
動的解析
本来は、どのような挙動をするか分からないため、仮想環境などで実行するのが好ましいが、今回は表示するだけなのでそのまま実行してみる。
普通に実行
# ./a.out hello world.
ltrace
どのようなライブラリ呼び出しを行っているかも確認。
# ltrace ./a.out puts("hello world."hello world. ) = 13 +++ exited (status 0) +++
strace
どのようなシステムコールを呼び出しているかも確認。
# strace ./a.out execve("./a.out", ["./a.out"], 0x7ffcc01548b0 /* 53 vars */) = 0 brk(NULL) = 0x55de02dcd000 access("/etc/ld.so.nohwcap", F_OK) = -1 ENOENT (No such file or directory) access("/etc/ld.so.preload", R_OK) = -1 ENOENT (No such file or directory) openat(AT_FDCWD, "/etc/ld.so.cache", O_RDONLY|O_CLOEXEC) = 3 fstat(3, {st_mode=S_IFREG|0644, st_size=154007, ...}) = 0 mmap(NULL, 154007, PROT_READ, MAP_PRIVATE, 3, 0) = 0x7f46ac1d2000 close(3) = 0 access("/etc/ld.so.nohwcap", F_OK) = -1 ENOENT (No such file or directory) openat(AT_FDCWD, "/lib/x86_64-linux-gnu/libc.so.6", O_RDONLY|O_CLOEXEC) = 3 read(3, "\177ELF\2\1\1\3\0\0\0\0\0\0\0\0\3\0>\0\1\0\0\0\240\33\2\0\0\0\0\0"..., 832) = 832 fstat(3, {st_mode=S_IFREG|0755, st_size=1800248, ...}) = 0 mmap(NULL, 8192, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_ANONYMOUS, -1, 0) = 0x7f46ac1d0000 mmap(NULL, 3906272, PROT_READ|PROT_EXEC, MAP_PRIVATE|MAP_DENYWRITE, 3, 0) = 0x7f46abc1a000 mprotect(0x7f46abdcb000, 2093056, PROT_NONE) = 0 mmap(0x7f46abfca000, 24576, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_FIXED|MAP_DENYWRITE, 3, 0x1b0000) = 0x7f46abfca000 mmap(0x7f46abfd0000, 15072, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_FIXED|MAP_ANONYMOUS, -1, 0) = 0x7f46abfd0000 close(3) = 0 arch_prctl(ARCH_SET_FS, 0x7f46ac1d14c0) = 0 mprotect(0x7f46abfca000, 16384, PROT_READ) = 0 mprotect(0x55de017ed000, 4096, PROT_READ) = 0 mprotect(0x7f46ac1f8000, 4096, PROT_READ) = 0 munmap(0x7f46ac1d2000, 154007) = 0 fstat(1, {st_mode=S_IFCHR|0620, st_rdev=makedev(136, 0), ...}) = 0 brk(NULL) = 0x55de02dcd000 brk(0x55de02dee000) = 0x55de02dee000 write(1, "hello world.\n", 13hello world. ) = 13 exit_group(0) = ? +++ exited with 0 +++
内容的には、 puts
や write
が目につく。
静的解析
下準備や動的解析などを間に挟んできたが、本日のメインはこの静的解析。アセンブリと戦わなくてはならないのがここ。ちなみに、今回解析するELF形式の実行ファイルってどんな感じのフォーマットになっているのかという情報は、Wikiにのっているので割愛。(時間ができたらちょっと調べて補足的に載せたい)
https://ja.wikipedia.org/wiki/Executable_and_Linkable_Format
逆アセンブル
とにもかくにも、まずは逆アセンブルにより対象ファイルのアセンブリを吐き出す必要がある。実際の解析では IDA
などを使った方が視覚的にも使いやすさ的にも良いのだが、今回の内容はうっすいため objdump
で吐き出す。IDAに関してはこの後に続くであろうバイナリ解析N回目で使っていこうと思う。
# objdump -M intel -S a.out > hello.asm
M
オプションをつけないと、AT&T形式でのアセンブラが吐き出されるため今回はIntel形式での出力となるようにしている。
a.out: ファイル形式 elf64-x86-64 セクション .init の逆アセンブル: 00000000000004e8 <_init>: 4e8: 48 83 ec 08 sub rsp,0x8 4ec: 48 8b 05 f5 0a 20 00 mov rax,QWORD PTR [rip+0x200af5] # 200fe8 <__gmon_start__> 4f3: 48 85 c0 test rax,rax 4f6: 74 02 je 4fa <_init+0x12> 4f8: ff d0 call rax 4fa: 48 83 c4 08 add rsp,0x8 4fe: c3 ret セクション .plt の逆アセンブル: 0000000000000500 <.plt>: 500: ff 35 02 0b 20 00 push QWORD PTR [rip+0x200b02] # 201008 <_GLOBAL_OFFSET_TABLE_+0x8> 506: ff 25 04 0b 20 00 jmp QWORD PTR [rip+0x200b04] # 201010 <_GLOBAL_OFFSET_TABLE_+0x10> 50c: 0f 1f 40 00 nop DWORD PTR [rax+0x0] 0000000000000510 <puts@plt>: 510: ff 25 02 0b 20 00 jmp QWORD PTR [rip+0x200b02] # 201018 <puts@GLIBC_2.2.5> 516: 68 00 00 00 00 push 0x0 51b: e9 e0 ff ff ff jmp 500 <.plt> セクション .plt.got の逆アセンブル: 0000000000000520 <__cxa_finalize@plt>: 520: ff 25 d2 0a 20 00 jmp QWORD PTR [rip+0x200ad2] # 200ff8 <__cxa_finalize@GLIBC_2.2.5> 526: 66 90 xchg ax,ax セクション .text の逆アセンブル: 0000000000000530 <_start>: 530: 31 ed xor ebp,ebp 532: 49 89 d1 mov r9,rdx 535: 5e pop rsi 536: 48 89 e2 mov rdx,rsp 539: 48 83 e4 f0 and rsp,0xfffffffffffffff0 53d: 50 push rax 53e: 54 push rsp 53f: 4c 8d 05 8a 01 00 00 lea r8,[rip+0x18a] # 6d0 <__libc_csu_fini> 546: 48 8d 0d 13 01 00 00 lea rcx,[rip+0x113] # 660 <__libc_csu_init> 54d: 48 8d 3d e6 00 00 00 lea rdi,[rip+0xe6] # 63a <main> 554: ff 15 86 0a 20 00 call QWORD PTR [rip+0x200a86] # 200fe0 <__libc_start_main@GLIBC_2.2.5> 55a: f4 hlt 55b: 0f 1f 44 00 00 nop DWORD PTR [rax+rax*1+0x0] 0000000000000560 <deregister_tm_clones>: 560: 48 8d 3d c9 0a 20 00 lea rdi,[rip+0x200ac9] # 201030 <__TMC_END__> 567: 55 push rbp 568: 48 8d 05 c1 0a 20 00 lea rax,[rip+0x200ac1] # 201030 <__TMC_END__> 56f: 48 39 f8 cmp rax,rdi 572: 48 89 e5 mov rbp,rsp 575: 74 19 je 590 <deregister_tm_clones+0x30> 577: 48 8b 05 5a 0a 20 00 mov rax,QWORD PTR [rip+0x200a5a] # 200fd8 <_ITM_deregisterTMCloneTable> 57e: 48 85 c0 test rax,rax 581: 74 0d je 590 <deregister_tm_clones+0x30> 583: 5d pop rbp 584: ff e0 jmp rax 586: 66 2e 0f 1f 84 00 00 nop WORD PTR cs:[rax+rax*1+0x0] 58d: 00 00 00 590: 5d pop rbp 591: c3 ret 592: 0f 1f 40 00 nop DWORD PTR [rax+0x0] 596: 66 2e 0f 1f 84 00 00 nop WORD PTR cs:[rax+rax*1+0x0] 59d: 00 00 00 00000000000005a0 <register_tm_clones>: 5a0: 48 8d 3d 89 0a 20 00 lea rdi,[rip+0x200a89] # 201030 <__TMC_END__> 5a7: 48 8d 35 82 0a 20 00 lea rsi,[rip+0x200a82] # 201030 <__TMC_END__> 5ae: 55 push rbp 5af: 48 29 fe sub rsi,rdi 5b2: 48 89 e5 mov rbp,rsp 5b5: 48 c1 fe 03 sar rsi,0x3 5b9: 48 89 f0 mov rax,rsi 5bc: 48 c1 e8 3f shr rax,0x3f 5c0: 48 01 c6 add rsi,rax 5c3: 48 d1 fe sar rsi,1 5c6: 74 18 je 5e0 <register_tm_clones+0x40> 5c8: 48 8b 05 21 0a 20 00 mov rax,QWORD PTR [rip+0x200a21] # 200ff0 <_ITM_registerTMCloneTable> 5cf: 48 85 c0 test rax,rax 5d2: 74 0c je 5e0 <register_tm_clones+0x40> 5d4: 5d pop rbp 5d5: ff e0 jmp rax 5d7: 66 0f 1f 84 00 00 00 nop WORD PTR [rax+rax*1+0x0] 5de: 00 00 5e0: 5d pop rbp 5e1: c3 ret 5e2: 0f 1f 40 00 nop DWORD PTR [rax+0x0] 5e6: 66 2e 0f 1f 84 00 00 nop WORD PTR cs:[rax+rax*1+0x0] 5ed: 00 00 00 00000000000005f0 <__do_global_dtors_aux>: 5f0: 80 3d 39 0a 20 00 00 cmp BYTE PTR [rip+0x200a39],0x0 # 201030 <__TMC_END__> 5f7: 75 2f jne 628 <__do_global_dtors_aux+0x38> 5f9: 48 83 3d f7 09 20 00 cmp QWORD PTR [rip+0x2009f7],0x0 # 200ff8 <__cxa_finalize@GLIBC_2.2.5> 600: 00 601: 55 push rbp 602: 48 89 e5 mov rbp,rsp 605: 74 0c je 613 <__do_global_dtors_aux+0x23> 607: 48 8b 3d 1a 0a 20 00 mov rdi,QWORD PTR [rip+0x200a1a] # 201028 <__dso_handle> 60e: e8 0d ff ff ff call 520 <__cxa_finalize@plt> 613: e8 48 ff ff ff call 560 <deregister_tm_clones> 618: c6 05 11 0a 20 00 01 mov BYTE PTR [rip+0x200a11],0x1 # 201030 <__TMC_END__> 61f: 5d pop rbp 620: c3 ret 621: 0f 1f 80 00 00 00 00 nop DWORD PTR [rax+0x0] 628: f3 c3 repz ret 62a: 66 0f 1f 44 00 00 nop WORD PTR [rax+rax*1+0x0] 0000000000000630 <frame_dummy>: 630: 55 push rbp 631: 48 89 e5 mov rbp,rsp 634: 5d pop rbp 635: e9 66 ff ff ff jmp 5a0 <register_tm_clones> 000000000000063a <main>: 63a: 55 push rbp 63b: 48 89 e5 mov rbp,rsp 63e: 48 8d 3d 9f 00 00 00 lea rdi,[rip+0x9f] # 6e4 <_IO_stdin_used+0x4> 645: e8 c6 fe ff ff call 510 <puts@plt> 64a: b8 00 00 00 00 mov eax,0x0 64f: 5d pop rbp 650: c3 ret 651: 66 2e 0f 1f 84 00 00 nop WORD PTR cs:[rax+rax*1+0x0] 658: 00 00 00 65b: 0f 1f 44 00 00 nop DWORD PTR [rax+rax*1+0x0] 0000000000000660 <__libc_csu_init>: 660: 41 57 push r15 662: 41 56 push r14 664: 49 89 d7 mov r15,rdx 667: 41 55 push r13 669: 41 54 push r12 66b: 4c 8d 25 76 07 20 00 lea r12,[rip+0x200776] # 200de8 <__frame_dummy_init_array_entry> 672: 55 push rbp 673: 48 8d 2d 76 07 20 00 lea rbp,[rip+0x200776] # 200df0 <__init_array_end> 67a: 53 push rbx 67b: 41 89 fd mov r13d,edi 67e: 49 89 f6 mov r14,rsi 681: 4c 29 e5 sub rbp,r12 684: 48 83 ec 08 sub rsp,0x8 688: 48 c1 fd 03 sar rbp,0x3 68c: e8 57 fe ff ff call 4e8 <_init> 691: 48 85 ed test rbp,rbp 694: 74 20 je 6b6 <__libc_csu_init+0x56> 696: 31 db xor ebx,ebx 698: 0f 1f 84 00 00 00 00 nop DWORD PTR [rax+rax*1+0x0] 69f: 00 6a0: 4c 89 fa mov rdx,r15 6a3: 4c 89 f6 mov rsi,r14 6a6: 44 89 ef mov edi,r13d 6a9: 41 ff 14 dc call QWORD PTR [r12+rbx*8] 6ad: 48 83 c3 01 add rbx,0x1 6b1: 48 39 dd cmp rbp,rbx 6b4: 75 ea jne 6a0 <__libc_csu_init+0x40> 6b6: 48 83 c4 08 add rsp,0x8 6ba: 5b pop rbx 6bb: 5d pop rbp 6bc: 41 5c pop r12 6be: 41 5d pop r13 6c0: 41 5e pop r14 6c2: 41 5f pop r15 6c4: c3 ret 6c5: 90 nop 6c6: 66 2e 0f 1f 84 00 00 nop WORD PTR cs:[rax+rax*1+0x0] 6cd: 00 00 00 00000000000006d0 <__libc_csu_fini>: 6d0: f3 c3 repz ret セクション .fini の逆アセンブル: 00000000000006d4 <_fini>: 6d4: 48 83 ec 08 sub rsp,0x8 6d8: 48 83 c4 08 add rsp,0x8 6dc: c3 ret
解析してみる
一番最初に、実行されるmain関数を探してみる。数行の命令がある。
000000000000063a <main>: 63a: 55 push rbp 63b: 48 89 e5 mov rbp,rsp 63e: 48 8d 3d 9f 00 00 00 lea rdi,[rip+0x9f] # 6e4 <_IO_stdin_used+0x4> 645: e8 c6 fe ff ff call 510 <puts@plt> 64a: b8 00 00 00 00 mov eax,0x0 64f: 5d pop rbp 650: c3 ret 651: 66 2e 0f 1f 84 00 00 nop WORD PTR cs:[rax+rax*1+0x0] 658: 00 00 00 65b: 0f 1f 44 00 00 nop DWORD PTR [rax+rax*1+0x0]
ざっくりみていく。 最初に出てくる2行は、関数呼び出しをする際のお決まり「Function prologue」コード
63a: 55 push rbp 63b: 48 89 e5 mov rbp,rsp
つぎにここで、文字列 hello world.
の先頭に当たるアドレスを rdi
レジスタに設定している。printfするときは、フォーマットとなる文字列がrdi
に設定されるらしい。
63e: 48 8d 3d 9f 00 00 00 lea rdi,[rip+0x9f] # 6e4 <_IO_stdin_used+0x4>
hello world.
と言う文字列はどこからくるの?という疑問が沸いてくるが、これは # 6e4 <_IO_stdin_used+0x4>
の部分から読み取ることができる。 6e4
というアドレスはrodataセクション内のアドレスのため、以下のようなコマンドで確認することが可能。ちなみに6e4
はrip(0x645 <- 次の命令のアドレス) + 0x9f
が対象のアドレスになる。
# objdump -s -j .rodata a.out a.out: ファイル形式 elf64-x86-64 セクション .rodata の内容: 06e0 01000200 68656c6c 6f20776f 726c642e ....hello world. 06f0 00
そして、ここではコンソールへの文字列表示を行っている。
645: e8 c6 fe ff ff call 510 <puts@plt>
アキュムレーターは、関数の戻り値を格納するときにも使用するらしい。今回は正常終了の 0
を eax
レジスタに設定している。
64a: b8 00 00 00 00 mov eax,0x0
その後は、スタックに積んでいたベースポインタレジスタを rbp
にpopしてきて(Function epilogue)retでmain関数から復帰する。
64f: 5d pop rbp 650: c3 ret
総括
すごくざっくりと走ったが、このくらいの内容から始めるなら何とかなりそうな気がしてきた。
参考になるサイト
レジスタについてまとまっている