型不明の関数呼び出しを別の関数に飛ばす

# 前提

このようなケースを想定します。

```cpp
void Main()
{
  auto res = (Func2)(void*)(&Func1)(0, "Hoge");
}
void Func1()
{
  // Mainに渡された引数を使い、Func2を呼び出したい
}
int Func2(int i, char* str);
```

`Func1`を宣言、実装する際に`(int, char*) -> int`だと分かっていれば、その通りに引数＆戻り値をして`Func2`を普通に呼び出せばよいわけですが、今回はそうではない場合を想定します。

# `Main`から`Func1`にコンテキストが移る直前の状態

Cレベルの呼び出しではなく、`call`命令の直前です。

引数の0と"Hoge"がそれぞれ`rcx`、`rdx`レジスタに設定され40B分のスタック領域がFunc1のために確保されています。

Returnアドレスは`call`によってスタックに積まれるのでまだです。

# `Func2`にコンテキストが移った直後の状態

スタックについては、`()->void`でもかわりません。
40B＋`call`によるReturnアドレス8Bの計48Bが積まれており、`rsp`はこのTopであるReturnアドレスの部分を指しています。スタック渡しの引数があると話が変わってきますが、後述のレジスタと同様になります。

レジスタは`rcx`、`rdx`に引数の値が設定されています。しかし、`Func1`の引数は宣言されていないのでCレベルで使うことはできませんし、そもそもこれが分からない前提です。

# レジスタ、スタックを維持した状態で`Func2`を呼び出す

Cレベルでは不可能ですがアセンブリを直接記述することで可能です。
MSVCのx64の場合インラインアセンブラが使えないので、別ファイルにしリンクします。

```asm
.data
  extern TARGET : qword ; 循環依存にならないためにコールバック
.code
  JumpToTarget proc
    jmp qword ptr [TARGET]
  JumpToTarget endp
end
```

これをmasmでアセンブルするようにしておきます。
一方`Func1`ではこれを呼び出します。

```cpp
void* TARGET;
void JumpToTarget();
void Func1()
{
  TARGET = &Func2;
  JumpToTarget();
}
```

これMSVCでは以下のようなアセンブリにコンパイルされます。

```asm
Func1 proc
  mov rax, Func2
  mov TARGET, rax
  jmp JumpToTarget
Func1 endp
```

唯一汚しているのは`rax`だけですが、`rax`は戻り値用レジスタであり関数を呼び出す前はまだ使われていないので問題ありません。
これにより、`Main`が用意したレジスタ、スタックの状態を維持したまま、`Func2`へコンテキストを飛ばすことができました。
`Func2`がReturnする際では`Main`の`call`によって積まれたReturnアドレスが参照されるので`Main`に直接復帰することになります。

`JumpToTarget`の呼び出しが`call`ではなく`jump`で実装されているのはMSVCの`/O2`最適化によるものです。
ちなみに`/O2`を切って`call`による呼び出しになった場合、スタック48B分を積み上げ/積み下ろし処理が挟まってしまうので状態が壊れます。
積み上げられた分に関しては`JumpToTarget`の頭で調整が聞きますが、`Func2`から戻ってくるときは`JumpToTarget`や`Func1`を経由せず直接`Main`にreturnするので調整が利きません。

# 動的に行う（機械語書き換え）

ここまでは`Func1`を自由にコーディングできる設定でしたが、実行時に動的に`Func1`を書き換えるしかない場合のことを書いておきます。

簡単な話、`&Func1`に先述のに相当する機械語を書き込んでいけばよいということです。
実行時に循環参照も何も関係ありませんのでコールバックは必要ありません。書き込むのは`jmp`命令だけです。

### x64の`jmp`命令

x86でしか使えないものも含めて11種類の`jmp`命令が存在しますが、実質`JMP rel8`と`JMP rel32`と`JMP r/m64`の三つです。

| Opcode | Instructuon |  |
|:-|:-|:-|
| EB cb	| JMP rel8     | if分岐のような近場の相対ジャンプ用（`rip`±2^8） | 
| E9 cw | JMP rel16    | x64使用不可 |
| E9 cd	| JMP rel32    | 即値で相対ジャンプ（`rip`±2^31） |
| FF /4	| JMP r/m16    | x64使用不可 |
| FF /4	| JMP r/m32    | x64使用不可 |
| FF /4	| JMP r/m64    | Register/Memory64bitで絶対ジャンプ（レジスタ、またはメモリの値へ） |
| EA cd	| JMP ptr16:16 | x64使用不可 |
| EA cp	| JMP ptr16:32 | x64使用不可 |
| FF /5	| JMP m16:16   | セグメントレジスタとMemory16bitでFarジャンプ（歴史的名残？） |
| FF /5	| JMP m16:32   | セグメントレジスタとMemory32bitでFarジャンプ（歴史的名残？） |
| REX.W FF /5 | JMP m16:64  | セグメントレジスタとMemory64bitでFarジャンプ（歴史的名残？） |

