Windows内核实验:InlineHook

Windows内核实验:InlineHook

hook的话,我们肯定要把代码hook到内核空间里,所以必须要把代码写到内核里,我们可以利用ExAllocatePool分配一块内存或者在内核里找一块安全的内核不会用到的内存

kd> dq @gdtr l100
8003f000 00000000`00000000 00cf9b00`0000ffff
8003f010 00cf9300`0000ffff 00cffb00`0000ffff
8003f020 00cff300`0000ffff 80008b04`200020ab
8003f030 ffc093df`f0000001 0040f300`00000fff
......
8003f110 f840933d`5400ffff 00000000`8003f120
8003f120 00000000`8003f128 00000000`8003f130
8003f130 00000000`8003f138 00000000`8003f140
8003f140 00000000`8003f148 00000000`8003f150
8003f150 00000000`8003f158 00000000`8003f160
8003f160 00000000`8003f168 00000000`8003f170
8003f170 00000000`8003f178 00000000`8003f180
8003f180 00000000`8003f188 00000000`8003f190
8003f190 00000000`8003f198 00000000`8003f1a0
8003f1a0 00000000`8003f1a8 00000000`8003f1b0
8003f1b0 00000000`8003f1b8 00000000`8003f1c0
8003f1c0 00000000`8003f1c8 00000000`8003f1d0
8003f1d0 00000000`8003f1d8 00000000`8003f1e0
8003f1e0 00000000`8003f1e8 00000000`8003f1f0
8003f1f0 00000000`8003f1f8 00000000`8003f200
8003f200 00000000`8003f208 00000000`8003f210
8003f210 00000000`8003f218 00000000`8003f220
8003f220 00000000`8003f228 00000000`8003f230
8003f230 00000000`8003f238 00000000`8003f240
8003f240 00000000`8003f248 00000000`8003f250
8003f250 00000000`8003f258 00000000`8003f260
.........

gdt表里其实有好多没有用的位置,完全满足我们写小代码hook的需求

hook的函数_KiFastCallEntry,这个函数会被频繁调用,3环到0环的需求是很频繁的

.text:80541510                               _KiFastCallEntry proc near              ; DATA XREF: KiLoadFastSyscallMachineSpecificRegisters(x)+24↑o
.text:80541510
.text:80541510 B9 23 00 00 00 mov ecx, 23h ; '#'
.text:80541515 6A 30 push 30h ; '0'
.text:80541517 0F A1 pop fs
.text:80541519 8E D9 mov ds, ecx
.text:8054151B 8E C1 mov es, ecx
.text:8054151D 64 8B 0D 40 00 00 00 mov ecx, large fs:40h
.text:80541524 8B 61 04 mov esp, [ecx+4]
.text:80541527 6A 23 push 23h ; '#'
.text:80541529 52 push edx
.text:8054152A 9C pushf

hook到8054151D处,这里ecx寄存器会再次被赋值,我们就可以利用ecx寄存器去避免jmp偏移地址的计算(膜拜周壑orz)

注入代码地址选择到gdt表8003f130处

#include <stdio.h>
#include <stdlib.h>
#include <windows.h>
void JmpTarget();
char* p = (char *)0x8003f130;
size_t i;
void __declspec(naked) IdtEntry() {
__asm {
push 0x30
pop fs
sti
}
for (i = 0; i < 0x30; i++) {
//裸函数里尽量不用局部变量,因为没有栈帧
//试了一下如果这里用size_t i=0;的话,使用了寄存器edi计数,但是还是在用全局变量保险
*p = ((char*)JmpTarget)[i];
p++;
}
__asm {
cli //这里关中断是为了防止还没把fs寄存器恢复时,被时钟中断调度走,导致其他程序用了不正确的fs寄存器而蓝屏
push 0x3B
pop fs
iretd
}
}
void __declspec(naked) JmpTarget() {
__asm {
mov ecx, 0x23
push 0x30
pop fs
mov ds, cx
mov es, cx
mov ecx,0x8054151D
jmp ecx
}
}
void interrupt() {
__asm {
int 0x20
}
}
int main() {
if (0x401040 != IdtEntry) {
printf("IdtRntry address wrong");
system("pause");
exit(-1);
}
interrupt();
system("pause");
}
kd> u 0x8003f130 l30
8003f130 b923000000 mov ecx,23h
8003f135 6a30 push 30h
8003f137 0fa1 pop fs
8003f139 668ed9 mov ds,cx
8003f13c 668ec1 mov es,cx
8003f13f b91d155480 mov ecx,offset nt!KiFastCallEntry+0xd (8054151d)
8003f144 ffe1 jmp ecx

80541510这个地址是不可写的,CR0[16] WP(Write Protect) 写保护位,为1时,禁止内核级代码写用户级的只读内存页;为0时允许

偏移=8003f130-80541515=FFAF DC1B

#include <stdio.h>
#include <stdlib.h>
#include <windows.h>
void __declspec(naked) IdtEntry() {
__asm {
mov eax, cr0
and eax, not 0x10000
mov cr0, eax

//E9 1B DC AF FF
mov al, 0xe9
mov ds : [0x80541510] , al
mov dword ptr ds : [0x80541511],0xFFAFDC1B

mov eax, cr0
or eax, 0x10000
mov cr0, eax
iretd
}
}
void interrupt() {
__asm {
int 0x20
}
}
int main() {
if (0x401040 != IdtEntry) {
printf("IdtRntry address wrong");
system("pause");
exit(-1);
}
interrupt();
system("pause");
}

这样写代码去改_KiFastCallEntry入口点代码主要是因为我们是单核单线程的机器

如果是多核多线程,显然会有并发问题,比如我cpu1在改的过程中还没改完,此时调度到cpu2执行可能执行到我们还没改完的代码造成蓝屏,需要加把锁

ok了

kd> u 80541510
nt!KiFastCallEntry:
80541510 e91bdcafff jmp 8003f130
80541515 6a30 push 30h
80541517 0fa1 pop fs
80541519 8ed9 mov ds,cx
8054151b 8ec1 mov es,cx
8054151d 648b0d40000000 mov ecx,dword ptr fs:[40h]
80541524 8b6104 mov esp,dword ptr [ecx+4]
80541527 6a23 push 23h
kd> u 8003f130
8003f130 b923000000 mov ecx,23h
8003f135 6a30 push 30h
8003f137 0fa1 pop fs
8003f139 668ed9 mov ds,cx
8003f13c 668ec1 mov es,cx
8003f13f b91d155480 mov ecx,offset nt!KiFastCallEntry+0xd (8054151d)
8003f144 ffe1 jmp ecx
8003f146 cc int 3

然后我们就可以把寄存器都保存一下然后写自己的代码

void __declspec(naked) JmpTarget() {
__asm {
pushad
pushfd
+++++your code++++++
popfd
popad
mov ecx, 0x23
push 0x30
pop fs
mov ds, cx
mov es, cx
mov ecx, 0x8054151D
jmp ecx
}
}