工控系统安全之HOOK技术(上课教程)
分别采用IAT Hook和inline Hook技术对系统函数Hook 1. 利用IAT Hook来挂钩user32.dll中的GetTopWindow函数,实现调用GetTopWindow会弹出提示框 2. 利用Inline Hook技术实现对user32.dll中的MessageBoxA的Hook,实现弹框前,先Beep一声
提示: 考虑到inline Hook实现对新手较难,可以先使用mhook库或Detours库来实现
#include "windows.h"
typedef struct _IATHOOK_DATA { DWORD dwValueAddr; //IAT项中保存函数地址的地址 DWORD dwOriginValue; //原始数据 DWORD dwNewValue; //新的数据 }IATHOOK_DATA, *PIATHOOK_DATA;
DWORD IATHook(CHAR szDllName[], CHAR szFuncName[], PIATHOOK_DATA pIATHookData); DWORD IATUnHook(PIATHOOK_DATA pIATHookData);
HWND WINAPI MyGetTopWindow(HWND hWnd);
IATHOOK_DATA IATHookData;
int WINAPI WinMain( __in HINSTANCE hInstance, __in_opt HINSTANCE hPrevInstance, __in_opt LPSTR lpCmdLine, __in int nShowCmd ) { IATHookData.dwNewValue = (DWORD)MyGetTopWindow; IATHook("user32.dll", "GetTopWindow", &IATHookData); GetTopWindow(NULL); return 0; }
typedef HWND (WINAPI *GETTOPWINDOW)(HWND hWnd); HWND WINAPI MyGetTopWindow(HWND hWnd) { GETTOPWINDOW OriginGetTopWindow = (GETTOPWINDOW)IATHookData.dwOriginValue; Beep(3000, 100); MessageBoxA(NULL, "GetTopWindow哦", "3170604037", MB_OK); return OriginGetTopWindow(hWnd); }
/************************************************************************/ /* 功能:利用修改导入函数表中地址来hook函数 参数:szDllName-导入函数的dll名,szFuncName-函数名,pIATHookData-hook使用的数据 原理:通过判断IMAGE_THUNK_DATA中函数名来得到对应IAT中地址,修改导入函数表中函数地址 */ /************************************************************************/ DWORD IATHook(CHAR szDllName[], CHAR szFuncName[], PIATHOOK_DATA pIATHookData) { DWORD dwBaseAddr; PIMAGE_DOS_HEADER pDosHeader; PIMAGE_NT_HEADERS pNtHeader; PIMAGE_OPTIONAL_HEADER pOptHeaer; PIMAGE_THUNK_DATA pThunk, pIAT; PIMAGE_IMPORT_DESCRIPTOR pImportDes; DWORD dwIndex; DWORD dwOriginProtect;
if (NULL == szDllName || NULL == szFuncName || NULL == pIATHookData) { return ERROR_INVALID_PARAMETER; }
//获取当前文件的基址 dwBaseAddr = (DWORD)GetModuleHandle(NULL); if (NULL == dwBaseAddr) { return GetLastError(); } //从头计算得到引入表的地址 pDosHeader = (PIMAGE_DOS_HEADER)dwBaseAddr; pNtHeader = (PIMAGE_NT_HEADERS)(dwBaseAddr + pDosHeader->e_lfanew); pOptHeaer = &(pNtHeader->OptionalHeader);
pImportDes = (PIMAGE_IMPORT_DESCRIPTOR)(dwBaseAddr + pOptHeaer->DataDirectory[IMAGE_DIRECTORY_ENTRY_IMPORT].VirtualAddress);
while (pImportDes->FirstThunk) { //判断是否是对应模块 if (_stricmp((CHAR*)(dwBaseAddr+pImportDes->Name), szDllName)) { pImportDes ++; continue; }
//当PE文件被装载到内存时,PE装载器将查找IMAGE_THUNK_DATA 和 IMAGE_IMPORT_BY_NAME 这些结构数组, //以此决定引入函数的地址。然后用引入函数真实地址来替代由FirstThunk指向的 IMAGE_THUNK_DATA 数组里的元素值 //若 OriginalFirstThunk 为0,就改用FirstThunk值。有些连接器生成PE文件时会置OriginalFirstThunk值为0,这应该算是个bug。 pIAT = (PIMAGE_THUNK_DATA)(dwBaseAddr + pImportDes->FirstThunk); if (pImportDes->OriginalFirstThunk) { pThunk = (PIMAGE_THUNK_DATA)(dwBaseAddr + pImportDes->OriginalFirstThunk); } else { pThunk = pIAT; }
dwIndex = 0; do { //有些情况下一些函数仅由序数引出,对应该函数的 IMAGE_THUNK_DATA 值 //的低位字指示函数序数,而最高二进位 (MSB)设为1。 //32位时IMAGE_ORDINAL_FLAG = 0x80000000h。 dwIndex = *(DWORD*)pThunk; if (dwIndex != 0) { if ((dwIndex & IMAGE_ORDINAL_FLAG) == 0) { //得到函数名,继续向后跳2字节 if (strcmp((PCHAR)(dwBaseAddr + dwIndex + 2), szFuncName) == 0) { //找到了,先保存原始数据 pIATHookData->dwValueAddr = (DWORD)pIAT; pIATHookData->dwOriginValue = *(DWORD*)pIAT; //修改IAT VirtualProtect(pIAT, 4, PAGE_EXECUTE_READWRITE, &dwOriginProtect); *(DWORD*)pIAT = pIATHookData->dwNewValue; VirtualProtect(pIAT, 4, dwOriginProtect, &dwOriginProtect); return 0; } } } //换下一个 pThunk ++; pIAT ++; } while (1); //取下一模块 pImportDes ++; } return 0; }
/************************************************************************/ /* 功能:修股 参数: 原理: */ /************************************************************************/ DWORD IATUnHook(PIATHOOK_DATA pIATHookData) { DWORD dwOriginProtect;
VirtualProtect((PVOID)pIATHookData->dwValueAddr, 4, PAGE_EXECUTE_READWRITE, &dwOriginProtect); *(DWORD*)pIATHookData->dwValueAddr = pIATHookData->dwOriginValue; VirtualProtect((PVOID)pIATHookData->dwValueAddr, 4, dwOriginProtect, &dwOriginProtect); return 0; }
实现结果
会听到电脑哔一声然后弹出窗口
