前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >SSDT Hook的妙用-对抗ring0 inline hook

SSDT Hook的妙用-对抗ring0 inline hook

作者头像
战神伽罗
发布2019-07-24 15:15:01
9640
发布2019-07-24 15:15:01
举报

******************************************************* 1,SSDT SSDT即系统服务描述符表,它的结构如下(参考《Undocument Windows 2000 Secretes》第二章): typedef struct _SYSTEM_SERVICE_TABLE { PVOID ServiceTableBase; //这个指向系统服务函数地址表 PULONG ServiceCounterTableBase; ULONG NumberOfService; //服务函数的个数,NumberOfService*4 就是整个地址表的大小 ULONG ParamTableBase; }SYSTEM_SERVICE_TABLE,*PSYSTEM_SERVICE_TABLE; typedef struct _SERVICE_DESCRIPTOR_TABLE { SYSTEM_SERVICE_TABLE ntoskrnel; //ntoskrnl.exe的服务函数 SYSTEM_SERVICE_TABLE win32k; //win32k.sys的服务函数,(gdi.dll/user.dll的内核支持) SYSTEM_SERVICE_TABLE NotUsed1; SYSTEM_SERVICE_TABLE NotUsed2; }SYSTEM_DESCRIPTOR_TABLE,*PSYSTEM_DESCRIPTOR_TABLE; 内核中有两个系统服务描述符表,一个是KeServiceDescriptorTable(由ntoskrnl.exe导出),一个是KeServieDescriptorTableShadow(没有导出)。两者的区别是,KeServiceDescriptorTable仅有ntoskrnel一项,KeServieDescriptorTableShadow包含了ntoskrnel以及win32k。一般的Native API的服务地址由KeServiceDescriptorTable分派,gdi.dll/user.dll的内核API调用服务地址由KeServieDescriptorTableShadow分派。还有要清楚一点的是win32k.sys只有在GUI线程中才加载,一般情况下是不加载的,所以要Hook KeServieDescriptorTableShadow的话,一般是用一个GUI程序通过IoControlCode来触发(想当初不明白这点,蓝屏死机了N次都想不明白是怎么回事)。 2,SSDT HOOK SSDT HOOK 的原理其实非常简单,我们先实际看看KeServiceDescriptorTable是什么样的。 lkd> dd KeServiceDescriptorTable 8055ab80 804e3d20 00000000 0000011c 804d9f48 8055ab90 00000000 00000000 00000000 00000000 8055aba0 00000000 00000000 00000000 00000000 8055abb0 00000000 00000000 00000000 00000000 在windbg.exe中我们就看得比较清楚,KeServiceDescriptorTable中就只有第一项有数据,其他都是0。其中804e3d20就是 KeServiceDescriptorTable.ntoskrnel.ServiceTableBase,服务函数个数为0x11c个。我们再看看804e3d20地址里是什么东西: lkd> dd 804e3d20 804e3d20 80587691 805716ef 8057ab71 80581b5c 804e3d30 80599ff7 80637b80 80639d05 80639d4e 804e3d40 8057741c 8064855b 80637347 80599539 804e3d50 8062f4ec 8057a98c 8059155e 8062661f 如上,80587691 805716ef 8057ab71 80581b5c 这些就是系统服务函数的地址了。比如当我们在ring3调用OpenProcess时,进入sysenter的ID是0x7A(XP SP2),然后系统查KeServiceDescriptorTable,大概是这样KeServiceDescriptorTable.ntoskrnel.ServiceTableBase(804e3d20) + 0x7A * 4 = 804E3F08,然后804E3F08 ->8057559e 这个就是OpenProcess系统服务函数所在,我们再跟踪看看: lkd> u 8057559e nt!NtOpenProcess: 8057559e 68c4000000 push 0C4h 805755a3 6860b54e80 push offset nt!ObReferenceObjectByPointer+0x127 (804eb560) 805755a8 e8e5e4f6ff call nt!InterlockedPushEntrySList+0x79 (804e3a92) 805755ad 33f6 xor esi,esi 原来8057559e就是NtOpenProcess函数所在的起始地址。 嗯,如果我们把8057559e改为指向我们函数的地址呢?比如 MyNtOpenProcess,那么系统就会直接调用MyNtOpenProcess,而不是原来的NtOpenProcess了。这就是SSDT HOOK 原理所在。 3, ring0 inline hook ring0 inline hook 跟ring3的没什么区别了,如果硬说有的话,那么就是ring3发生什么差错的话程序会挂掉,ring0发生什么差错的话系统就挂掉,所以一定要很小心。inline hook的基本思想就是在目标函数中JMP到自己的监视函数,做一些判断然后再JMP回去。一般都是修改函数头,不过再其他地方JMP也是可以的。下面我们来点实际的吧: lkd> u nt!NtOpenProcess nt!NtOpenProcess: 8057559e e95d6f4271 jmp f199c500 805755a3 e93f953978 jmp f890eae7 805755a8 e8e5e4f6ff call nt!InterlockedPushEntrySList+0x79 (804e3a92) ... 同时打开“冰刃”跟“Rootkit Unhooker”我们就能在NtOpenProcess函数头看到这样的“奇观”,第一个jmp是“冰刃”的,第二个jmp是“Rootkit Unhooker”的。他们这样是防止被恶意程序通过TerminateProcess关闭。当然“冰刃”还Hook了NtTerminateProcess等函数。 ××××××××××××××××××××××××××××××××××××××××××××××××××××××××××××××× 好了,道理就说完了,下面就进入本文正题。 对付ring0 inline hook的基本思路是这样的,自己写一个替换的内核函数,以NtOpenProcess为例,就是MyNtOpenProcess。然后修改SSDT表,让系统服务进入自己的函数MyNtOpenProcess。而MyNtOpenProcess要做的事就是,实现NtOpenProcess前10字节指令,然后再JMP到原来的NtOpenProcess的十字节后。这样NtOpenProcess函数头写的JMP都失效了,在ring3直接调用OpenProcess再也毫无影响。 ***************************************************************************************************************************

[cpp] view plain copy

  1. #include<ntddk.h>
  2. typedef struct _SERVICE_DESCRIPTOR_TABLE
  3. {
  4. PVOID ServiceTableBase;
  5. PULONG ServiceCounterTableBase;
  6. ULONG NumberOfService;
  7. ULONG ParamTableBase;
  8. }SERVICE_DESCRIPTOR_TABLE,*PSERVICE_DESCRIPTOR_TABLE; //由于KeServiceDescriptorTable只有一项,这里就简单点了
  9. extern PSERVICE_DESCRIPTOR_TABLE KeServiceDescriptorTable;//KeServiceDescriptorTable为导出函数
  10. /////////////////////////////////////
  11. VOID Hook();
  12. VOID Unhook();
  13. VOID OnUnload(IN PDRIVER_OBJECT DriverObject);
  14. //////////////////////////////////////
  15. ULONG JmpAddress;//跳转到NtOpenProcess里的地址
  16. ULONG OldServiceAddress;//原来NtOpenProcess的服务地址
  17. //////////////////////////////////////
  18. __declspec(naked) NTSTATUS __stdcall MyNtOpenProcess(PHANDLE ProcessHandle,
  19. ACCESS_MASK DesiredAccess,
  20. POBJECT_ATTRIBUTES ObjectAttributes,
  21. PCLIENT_ID ClientId)
  22. {
  23. DbgPrint("NtOpenProcess() called");
  24. __asm{
  25. push 0C4h
  26. push 804eb560h //共十个字节
  27. jmp [JmpAddress]
  28. }
  29. }
  30. ///////////////////////////////////////////////////
  31. NTSTATUS DriverEntry(IN PDRIVER_OBJECT DriverObject,PUNICODE_STRING RegistryPath)
  32. {
  33. DriverObject->DriverUnload = OnUnload;
  34. DbgPrint("Unhooker load");
  35. Hook();
  36. return STATUS_SUCCESS;
  37. }
  38. /////////////////////////////////////////////////////
  39. VOID OnUnload(IN PDRIVER_OBJECT DriverObject)
  40. {
  41. DbgPrint("Unhooker unload!");
  42. Unhook();
  43. }
  44. /////////////////////////////////////////////////////
  45. VOID Hook()
  46. {
  47. ULONG Address;
  48. Address = (ULONG)KeServiceDescriptorTable->ServiceTableBase + 0x7A * 4;//0x7A为NtOpenProcess服务ID
  49. DbgPrint("Address:0x%08X",Address);
  50. OldServiceAddress = *(ULONG*)Address;//保存原来NtOpenProcess的地址
  51. DbgPrint("OldServiceAddress:0x%08X",OldServiceAddress);
  52. DbgPrint("MyNtOpenProcess:0x%08X",MyNtOpenProcess);
  53. JmpAddress = (ULONG)NtOpenProcess + 10; //跳转到NtOpenProcess函数头+10的地方,这样在其前面写的JMP都失效了
  54. DbgPrint("JmpAddress:0x%08X",JmpAddress);
  55. __asm{//去掉内存保护
  56. cli
  57. mov eax,cr0
  58. and eax,not 10000h
  59. mov cr0,eax
  60. }
  61. *((ULONG*)Address) = (ULONG)MyNtOpenProcess;//HOOK SSDT
  62. __asm{//恢复内存保护
  63. mov eax,cr0
  64. or eax,10000h
  65. mov cr0,eax
  66. sti
  67. }
  68. }
  69. //////////////////////////////////////////////////////
  70. VOID Unhook()
  71. {
  72. ULONG Address;
  73. Address = (ULONG)KeServiceDescriptorTable->ServiceTableBase + 0x7A * 4;//查找SSDT
  74. __asm{
  75. cli
  76. mov eax,cr0
  77. and eax,not 10000h
  78. mov cr0,eax
  79. }
  80. *((ULONG*)Address) = (ULONG)OldServiceAddress;//还原SSDT
  81. __asm{
  82. mov eax,cr0
  83. or eax,10000h
  84. mov cr0,eax
  85. sti
  86. }
  87. DbgPrint("Unhook");
  88. }

×××××××××××××××××××××××××××××××××××××××××××××××××××××××××× 就这么多了,或许有人说,没必要那么复杂,直接恢复NtOpenProcess不就行了吗?对于象“冰刃”“Rookit Unhooker”这些“善良”之辈的话是没问题的,但是象NP这些“穷凶极恶”之流的话,它会不断检测NtOpenProcess是不是已经被写回去,是的话,嘿嘿,机器马上重启。这也是这种方法的一点点妙用。

本文参与 腾讯云自媒体分享计划,分享自作者个人站点/博客。
如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 作者个人站点/博客 前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体分享计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
相关产品与服务
对象存储
对象存储(Cloud Object Storage,COS)是由腾讯云推出的无目录层次结构、无数据格式限制,可容纳海量数据且支持 HTTP/HTTPS 协议访问的分布式存储服务。腾讯云 COS 的存储桶空间无容量上限,无需分区管理,适用于 CDN 数据分发、数据万象处理或大数据计算与分析的数据湖等多种场景。
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档