バイナリアン入門 第一回(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 +++

内容的には、 putswrite が目につく。

静的解析

下準備や動的解析などを間に挟んできたが、本日のメインはこの静的解析。アセンブリと戦わなくてはならないのがここ。ちなみに、今回解析する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セクション内のアドレスのため、以下のようなコマンドで確認することが可能。ちなみに6e4rip(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>

アキュムレーターは、関数の戻り値を格納するときにも使用するらしい。今回は正常終了の 0eax レジスタに設定している。

 64a:   b8 00 00 00 00          mov    eax,0x0

その後は、スタックに積んでいたベースポインタレジスタrbp にpopしてきて(Function epilogue)retでmain関数から復帰する。

 64f:   5d                      pop    rbp
 650:   c3                      ret

総括

すごくざっくりと走ったが、このくらいの内容から始めるなら何とかなりそうな気がしてきた。

参考になるサイト

レジスタについてまとまっている

www.zombie-hunting-club.com