x64インラインフッキング（Detour, CodeCaves, Trampoline）

# 環境
windows10 で VisualStudio 使ってる。x64用でDLL作ってる。
DLL Injection 必要。

# 目的

以下の一秒間隔で "hello" と表示し続けるプログラムを `sample.exe` としてgccでコンパイルしたものに対し、Function Hook をかける。具体的には `printf("hello\n")` の部分を別の自分が書いたアセンブラの方に飛ばして、次の `Sleep(1000)` に戻してくる。

```c:sample.c
#include <stdio.h>
#include <windows.h>

int main(void) {
	while(1) {
		printf("hello\n");
		Sleep(1000);
	}
	return 0;
}
```

※ ちなみになぜかコンパイルすると `printf` ではなく `puts` が呼ばれているので注意。


<br/>


# IAT Hook と Inline Hook の違い

### IAT Hook の利点

* PIEとか関係ない(たぶん)
* どこで何やってるかをアセンブリで解読しなくても、IATだけ書き換えればいい


### IAT Hook の欠点

* IATエントリのアドレスを対応するモジュールのアドレスの範囲内にあるかのチェックですぐ検出される
* ランタイムリンクでモジュールが利用される時に使えない(IAT再構築が必要)
* 関数が序数としてインポートされてるとできないらしい
* そもそもAPIしかフックできない
* WindowsのPatchGuard により弾かれるらしい


### Inline Hook の利点

* **APIだけでなく任意のコードをフックできる**
* モジュールがランタイムリンクされていようが関係ない


### Inline Hook の欠点

* アセンブリのどの部分で何をしているのかを把握しないといけない
* (この方法では)フックした後の処理を機械語で書かないといけない…
* PIE有効の場合アドレスが変わるので、これの対策しないといけない

※ `__asm` を使ってる記事が多いですが、x64の場合はなんか `__asm` 使えないっぽいので機械語です
※ 今回はPIE無しの場合

<br/>



# Inline Hook の仕組み

まず、Inline Hook、Inline Patch、Function Hook、Detour はどれも同じような意味。
大まかな流れは以下のような感じで、全て textセクション の中で起こる。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/535664/7bb61f04-eee2-79dc-8c0b-11e1deee55de.png)

まず、左の図が `sample.exe` の textセクション の様子で、textセクションには実際のプログラムのアセンブリと、その下にパディングのような用途で0で埋められた部分がある。この0で埋められた部分を "**Code Cave**" というらしい。

Inline Hook を行った場合、フックしたい部分、すなわち今回だと `puts` の部分を `jmp` 命令に置き換えて、自分が実行させたいアセンブラを書き込んだ Code Cave に処理を移す。
最後に、元の処理に戻れるように、フックした部分(`call puts`)の下の命令に飛ばす。

`jmp` で置換したコードや、Code Cave に書いた最後の元の処理に戻す命令は "**Trampoline**" と呼ばれる。


<br/>



# Inline Hook やり方

DLL Injection を結局使うので、Visual Studio で新規DLLを作り、以下のようにコードを書く。

