はじめに
最近、WindowsとLinuxの双方をターゲットに、Reversingをしている。AMD64はともにfastcallを標準の呼び出し規約として採用しているが、ディスアセンブル結果を見比べたら微妙に挙動が異なっていて、解析対象が変わると見るべきレジスタも変わるので、備忘録としてまとめる。
自分が把握するべき範囲内の理解を優先するため、正確性はだいぶ犠牲にしていると思う。その点は留意されたい。
Application Binary Interface: ABI
ABIとは、関数の呼び出し規約、バイナリフォーマット(PEやELFなどのこと)など低レイヤの共通のルールを定め、OSとアプリケーションの互換性を維持する技術仕様のこと。WindowsではWindows ABI、LinuxやBSDなどはSystem V ABIを採用しているとのこと。
スタックアライメントや浮動小数点数の扱いなどもABIに含まれているが、今回は整数値の引数の扱いにのみ着目する。
ソースコード
以下のソースコードを共通して用いる。コンパイラは、WindowsではVisual Studio 2026付属のもの、LinuxではUbuntuのGCC 13.3.0を用いる。いずれの場合もコンパイラの最適化は無効にし、func関数の呼び出しの様子を比較する。
#include <stdio.h>
void func (
int arg1, int arg2, int arg3, int arg4,
int arg5, int arg6, int arg7, int arg8
);
int main (void) {
func(1, 2, 3, 4, 5, 6, 7, 8);
return 0;
}
void func (
int arg1, int arg2, int arg3, int arg4,
int arg5, int arg6, int arg7, int arg8
) {
printf("%d\n", arg1);
printf("%d\n", arg2);
printf("%d\n", arg3);
printf("%d\n", arg4);
printf("%d\n", arg5);
printf("%d\n", arg6);
printf("%d\n", arg7);
printf("%d\n", arg8);
return;
}
Windows
Windowsでコンパイルし、生成した実行形式のfunc関数呼び出し部分をディスアセンブルした結果は以下の通り。スタックが必要以上に確保されているように見えるが、呼び出し先(今回はfunc関数)のプロローグでレジスタの値をスタックに退避する分も含めて確保されていた。shadow storeと呼ぶらしい。
レジスタやスタックに格納される値を見ると、第1引数はrcxレジスタ、第2引数はrdxレジスタ、第3引数はr8レジスタ、第4引数はr9レジスタ、第5引数以降はスタックに積まれることがわかる。
sub rsp,0x48
mov dword ptr [rsp+0x38], 0x8
mov dword ptr [rsp+0x30], 0x7
mov dword ptr [rsp+0x28], 0x6
mov dword ptr [rsp+0x20], 0x5
mov r9d, 0x4
mov r8d, 0x3
mov edx, 0x2
mov ecx, 0x1
call func
これらはMicrosoft公式の仕様通りの挙動である。
System V(Linux)
Linuxでコンパイルし、生成した実行形式の関数呼び出し部分をディスアセンブルした結果は以下の通り。同じようにレジスタやスタックに格納される値を見ると、第1引数はrdiレジスタ、第2引数はrsiレジスタ、第3引数はrdxレジスタ、第4引数はrcxレジスタ、第5引数はr8レジスタ、第6引数はr9レジスタ、第7引数以降はスタックに積まれることがわかる。
push 0x8
push 0x7
mov r9d, 0x6
mov r8d, 0x5
mov ecx, 0x4
mov edx, 0x3
mov esi, 0x2
mov edi, 0x1
call func
これも仕様通りの挙動である。ちなみに、func関数内でレジスタに渡されていた引数がスタックに退避されていた。System Vでも引数は全てスタックに退避されると考えても良いかは要検証もしくは要出典。
まとめ
関数が呼び出される際の引数は以下の通りに格納されることがわかった。見るべきレジスタが変わるのもそうだし、Linuxはrdx→rcxの順番なので、これも要注意ポイント。
| ABI | Windows | System V |
|---|---|---|
| 第1引数 | rcxレジスタ | rdiレジスタ |
| 第2引数 | rdxレジスタ | rsiレジスタ |
| 第3引数 | r8レジスタ | rdxレジスタ |
| 第4引数 | r9レジスタ | rcxレジスタ |
| 第5引数 | スタック | r8レジスタ |
| 第6引数 | スタック | r9レジスタ |
| 第7引数以降 | スタック | スタック |
今後のreversingやpwnではこのことを意識して取り組みたいと思う。