ARM64でのAPIフックはどのように行うのでしょうか?
x86/x64系のCPUでは、以下のようなジャンプ命令をAPIの先頭に書き込んでいました。
USER32!MessageBoxA:
00007ffd`f6ff8b70 e90376febf jmp 00007ffd`b6fe0178
「e9」がジャンプ命令を表し、「0376febf」が差分アドレスを表しています。
マシン語の命令部分とデータ部分が分かれているので、分かりやすいです。
(0376febf → bffe7603 に5を足して64bit幅に揃えffffffffbffe7608とし、00007ffdf6ff8b70に足す)
命令に1バイト、差分アドレスに4バイトを使い、計5バイトの命令でジャンプします。
しかしARM64系のCPUでは、アドレッシング64ビットなのにマシン語は32ビット固定であるため、32ビットに無理やり詰め込んでいます。
以下は、「Microsoft Detours」を使ったAPIフックの様子です。
赤色で囲った部分がジャンプ命令です。
x86/x64系では書き換えサイズは5バイトでしたが、ARM64系では12バイトもの領域の書き換えが必要です。
最初の行の、adrp xip1,00007ff8`86aa0000 は、「xip1レジスタに、00007ff886aa0000という値を入れなさい」という意味です。
d0dfff11 adrp xip1,00007ff8`86aa0000
この命令を「d0dfff11」という4バイトに詰め込んでいます。
d0dfff11を2進数で表すと、11010000110111111111111100010001 になります。
これの各ビットに意味が込められています。
上の図の、 水色 の部分が adrp 命令であることを表しています。
そして 緑色 の部分は、差分アドレスの上位19ビットと、下位2ビットが分かれて格納されています。
赤色 の部分は、格納先レジスタの番号です。10001は10進数だと17です。
ですので、レジスタのx17に入れるということになります。
おや? xip1 はどこへ行ったのでしょうか。
実は xip0はx16レジスタのことで、xip1はx17レジスタのことです。
呼び名が違うだけで全く同じものを指します。
ちなみにxip2は無く、x18レジスタにはTEBのアドレスが入っています。
差分アドレスの21ビット分ですが、これだけでは差分とはならず、これを12ビット左シフトします。
12ビット左シフトするということは、4096倍するということです。これは、メモリのページサイズに起因しています。
110111111111111000000 << 12 → 110111111111111100010000000000000
これで終わりではありません。
110111111111111100010000000000000 の最上位ビットが1になっています。
これは負の数を表していますので、この値を64ビット幅に増やすとき、同じもので上位ビットを埋めます。
1111111111111111111111111111111110111111111111100010000000000000
これが差分アドレスになります。
つまり、FFFFFFFFBFFE2000 です。
命令の現在のアドレスが00007ff8c6abe260なので
00007ff8c6abe260 + FFFFFFFFBFFE2000 = 7FF886AA0260
adrpで代入されるアドレス4KBごとになるので、下3桁は無視されます。
次の行の *ldr xip1,[xip1,#0x2D8] *{.b} は、「xip1レジスタの値に0x2D8を足したアドレスにアクセスし、そこにある値をxip1レジスタに入れなさい」と言う意味です。
f9416e31 ldr xip1,[xip1,#0x2D8]
同じく「f9416e31」に詰め込まれています。
f9416e31を2進数で表すと、11111001010000010110111000110001 になります。
これの各ビットに意味が込められています。
上の図の、 水色 の部分が ldr 命令であることを表しています。
灰色 の部分は、扱うサイズが64bitであることを意味します。ちなみに32ビットのときは10です。
そして 緑色 の部分は扱う数値を表しますが、64bitを扱うのでこの値を8倍した値が使われます。
32bitを扱う場合は4倍です。
黄色 の部分はコピー元レジスタの番号。
赤色 はコピー先レジスタの番号です。
レジスタの番号は両方とも10001です。
x17つまり、xip1のことです。
001011011は0x5B、これを8倍すると、0x2D8になります。
最後の行の *br xip1 *{.b}は、「xip1レジスタに入っているアドレスにジャンプしなさい」です。
マシン語は「d61f0220」になります。これは詰め込む必要は無さそうです。
f9416e31を2進数で表すと、11010110000111110000001000100000 になります。
上の図の、 水色 の部分が br命令であることを表しています。
赤色 はジャンプ先アドレスが格納されているレジスタの番号です。x17つまり、xip1のことです。
そして残りの5ビットは未使用です。