API Hook (Sleep関数に対するIAT Hook)

# IATの場所
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/535664/ddd2ed28-ab51-9953-07f5-09725a422920.png)
注意としてIATはプログラムロード前とロード後で入ってる内容が違う。今回は実際にプログラムを走らせてそのプロセスのIATをいじっていくので、ロード後をいじっていくことになる。
ILTの方も関数の名前を調べるのに使う(このアドレスがSleep関数のものかみたいな)。

# IAT Hook のイメージ
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/535664/fdc61393-c763-450e-215a-2e7ea17e88ae.png)
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/535664/25523a06-453a-64d8-ca4a-6789ccfc9eb8.png)

# 自プロセスに対するIAT Hook
まずは自分の実行ファイルのSleep関数の呼び出しを `MessageBox` を表示する `MySleepFunc` に書き換える方法が以下。しかし、本来の "Hook" は別の処理をさせた後、本来のSleepに処理を戻すので、 `MySleepFunc` の `originalSleep(dwMilliseconds)` をコメントアウトすれば元のSleepに処理が戻るようになっている。

```c++:self-iat-hook.cpp
#include <windows.h>
#include <stdio.h>
#include <Dbghelp.h>
#include <sstream>
#pragma comment(lib, "Dbghelp")


typedef void* (WINAPI* OriginalSleep)(DWORD);
OriginalSleep originalSleep;


void WINAPI MySleepFunc(DWORD dwMilliseconds) {
    MessageBox(NULL, TEXT("Hooked Sleep!!!"), TEXT("success"), MB_OK | MB_ICONEXCLAMATION);
    // originalSleep(dwMilliseconds);
    return;
}



int WINAPI WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, LPSTR lpCmdLine, int nCmdShow) {
    TCHAR szMyPath[MAX_PATH];
    ULONG cbSize = 0;

    GetModuleFileName(NULL, szMyPath, _countof(szMyPath));
    HANDLE hModule = GetModuleHandle((LPCSTR)szMyPath);
    
    originalSleep = (OriginalSleep)GetProcAddress(GetModuleHandle("kernel32.dll"), "Sleep");
    PIMAGE_IMPORT_DESCRIPTOR pImageImportDescriptor = (PIMAGE_IMPORT_DESCRIPTOR)ImageDirectoryEntryToData(hModule, TRUE, IMAGE_DIRECTORY_ENTRY_IMPORT, &cbSize);

    for (; pImageImportDescriptor->Name; pImageImportDescriptor++) {
        LPCSTR pModuleName = (LPCSTR)((PBYTE)hModule + pImageImportDescriptor->Name);
        DWORD ModuleBase = (DWORD)GetModuleHandle(pModuleName);

        PIMAGE_THUNK_DATA pFirstThunk = (PIMAGE_THUNK_DATA)((PBYTE)hModule + pImageImportDescriptor->FirstThunk);
        PIMAGE_THUNK_DATA pOriginalFirstThunk = (PIMAGE_THUNK_DATA)((PBYTE)hModule + pImageImportDescriptor->OriginalFirstThunk);

        for (; pFirstThunk->u1.Function; pFirstThunk++, pOriginalFirstThunk++) {
            FARPROC pfnImportedFunc = (FARPROC)(pFirstThunk->u1.Function);
            PIMAGE_IMPORT_BY_NAME pImageImportByName = (PIMAGE_IMPORT_BY_NAME)((PBYTE)hModule + pOriginalFirstThunk->u1.AddressOfData);

            if (pfnImportedFunc == (FARPROC)originalSleep) {
                MEMORY_BASIC_INFORMATION mbi;
                DWORD dwJunk = 0;

                VirtualQuery(pFirstThunk, &mbi, sizeof(MEMORY_BASIC_INFORMATION));
                if (!VirtualProtect(mbi.BaseAddress, mbi.RegionSize, PAGE_EXECUTE_READWRITE, &mbi.Protect)) {
                    MessageBox(NULL, TEXT("VirtualProtect Failed!"), TEXT("error"), MB_OK | MB_ICONERROR);
                    return FALSE;
                }

                pFirstThunk->u1.Function = (ULONGLONG)(DWORD_PTR)MySleepFunc;
                if (VirtualProtect(mbi.BaseAddress, mbi.RegionSize, mbi.Protect, &dwJunk))
                    break;
            }

        }
    }

    MessageBox(NULL, TEXT("start sleep"), TEXT("info"), MB_OK);
    Sleep(3000);
    MessageBox(NULL, TEXT("end sleep"), TEXT("info"), MB_OK);

    return TRUE;
}
```


注意としては、

```c++
originalSleep = (OriginalSleep)GetProcAddress(GetModuleHandle("kernel32.dll"), "Sleep");
```

をやっているので、`originalSleep=MySleepFunc` とすればいいのではと思うかもしれないが、`GetProcAddress` はDLLがエクスポートしている関数のアドレスをただ取得しているだけで、IATに格納されているアドレスへのポインタではない。つまり、`originalSleep` を書き換えてもIATを書き換えている事にはならない。

コードの大まかな流れは、

