リバースエンジニアリングへの道
出田 守です。
最近、情報セキュリティに興味を持ち、『リバースエンジニアリング-Pythonによるバイナリ解析技法』という本(以降、「教科書」と呼びます)を読みました。
「こんな世界があるのか!かっこいい!」と感動し、私も触れてみたいということでド素人からリバースエンジニアリングができるまでを書いていきたいと思います。
ちなみに、教科書ではPython言語が使用されているので私もPython言語を使用しています。
ここを見ていただいた諸先輩方からの意見をお待ちしております。
軌跡
環境
OS: Windows10 64bit Home (日本語)
CPU: Intel® Core™ i3-6006U CPU @ 2.00GHz × 1
メモリ: 2048MB
Python: 3.6.5
私の環境は、普段Ubuntu16.04を使っていますが、ここではWindows10 64bitを仮想マシン上で立ち上げております。
ちなみに教科書では、Windowsの32bitで紹介されています。
ソフトフックがやりたくて - その2
前回はソースコードがわかっている上での初歩的なフックを学びました。
今回は前回のprintf_loop.exeのアセンブリをちゃんと読んでみます。
printf_loop.exeのアセンブリ解読
実行ファイルから必要な情報を抽出
まず、objdump(windowsのdumpbinでも良い)を使って、必要そうな情報を出力しました。
ただ、基本objdumpの情報を見ますが、必要な時はdumpbinの情報も見ることにします。
$objdump -x printf\_loop.exe > printf\_loop\_info.txt
すべてのヘッダやセクションの情報をprintf_loop_info.txtとして出力。
$objdump -s printf\_loop.exe > printf\_loop\_memory.txt
すべてのメモリダンプをprintf_loop_memory.txtとして出力。
$objdump -D printf\_loop.exe > printf\_loop\_asm.txt
すべての逆アセンブル結果をprintf_loop_asm.txtとして出力。
以下はdumpbinバージョン。
dumpbin /ALL /OUT:printf_loop_info_dumpbin.txt printf_loop.exe
dumpbin /DISASM /OUT:printf_loop_asm_dumpbin.txt printf_loop.exe
解読
必要な情報ファイルが揃ったので解読してみます。
まずエントリーポイントを探してみます。探すといってもprintf_loop_infoに書いてありましたね。
printf_loop.exe: ファイル形式 pei-x86-64
printf_loop.exe
アーキテクチャ: i386:x86-64, フラグ 0x0000012f:
HAS_RELOC, EXEC_P, HAS_LINENO, HAS_DEBUG, HAS_LOCALS, D_PAGED
開始アドレス 0x0000000140011028
固有 0x22
executable
large address aware
Time/Date ****
Magic 020b (PE32+)
MajorLinkerVersion 14
MinorLinkerVersion 15
SizeOfCode 00007c00
SizeOfInitializedData 00007400
SizeOfUninitializedData 00000000
AddressOfEntryPoint 0000000000011028 # エントリポイント
BaseOfCode 0000000000001000
ImageBase 0000000140000000 # ベースアドレス
SectionAlignment 0000000000001000
FileAlignment 0000000000000200
MajorOSystemVersion 6
MinorOSystemVersion 0
MajorImageVersion 0
MinorImageVersion 0
MajorSubsystemVersion 6
MinorSubsystemVersion 0
Win32Version 00000000
SizeOfImage 00025000
SizeOfHeaders 00000400
CheckSum 00000000
Subsystem 00000003 (Windows CUI)
DllCharacteristics 00008160
SizeOfStackReserve 0000000000100000
SizeOfStackCommit 0000000000001000
SizeOfHeapReserve 0000000000100000
SizeOfHeapCommit 0000000000001000
LoaderFlags 00000000
NumberOfRvaAndSizes 00000010
The Data Directory
Entry 0 0000000000000000 00000000 Export Directory [.edata (or where ever we found it)]
Entry 1 00000000000203a0 00000050 Import Directory [parts of .idata]
Entry 2 0000000000023000 0000043c Resource Directory [.rsrc]
Entry 3 000000000001d000 00001cd4 Exception Directory [.pdata]
Entry 4 0000000000000000 00000000 Security Directory
Entry 5 0000000000024000 0000004c Base Relocation Directory [.reloc]
Entry 6 000000000001a6a0 00000038 Debug Directory
Entry 7 0000000000000000 00000000 Description Directory
Entry 8 0000000000000000 00000000 Special Directory
Entry 9 0000000000000000 00000000 Thread Storage Directory [.tls]
Entry a 000000000001a6e0 00000100 Load Configuration Directory
Entry b 0000000000000000 00000000 Bound Import Directory
Entry c 0000000000020000 000003a0 Import Address Table Directory
Entry d 0000000000000000 00000000 Delay Import Directory
Entry e 0000000000000000 00000000 CLR Runtime Header
Entry f 0000000000000000 00000000 Reserved
上記でベースアドレスが140000000で、エントリポイントアドレスが11028となっていることが分かります。つまりエントリポイントは140011028(140000000+11028)ということでしょうか。
140011028: e9 03 13 00 00 jmp 0x140012330 # entry point
エントリポイントのアセンブリを見てみるとすぐにジャンプしちゃうようですね。
ではジャンプ先を見てみます。
140012330: 48 83 ec 28 sub rsp,0x28
140012334: e8 c7 fc ff ff call 0x140012000
140012339: 48 83 c4 28 add rsp,0x28
14001233d: c3 ret
1-4行目
一行目でrspから0x28を減算して領域を確保しています。
二行目で0x140012000のアドレスをcallしています。
三行目で確保した領域のポインタアドレスを元に戻しています。
四行目でリターン。
では、call先を見てみます。
140012000: 48 83 ec 28 sub rsp,0x28
140012004: e8 35 f3 ff ff call 0x14001133e
140012009: e8 12 00 00 00 call 0x140012020
14001200e: 48 83 c4 28 add rsp,0x28
140012012: c3 ret
1-2行目
先ほどとcall命令が一つ増えただけでほぼ同じです。
一つ目のcall先をまず見てみます。
14001133e: e9 cd 29 00 00 jmp 0x140013d10
また飛びます。
140013d10: 48 83 ec 38 sub rsp,0x38
140013d14: 48 b8 32 a2 df 2d 99 movabs rax,0x2b992ddfa232
140013d1b: 2b 00 00
140013d1e: 48 39 05 e3 82 00 00 cmp QWORD PTR [rip+0x82e3],rax # 0x14001c008
140013d25: 74 13 je 0x140013d3a
140013d27: 48 8b 05 da 82 00 00 mov rax,QWORD PTR [rip+0x82da] # 0x14001c008
140013d2e: 48 f7 d0 not rax
140013d31: 48 89 05 c8 82 00 00 mov QWORD PTR [rip+0x82c8],rax # 0x14001c000
140013d38: eb 45 jmp 0x140013d7f
140013d3a: e8 e1 fe ff ff call 0x140013c20
140013d3f: 48 89 44 24 20 mov QWORD PTR [rsp+0x20],rax
140013d44: 48 b8 32 a2 df 2d 99 movabs rax,0x2b992ddfa232
140013d4b: 2b 00 00
140013d4e: 48 39 44 24 20 cmp QWORD PTR [rsp+0x20],rax
140013d53: 75 0f jne 0x140013d64
140013d55: 48 b8 33 a2 df 2d 99 movabs rax,0x2b992ddfa233
140013d5c: 2b 00 00
140013d5f: 48 89 44 24 20 mov QWORD PTR [rsp+0x20],rax
140013d64: 48 8b 44 24 20 mov rax,QWORD PTR [rsp+0x20]
140013d69: 48 89 05 98 82 00 00 mov QWORD PTR [rip+0x8298],rax # 0x14001c008
140013d70: 48 8b 44 24 20 mov rax,QWORD PTR [rsp+0x20]
140013d75: 48 f7 d0 not rax
140013d78: 48 89 05 81 82 00 00 mov QWORD PTR [rip+0x8281],rax # 0x14001c000
140013d7f: 48 83 c4 38 add rsp,0x38
140013d83: c3 ret
1-5行目
1行目はRSPから0x38を減算しています。56Byte分の領域を確保しています。
2行目でRAXに0x2b992ddfa232をコピーしていますね。movabsですが調べるとコンパイルする際に64bitのディスプレイスメントまたは即値のmov場合にmovabsに置き換えられるそうです。
4行目は先ほどのRAXと0x14001c008(RIP+0x82e3)を比較しているようです。ここで疑問に思ったのがどの時点でのRIPなのでしょうか。cmp直前のRIPは0x140013d1eです。この時点での計算結果は0x14001c001ですね。あと0x7足りないです。ということは数えていくと次の命令の直前のRIPということですね。理解しました。そして、0x14001c008は.dataセクションでした。
セクション:
Idx Name Size VMA LMA File off Algn
0 .textbss 00010000 0000000140001000 0000000140001000 00000000 2**4
ALLOC, LOAD, CODE
1 .text 00007adf 0000000140011000 0000000140011000 00000400 2**4
CONTENTS, ALLOC, LOAD, READONLY, CODE
2 .rdata 0000292e 0000000140019000 0000000140019000 00008000 2**4
CONTENTS, ALLOC, LOAD, READONLY, DATA
3 .data 00000200 000000014001c000 000000014001c000 0000aa00 2**4 # ここ
CONTENTS, ALLOC, LOAD, DATA
4 .pdata 000020dc 000000014001d000 000000014001d000 0000ac00 2**2
CONTENTS, ALLOC, LOAD, READONLY, DATA
5 .idata 00000ebd 0000000140020000 0000000140020000 0000ce00 2**2
CONTENTS, ALLOC, LOAD, READONLY, DATA
6 .msvcjmc 00000114 0000000140021000 0000000140021000 0000de00 2**2
CONTENTS, ALLOC, LOAD, DATA
7 .00cfg 0000011b 0000000140022000 0000000140022000 0000e000 2**2
CONTENTS, ALLOC, LOAD, READONLY, DATA
8 .rsrc 0000043c 0000000140023000 0000000140023000 0000e200 2**2
CONTENTS, ALLOC, LOAD, READONLY, DATA
9 .reloc 00000233 0000000140024000 0000000140024000 0000e800 2**2
CONTENTS, ALLOC, LOAD, READONLY, DATA
そしてメモリダンプには0x14001c008からQWORDのリトルエンディアンで0x2b992ddfa232が格納されていることが確認できますね。ちゃんと流れを追えてる!面白い!
tセクション .data の内容:
14001c000 cd5d20d2 66d4ffff 32a2df2d 992b0000 .] .f...2..-.+..
14001c010 00000000 00000000 01000000 01000000 ................
5行目はもし比較結果が等しいなら少し先の0x140013d3aにとびます。
6-9行目
4行目の比較結果が正しくない場合に6-9行目が実行されます。
6行目はraxに0x2b992ddfa232をコピーしてます。
7行目はNOT命令でRAXを反転してますね。
8行目は反転したRAXを0x14001c000のメモリ領域にコピーしています。
9行目で0x140013d7f(つまり24行目)にジャンプします。
10-15行目
10行目は0x140013c20がcallされています。これもあとで見てみます。
11行目は10行目のcallの戻り値RAX(恐らく)をRSP+0x20(RSPから32Byte目)のQWORD領域にコピーします。
12行目は0x2b992ddfa232をRAXにコピーしています。
14行目はRSP+0x20(10行目のcallの戻り値)とRAX(0x2b992ddfa232)を比較しています。
15行目で比較結果が等しくない場合に0x140013d64にジャンプします。
16-18行目
14行目の比較結果が等しい場合に実行されます。
16行目はRAXに0x2b992ddfa233をコピー。
18行目はRAXを先ほど戻り値を格納したRAX+0x20にコピー。
19-25行目
19行目はRSP+20に格納されている値をRAXにコピー。
20行目はRAXを反転。
21行目は0x14001c000(RIP+0x8281)にRAXをコピー。
22行目で確保した領域を元に戻す。
23行目でリターン。
さっくり見た感じ.dataの0x14001c000と0x14001c008に0x2b992ddfa232やその反転の値をコピーしたかったのかもしれません。
10行目に関してはインポート関数が分からなかったのでdumpbinの出力結果を見ます。
0000000140013C20: 40 57 push rdi
0000000140013C22: 48 83 EC 40 sub rsp,40h
0000000140013C26: 48 8D 44 24 28 lea rax,[rsp+28h]
0000000140013C2B: 48 8B F8 mov rdi,rax
0000000140013C2E: 33 C0 xor eax,eax
0000000140013C30: B9 08 00 00 00 mov ecx,8
0000000140013C35: F3 AA rep stos byte ptr [rdi]
0000000140013C37: 48 8D 4C 24 28 lea rcx,[rsp+28h]
0000000140013C3C: FF 15 0E C4 00 00 call qword ptr [__imp_GetSystemTimeAsFileTime]
0000000140013C42: 48 8B 44 24 28 mov rax,qword ptr [rsp+28h]
0000000140013C47: 48 89 44 24 20 mov qword ptr [rsp+20h],rax
0000000140013C4C: FF 15 76 C4 00 00 call qword ptr [__imp_GetCurrentThreadId]
0000000140013C52: 8B C0 mov eax,eax
0000000140013C54: 48 8B 4C 24 20 mov rcx,qword ptr [rsp+20h]
0000000140013C59: 48 33 C8 xor rcx,rax
0000000140013C5C: 48 8B C1 mov rax,rcx
0000000140013C5F: 48 89 44 24 20 mov qword ptr [rsp+20h],rax
0000000140013C64: FF 15 EE C3 00 00 call qword ptr [__imp_GetCurrentProcessId]
0000000140013C6A: 8B C0 mov eax,eax
0000000140013C6C: 48 8B 4C 24 20 mov rcx,qword ptr [rsp+20h]
0000000140013C71: 48 33 C8 xor rcx,rax
0000000140013C74: 48 8B C1 mov rax,rcx
0000000140013C77: 48 89 44 24 20 mov qword ptr [rsp+20h],rax
0000000140013C7C: 48 8D 4C 24 30 lea rcx,[rsp+30h]
0000000140013C81: FF 15 D9 C3 00 00 call qword ptr [__imp_QueryPerformanceCounter]
0000000140013C87: 8B 44 24 30 mov eax,dword ptr [rsp+30h]
0000000140013C8B: 48 C1 E0 20 shl rax,20h
0000000140013C8F: 48 33 44 24 30 xor rax,qword ptr [rsp+30h]
0000000140013C94: 48 8B 4C 24 20 mov rcx,qword ptr [rsp+20h]
0000000140013C99: 48 33 C8 xor rcx,rax
0000000140013C9C: 48 8B C1 mov rax,rcx
0000000140013C9F: 48 89 44 24 20 mov qword ptr [rsp+20h],rax
0000000140013CA4: 48 8D 44 24 20 lea rax,[rsp+20h]
0000000140013CA9: 48 8B 4C 24 20 mov rcx,qword ptr [rsp+20h]
0000000140013CAE: 48 33 C8 xor rcx,rax
0000000140013CB1: 48 8B C1 mov rax,rcx
0000000140013CB4: 48 89 44 24 20 mov qword ptr [rsp+20h],rax
0000000140013CB9: 48 B8 FF FF FF FF mov rax,0FFFFFFFFFFFFh
FF FF 00 00
0000000140013CC3: 48 8B 4C 24 20 mov rcx,qword ptr [rsp+20h]
0000000140013CC8: 48 23 C8 and rcx,rax
0000000140013CCB: 48 8B C1 mov rax,rcx
0000000140013CCE: 48 89 44 24 20 mov qword ptr [rsp+20h],rax
0000000140013CD3: 48 8B 44 24 20 mov rax,qword ptr [rsp+20h]
0000000140013CD8: 48 83 C4 40 add rsp,40h
0000000140013CDC: 5F pop rdi
0000000140013CDD: C3 ret
1-11行目
1行目はRDIをスタックにpushしています。
2行目はRSPから0x40を減算して64Byte確保しています。
3行目はRAXにRSP+0x28のアドレスをコピー。RAX=RSP+0x28のアドレス
4行目はRAXの値(RSP+0x28のアドレス)をRDIにコピー。RDI=RAX=RSP+0x28のアドレス
5行目はEAXとEAXのXORなのでEAXは0。EAX=0
6行目はECXに8を代入。ECX=8
7行目のrep stos命令はES(エクストラセグメント)のRDIにALの内容をECX回コピーするようです。コピーされるたびにRDIは1Byteずつずれて(インクリメントまたはデクリメントされて)いきます。このときALは0のはずなので、ES:EDIの指すメモリの初期化を行っているのでしょう。ちなみにESはDSと同様にデータを格納する際に使用されるセグメントだそうです。BYTE PTR ES:[RDI]からBYTE PTR ES:[RDI+0x8]=0
8行目はRCXにRSP+0x28のアドレスをコピー。RCX=RSP+0x28のアドレス
9行目でGetSystemTimeAsFileTime関数をcall。この間数を調べてみると、
VOID GetSystemTimeAsFileTime(
LPFILETIME lpSystemTimeAsFileTime // ファイル時刻の構造体へのポインタ
);
lpSystemTimeAsFileTime
FILETIME 構造体へのポインタを指定します。システムの現在の日付と時刻が UTC 形式で格納されます。
だそうです。ということはRCXを恐らくこの関数の引数として渡したということでしょう。RCX(RSP+0x28)=システム日付と時刻
10行目は戻り値RSP+0x28をRAXにコピー。RAX=GetSystemTimeAsFileTimeの戻り値
11行目はRSP+0x20にRAXをコピー。RSP+0x20=RAX=GetSystemTimeAsFileTimeの戻り値
12-17行目
12行目でGetCurrentThreadId関数をcallしています。この関数はなく、呼出側スレッドのスレッドIDが返ってくるそうです。
13行目はEAXをEAXにコピー?なぜ?恐らくEAXにはThreadIDが入って入るのでしょう。でもなぜ同じレジスタにコピーしているのか分かりません...。EAX=ThreadID
14行目はRCXにRSP+0x20をコピー。RCX=GetSystemTimeAsFileTimeの戻り値
15行目はRCXとRAXをXORしてRCXに格納。RCX=GetSystemTimeAsFileTimeの戻り値^ThreadID
16行目はRAXにRCXをコピー。RAX=RCX=GetSystemTimeAsFileTimeの戻り値^ThreadID
17行目はRSP+0x20にRAXをコピー。RSP+0x20=RAX=RCX=GetSystemTimeAsFileTimeの戻り値^ThreadID
18-23行目
18行目でGetCurrentProcessId関数をcall。これはPIDを返します。
19行目は再び謎のeax同士コピー。EAX=PID
20行目はRSP+0x20をRCXにコピー。RCX=GetSystemTimeAsFileTimeの戻り値^ThreadID
21行目はRCXとRAXをXOR。RCX=GetSystemTimeAsFileTimeの戻り値^ThreadID^PID
22行目はRAXにRCXをコピー。RAX=RCX=GetSystemTimeAsFileTimeの戻り値^ThreadID^PID
23行目はRSP+0x20にRAXをコピー。RSP+0x20=RAX=RCX=GetSystemTimeAsFileTimeの戻り値^ThreadID^PID
24-38行目
24行目はRSP+0x30のアドレスをRCXにコピー。RCX=RSP+0x30のアドレス
25行目でQueryPerformanceCounter関数をcall。この関数は調べるとクロックを指定された引数に返すようです。
26行目は戻り値のRSP+0x30をEAXにコピー。EAX=RSP+0x30=QueryPerformanceCounterの戻り値
27行目はRAXを0x20(32bit)左シフト。RAX上位=RSP+0x30=QueryPerformanceCounterの戻り値
28行目はRAXとRSP+0x30をXOR。恐らくRAX下位を0に初期化という意味。
29行目はRCXにRSP+0x20をコピー。RCX=RSP+0x20=GetSystemTimeAsFileTimeの戻り値^ThreadID^PID
30行目はRCXとRAXをXOR。RCX=GetSystemTimeAsFileTimeの戻り値^ThreadID^PID^QueryPerformanceCounterの戻り値
31行目はRAXにRCXをコピー。RAX=RCX=GetSystemTimeAsFileTimeの戻り値^ThreadID^PID^QueryPerformanceCounterの戻り値
32行目はRSP+0x20にRAXをコピー。RSP+0x20=RAX=RCX=GetSystemTimeAsFileTimeの戻り値^ThreadID^PID^QueryPerformanceCounterの戻り値
33行目はRAXに0xFFFFFFFFFFFFをコピー。RAX=0xFFFFFFFFFFFF
34行目はRCXにRSP+0x20をコピー。RCX=RSP+0x20=GetSystemTimeAsFileTimeの戻り値^ThreadID^PID^QueryPerformanceCounterの戻り値
35行目はRCXとRAXをAND。RCX=(((GetSystemTimeAsFileTimeの戻り値^ThreadID)^PID)^QueryPerformanceCounterの戻り値)&0xFFFFFFFFFFFF
36行目はRAXにRCXをコピー。RAX=RCX=(((GetSystemTimeAsFileTimeの戻り値^ThreadID)^PID)^QueryPerformanceCounterの戻り値)&0xFFFFFFFFFFFF
37行目はRSP+0x20にRAXをコピー。RSP+0x20=RAX=RCX=(((GetSystemTimeAsFileTimeの戻り値^ThreadID)^PID)^QueryPerformanceCounterの戻り値)&0xFFFFFFFFFFFF
38行目はなぜかRAXにRSP+0x20をコピー。RSP+0x20=RAX=RCX=(((GetSystemTimeAsFileTimeの戻り値^ThreadID)^PID)^QueryPerformanceCounterの戻り値)&0xFFFFFFFFFFFF
39行目はRSPに0x40を加算し領域を元に戻す。
40行目はRDIをpop。
41行目でリターン。
つまり、この流れの戻り値は(((GetSystemTimeAsFileTimeの戻り値^ThreadID)^PID)^QueryPerformanceCounterの戻り値)&0xFFFFFFFFFFFFの結果ということですね!(長いわ!笑)
実はこの一つの流れは__security_init_cookie関数というものでした。
以下にURLを貼っておきます。
関数の説明で
オーバーランから保護されている関数を開始するときにクッキーはスタックに配置され、関数が終了するときにスタックの値がグローバルなクッキーと比較されます。 違いがある場合はバッファー オーバーランが発生したことを意味し、プログラムは直ちに終了します。
とあります。先ほどの一連の流れはグローバルなクッキーを設定していたということですね!面白い!
そして再び
140012000: 48 83 ec 28 sub rsp,0x28
140012004: e8 35 f3 ff ff call 0x14001133e # __security_init_cookie
140012009: e8 12 00 00 00 call 0x140012020
14001200e: 48 83 c4 28 add rsp,0x28
140012012: c3 ret
に戻ってきます。
いきなりmainへ
この後も色々設定があるようなので、いっきにmainへとびます。笑
140011830: 40 55 rex push rbp
140011832: 57 push rdi
140011833: 48 81 ec 28 01 00 00 sub rsp,0x128
14001183a: 48 8d 6c 24 20 lea rbp,[rsp+0x20]
14001183f: 48 8b fc mov rdi,rsp
140011842: b9 4a 00 00 00 mov ecx,0x4a
140011847: b8 cc cc cc cc mov eax,0xcccccccc
14001184c: f3 ab rep stos DWORD PTR es:[rdi],eax
14001184e: 48 8d 0d bb f7 00 00 lea rcx,[rip+0xf7bb] # 0x140021010
140011855: e8 2d f8 ff ff call 0x140011087
14001185a: c7 45 04 00 00 00 00 mov DWORD PTR [rbp+0x4],0x0
140011861: c7 45 24 00 00 00 00 mov DWORD PTR [rbp+0x24],0x0
140011868: ff 15 4a ea 00 00 call QWORD PTR [rip+0xea4a] # 0x1400202b8
14001186e: 89 45 24 mov DWORD PTR [rbp+0x24],eax
140011871: 33 c0 xor eax,eax
140011873: 83 f8 01 cmp eax,0x1
140011876: 74 28 je 0x1400118a0
140011878: 44 8b 45 04 mov r8d,DWORD PTR [rbp+0x4]
14001187c: 8b 55 24 mov edx,DWORD PTR [rbp+0x24]
14001187f: 48 8d 0d a2 83 00 00 lea rcx,[rip+0x83a2] # 0x140019c28
140011886: e8 4b f9 ff ff call 0x1400111d6
14001188b: 8b 45 04 mov eax,DWORD PTR [rbp+0x4]
14001188e: ff c0 inc eax
140011890: 89 45 04 mov DWORD PTR [rbp+0x4],eax
140011893: b9 e8 03 00 00 mov ecx,0x3e8
140011898: ff 15 62 e7 00 00 call QWORD PTR [rip+0xe762] # 0x140020000
14001189e: eb d1 jmp 0x140011871
1400118a0: 48 8d a5 08 01 00 00 lea rsp,[rbp+0x108]
1400118a7: 5f pop rdi
1400118a8: 5d pop rbp
1400118a9: c3 ret
1-10行目
1行目はRBPをスタックにpushしています。
2行目はRDIをスタックにpushしています。
3行目はRSPから0x128(296Byte)を減算しています。
4行目はRBPにRSP+0x20のアドレスをコピー。RBP=RSP+0x20のアドレス
5行目はRDIにRSPをコピー。RDI=RSP
6行目はECXに0x4Aをコピー。ECX=0x4A
7行目はEAXに0xCCCCCCCCをコピー。EAX=0xCCCCCCCC
8行目はES:RDIを0x4A(74)回EAXをコピー。
9行目はRCXにRIS+0xF7BBのアドレスをコピー。ECX=0x140021010
10行目で0x140011087をcall。これは__CheckForDebuggerJustMyCode関数だそうです。さっくり調べた感じですと、デバッガーが自分の所有するコードをデバッグしているかチェックしているようです(間違ってたらすみません)。
11-17行目
11行目はRBP+0x4に0x0をDWORDでコピー。DWORD PTR [RBP+0x4]=0x0
12行目はRBP+0x24に0x0をDWORDでコピー。DWORD PTR [RBP+0x24]=0x0
13行目で0x1400202b8(rip+0xEA4A)のQWORDアドレスをcall。これは__getpid関数です。
14行目でRBP+0x24にEAXをDWORDでコピー。DWORD PTR [RBP+0x24]=EAX(pid?)
15行目はEAXとEAXをXOR。
16行目はEAXと0x1を比較。あれ、15行目でEAX同士XORしているので、絶対この比較Falseちゃいますか?あ、ここがwhile(1)のところでしょうか!?そうに違いない!
17行目は等しければ0x1400118a0へジャンプ。
18-21行目
18行目はR8DにRBP+0X4をDWORDでコピー。R8D=DWORD PTR [RBP+0X4]
19行目はRDXにRBP+0x24をDWORDでコピー。RDX=DWORD PTR [RBP+0x24]
20行目はRCXに0x140019C28(rip+0x83A2)をコピー。RCX=0x140019C28のアドレス。
21行目で0x1400111D6をcall。printf関数です。
22-26行目
22行目はEAXにRBP+0x4をDWORDでコピー。EAX=RBP+0x4
23行目はEAXをインクリメント。
24行目はRBP+0x4にEAXをDWORDでコピー。おやおや?i+=1? RBP+0x4=EAX
25行目はECXに0x3E8(1000)をコピー。
26行目で0x140020000(RIP+0XE762)をcall。Sleep関数です。
27-31行目
27行目は0x140011871(15行目)にジャンプ。
28行目はRSPにRBP+0X108をコピー。RSP=RBP+0X108のアドレス。
29行目はRDIをpop。
30行目はRBPをpop。
31行目はリターン。
こうしてみると、一目瞭然ですね!
DWORD PTR [RBP+0x4]が変数iでDWORD PTR [RBP+0x24]が変数iですね。
ちなみに20行目の0x140019C28はメモリマップを見ると、
140019c20 00000000 00000000 5b25645d 4c6f6f70 ........[%d]Loop
140019c30 20697465 72617469 6f6e2025 64210a00 iteration %d!..
でした。
アセンブリを初めて解読しました。ソースコードを知った上での解読なので、理解も早かったように思います。
今までアセンブリを見ても記号の羅列のように見えましたがこうして時間をかけて解読すると当然ですが一つ一つ意味があり、理解できるのだと実感しました。今後もっとアセンブリを解読し、理解を深めていきたいです。
まとめ
- objdumpの出力結果でmovがmovabsに置き換えられることがある。
- dumpbinで実行ファイルの情報を得ることができる。
- アセンブリも時間をかけて解読すると理解できる。