x64インラインフッキング 改

# 前置き
[前回](https://qiita.com/KazuMatsuHack9981/items/dea163cc00934567afa8)、機械語を直接CodeCaveに打ち込んで、そっちにジャンプさせていたので、フック後の処理内容を機械語で書かないといけなかったが、今回は、InjectするDLLの方に関数を定義し、そこに飛ばすことによってフック後の処理をC++でかけるようにした。

<br/>

# フック対象のプログラム
[前回](https://qiita.com/KazuMatsuHack9981/items/dea163cc00934567afa8)と変わらず、以下のsample.cをgccでコンパイルした `sample.exe` が対象。1秒毎に "hello" と表示するだけのプログラム。

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

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

<br/>

# 仕組み
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/535664/5ad66edb-d0d7-46db-be4d-2202271554a8.png)
`sample.exe` に、DLL Inject してフックする。
`sample.exe` の `puts` と `sleep` を `call myfunc` に置き換え、DLLの方に定義した `myfunc`(MessageBox表示するだけの関数) を呼び出すようにする。`call` を使うので、呼び出す際はリターンアドレスがスタックに入るので、戻るときは `ret` をするれば元の場所に戻る。

<br/>

# やり方
VisualStudioでDLLのプロジェクト立ち上げ、以下をdllmain.cppにコピペする。

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

typedef unsigned __int64 QWORD;

void __stdcall myfunc() {
    MessageBox(NULL, TEXT("detoured!!"), TEXT("success"), MB_OK | MB_ICONEXCLAMATION);
    return;
}

bool Hook(void* target, void* myfunc, int len) {
    if (len < 12)
        return false;

    DWORD curProtection;
    VirtualProtect(target, len, PAGE_EXECUTE_READWRITE, &curProtection);
    memset(target, 0x90, len);

    // このデバッグ出力の部分は後述
    // char buf[256];
    // snprintf(buf, 256, "myfunc=%p", myfunc);
    // OutputDebugString(buf);

    *(BYTE*)target = 0x48;
    *(BYTE*)((DWORD)target + 1) = 0xb8;
    *(QWORD*)((QWORD)target + 2) = (QWORD)myfunc; // mov rax, <myfuncAddr>
    *(BYTE*)((DWORD)target + 10) = 0xff;
    *(BYTE*)((DWORD)target + 11) = 0xd0;          // call rax


    DWORD temp;
    VirtualProtect(target, len, curProtection, &temp);

    return true;
}


DWORD WINAPI dothread(LPVOID param) {
    int hookLength = 19;
    DWORD hookAddr = 0x00401564;

    if (Hook((void*)hookAddr, myfunc, 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;
}
```

注目する部分は以下の部分。

```c++
*(BYTE*)target = 0x48;
*(BYTE*)((DWORD)target + 1) = 0xb8;
*(QWORD*)((QWORD)target + 2) = (QWORD)myfunc; // mov rax, <myfuncAddr>
*(BYTE*)((DWORD)target + 10) = 0xff;
*(BYTE*)((DWORD)target + 11) = 0xd0;          // call rax
```

ここで言う、`target` には実際にフックしたい場所のアドレス(今回はsample.exeの `call puts` があるアドレス)が入っていて、これを `call myfunc` になるようにしている。
しかし、これは x64 の環境でやっていて、x64のDLLとしてビルドしているので、アドレスが8Byte(64bit)になる場合があるが、実は4Byte(32bit)の `DWORD` は定義されてるが、8Byteの `QWORD` は定義されていないので、`typedef unsigned __int64 QWORD;` をする必要がある。

さらに、なぜ一回 `rax` にジャンプ先アドレスを格納してるかというと、`call <64bitの絶対アドレス>` 用の `call` のオプコードが無いため、このようにしている。
(64bitのアドレスのcallのテンプレとして覚えておくと便利かも)

<br/>

# 実行してみて挙動確認
実際にでは "./sample.exe" を実行してみて、上記DLLをインジェクトしてみる。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/535664/140c507a-9f32-4428-e34c-56040df33765.png)
このように、何回も上図のようなMessageBoxが表示されればいい。

<br/>

# x64dbgで上記コードの意味を確認
実際にではデバッガで処理を追って、上記のフックのコードの意味を確認する。ざっくりと天順は以下のような感じ。

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/fa802497-1514-a87c-e825-ada1f62c0089.png)

このように、./sample.exe は本来は上記のようなアセンブリになっていて、`DWORD hookAddr = 0x00401564` としたように、`0x00401564` で `call puts` をしている。今回はこのアドレスから19バイト分、つまり、

```
call <JMP.&puts>
mov ecx, 3E8
mov rax, qword ptr ds:[<&Sleep>]
call rax
```

を、書き換えるような処理になっていて、以下のようにInject後なるはずである。

```
mov rax, <myfuncAddr>
call rax
```

では実際に手順5までやってみると、

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/535664/b6db449b-991d-2dc7-3501-639d9854b301.png)

インジェクトしたDLLのように、先ほどの部分が上図のように書き換わるはずである。
(ちなみに、ブレークポイントで処理を止めているので、DLL Injectした瞬間には書き換わらないが、一周ループされるとInjectされた状態になる)

では最後の手順6の通り、`call rax` をした直後に、ちゃんと `myfunc` に飛ぶか見てみる。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/535664/3eb335ec-9c9d-3656-6f2e-ec96e9bf2670.png)
こんな感じでおそらくインポートテーブルを参照しに行き、そのままもう一回ステップ実行すると、
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/535664/13957dfc-0be8-c624-2f28-1da8a7007184.png)

うまく行ってればこのように myfunc の中に飛ぶ。あとは `ret` でちゃんと元の位置に戻るかだが、このままステップ実行して一番最後の `ret` の直後に行ってみると、

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/535664/1f8fc6f5-c9b4-6df9-5629-1b81375bc9e6.png)

ちょうど `call rax` した直後に来るはず。これで、最後の `jmp sample.40155D` でループで先頭に戻り、何度も何度もクラッシュせずに MessageBox が表示されればうまくいってる。


<br/>


# デバッグ方法(上手くいかなかった場合)

鬼門が `myfunc` へ飛ばないという所だと思うが、そもそもじゃあ `myfunc` がどのアドレスにあるのかを調べる方法が以下。

まず、VisualStudioだと `printf` とかがなぜか使えないので、[DebugView](https://docs.microsoft.com/en-us/sysinternals/downloads/debugview)というものをダウンロードして適当なディレクトリに置いておく。
ダウンロードした後、`DbgView.exe` を実行して置けば、他のプログラムとかで `OutputDebugString(TEXT("hoge"))` のように呼び出した際に、それを拾って表示してくれるようになる。

ダウンロードして起動出来たら、ソースコードの方でこの部分のコメントアウトを外す。

```c++
// このデバッグ出力の部分は後述
char buf[256];
snprintf(buf, 256, "myfunc=%p", myfunc);
OutputDebugString(buf);
```

これで実際に先ほどの手順の5までやってみると、

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/535664/81a2034e-1422-3e15-685b-4aca5777a198.png)

関係ない出力とかもあるが、`myfunc=00007FFD5BAB1294` と表示されていることがわかり、ここにちゃんと `call` でジャンプできているかどうかを確認すればいいことがわかる。