備忘録

備忘録

呼出規約とは

Ⅰ. はじめに

呼出規約は呼出規約(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
スタック処理 関数側
ポイント
  • レジスタとスタックの両方を利用して引数を渡している
  • Windows x86の場合は2個までレジスタ(ECX, EDX)経由で引数を渡すことが出来る
  • 3個目以降はスタック経由で引数が渡される
  • レジスタを利用する為速い
リバースエンジニア向けのポイント
  • 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_401030MyClasssum):
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
スタック処理 呼出側 関数側 関数側 関数側