```c++:dllmain.cpp
#include "pch.h"
#include <Windows.h>


// jmpに置き換える
bool Hook(void* target, void* myfunc, int len) {
    if (len < 5)
        return false;
    
    DWORD curProtection;
    VirtualProtect(target, len, PAGE_EXECUTE_READWRITE, &curProtection);
    memset(target, 0x90, len);

    DWORD relativeAddr = ((DWORD)myfunc - (DWORD)target) - 5;
    *(BYTE*)target = 0xe8;
    *(DWORD*)((DWORD)target + 1) = relativeAddr;

    DWORD temp;
    VirtualProtect(target, len, curProtection, &temp);
    return true;
}


// CodeCaveに機械語書き込む
bool Inject(void *addr, void *retaddr, int len) {
    DWORD curProtection;
    VirtualProtect(addr, len, PAGE_EXECUTE_READWRITE, &curProtection);
    memset(addr, 0x90, len);
    DWORD putsAddr = 0x00402a60;

    // inject code
    *(BYTE*)((DWORD)addr)      = 0x68;
    *(DWORD*)((DWORD)addr + 1) = (DWORD)addr+11 ; // push return address
    *(BYTE*)((DWORD)addr + 5)  = 0x68;
    *(DWORD*)((DWORD)addr + 6) = (DWORD)putsAddr; // push <&puts>
    *(BYTE*)((DWORD)addr + 10) = 0xc3;            // ret

    // jump back to original flow
    *(BYTE*)((DWORD)addr + len - 6)  = 0x68;
    *(DWORD*)((DWORD)addr + len - 5) = (DWORD)retaddr; // push imm32
    *(BYTE*)((DWORD)addr + len - 1)  = 0xc3;           // ret

    DWORD temp;
    VirtualProtect(addr, len, curProtection, &temp);
    return true;
}


DWORD WINAPI dothread(LPVOID param) {
    int hookLength = 5;
    DWORD hookAddr = 0x00401564;
    int codecaveLength = 100;
    DWORD codecaveAddr = 0x00402cc3;
    DWORD jmpBackAddy = hookAddr + hookLength;

    Inject((void*)codecaveAddr, (void*)jmpBackAddy, codecaveLength);
    if (Hook((void*)hookAddr, (void*)codecaveAddr, hookLength))
        MessageBox(NULL, TEXT("hooked"), TEXT("info"), MB_OK);

    while (1) {
        if (GetAsyncKeyState(VK_ESCAPE))
            break;
        Sleep(50);
    }

    FreeLibraryAndExitThread((HMODULE)param, 0);
    return 0;
}


BOOL APIENTRY DllMain(HMODULE hModule, DWORD ul_reason_for_call, LPVOID lpReserved) {
    if (ul_reason_for_call == DLL_PROCESS_ATTACH)
        CreateThread(0, 0, dothread, hModule, 0, 0);
    return TRUE;
}
```

大まかな手順としては、

1. `Inject`関数で、CodeCaveに `push returnAddr; push puts; ret` を書き込む
1. `Hook`関数で、元のコードの方の `call puts` を `jmp <書き込んだCodeCaveへのアドレス>` に書き換える

という流れで、`call puts` がうまく行かなかったので `push <&puts>; ret` にしてる。
注意としては `push returnAddr; push puts; ret` これの最初の `push returnAddr` をしている理由は、`puts` を呼び出した後、CodeCaveの方のコードにちゃんと戻ってくるようにするためである。
実際にどうなるかをデバッガで確認しながらコードを理解するのが一番わかりやすい。


<br/>


# x64dbgで挙動を確認

実際に上記のDLLをビルドして、DLL Injectionしてデバッガでどうなるか見てみる。
実行の手順は以下。

1. `./sample.exe` でプログラム起動
1. x64dbgを起動して、上記プロセスにアタッチする
1. x64dbgで `call puts` の所にブレークポイント打ってそこで止めておく
1. DLL Injection を実行して先ほどのDLLをInject
1. x64dbgでブレークポイント残したままとりあえずループ一回分実行させる
1. 5の後コードが書き換わっているので、そこからステップ実行とかして挙動確かめる


それなので、まず3までやってみると以下のようになる。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/535664/9b348cd4-c7c0-ed88-680a-4d50e7d07b18.png)

上記のコードで `hookAddr = 0x00401564` としていたが、これはこの `call <JMP.&puts>` の部分のアドレスである。ここを書き換えたい。
ちなみに機械語を挿入するCodeCaveの部分も見てみる。`codecaveAddr = 0x00402cc3` なので、`0x402cc3` を見てみると、

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/535664/f67bd605-2f8d-221b-938d-3dc25c13f32f.png)

たしかに0で埋められていることがわかる。
次に、手順4の通りDLL Injectionをすると、デバッガで止まっているので、DLLがアタッチされた際の `MessageBox(NULL, TEXT("hooked"), TEXT("info"), MB_OK);` はまだ出てこないはず。
ここで、手順5の通り一回実行させると、

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/535664/d1b3d53a-8e46-3a4f-2e8b-ca0499f3eac0.png)

このようにポップは最前面に出てこないものの、一応ちゃんと "hooked" となっていることがわかる。
またよくアセンブラを見てみると、

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/535664/4fe85c65-f2c4-295d-058f-50c57e3765c3.png)

先ほどと違って、 `call <JMP.&puts>` の所が `call sample.402cc3` になっていることがわかる。この`402cc3` は `codecaveAddr = 0x00402cc3` の通り、CodeCaveに挿入した部分の先頭アドレスなので、手順6の通りステップ実行して、CodeCaveの `0x402cc3` に飛んでみる。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/535664/49baaaea-8d3e-04ca-e998-3e780bdc24ac.png)

