第十三章 HOOK技术
13.1 Hook概述
IAT HOOK(改地址)
BOOL IAT_InstallHook()
{
BOOL bResult = FALSE ;
HMODULE hCurExe = GetModuleHandle(NULL);
PULONG_PTR pt ;
ULONG_PTR OrginalAddr;
bResult = InstallModuleIATHook(hCurExe,"user32.dll","MessageBoxA",(PVOID)My_MessageBoxA,&pt,&OrginalAddr);
if (bResult)
{
printf("[*]Hook安装完毕! pThunk=0x%p OriginalAddr = 0x%p\n",pt,OrginalAddr);
g_PointerToIATThunk = pt ;
OldMessageBox = (PFN_MessageBoxA)OrginalAddr ;
}
return bResult;
}
//************************************
// FullName: InstallModuleIATHook
// Description: 为指定模块安装IAT Hook
// Access: public
// Returns: BOOL
// Parameter: HMODULE hModToHook , 待Hook的模块基址
// Parameter: char * szModuleName , 目标函数所在模块的名字
// Parameter: char * szFuncName , 目标函数的名字
// Parameter: PVOID DetourFunc , Detour函数地址
// Parameter: PULONG * pThunkPointer , 用以接收指向修改的位置的指针
// Parameter: ULONG * pOriginalFuncAddr , 用以接收原始函数地址
//************************************
BOOL InstallModuleIATHook(
HMODULE hModToHook,// 要钩取的模块的句柄
char *szModuleName,// 要钩取的模块的名称
char *szFuncName,// 要钩取的函数的名称
PVOID DetourFunc,// 替换原始函数的钩子函数的地址
PULONG_PTR *pThunkPointer,// 用于存储指向导入地址的指针的指针
ULONG_PTR *pOriginalFuncAddr// 用于存储原始函数地址的指针
)
{
PIMAGE_IMPORT_DESCRIPTOR pImportDescriptor; // 指向导入描述符表的指针
PIMAGE_THUNK_DATA pThunkData; // 指向导入函数表的指针
ULONG ulSize; // 导入描述符表的大小
HMODULE hModule=0; // 加载模块的句柄
ULONG_PTR TargetFunAddr; // 目标函数的地址
PULONG_PTR lpAddr; // 导入地址的指针
char *szModName; // 当前模块的名称
BOOL result = FALSE ; // 返回值,默认为失败
BOOL bRetn = FALSE; // 操作结果,默认为失败
hModule = LoadLibrary(szModuleName); // 加载要钩取的模块
TargetFunAddr = (ULONG_PTR)GetProcAddress(hModule,szFuncName); // 获取要钩取的函数的地址
printf("[*]Address of %s:0x%p\n",szFuncName,TargetFunAddr); // 输出目标函数的地址
printf("[*]Module To Hook at Base:0x%p\n",hModToHook); // 输出要钩取的模块的基地址
pImportDescriptor = (PIMAGE_IMPORT_DESCRIPTOR)ImageDirectoryEntryToData(hModToHook, TRUE,IMAGE_DIRECTORY_ENTRY_IMPORT, &ulSize); // 获取导入描述符表的地址
printf("[*]Find ImportTable,Address:0x%p\n",pImportDescriptor); // 输出导入描述符表的地址
while (pImportDescriptor->FirstThunk) // 遍历导入描述符表,直到遇到空描述符
{
szModName = (char*)((PBYTE)hModToHook+pImportDescriptor->Name) ; // 获取当前模块的名称
printf("[*]Cur Module Name:%s\n",szModName); // 输出当前模块的名称
if (stricmp(szModName,szModuleName) != 0) // 若当前模块名称与要钩取的模块名称不匹配
{
printf("[*]Module Name does not match, search next...\n"); // 输出模块名称不匹配的信息
pImportDescriptor++; // 继续下一个导入描述符表
continue; // 继续下一次循环
}
// 程序的导入表处理完毕后OriginalFirstThunk可能是无效的,不能再根据名称来查找,而是遍历FirstThunk直接根据地址判断
pThunkData = (PIMAGE_THUNK_DATA)((BYTE *)hModToHook + pImportDescriptor->FirstThunk); // 获取导入函数表的地址
while(pThunkData->u1.Function) // 遍历导入函数表,直到遇到空条目
{
lpAddr = (ULONG_PTR*)pThunkData; // 获取导入地址的指针
// 找到了地址
if((*lpAddr) == TargetFunAddr) // 如果导入地址与目标函数地址相等
{
printf("[*]Find target address!\n"); // 输出找到目标地址的信息
// 通常情况下导入表所在内存页都是只读的,因此需要先修改内存页的属性为可写
DWORD dwOldProtect; // 旧的保护属性
MEMORY_BASIC_INFORMATION mbi; // 内存页信息结构体
VirtualQuery(lpAddr,&mbi,sizeof(mbi)); // 获取内存页的信息
bRetn = VirtualProtect(mbi.BaseAddress,mbi.RegionSize,PAGE_EXECUTE_READWRITE,&dwOldProtect); // 修改内存页的保护属性为可写
if (bRetn) // 如果修改成功
{
// 内存页属性修改成功,继续下一步操作,先保存原始数据
if (pThunkPointer != NULL) // 如果指针不为空
{
*pThunkPointer = lpAddr ; // 将导入地址的指针赋值给pThunkPointer
}
if (pOriginalFuncAddr != NULL) // 如果指针不为空
{
*pOriginalFuncAddr = *lpAddr ; // 将导入地址的值赋值给pOriginalFuncAddr
}
// 修改地址
*lpAddr = (ULONG_PTR)DetourFunc; // 将导入地址的值修改为钩子函数的地址
result = TRUE ; // 操作成功
// 恢复内存页的属性
VirtualProtect(mbi.BaseAddress,mbi.RegionSize,dwOldProtect,0); // 恢复内存页的保护属性
printf("[*]Hook ok.\n"); // 输出钩子成功的信息
}
break; // 跳出循环
}
//---------
pThunkData++; // 继续下一个导入函数表条目
}
pImportDescriptor++; // 继续下一个导入描述符表
}
FreeLibrary(hModule); // 释放加载的模块
return result; // 返回操作结果
}
InLine Hook(改内容)
13.2 HOOK的分类
名目繁多的 Hook,总结起来其实只有两种:Address Hook和Inline Hook。
Address Hook:IAT、EAT、user32.dll的回调函数表、IDT(中断描述符表)、SSDT(系统服务描述符表)、C++类的虚函数表、COM接口的功能函数表、处理例程地址、特殊寄存器中的地址、特定的函数指针
Inline Hook:
基于异常处理的HOOK
13.3 HOOK位置的挑选
影响最小的 Hook:应用程序中的call Hook,可精确到特定位置对特定 API的调用。
影响最大的Hook:在系统内核中,大部分Hook的位置都会影响整个系统的调用过程,越往下就越明显。
在内核中,KiFastCallEnlry和KeServiceDescriptorTable(含 Shadow)是两个绝佳的Hook位置。
13.4 HOOK的典型过程
Address Hook 的实施过程:定义Detour()函数、定义函数指针、查表(遍历匹配)替换原函数地址、关闭写保护、写入Detour()函数的地址
Inline Hook 的实施过程:确定 Hook方式及需要在Trampoline 中执行的指令、准备TrampolineFun函数、准备jmp指令并写入、CALLL HOOK
二次HOOK
13.5 Detour函数的典型用法
检查参数、检查结果、拦截调用或下发
13.6 HOOK中的注意事项
多线程安全、保存和恢复现场、注意返回值、避免重入
13.7 HOOK在X64平台上的新问题
指针的定义与操作、内存地址对齐、PE格式、调用约定的变化、跳转指令的问题、PatchGuard问题
13.8 HOOK技术的应用
实现增强的二次开发或补丁、信息截获、安全防护
13.9 HOOK的检测、恢复与对抗