https://www.felixcloutier.com/x86/jmp
https://www.intel.co.jp/content/dam/www/public/ijkk/jp/ja/documents/developer/EM64T_VOL1_30083402_i.pdf (P.375)

### cb,cw,rel,r/mとかの呪文の意味

https://www.intel.co.jp/content/dam/www/public/ijkk/jp/ja/documents/developer/EM64T_VOL1_30083402_i.pdf （P.74）

ModR/Mだけは複雑なので補足します。
ARMやILのイメージだと命令はOpeCode+Operandだと思うのですが、x86系ではOpeCode+ModR/M+Operandの形式を基本型とします。（更に前にREXが付いたり、間にSIBが挟まることも…）
x86系ではそもそもOpeCode（何するか）のOperand（入出力先）の境界が微妙でその微妙な部分がModR/Mです。どちらかといえばOperandに近いですが。
ModR/Mは基本的に、その命令でレジスタorメモリor即値が使われるのか、レジスタならばどのレジスタなのかを指定します。（Opecodeによってはreg部をOpecodeの追加Bitになります。/4,/5 などのことです）ビットの構成は「r/mでレジスタorメモリのどちらを使うか」のmodが2bit、「片方のレジスタ」を指定するregが3bit、「もう片方のレジスタまたはメモリ」を指定するr/m部が3bitとなっています。

例えば、先の`JumpToTarget`ですと`FF 25 D2 DA 00 00`となっていました。分解すると…

```
FF -> Opecode=JMP
25 -> 00 100 101
    00  -> ModR/Mのmod部
    100 -> ModR/Mのreg部ではなく、FFに /4 として貸し出し Opecode=JMP r/m64
    101 -> ModR/Mのr/m部、modと合わせて Operand=[`rip`+disp32](memory)
D2DA0000 -> ripからのオフセットの即値（Dispracement32bit）、LEなので 0xDAD2
```

つまり、「`rip`+0xDAD2のメモリを読みだした値に絶対ジャンプをする」という意味になります。

https://www.wdic.org/w/SCI/ModR/M

### `&Func1`の指定

絶対ジャンプをしたいので使うInstructuon（Opecode）としては`JMP r/m64`となりますが問題はOperandです。
アドレス即値にJmpということができません。そのため一旦メモリかレジスタを経由する必要があります。幸いにも呼び出し前なので`rax`を汚してしまっても問題ありませんから、`rax`に`&Func2`をセットして`rax`をOperandとしたJMPをしましょう。

`rax`に64bit即値をセットする命令は以下のようになります。

```
mov rax, 1122334455667788h
48 B8 88 77 66 55 44 33 22 11
48 -> 0100 1 0 0 0
    0100 -> REXプレフィックス
    1 -> 有効 演算を64bitに拡張
    // R = B + X * 即値
    0 -> 無効 R(Dst)指定レジスタをx64追加レジスタに切り替え (Ex: rcx->r8)
    0 -> 無効 X(SrcIndex)指定レジスタをx64追加レジスタに切り替え
    0 -> 無効 B(SrcBase)指定レジスタをx64追加レジスタに切り替え
8877665544332211 -> セットする即値（Immidiate64bit）、LEなので0x1122334455667788
```

https://www.felixcloutier.com/x86/mov

### `rax`にジャンプ

`JumpToTarget`を読み解いたときの要領でOperandを[`rip`+disp32]から`rax`に変更します

```
jmp rax
FF 25
FF -> Opecode=JMP
25 -> 00 100 000
    11  -> ModR/Mのmod部
    100 -> ModR/Mのreg部ではなく、FFに /4 として貸し出し Opecode=JMP r/m64
    000 -> ModR/Mのr/m部、modと合わせて Operand=rax(register)
```

### 機械語を書き込む

命令の書いてある`.code`セクションは普通には書き込みができませんので、`VirtualProtect`でそれを外して書き込みます。

```cpp
typedef void* FUNC;
FUNC func1 = (FUNC)0x8877665544332211;
FUNC func2 = (FUNC)0x1122334455667788;
unsigned char machineCode[]
{
  0x48, 0xB8, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // mov rax, 0000000000000000h
  0xFF, 0x25, // jmp rax
};
*(FUNC*)(&machineCode[2]) = func2;
DWORD oldProtection;
VirtualProtect(func1, _countof(machineCode), PAGE_EXECUTE_READWRITE, &oldProtection);
memcpy(func1, machineCode, _countof(machineCode));
```

# 参考

https://www.intel.co.jp/content/dam/www/public/ijkk/jp/ja/documents/developer/EM64T_VOL1_30083402_i.pdf
https://godbolt.org/
https://tanakamura.github.io/pllp/docs/x8664_language.html