IATの場所
注意としてIATはプログラムロード前とロード後で入ってる内容が違う。今回は実際にプログラムを走らせてそのプロセスのIATをいじっていくので、ロード後をいじっていくことになる。
ILTの方も関数の名前を調べるのに使う(このアドレスがSleep関数のものかみたいな)。
IAT Hook のイメージ
自プロセスに対するIAT Hook
まずは自分の実行ファイルのSleep関数の呼び出しを MessageBox
を表示する MySleepFunc
に書き換える方法が以下。しかし、本来の "Hook" は別の処理をさせた後、本来のSleepに処理を戻すので、 MySleepFunc
の originalSleep(dwMilliseconds)
をコメントアウトすれば元のSleepに処理が戻るようになっている。
#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;
}
注意としては、
originalSleep = (OriginalSleep)GetProcAddress(GetModuleHandle("kernel32.dll"), "Sleep");
をやっているので、originalSleep=MySleepFunc
とすればいいのではと思うかもしれないが、GetProcAddress
はDLLがエクスポートしている関数のアドレスをただ取得しているだけで、IATに格納されているアドレスへのポインタではない。つまり、originalSleep
を書き換えてもIATを書き換えている事にはならない。
コードの大まかな流れは、
- ImageBase取得
- Sleep関数のアドレスをDLLのエクスポートを見て取得(originalSleep)
- DLL毎にある各インポートテーブル(IMAGE_IMPORT_DESCRIPTOR)のNameメンバから、Dllの名前とそのDLLが配置されてるベースアドレスを取得
- インポートテーブルのメンバからIAT(FirstThunk)、ILT(OriginalFirstThunk)へのポインタ取得
- IAT/ILTには各関数毎にIMAGE_THUNK_DATAがあるのでそれをfor文で回してる
- pfnImportedFuncにu1.Function(IAT内の関数の実際のアドレス)へのポインタが入る
- pdfImporedFunc(IATのアドレス)がSleepの関数のアドレスとマッチしたらif文内に入る
- IATを書き換えるため、読み書きの権限にメモリの権限をセット
- pFirstThunk->u1.Function(IAT内のSleepのアドレス)にMySleepFuncのアドレスをセット
- メモリの権限戻す
- Sleepを呼び出す(これがhookされる)
リモートプロセスに対するIAT Hook
ぶっちゃけ上記のコードをDLLにして、DLL Injectionすればいいだけ(DLL Injectionは適当にツール使う)。
というのも、
GetModuleFileName(NULL, szMyPath, _countof(szMyPath));
HANDLE hModule = GetModuleHandle((LPCSTR)szMyPath);
これは、DLL InjectionしてDLLがアタッチされた後に上記が実行されると、Injectした対象のプロセスが取得でき、かつInjectした対象のメモリをいじれるようになるので、あまりリモートにやるのも自分自身のプロセスにやるのも変わらない。
以下ソース。
#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を実行する以下を使う。
#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;
}
上記をコンパイルし、実際にインジェクトしてみると、
Hookできる!