バイナリアン入門 第二回(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変数初期化
変数領域sum
を0
で初期化する。
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
まとめ
雑にサクサクと読んでいったけど、関数呼び出しを含めた基礎の基礎の部分をふんわりと触れた。