参考:黑客编程
以CreateFileW为例演示iat hook,代码如下:
#include<Windows.h>
#include<winnt.h>
#include<stdio.h>
#pragma warning(disable:4996)
typedef HANDLE(WINAPI *CREATEFILEW)(
_In_ LPCWSTR lpFileName,
_In_ DWORD dwDesiredAccess,
_In_ DWORD dwShareMode,
_In_opt_ LPSECURITY_ATTRIBUTES lpSecurityAttributes,
_In_ DWORD dwCreationDisposition,
_In_ DWORD dwFlagsAndAttributes,
_In_opt_ HANDLE hTemplateFile
);
CREATEFILEW dwCreateFileWAddr = NULL;
HANDLE WINAPI MyCreateFileW(
_In_ LPCWSTR lpFileName,
_In_ DWORD dwDesiredAccess,
_In_ DWORD dwShareMode,
_In_opt_ LPSECURITY_ATTRIBUTES lpSecurityAttributes,
_In_ DWORD dwCreationDisposition,
_In_ DWORD dwFlagsAndAttributes,
_In_opt_ HANDLE hTemplateFile
)
{
wprintf(L"lpFileName:%s\n", lpFileName);
return dwCreateFileWAddr(lpFileName,
dwDesiredAccess,
dwShareMode,
lpSecurityAttributes,
dwCreationDisposition,
dwFlagsAndAttributes,
hTemplateFile);
}
/*
* moduleToHook 需要hook的模块 字符串
* funcToHook 需要hook的函数名称 字符串
* oriFuncAddr 用来保存被hook函数的原始地址 二级指针
* newFuncAddr 用来接管被hook函数的新函数地址
*/
DWORD hookIat(char* moduleToHook,char* funcToHook,PVOID* oriFuncAddr,PVOID newFuncAddr)
{
DWORD result = 0;
do
{
if (moduleToHook == NULL || funcToHook == NULL)
{
result = 1;
break;
}
HMODULE hMod = GetModuleHandleA(moduleToHook);
if (hMod == NULL)
{
printf("fails to get %s module!\n", moduleToHook);
result = 2;
break;
}
ULONG64 dwFuncAddr = (ULONG64)GetProcAddress(hMod, funcToHook);
CloseHandle(hMod);
//获取当前进程模块基地址
HMODULE hModule = GetModuleHandleA(NULL);//调试的时候这个地方会抛出异常,0xc000008:an invalid handle was specified
if (dwFuncAddr)
{
printf("dwFuncAddr:%llx\n", dwFuncAddr);
}
if (hModule)
{
printf("hModule:%p\n", (PVOID)hModule);
}
//定位PE结构
PIMAGE_DOS_HEADER pDosHdr = (PIMAGE_DOS_HEADER)hModule;
PIMAGE_NT_HEADERS64 pNtHdr = (PIMAGE_NT_HEADERS64)((ULONGLONG)hModule + pDosHdr->e_lfanew);
//保存映像基地址及导入表的rva
ULONGLONG dwImageBase = pNtHdr->OptionalHeader.ImageBase;
ULONG64 dwImpRva = pNtHdr->OptionalHeader.DataDirectory[IMAGE_DIRECTORY_ENTRY_IMPORT].VirtualAddress;
//导入表的va
PIMAGE_IMPORT_DESCRIPTOR pImgDes = (PIMAGE_IMPORT_DESCRIPTOR)(dwImageBase + dwImpRva);
PIMAGE_IMPORT_DESCRIPTOR pTmpImpDes = pImgDes;
BOOL bFound = FALSE;
//查找欲hook函数所在的模块名
while (pTmpImpDes->Name)
{
ULONG64 dwNameAddr = dwImageBase + pTmpImpDes->Name;
char szName[MAXBYTE] = { 0 };
strcpy(szName, (char*)dwNameAddr);
//查找模块
if (strcmp(strlwr(szName), moduleToHook) == 0)
{
bFound = TRUE;
break;
}
pTmpImpDes++;
}
//判断查找结果
if (bFound == TRUE)
{
//
printf("%s base:%llx\n", moduleToHook, dwImageBase);
bFound = FALSE;
//
//逐个遍历模块的iat地址
//注意iat中OriginalFirstThunk和FirstThunk的区别
//
PIMAGE_THUNK_DATA64 pThunk = (PIMAGE_THUNK_DATA64)(pTmpImpDes->FirstThunk + dwImageBase);
PIMAGE_THUNK_DATA64 pOriFirstThunk = (PIMAGE_THUNK_DATA64)(pTmpImpDes->OriginalFirstThunk + dwImageBase);
while (pThunk->u1.Function)
{
ULONG64* pAddr = (ULONG64*)&(pThunk->u1.Function);
PIMAGE_IMPORT_BY_NAME pImgImpName = (PIMAGE_IMPORT_BY_NAME)(pOriFirstThunk->u1.AddressOfData + dwImageBase);
//打印导入的函数名
if (strlen(pImgImpName->Name))
printf("name:%s\n", pImgImpName->Name);
//
if (*pAddr == dwFuncAddr)
{
//
printf("addr of function %s:%llx\n", funcToHook, dwFuncAddr);
bFound = TRUE;
*oriFuncAddr = (PVOID)*pAddr;
ULONG64 dwMyHookAddr = (ULONG64)newFuncAddr;
DWORD oldProtect = 0;
//正常情况下导入表所在的内存页是无法写入数据的
//但是可以通过修改其页属性,然后再写入数据
VirtualProtect(pAddr, sizeof(ULONG64), PAGE_READWRITE, &oldProtect);
//修改为hook函数的地址
if (FALSE == WriteProcessMemory(GetCurrentProcess(),
(LPVOID)pAddr,
&dwMyHookAddr,
sizeof(ULONG64),
NULL))
{
printf("WriteProcessMemory fails,err:%d\n", GetLastError());
}
VirtualProtect(pAddr, sizeof(ULONG64), oldProtect, NULL);
break;
}
pThunk++;
pOriFirstThunk++;
}
//
break;
}
else
{
result = 3;
printf("do not find module %s in iat!\n", moduleToHook);
break;
}
}while (0);
printf("done!\n");
return result;
}
//导入表hook
//
// 使用情景
//
// exe或dll通过隐式调用某个api,例如CreateFileA/CloseHandle,
// 这些api通常直接调用;然后,编译生成pe文件
//之后,可以发现pe文件导入表中有相应的模块及函数。在这种情况下是可以使用iat hook的。
//
//
// 但是,如果是通过GetProcAddress函数来动态获取api地址,也就是显示调用,
// 编译生成的pe文件导入表中是没有相关api的信息,iat hook也就无法排上用场。
//
// 这个时候就可以考虑eat(导出表)hook
//
void testhook()
{
CreateFileW(L"110.txt", 0, 0, 0, 0, 0, 0);
printf("hookIat result:%d\n",hookIat("kernel32.dll", "CreateFileW", (PVOID*)&dwCreateFileWAddr, MyCreateFileW));
printf("hookIat result:%d\n", hookIat("Advapi32.dll", "CreateFileW", (PVOID*)&dwCreateFileWAddr, MyCreateFileW));
//触发被hook的函数
CreateFileW(L"___???.txt", 0, 0, 0, 0, 0, 0);
}
相对于导入表hook,针对的某个pe文件的导入表里面的某个函数,不会影响其他也导入了 该函数的pe文件。导出表hook,就有一个全局的作用,在其他文件中,只要导入了该导出函数就会受影响。由于导出表数据结构的限制,使得其在64位环境下操作起来不像iat hook那么简便,不过eat hook在某些特殊的场景下还是非常适合的。下面是eat hood的基本流程:
ULONG64 eatAddr = 0;
if ((ULONG64)newFuncAddr >dwImageBase)
{
eatAddr = (ULONG64)newFuncAddr - dwImageBase;
}
else
{
eatAddr = dwImageBase - (ULONG64)newFuncAddr;
}
printf("eatAddr:%lld\n", eatAddr);
if (FALSE == WriteProcessMemory(GetCurrentProcess(),
(LPVOID)(tmpAddr+oridinals),
&eatAddr,
sizeof(DWORD),
NULL))
{
printf("WriteProcessMemory fails,err:%d\n", GetLastError());
}
else
{
printf("after eathook addr of %s is %llx\n", funcToHook, dwImageBase + tmpAddr[oridinals]);
}
硬件hook,
PVOID kerCreateFileW = NULL;//kernel32.dll 导出的CreateFileW
PVOID baseCreateFileW = NULL;//kernelbase.dll 导出的CreateFileW
DWORD NTAPI ExceptionHandler(PEXCEPTION_POINTERS pExceptionInfo)
{
if (pExceptionInfo->ExceptionRecord->ExceptionAddress == kerCreateFileW)
{
已经处理了异常,不需要调用下一个异常处理来处理该异常
//
// 在当前系统版本10.0.19045.3086下,kernel32!CreateFileW 函数内部是一个跳转指令。
//
// 直接跳到kernelbase!CreateFileW。所以出来被hook的执行流程是直接将Rip设置为kernelbase!CreateFileW
//
// 其他的应用场景,需要做一定的调整
//
pExceptionInfo->ContextRecord->Rip =(ULONG64)baseCreateFileW;
wprintf(L"(rcx):%s\n", (WCHAR*)(pExceptionInfo->ContextRecord->Rcx));
return EXCEPTION_CONTINUE_EXECUTION;
}
return EXCEPTION_CONTINUE_SEARCH;
}
//
// hardHook根据参数pointToHook,对指定的地址进行硬件hook(硬件断点的类型,长度省略)
//
// 首先,通过CreateThread创建一个线程,在线程内部会循环调用待hook的函数
//
// 然后,暂停线程,设置硬件断点来hook pointToHook对于的地址
//
// 最后,恢复线程执行,触发硬件hook
//
DWORD hardHook(PVOID pointToHook)
{
CONTEXT context;
HANDLE hThread = INVALID_HANDLE_VALUE;
DWORD threadId = 0;
//设置异常出来函数
SetUnhandledExceptionFilter(ExceptionHandler);
//创建线程,然后通过硬件断点来hook CreateFileW函数
//
//由于硬件断点的使用是与具体的线程相关的,所以在没有设置硬件hook的线程中,
// CreateFileW的执行流程是不会被劫持的
//
hThread = CreateThread(NULL, 0, ThreadRoutine, NULL, 0, &threadId);
//暂停线程
SuspendThread(hThread);
//获取线程环境
context.ContextFlags = CONTEXT_ALL;
if (GetThreadContext(hThread, &context) == TRUE)
{
//设置硬件断点,使用dr0寄存器
context.Dr7 = (DWORD)1;
//设置线性地址
context.Dr0 = (ULONG64)pointToHook;
// 设置硬件断点
if (SetThreadContext(hThread, &context) == TRUE)
{
printf("new thread context is set!\n");
}
}
else
{
printf("GetThreadContext fails,error:%d\n", GetLastError());
}
//唤醒线程
ResumeThread(hThread);
CreateFileW(L"___?00o??.txt", 0, 0, 0, 0, 0, 0);
WaitForSingleObject(hThread, INFINITE);
return 0;
}