Ⅰ. はじめに
呼出規約は呼出規約(Wikipedia)を見る限りいくつか種類があるようですが、以下4つの呼出規約のみをこの記事でまとめます。
この4つ以外はマイナーな為省略します。
- cdecl
- stdcall
- fastcall
- thiscall
Ⅱ. 環境
この記事の内容は以下の環境の場合について書かれています。
コンパイラやアーキテクチャにより詳細が異なる場合があります。
OS | Windows |
IDE | VisualStudio 2017 Community |
Windows SDK Version | 10.0.16299.0 |
Platform Toolset | Visual Studio 2017(v141) |
Target Platform | Win32 x86 |
Ⅲ. 呼出規約(calling convention)とは
関数を呼び出す際の決め事です。
以下の事柄がそれぞれの呼出規約ごとに決まっています。
- 引数をどのように渡すか
- 戻り値をどのように返すか
- スタック処理をどのように行うか
...等
上記の事が決まっていない場合、どこに引数があるのか、戻り値をどうやって返したら良いのか等が分からない状態となります。
ABIの定義にも含まれます。
Ⅳ. cdecl
main.cpp
int __cdecl sum(int a, int b, int c) { return a + b + c; } int main() { sum(1, 2, 3); return 0; }
逆アセンブル
sub_401000(sum): push ebp mov ebp, esp mov eax, [ebp + 0x8] // [ebp + 0x8] = 第1引数 add eax, [ebp + 0xC] // [ebp + 0xC] = 第2引数 add eax, [ebp + 0x10] // [ebp + 0x10] = 第3引数 pop ebp retn main: push 3 // 第3引数 push 2 // 第2引数 push 1 // 第1引数 call sub_401000 add esp, 0xC
引数 | スタックのみ |
戻り値 | EAX |
スタック処理 | 呼出側 |
ポイント
- sum関数呼び出し後にsum関数呼び出し側でスタック処理(add esp, 0xC)をしている
- 関数呼び出し側がスタック処理を行う為、可変引数を持つ関数の呼出規約は必ずcdeclになる
リバースエンジニア向けのポイント
- call の後に add esp, ? となっていた場合の呼び出し規約はcdeclの可能性が高い
- push が3個とadd esp, 0xCから4バイトの引数が3個あるという事が分かる
Ⅴ. stdcall
main.cpp
int __stdcall sum(int a, int b, int c) { return a + b + c; } int main() { sum(1, 2, 3); return 0; }
逆アセンブル
sub_401000(sum): push ebp mov ebp, esp mov eax, [ebp + 0x8] // [ebp + 0x8] = 第1引数 add eax, [ebp + 0xC] // [ebp + 0xC] = 第2引数 add eax, [ebp + 0x10] // [ebp + 0x10] = 第3引数 pop ebp retn 0xC main: push 3 // 第3引数 push 2 // 第2引数 push 1 // 第1引数 call sub_401000
引数 | スタックのみ |
戻り値 | EAX |
スタック処理 | 関数側 |
ポイント
- sum関数呼び出し後にsum関数側でスタック処理(retn 0xC)をしている
リバースエンジニア向けのポイント
- callの後に add esp, ?が無い場合の呼び出し規約は stdcall の可能性が高い
- call先の retn に値の指定がある場合はstdcallの可能性が高い
Ⅵ. fastcall
main.cpp
int __fastcall sum(int a, int b, int c) { return a + b + c; } int main() { sum(1, 2, 3); return 0; }
逆アセンブル
sub_401000(sum): push ebp mov ebp, esp lea eax, [ecx + edx] add eax, [ebp + 0x8] pop ebp retn 4 main: push 3 // 第3引数 mov edx, 2 // 第2引数 mov ecx, 1 // 第1引数 call sub_401000
引数 | レジスタとスタック |
戻り値 | EAX |
スタック処理 | 関数側 |
ポイント
リバースエンジニア向けのポイント
- callの前でECXとEDXに値を入れている場合の呼び出し規約はfastcallである可能性が高い
- callの前でECXだけに値を入れている場合の呼出規約はfastcallかthiscallである可能性が高い
Ⅶ. thiscall
main.cpp
class MyClass { private: int a, b, c; public: MyClass(int a, int b, int c) { this->a = a; this->b = b; this->c = c; } int __thiscall sum() { return this->a + this->b + this->c; } }; int main() { MyClass myClass(1, 2, 3); myClass.sum(); return 0; }
逆アセンブル
sub_401000(MyClassのコンストラクタ): push ebp mov ebp, esp mov eax, [ebp + 0x8] // [ebp + 0x8] = 第1引数 mov [ecx], eax mov eax, [ebp + 0xC] // [ebp + 0xC] = 第2引数 mov [ecx + 4], eax mov eax, [ebp + 0x10] // [ebp + 0x10] = 第3引数 mov [ecx + 8], eax mov eax, ecx pop ebp retn 0xC sub_401030(MyClassのsum): push ebp mov ebp, esp mov eax, [ecx] // [ecx] = MyClassの第1メンバ変数 add eax, [ecx + 0x4] // [ecx + 0x4] = MyClassの第2メンバ変数 add eax, [ecx + 0x8] // [ecx + 0x8] = MyClassの第3メンバ変数 pop ebp retn main: push 3 // 第3引数 push 2 // 第2引数 push 1 // 第1引数 lea ecx, [ebp - 0xC] // [ebp - 0xC] = MyClassのthisポインタ call sub_401000 lea ecx, [ebp - 0xC] // [ebp - 0xC] = MyClassのthisポインタ call sub_401030
引数 | スタックのみ |
戻り値 | EAX |
スタック処理 | 関数側 |
ポイント
- ECXが必ずthisポインタの役割をする
- ECXのthisポインタ以外はstdcallと同じ
リバースエンジニア向けのポイント
- callの前でEDXに値を入れず、ECXのみに値を入れて、PUSHしている場合の呼び出し規約はthiscallである可能性が高い
まとめ
cdecl | stdcall | fastcall | thiscall | |
引数 | スタックのみ | スタックのみ | レジスタとスタック | スタックのみ |
戻り値 | EAX | EAX | EAX | EAX |
スタック処理 | 呼出側 | 関数側 | 関数側 | 関数側 |