これが、 `push returnAddr; push puts; ret` の部分で、実際に挿入した機械語の通りになっていることがわかる。
コードの方で、実際に元の処理に戻る際のものが以下だったが、

```c++
// jump back to original flow
*(BYTE*)((DWORD)addr + len - 6)  = 0x68;
*(DWORD*)((DWORD)addr + len - 5) = (DWORD)retaddr; // push imm32
*(BYTE*)((DWORD)addr + len - 1)  = 0xc3;           // ret
```

ここで、`len` は今回は大きく `codecaveLength = 100` のように100取っているので、100Byteほどしたに行くと、
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/535664/d0283d86-7510-d768-39ea-0968e2c2db91.png)
ちゃんと `0x401569` に戻っていることがわかる。実際にここに処理が来ているのかを確認するため、`0x402D21` にブレークポイントを打ち、実行を再開してみると、
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/535664/d4911ee5-135a-b5fb-ab2d-9a765f7793ea.png)
ちゃんと来る。
このままステップ実行すると、
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/535664/47f9695e-8a60-74b5-8ebf-5cda61708e53.png)
このように `call sample.402cc3` の直後に戻ってきていることがわかる。


<br/>


# 普通に動かしてみて挙動を確認
今回は本来の `call puts` をCodeCaveにジャンプするように書き換え、CodeCaveの方で出力する文字とかを変えるとかもしないでただ同じように `puts` を呼び出しただけなので、ぶっちゃけ実行しても結果は変わらないが、まぁ一応やってみる。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/535664/d8abd160-23fb-d576-0126-96e9913dd101.png)

こんな感じでInjectした後も、helloをと表示され続ければOK。
もっとちゃんと確認したければ、

```c++
bool Inject(void *addr, void *retaddr, int len) {
    DWORD curProtection;
    VirtualProtect(addr, len, PAGE_EXECUTE_READWRITE, &curProtection);
    memset(addr, 0x90, len);
    DWORD putsAddr = 0x00402a60;

    // inject code
    // *(BYTE*)((DWORD)addr)      = 0x68;
    // *(DWORD*)((DWORD)addr + 1) = (DWORD)addr+11 ; // push return address
    // *(BYTE*)((DWORD)addr + 5)  = 0x68;
    // *(DWORD*)((DWORD)addr + 6) = (DWORD)putsAddr; // push <&puts>
    // *(BYTE*)((DWORD)addr + 10) = 0xc3;            // ret

    // jump back to original flow
    *(BYTE*)((DWORD)addr + len - 6)  = 0x68;
    *(DWORD*)((DWORD)addr + len - 5) = (DWORD)retaddr; // push imm32
    *(BYTE*)((DWORD)addr + len - 1)  = 0xc3;           // ret

    DWORD temp;
    VirtualProtect(addr, len, curProtection, &temp);
    return true;
}
```

このようにCodeCaveの方で `puts` を呼び出す処理をコメントアウトしてビルドした後、このDLLをInjectしたときに "hello" という表示が止まればOK。

<br/>


# ゲームチートにおける IAT Hook と Inline Hook

[IAT Hook](https://qiita.com/KazuMatsuHack9981/items/3685e55f12969b56bede) を習得した後、実際に自分で作ったUnityのゲームで当たり判定を無効化するために、InterSectRectみたいなAPIをフックしようとしたが、どうやら動的にこのAPIのDLLがリンクされているのか、パッキングされているのかわからないが、このAPIのアドレスがわからずフックできなかった。

どっかの記事でUnityのゲームはパッキングされているという話を聞いたので、[IAT再構築](https://qiita.com/KazuMatsuHack9981/items/7dfbbfeeb6123fe4d28b)を習得して、アンパックをしようと思ったが、そもそもゲームのエントリーポイントとかよくわからないしメンドクサイ。

IAT系は無理だと思い、今回のInline Hookを習得してみた。CheatEngineには変数に値を書き込んでる処理のアセンブリとかそういうのが見れたりするので、こっちの方が使いやすいと思った。
機械語で書かないといけないのがかなり痛いが、全部 `nop(0x90)` に置き換えるのは簡単そう。


<br/>

# 参考
https://www.youtube.com/watch?v=e4HZgx3A-wk