Windows内核实验 ——— 页面异常

前言

本次实验我们将简单的构造一个页面异常的处理函数,利用之前的Hook技术将相应代码写入GDT中,为我们进程调用提供处理函数以及相关全局变量,通过实验中的一些现象来进行了解页面异常

实验过程

我们首先需要做的是构造一个写入代码到GDT中的程序,以及将我们的Hook代码进行写入目的地址处

我们尝试在地址:805454A8处进行写入我们的代码,将用来处理中断服务的函数写入到GDT

1
2
3
4
5
p = (char*)0x8003f120; // gdt 中内存
for (int i = 0; i < 128; i++) {
*p = ((char*)GlobalFunc)[i];
p++;
}

在对应处理过程中,我们保存程序产生页异常时候的eipespcr2信息,将其同样保存在GDT中,为我们程序后续读取做准备

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
#define K_ESP 0x8003f3f4
#define K_ESP_4 0x8003f3f0
#define Addr_CR3 0x8003f3ec
#define Addr_CR2 0x8003f3e8

void __declspec(naked) GlobalFunc() {
__asm {
push eax
mov eax, cr3
cmp eax, ds : [Addr_CR3]
jne _end

; 对于页面异常会压入一个 error code
; 之后顺序压入 eip cs eflags esp ss
; eip 指向的是触发异常的指令地址
; cs 指向的是触发异常的指令所在的段
; esp 指向的是触发异常时的 esp
; cr2 指向的是触发异常的地址

mov eax, ds : [esp + 4] ; 获取压入的 eip
mov ds : [K_ESP] , eax
mov eax, ds : [esp + 8] ; 获取压入的 cs
mov ds : [K_ESP_4] , eax
mov eax, cr2 ; 获取cr2
mov ds : [Addr_CR2] , eax
_end :
pop eax
mov word ptr[esp + 2], 0 ; 源_KiTrap0E中开始代码
push 0x805454af ; 目标跳转地址
ret
}
}

而在Hook部分我们构造一个push ret的结构来实现跳转,将原代码从_KiTrap0E中进行直接返回,到我们的目的地址

1
2
0x0:    push    0x8003f120
0x5: ret

此处跳转目标为我们构建的Hook函数具体内容,同时我们为了能够在内核进行修改需要关闭写保护,写入后开启保护,避免触发崩溃而导致蓝屏

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
// hook part 
__asm {
mov eax, 0xffffffff
mov ds : [Addr_CR3] , eax; 全局变量Cr3初始化为 0xffffffff

; 关闭写保护
mov eax, cr0
and eax, ~0x10000
mov cr0, eax

; Hook main
mov eax, 0x03f12068
mov ds : [0x805454A8] , eax
mov ax, 0xC380
mov ds : [0x805454AC] , ax

xor eax, eax
mov ds : [Addr_CR2] , eax
mov ds : [K_ESP] , eax
mov ds : [K_ESP_4] , eax

; 开启写保护
mov eax, cr0
or eax, 0x10000
mov cr0, eax
}

至此我们的Hook程序已经完成对应的工作,接下来需要实现的是对GDT中保存的信息进行读取,代码如下:

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
#include <stdio.h>
#include <Windows.h>

#define K_ESP 0x8003f3f4
#define K_ESP_4 0x8003f3f0
#define Addr_CR3 0x8003f3ec
#define Addr_CR2 0x8003f3e8

DWORD g_esp;
DWORD g_esp_4;
DWORD g_cr2;

void __declspec(naked) IdtEntry() {
__asm {
mov eax, cr3
mov ds:[Addr_CR3], eax

mov eax, ds:[K_ESP]
mov g_esp, eax

mov eax, ds:[K_ESP_4]
mov g_esp_4, eax

mov eax, ds:[Addr_CR2]
mov g_cr2, eax

xor eax, eax
mov ds:[K_ESP_4],eax

iretd
}
}

#pragma code_seg(".mycode") __declspec(allocate(".mycode")) void Crash();
#pragma code_seg(".mycode") __declspec(allocate(".mycode")) void main();

void Crash() {
__asm {
int 0x20
}
}

// eq 8003f500 0040ee00`00081040
int main() {
if ((DWORD)IdtEntry != 0x00401040) {
printf("IdtEntry : %p\n", IdtEntry);
return 0;
}
while (1) {
Crash();
if (g_esp_4) {
printf("eip: %p, errorNo: %p, cr2: %p \n", g_esp_4, g_esp, g_cr2);
}
Sleep(1000);
}

return 0;
}

我们直接取GDT中数据即可,同时我们构建一个新的节,来保存我们的Crash以及main函数,让其处于页边界上,便于我们进行保护以及管理

成功运行后我们可以看到其已经捕获了对应页面异常信息,同时将其进行输出

此时我们成功的获取到了对应页异常信息目标的地址,异常号,以及发生异常的代码已经成功的实现了对页异常的Hook

总结

利用之前所使用的inlineHook的方式,我们将对应地址的写保护进行关闭,在这之后替换为我们自己的push ret结构跳转到我们的执行函数中,之后获取到相应的堆栈信息,将其保存在GDT中,随后打印,完成此次实验。


Windows内核实验 ——— 页面异常
https://equinox-shame.github.io/2023/07/05/Windows内核实验 — 页面异常/
作者
梓曰
发布于
2023年7月5日
许可协议