Windows内核实验:ShadowWalker

Windows内核实验:ShadowWalker

接着上一次inlinehook缺页异常来做进程保护,反调试等

我们hook住缺页异常后可以根据错误码的第五位判断是取指令造成的缺页,还是读写造成的异常,我们就可以对两种不同缺页做不同的异常处理,可以在指令造成缺页时,把正确页挂到tlb里来使程序可以正常执行,读写造成缺页时,挂到一个fake页上来反调试

0环感觉要做到稳定是很难的

inlinehook.c

#include <stdio.h>
#include <stdlib.h>
#include <windows.h>
#define PTE(x) (DWORD64 *) (((x >> 12) << 3) + 0xc0000000)
#define REAL_PTE (DWORD64 *)0x8003f250
#define FAKE_PTE (DWORD64 *)0x8003f258
#define T_CR3 0x8003f260
void JmpTarget();
char* p = (char*)0x8003f130;
size_t i;
void __declspec(naked) IdtEntry() {
for (i = 0; i < 0x100; i++) {
//裸函数里尽量不用局部变量,因为没有栈帧
//试了一下如果这里用size_t i=0;的话,使用了寄存器edi计数,但是还是在用全局变量保险
*p = ((char*)JmpTarget)[i];
p++;
}
__asm {
//关闭写保护
mov eax, cr0
and eax, not 0x10000
mov cr0, eax
//构造_KiTrap0E hook头
//push 0x8003f130 68 30 F1 03 80
//ret C3
mov al,0x68
mov ds:[0x80541450],al
mov dword ptr ds:[0x80541451],0x8003f130
mov al, 0xc3
mov ds:[0x80541455],al

mov eax,0xffffffff
mov ds:[T_CR3],eax
//开启写保护
mov eax, cr0
or eax, 0x10000
mov cr0, eax
iretd
}
}
void __declspec(naked) JmpTarget() {
__asm {
pushad
mov eax, cr3
cmp eax, ds: [T_CR3]
jnz PASS

mov eax, cr2
shr eax, 0xc
cmp eax, 0x412
jnz PASS
//取出错误码,+20是因为pushad压了栈
mov eax, ss: [esp + 0x20]
test eax, 0x10
jne EXECUTE
jmp READWRITE
//取指令造成异常
EXECUTE:
}
*PTE(0x412000)=*REAL_PTE;
__asm {
//放入指令tlb里
mov eax, 0x412004
call eax
}
//再次销毁页,使读写时造成缺页
*PTE(0x412000) = 0;
__asm {
popad
//错误码不会被iret弹出,要手动平掉这个栈
add esp, 4
iretd
READWRITE :
}
*PTE(0x412000) = *FAKE_PTE;
__asm {
//放入数据tlb
mov eax,ds:[0x41c000]
}
*PTE(0x412000) = 0;
__asm {
popad
//错误码不会被iret弹出,要手动平掉这个栈
add esp, 4
iretd
PASS://无关进程返回到原来的_KiTrap0E处理
popad
mov word ptr[esp + 2], 0
push 0x80541457
ret
}
}
void interrupt() {
__asm {
int 0x20
}
}
int main() {
if (0x401040 != IdtEntry) {
printf("IdtRntry address wrong");
system("pause");
exit(-1);
}
interrupt();
system("pause");
}

test.c

#include <stdio.h>
#include <stdlib.h>
#include <windows.h>
#define PTE(x) (DWORD64 *) (((x >> 12) << 3) + 0xc0000000)
#define REAL_PTE (DWORD64 *)0x8003f250
#define FAKE_PTE (DWORD64 *)0x8003f258
#define T_CR3 0x8003f260
#pragma section("fake_data",read,write)
__declspec(allocate("fake_data")) DWORD fake_page[1024];//0x41c000
void __declspec(naked) IdtEntry() {//裸函数不会帮我们生成栈帧,单纯一个call
*REAL_PTE = *PTE(0x412000);
*FAKE_PTE = *PTE(0x41c000);
//使页失效,不存在,每次都会触发缺页,从而接受我们hook的检测
*PTE(0x412000)=0;
__asm {
mov eax,cr3
mov ds:[T_CR3],eax//缺页异常时会跳到被我们inlinhook的KiTrap0E,jnz end比较起来就会一样
iretd
}
}
void interrupt() {
fake_page[0] = 0x12345678;//确保加载物理页
__asm {
int 0x20
}
}
#pragma code_seg(".mycode") __declspec(allocate(".mycode")) int main();
//0x412000
int main() {
__asm{
jmp L
ret //0412004
L:
}
if (0x401040 != IdtEntry) {
printf("IdtRntry address wrong");
system("pause");
exit(-1);
}
int i = 0;
interrupt();
while (TRUE) {
printf("%d\n", i++);
Sleep(1000);
}
system("pause");
}

读的话是读到了fake页,执行正常

image-20230604120326540

由于进程结束时要回收物理内存,所以要在main退出时,恢复页面值