バイナリアン入門 第二回(x64, Linux)

はじめに

第一回ではmain関数の中で引数なしのprintfを利用しただけなので、今回は関数呼び出しと可変長引数のprintfを使用して解析してみようと思う。

解析用コードの作成

引数を5つ貰う関数の呼び出しとその結果を出力する単純なコードを用意する。
また、アセンブラコードではローカル変数がどのように扱われるのかを確認するため、add関数内では敢えてsum変数を用意してみた。

#include <stdio.h>

int add(int a, int b, int c, int d, int e) {
    int sum = 0;
    sum = a + b + c + d + e;
    return sum;
}

int main(void)
{
    printf("sum = %d\n", add(1, 2, 3, 4, 0));
    return 0;
}

実行ファイルを作成する

gcc sample.c

実行ファイルの解析

Radare2を使って、バイナリを解析する。
Radare2は以前Twitterで知ったんだけど、結構使いやすくて重宝しそうな予感満載。

使い方は、このページがとても詳しい。
radare.gitbooks.io

何ができるか調べる分にはいいんだけど、詳しすぎてあれなので、解析する上で最低限必要そうなコマンドが紹介されているこのサイトが大変参考になりました。 www.bioerrorlog.work

r2 -d a.out
Process with PID 14020 started...
= attach 14020 14020
bin.baddr 0x5564c6ff6000
Using 0x5564c6ff6000
asm.bits 64
[0x7f7b5ad84090]> aaa
[x] Analyze all flags starting with sym. and entry0 (aa)
[Warning: Invalid range. Use different search.in=? or anal.in=dbg.maps.x
Warning: Invalid range. Use different search.in=? or anal.in=dbg.maps.x
[x] Analyze function calls (aac)
[x] Analyze len bytes of instructions for references (aar)
[x] Check for objc references
[x] Check for vtables
[TOFIX: aaft can't run in debugger mode.ions (aaft)
[x] Type matching analysis for all functions (aaft)
[x] Propagate noreturn information
[x] Use -AA or aaaa to perform additional experimental analysis.

[0x7f7b5ad84090]> afl
0x5564c6ff7050    1 42           entry0
0x5564c6ff9fe0    1 4124         reloc.__libc_start_main
0x5564c6ff7080    4 41   -> 34   sym.deregister_tm_clones
0x5564c6ff70b0    4 57   -> 51   sym.register_tm_clones
0x5564c6ff70f0    5 57   -> 50   entry.fini0
0x5564c6ff7040    1 6            sym..plt.got
0x5564c6ff7130    1 5            entry.init0
0x5564c6ff7000    3 23           map.root_a.out.r_x
0x5564c6ff7210    1 1            sym.__libc_csu_fini
0x5564c6ff7135    1 58           sym.add
0x5564c6ff7214    1 9            sym._fini
0x5564c6ff71b0    4 93           sym.__libc_csu_init
0x5564c6ff716f    1 61           main
0x5564c6ff7030    1 6            sym.imp.printf
0x5564c6ff6000    3 404  -> 393  loc.imp._ITM_deregisterTMCloneTable
0x5564c6ff61aa    5 32   -> 55   fcn.5564c6ff61aa

[0x7f7b5ad84090]> s main
[0x5564c6ff716f]> pdf
/ (fcn) main 61
|   int main (int argc, char **argv, char **envp);
|           ; DATA XREF from entry0 @ 0x5564c6ff706d
|           0x5564c6ff716f      55             push rbp
|           0x5564c6ff7170      4889e5         mov rbp, rsp
|           0x5564c6ff7173      41b800000000   mov r8d, 0
|           0x5564c6ff7179      b904000000     mov ecx, 4
|           0x5564c6ff717e      ba03000000     mov edx, 3
|           0x5564c6ff7183      be02000000     mov esi, 2
|           0x5564c6ff7188      bf01000000     mov edi, 1
|           0x5564c6ff718d      e8a3ffffff     call sym.add
|           0x5564c6ff7192      89c6           mov esi, eax
|           0x5564c6ff7194      488d3d690e00.  lea rdi, qword str.sum____d ; 0x5564c6ff8004 ; "sum = %d\n"
|           0x5564c6ff719b      b800000000     mov eax, 0
|           0x5564c6ff71a0      e88bfeffff     call sym.imp.printf     ; int printf(const char *format)
|           0x5564c6ff71a5      b800000000     mov eax, 0
|           0x5564c6ff71aa      5d             pop rbp
\           0x5564c6ff71ab      c3             ret

[0x5564c6ff716f]> s sym.add
[0x5564c6ff7135]> pdf
/ (fcn) sym.add 58
|   sym.add (int32_t arg1, int32_t arg2, int32_t arg5, int32_t arg3, int32_t arg4);
|           ; var int32_t var_24h @ rbp-0x24
|           ; var int32_t var_20h @ rbp-0x20
|           ; var int32_t var_1ch @ rbp-0x1c
|           ; var int32_t var_18h @ rbp-0x18
|           ; var int32_t var_14h @ rbp-0x14
|           ; var int32_t var_4h @ rbp-0x4
|           ; arg int32_t arg1 @ rdi
|           ; arg int32_t arg2 @ rsi
|           ; arg int32_t arg5 @ r8
|           ; arg int32_t arg3 @ rdx
|           ; arg int32_t arg4 @ rcx
|           ; CALL XREF from main @ 0x5564c6ff718d
|           0x5564c6ff7135      55             push rbp
|           0x5564c6ff7136      4889e5         mov rbp, rsp
|           0x5564c6ff7139      897dec         mov dword [var_14h], edi ; arg1
|           0x5564c6ff713c      8975e8         mov dword [var_18h], esi ; arg2
|           0x5564c6ff713f      8955e4         mov dword [var_1ch], edx ; arg3
|           0x5564c6ff7142      894de0         mov dword [var_20h], ecx ; arg4
|           0x5564c6ff7145      448945dc       mov dword [var_24h], r8d ; arg5
|           0x5564c6ff7149      c745fc000000.  mov dword [var_4h], 0
|           0x5564c6ff7150      8b55ec         mov edx, dword [var_14h]
|           0x5564c6ff7153      8b45e8         mov eax, dword [var_18h]
|           0x5564c6ff7156      01c2           add edx, eax
|           0x5564c6ff7158      8b45e4         mov eax, dword [var_1ch]
|           0x5564c6ff715b      01c2           add edx, eax
|           0x5564c6ff715d      8b45e0         mov eax, dword [var_20h]
|           0x5564c6ff7160      01c2           add edx, eax
|           0x5564c6ff7162      8b45dc         mov eax, dword [var_24h]
|           0x5564c6ff7165      01d0           add eax, edx
|           0x5564c6ff7167      8945fc         mov dword [var_4h], eax
|           0x5564c6ff716a      8b45fc         mov eax, dword [var_4h]
|           0x5564c6ff716d      5d             pop rbp
\           0x5564c6ff716e      c3             ret

main関数の解析

main関数から読んでみる。

push rbp
mov rbp, rsp
mov r8d, 0
mov ecx, 4
mov edx, 3
mov esi, 2
mov edi, 1
call sym.add
mov esi, eax
lea rdi, qword str.sum____d ; 0x5564c6ff8004 ; "sum = %d\n"
mov eax, 0
call sym.imp.printf     ; int printf(const char *format)
mov eax, 0
pop rbp
ret

Function prologue

関数呼び出しをする際のお決まり(Function prologue)

push   rbp
mov    rbp,rsp

add関数への引数設定

add関数に渡すための値をレジスタに設定している。

引数の渡し方はx86-64のABIに準拠している。 どのABIか調べるにはIntelのサイトで「linux 64」などのキーワードで検索すると、該当するPDFが見つかると思う。
関数呼び出しに関する決まりごとは23ページのFigure 3.4: Register Usageに記載がある。

%rdi used to pass 1st argument to functions
%rsi used to pass 2nd argument to functions
%rdx used to pass 3rd argument to functions; 2nd return
%rcx used to pass 4th integer argument to functions
%r8 used to pass 5th argument to functions
%r9 used to pass 6th argument to functions

mov r8d, 0
mov ecx, 4
mov edx, 3
mov esi, 2
mov edi, 1

add関数呼び出し

call sym.add

printfへの引数設定

printf関数に渡すための値をレジスタに設定している。

第一引数のrdiにはフォーマットとなる文字列のアドレスを設定し、第二引数のesiにはsum = %d\nの一つ目の%dに表示するための合計値を格納している(戻り値はeaxに格納される)

mov eax, 0命令は、今まで見てきた引数渡しのパターンに当てはまらないため調べてみると、printfの引数がベクトルレジスタ浮動小数点)を必要とする場合、その数を設定する必要があるらしい。今回は整数型なので0を設定している。

ここに関しては、PDFの55ページ3.5.7 Variable Argument Listsで説明されている。

When a function taking variable-arguments is called, %rax must be set to the total number of floating point parameters passed to the function in vector registers.

mov esi, eax
lea rdi, qword str.sum____d ; 0x5564c6ff8004 ; "sum = %d\n"
mov eax, 0
call sym.imp.printf         ; int printf(const char *format)

main()終了

main関数の戻り値としてeaxに0を設定し、スタックに積んでいたベースポインタの値をrbpにポップ(Function epilogue)してきたらretでmain関数を終了する。

mov eax, 0
pop rbp
ret   

add関数の解析

次にadd関数内の処理を読んでみる。

push rbp
mov rbp, rsp
mov dword [var_14h], edi ; arg1
mov dword [var_18h], esi ; arg2
mov dword [var_1ch], edx ; arg3
mov dword [var_20h], ecx ; arg4
mov dword [var_24h], r8d ; arg5
mov dword [var_4h], 0
mov edx, dword [var_14h]
mov eax, dword [var_18h]
add edx, eax
mov eax, dword [var_1ch]
add edx, eax
mov eax, dword [var_20h]
add edx, eax
mov eax, dword [var_24h]
add eax, edx
mov dword [var_4h], eax
mov eax, dword [var_4h]
pop rbp
ret

関数呼び出しをする際のお決まり(Function prologue)

push rbp
mov rbp, rsp

コールスタックに引数を格納

渡された引数をコールスタックに格納している。

なぜ[rbp-0x14]という中途半端な場所からスタートしているのか調べてみると、「アライメント境界」というのが関係しているらしいが、よくわからなかったのでそのまま進める。

mov dword [var_14h], edi ; arg1
mov dword [var_18h], esi ; arg2
mov dword [var_1ch], edx ; arg3
mov dword [var_20h], ecx ; arg4
mov dword [var_24h], r8d ; arg5

sum変数初期化

変数領域sum0で初期化する。

mov dword [var_4h], 0

引数の合計値を求める

渡された引数1から引数5までの値を足している。

mov edx, dword [var_14h]
mov eax, dword [var_18h]
add edx, eax
mov eax, dword [var_1ch]
add edx, eax
mov eax, dword [var_20h]
add edx, eax
mov eax, dword [var_24h]
add eax, edx

sum変数に結果を格納

足し算の結果を変数sumに当たるアドレス[rbp-0x4]に格納し、戻り値としてeaxレジスタに値を格納する。

mov dword [var_4h], eax
mov eax, dword [var_4h]

add関数終了

add関数の戻り値としてeaxに0を設定し、スタックに積んでいたベースポインタの値をrbpにポップ(Function epilogue)してきたらretでmain関数に戻る。

pop rbp
ret 

まとめ

雑にサクサクと読んでいったけど、関数呼び出しを含めた基礎の基礎の部分をふんわりと触れた。