1. ImageBase取得
1. Sleep関数のアドレスをDLLのエクスポートを見て取得(originalSleep)
1. DLL毎にある各インポートテーブル(IMAGE_IMPORT_DESCRIPTOR)のNameメンバから、Dllの名前とそのDLLが配置されてるベースアドレスを取得
1. インポートテーブルのメンバからIAT(FirstThunk)、ILT(OriginalFirstThunk)へのポインタ取得
1. IAT/ILTには各関数毎にIMAGE_THUNK_DATAがあるのでそれをfor文で回してる
1. pfnImportedFuncにu1.Function(IAT内の関数の実際のアドレス)へのポインタが入る
1. pdfImporedFunc(IATのアドレス)がSleepの関数のアドレスとマッチしたらif文内に入る
1. IATを書き換えるため、読み書きの権限にメモリの権限をセット
1. pFirstThunk->u1.Function(IAT内のSleepのアドレス)にMySleepFuncのアドレスをセット
1. メモリの権限戻す
1. Sleepを呼び出す(これがhookされる)


# リモートプロセスに対するIAT Hook
ぶっちゃけ上記のコードをDLLにして、DLL Injectionすればいいだけ(DLL Injectionは適当にツール使う)。
というのも、

```c++
GetModuleFileName(NULL, szMyPath, _countof(szMyPath));
HANDLE hModule = GetModuleHandle((LPCSTR)szMyPath);
```

これは、DLL InjectionしてDLLがアタッチされた後に上記が実行されると、Injectした対象のプロセスが取得でき、かつInjectした対象のメモリをいじれるようになるので、あまりリモートにやるのも自分自身のプロセスにやるのも変わらない。

以下ソース。

```c++:remote-iat-hook.cpp
#include "pch.h"
#include <windows.h>
#include <Dbghelp.h>
#include <sstream>
#pragma comment(lib, "Dbghelp")


typedef void* (WINAPI* OriginalSleep)(DWORD);
OriginalSleep originalSleep;


void WINAPI MySleepFunc(DWORD dwMilliseconds) {
    MessageBox(NULL, TEXT("Hooked Sleep!!!"), TEXT("success"), MB_OK | MB_ICONEXCLAMATION);
    // originalSleep(dwMilliseconds);
    return;
}


BOOL APIENTRY DllMain( HMODULE hModule,
                       DWORD  ul_reason_for_call,
                       LPVOID lpReserved
                     )
{
    if (ul_reason_for_call == DLL_PROCESS_ATTACH) {
        TCHAR szMyPath[MAX_PATH];
        ULONG cbSize = 0;

        GetModuleFileName(NULL, szMyPath, _countof(szMyPath));
        HANDLE hModule = GetModuleHandle((LPCSTR)szMyPath);
        std::stringstream s;
        s << szMyPath << std::endl;
        MessageBox(NULL, s.str().c_str(), TEXT("target"), MB_OK);

        originalSleep = (OriginalSleep)GetProcAddress(GetModuleHandle("kernel32.dll"), "Sleep");
        PIMAGE_IMPORT_DESCRIPTOR pImageImportDescriptor = (PIMAGE_IMPORT_DESCRIPTOR)ImageDirectoryEntryToData(hModule, TRUE, IMAGE_DIRECTORY_ENTRY_IMPORT, &cbSize);

        for (; pImageImportDescriptor->Name; pImageImportDescriptor++) {
            LPCSTR pModuleName = (LPCSTR)((PBYTE)hModule + pImageImportDescriptor->Name);
            DWORD ModuleBase = (DWORD)GetModuleHandle(pModuleName);

            PIMAGE_THUNK_DATA pFirstThunk = (PIMAGE_THUNK_DATA)((PBYTE)hModule + pImageImportDescriptor->FirstThunk);
            PIMAGE_THUNK_DATA pOriginalFirstThunk = (PIMAGE_THUNK_DATA)((PBYTE)hModule + pImageImportDescriptor->OriginalFirstThunk);

            for (; pFirstThunk->u1.Function; pFirstThunk++, pOriginalFirstThunk++) {
                FARPROC pfnImportedFunc = (FARPROC)(pFirstThunk->u1.Function);
                PIMAGE_IMPORT_BY_NAME pImageImportByName = (PIMAGE_IMPORT_BY_NAME)((PBYTE)hModule + pOriginalFirstThunk->u1.AddressOfData);

                if (pfnImportedFunc == (FARPROC)originalSleep) {
                    MEMORY_BASIC_INFORMATION mbi;
                    DWORD dwJunk = 0;

                    VirtualQuery(pFirstThunk, &mbi, sizeof(MEMORY_BASIC_INFORMATION));
                    if (!VirtualProtect(mbi.BaseAddress, mbi.RegionSize, PAGE_EXECUTE_READWRITE, &mbi.Protect)) {
                        MessageBox(NULL, TEXT("VirtualProtect Failed!"), TEXT("error"), MB_OK | MB_ICONERROR);
                        return FALSE;
                    }

                    pFirstThunk->u1.Function = (ULONGLONG)(DWORD_PTR)MySleepFunc;
                    if (VirtualProtect(mbi.BaseAddress, mbi.RegionSize, mbi.Protect, &dwJunk))
                        break;
                }
            }
        }
    }

    return TRUE;
}
```

これをInjectする対象として、1秒間隔でずっとSleepを実行する以下を使う。

```c++:test-sleep.cpp
#include <windows.h>
#include <stdio.h>

int WINAPI WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, LPSTR lpCmdLine, int nCmdShow) {
	int i=0;
	while(1) {
		if(i%5==0) printf("%d\n", i);
		Sleep(1000);
		i++;
	}
	return TRUE;
}
```

上記をコンパイルし、実際にインジェクトしてみると、

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/535664/fc577f88-d823-1f52-809e-cad1578306c3.png)

Hookできる!