Windows内核实验:页面异常

Windows内核实验:页面异常

要inlinehook的是,0e号缺页异常的处理程序_KiTrap0E,在发生缺页的时候检测一下缺页时的栈

image-20230603210651048

.text:80541450                               _KiTrap0E proc near
.text:80541450 66 C7 44 24 02 00 00 mov word ptr [esp+2], 0
.text:80541457 55 push ebp
.text:80541458 53 push ebx
.text:80541459 56 push esi
.text:8054145A 57 push edi
.text:8054145B 0F A0 push fs
.text:8054145D BB 30 00 00 00 mov ebx, 30h ; '0'
.text:80541462 66 8E E3 mov fs, bx
.text:80541465 assume fs:nothing
.text:80541465 64 8B 1D 00 00 00 00 mov ebx, large fs:0
.text:8054146C 53 push ebx
.text:8054146D 83 EC 04 sub esp, 4
.text:80541470 50 push eax

inlinehook.c

#include <stdio.h>
#include <stdlib.h>
#include <windows.h>
//内核里gdtr空闲位存储从出现缺页异常读取出来数据
#define T_ERRNO 0x8003f250
#define T_EIP 0x8003f254
#define T_CR3 0x8003f258
#define T_CR2 0x8003f25c
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
xor eax,eax
mov ds:[T_ERRNO],eax
mov ds:[T_CR2],eax
mov ds:[T_EIP],eax
//开启写保护
mov eax, cr0
or eax, 0x10000
mov cr0, eax
iretd
}
}
void __declspec(naked) JmpTarget() {
__asm {
push eax
mov eax,cr3
cmp eax,ds:[T_CR3]
jnz end
//因为前面push了eax,所以现在栈比原中断栈小了4
mov eax,ss:[esp+4]
mov ds:[T_ERRNO],eax
mov eax,ss:[esp+8]
mov ds:[T_EIP],eax
//cr2保存了导致页错误的线性地址
mov eax,cr2
mov ds:[T_CR2],eax
end://返回到原来的_KiTrap0E处理
pop eax
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");
}

缺页异常会压一个出错码

image-20230603233450894

cr2保存了导致页错误的线性地址

image-20230603233002202

test.c

#include <stdio.h>
#include <stdlib.h>
#include <windows.h>
//内核里gdtr空闲位存储从出现缺页异常读取出来数据
#define T_ERRNO 0x8003f250
#define T_EIP 0x8003f254
#define T_CR3 0x8003f258
#define T_CR2 0x8003f25c
DWORD g_errno,g_eip,g_cr2;
void __declspec(naked) IdtEntry() {//裸函数不会帮我们生成栈帧,单纯一个call
__asm {
mov eax,cr3
mov ds:[T_CR3],eax//缺页异常时会跳到被我们inlinhook的KiTrap0E,jnz end比较起来就会一样
mov eax,ds:[T_EIP]
mov g_eip,eax
mov eax,ds:[T_CR2]
mov g_cr2,eax
mov eax,ds:[T_ERRNO]
mov g_errno,eax
xor eax,eax
mov ds:[T_EIP],eax
iretd
}
}
void interrupt() {
__asm {
int 0x20
}
}
int main() {
if (0x401040 != IdtEntry) {
printf("IdtRntry address wrong");
system("pause");
exit(-1);
}
while (TRUE) {
interrupt();
if(g_eip)
printf("eip:0x%x errno:0x%x cr2:0x%x\n",g_eip,g_errno,g_cr2);
Sleep(10000);
}
system("pause");
}

只要不有太多的中间操作,第二次打开text.exe的cr3基本不变

kd> u 80541450
ReadVirtual: 80541450 not properly sign extended
80541450 6830f10380 push 8003F130h
80541455 c3 ret
80541456 005553 add byte ptr [ebp+53h],dl
80541459 56 push esi
8054145a 57 push edi
8054145b 0fa0 push fs
8054145d bb30000000 mov ebx,30h
80541462 668ee3 mov fs,bx
kd> u 8003F130 l20
ReadVirtual: 8003f130 not properly sign extended
8003f130 50 push eax
8003f131 0f20d8 mov eax,cr3
8003f134 3e3b0558f20380 cmp eax,dword ptr ds:[8003F258h]
8003f13b 751f jne 8003f15c
8003f13d 368b442404 mov eax,dword ptr ss:[esp+4]
8003f142 3ea350f20380 mov dword ptr ds:[8003F250h],eax
8003f148 368b442408 mov eax,dword ptr ss:[esp+8]
8003f14d 3ea354f20380 mov dword ptr ds:[8003F254h],eax
8003f153 0f20d0 mov eax,cr2
8003f156 3ea35cf20380 mov dword ptr ds:[8003F25Ch],eax
8003f15c 58 pop eax
8003f15d 66c74424020000 mov word ptr [esp+2],0
8003f164 6857145480 push offset nt!KiTrap0E+0x7 (80541457)

image-20230603224451243