Windows内核实验 ——— 系统调用

前言

在之前的实验过程中都需要我们使用WinDBG来中断内核,在系统中断表中添加int 20,将我们自己构造的内核代码进行运行,而在此处实验中我们将构建一个处于三环的程序,通过系统调用来完成对应的功能。

实验过程

因为在程序中ntdll都会被程序所调用对此我们哦那个给模拟该方式来实现从三环到零环的权限。

我们此处尝试实现两个函数ReadMemAllocMem。我们同样需要两个程序来构建,第一个用于构建内核,注册对应的中断服务,同时将函数实现进行拷贝到GDT表项中(不再额外分配内存),另外一个程序则是使用其注册的表项通过中断来跳转到执行我们的在三环完成对零环权限的执行。

此处借用一下周壑师傅的图:

我们根据这张表的地址来进行更改GDT表中的空闲内存空间,在SystemCallEntry我们需要实现一个山寨版本的入口KiFastCallEntry,然后再实现下面的两个函数的功能。

内核构建程序

当我们从三环进入到零环时对应会将eip、cs、eflags、esp、ss进行压入栈中来保存当前三环环境,随后进入到零环中,其栈结构大致如下:

我们需要取出三环的ESP,因为三环的栈顶指向的是返回地址。又因为我们构建的都是裸函数,相当于没有push espmov ebp,esp的操作来开辟栈帧,因此我们直接使用零环的ESP来控制获取三环的ESP,然后我们再通过三环的ESP来获取参数,根据这个参数来获取我们对应执行的函数

1
2
3
4
mov ebx, ss:[esp + 0xc]; 获取三环ESP
mov ecx, ds:[ebx + 0x4]; 获取三环参数
mov ebx, 0x8003f3c0; 获取SysCallTable
call dword ptr[ebx + ecx * 4]; 调用SysCallTable中的函数

当我们获取到需要执行的函数时,我们需要在GDT中提前写好上面两个函数(ReadMem、AllocMem)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
void __declspec(naked) IdtEntry() {
// 覆写代码到GDT中
p = (char*)Target[0];
for (i = 0; i < 128; i++) {
*p = ((char*)SysCallEntry)[i];
p++;
}
p = (char*)Target[1];
for (i = 0; i < 64; i++) {
*p = ((char*)ReadMem)[i];
p++;
}
p = (char*)Target[2];
for (i = 0; i < 64; i++) {
*p = ((char*)AllocMem)[i];
p++;
}
// 构建服务程序表
SysCallTable[0] = 0x8003f1c0; // ReadMem 函数首地址
SysCallTable[1] = 0x8003f200; // AllocMem 函数首地址
__asm {
iretd
}
}

当我们利用该程序构建内核时,便可以将需要执行的函数写入以及获取我们在三环的参数,通过该参数来实现对应偏移的调用服务程序。

所有代码如下:

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

void SysCallEntry();
void ReadMem();
void AllocMem();

int i;
char* p;
DWORD* SysCallTable = (DWORD*)0x8003f3c0;
DWORD Target[3] = { 0x8003f120, 0x8003f1c0, 0x8003f200 };
// eq 8003f500 0040ee00`00081040
void __declspec(naked) IdtEntry() {
// 覆写代码到GDT中
p = (char*)Target[0];
for (i = 0; i < 128; i++) {
*p = ((char*)SysCallEntry)[i];
p++;
}
p = (char*)Target[1];
for (i = 0; i < 64; i++) {
*p = ((char*)ReadMem)[i];
p++;
}
p = (char*)Target[2];
for (i = 0; i < 64; i++) {
*p = ((char*)AllocMem)[i];
p++;
}
// 构建服务程序表
SysCallTable[0] = 0x8003f1c0;
SysCallTable[1] = 0x8003f200;
__asm {
iretd
}
}
void __declspec(naked) SysCallEntry() {
__asm {
push 0x30
pop fs
sti; 开中断

mov ebx, ss: [esp + 0xc] ; 获取三环ESP
mov ecx, ds: [ebx + 0x4] ; 获取三环参数
mov ebx, 0x8003f3c0; 获取SysCallTable, 同时将其设置为地址无关量
mov edx, dword ptr[ebx + eax * 4]; 调用SysCallTable中的函数
call edx

cli; 关中断
push 0x3b
pop fs
iretd
}
}

void __declspec(naked) ReadMem() {
__asm {
mov eax, ds: [ecx] ; 直接获取ecx参数里的值后返回
ret
}
}

void __declspec(naked) AllocMem() {
__asm {
push ecx; Size
push 0; 非分页内存
mov eax, 0x80537FF8
call eax; 调用ExAllocatePool
ret
}
}

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

int main()
{
if ((DWORD)IdtEntry != 0x00401040) {
printf("Wrong addr : %p \n", IdtEntry);
}
Crash();
system("pause");
return 0;
}

其中我们可以在里面顺便把0x21的注册表项进行注册为我们构造的伪入口切换程序,通过加入以下代码即可

1
2
3
4
5
6
; target addr 8003f120
; eq 8003f508 8003ee00`0008f120
mov eax, 0x0008f120
mov ds:[0x8003f508], eax
mov eax, 0x8003ee00
mov ds:[0x8003f50c], eax

执行程序

执行程序来说就相对比较简单,我们将需要调用的参数通过eax保存,随后通过中断0x21来跳转到我们内核构建程序中。由于我们在内核构建程序中获取了其参数,通过该方法来获取eax的值,随后计算偏移量来跳转。代码如下:

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

DWORD ReadMem(DWORD addr);
DWORD AllocMem(DWORD size);

DWORD __declspec(naked)ReadMem(DWORD addr)
{
__asm {
mov eax,0
int 0x21
ret
}
}

DWORD __declspec(naked)AllocMem(DWORD size) {
__asm {
mov eax, 1
int 0x21
ret
}
}


int main()
{
printf("%p\n", ReadMem(0x8003f3c0));
system("pause");
return 0;
}

代码测试

我们将程序拷贝到XP虚拟机中,先使用WinDbg进行中断将int 0x20进行设置,然后恢复继续。

我们可以成功的看到对应代码已经成功写入了对应位置

此时我们再次运行相应的函数调用的三环程序,尝试从三环中获取零环的信息,过程如下

可以看到我们已经成功的将零环的地址内容进行输出,我们可以在WinDbg上进行验证

总结

我们成功的在三环中调用了我们在内核中编写的代码,通过一个简单的程序构建代码于内核环境中,我们可以尝试通过自己编写一些相应的代码来实现更加高级的功能,该提不提的是当初写地址表写成了char*,导致没能成功的将构建的函数写上去。对此需要更加注意下相应写入地址时采用的格式,否则容易导致内核崩溃,进而蓝屏。


Windows内核实验 ——— 系统调用
https://equinox-shame.github.io/2023/05/14/Windows内核实验 — 系统调用/
作者
梓曰
发布于
2023年5月14日
许可协议