初探windows内核代码
初探windows内核代码
前言
最近在研究win游戏外挂,想自己也飞天入地,于是有此文。
环境搭建
老规矩,vs2019,windows10 sdk与wdk。ps:这一步卡了我好久,在闲鱼找人弄好了。
第一个驱动
众所周知,大厂的游戏为了反作弊,都有反作弊系统,会借助r0层权限保护游戏进程。
这也就是为什么你用x64dbg,ce这种调试读写工具看不到内存,甚至直接闪退的原因,反作弊通过合法手段在r0层对所有可疑函数调用做了hook,不返回内存数据。
而过掉这层反作弊的关键只能靠驱动,也就是在0环编写驱动程序,直接操作内存cpu寄存器以一种诡谲的方式hook没被监控的函数实现绕过反作弊监管,返回你想要的内存数据。
前置条件
说完原理,那么写驱动需要什么呢?
需要coding(废话)。
你需要了解windows10内核运行的机制,以及cpu、内存是怎么与内核交换数据的。
你还需要掌握一些内核函数库与硬件知识,
- 点击跳转windows驱动开发文档。
- Intel® 64 and IA-32 Architectures Software Developer Manuals
- 英特尔® 64和IA-32架构软件开发人员手册
你需要了解cpp,至少能看懂。
你需要时常光顾各大论坛,看雪,吾爱,先知等,因为逆向圈不开源,好东西大家都想藏在自己手里,没人愿意上来就把成品注入器分享出来。所以你就得经常看看大家最新的思路,攻防最前沿。
你需要懂github搜索,至少知道去哪ctrlc,ctrlv。
如果你能用ai加快开发节奏,那更好(但ai很蠢,你知道的)。
第一次尝试
1 |
|
相信每个驱动圈的朋友都从这段代码开始,有头文件,有加载函数,有卸载函数,还有输出日志。
编译吧,别忘了切换x64平台,然后去虚拟机测试。
第二个驱动
经历了第一次尝试,你或许对驱动没那么陌生了。没错,驱动都有固定的写法,只是一些具体的实现不同。
现在你需要了解一些库函数了,再写一个稍微复杂的驱动。相信我,你可以的。
我们用到的函数有
函数
ntddk.h ✓
内存管理函数:
IoAllocateMdl
- 作用:分配一块内存映射。
- 类型:指向 MDL 的指针PMDL,分配失败返回 NULL 。
- 参数:
VirtualAddress(可选):指向 MDL 描述的缓冲区基虚拟地址的指针Length:指定 MDL 描述的缓冲区的字节长度SecondaryBuffer:指示缓冲区是主/辅助缓冲区,无关联 IRP 时设为 FALSEChargeQuota:系统保留参数,驱动程序必须设为 FALSEIrp(可选):指向要关联 MDL 的 IRP 指针
- 例:
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
89
90
91
92
93
94
95
96
97
98
// 驱动卸载例程声明(C++ 需显式声明为 C 链接)
extern "C" VOID DriverUnload(_In_ PDRIVER_OBJECT DriverObject);
// 资源释放辅助函数(C++ 封装,简化资源清理)
template <typename T>
inline void SafeFreePool(T*& ptr, ULONG tag) noexcept
{
if (ptr)
{
ExFreePoolWithTag(ptr, tag);
ptr = nullptr;
}
}
inline void SafeFreeMdl(PMDL& mdl) noexcept
{
if (mdl)
{
IoFreeMdl(mdl);
mdl = nullptr;
}
}
// MDL 操作示例函数
NTSTATUS SimpleMdlDemo() noexcept
{
// 定义常量(C++ constexpr 提升类型安全)
constexpr SIZE_T BUFFER_SIZE = 512;
constexpr ULONG POOL_TAG = 'emoD'; // 反向 TAG(驱动开发规范)
// 1. 分配分页池内存(C++ 类型推导)
PVOID buffer = ExAllocatePoolWithTag(PagedPool, BUFFER_SIZE, POOL_TAG);
if (!buffer)
{
DbgPrint("SimpleMdlDemo: 缓冲区分配失败\n");
return STATUS_INSUFFICIENT_RESOURCES;
}
// 2. 为缓冲区分配 MDL(无 IRP 关联)
PMDL mdl = IoAllocateMdl(
buffer, // 虚拟地址
BUFFER_SIZE, // 缓冲区长度
FALSE, // 非辅助缓冲区
FALSE, // 不占用配额(系统保留)
nullptr // 无关联 IRP(C++ nullptr 替代 NULL)
);
if (!mdl)
{
DbgPrint("SimpleMdlDemo: MDL 分配失败\n");
SafeFreePool(buffer, POOL_TAG); // 安全释放
return STATUS_INSUFFICIENT_RESOURCES;
}
// 3. 锁定 MDL 并获取系统地址(C++ 类型安全)
PVOID mdlSystemAddr = MmGetSystemAddressForMdlSafe(mdl, HighPagePriority);
if (mdlSystemAddr)
{
// C++ 方式清空内存(等价于 RtlZeroMemory)
RtlZeroMemory(mdlSystemAddr, BUFFER_SIZE);
DbgPrint("SimpleMdlDemo: 缓冲区已清空\n");
}
else
{
DbgPrint("SimpleMdlDemo: 获取 MDL 系统地址失败\n");
}
// 4. 按逆序释放资源(C++ 资源管理最佳实践)
SafeFreeMdl(mdl);
SafeFreePool(buffer, POOL_TAG);
return STATUS_SUCCESS;
}
// 驱动入口(必须 extern "C" 避免 C++ 名称修饰)
extern "C" NTSTATUS DriverEntry(_In_ PDRIVER_OBJECT DriverObject, _In_ PUNICODE_STRING RegistryPath)
{
UNREFERENCED_PARAMETER(RegistryPath);
// 设置卸载例程
DriverObject->DriverUnload = DriverUnload;
// 执行 MDL 示例
NTSTATUS status = SimpleMdlDemo();
DbgPrint("SimpleMdlDemo 执行结果: 0x%08X\n", status);
return STATUS_SUCCESS;
}
// 驱动卸载例程
extern "C" VOID DriverUnload(_In_ PDRIVER_OBJECT DriverObject)
{
UNREFERENCED_PARAMETER(DriverObject);
DbgPrint("MDL Demo 驱动已卸载\n");
}
IoFreeMdl
- 作用:释放调用方分配的内存描述符列表(MDL);若为部分MDL,会取消映射关联页面;需先解锁MDL描述的已锁定物理页面,且需单独释放MDL链中的每个MDL。
- 类型:VOID(无返回值)
- 参数:
[in] PMDL Mdl,指向要释放的MDL的指针。 - 例:
1
2
3
4
5
6
7
8
9
10
11
12
13
// 假设已通过IoAllocateMdl分配了一个MDL
PMDL pMyMdl = IoAllocateMdl(NULL, 1024, FALSE, FALSE, NULL);
if (pMyMdl != NULL)
{
// 此处可添加使用MDL的相关操作
// ...
// 释放分配的MDL
IoFreeMdl(pMyMdl);
pMyMdl = NULL; // 避免野指针
}
MmProbeAndLockPages
- 作用:探测指定虚拟内存页并使其驻留,同时锁定页面(多用于DMA传输),防止驱动或硬件使用期间页面被释放和重新分配;成功后会更新MDL以描述物理页。仅分层驱动程序链中使用直接I/O的最高级别驱动程序可调用。
- 类型:VOID(无返回值)
- 参数:
MemoryDescriptorList:输入输出参数,指向描述虚拟内存缓冲区的MDL指针,成功锁定后会被更新。AccessMode:输入参数,访问模式,可选KernelMode(内核模式)或UserMode(用户模式)。Operation:输入参数,探测和锁定的操作类型,可选IoReadAccess(只读)、IoWriteAccess/IoModifyAccess(读写)。
- 例:
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
// 假设已有虚拟内存地址和长度
PVOID pBuffer = NULL;
SIZE_T bufferSize = 1024;
PMDL pMdl = NULL;
// 1. 分配并初始化MDL
pMdl = IoAllocateMdl(pBuffer, bufferSize, FALSE, FALSE, NULL);
if (pMdl == NULL)
{
// 处理分配失败逻辑
return;
}
// 2. 构建MDL以描述虚拟内存
__try
{
MmProbeAndLockPages(pMdl, UserMode, IoModifyAccess);
}
__except(EXCEPTION_EXECUTE_HANDLER)
{
// 处理探测或锁定失败的异常
IoFreeMdl(pMdl);
return;
}
// 3. 使用锁定的页面(例如用于DMA操作)
// ...
// 4. 解锁页面并释放MDL
MmUnlockPages(pMdl);
IoFreeMdl(pMdl);
MmUnlockPages
- 作用:解锁由指定内存描述符列表(MDL)描述的物理页面;若该MDL映射到系统地址空间,会先释放此映射再解锁页面。
- 类型:
VOID(无返回值) - 参数:
[in, out] PMDL MemoryDescriptorList- 指向目标内存描述符列表(MDL)的指针。 - 例:
1
2
3
4
5
6
7
8
9
10
11
// 假设已经通过 MmProbeAndLockPages 锁定了 MDL
VOID UnlockExample(PMDL pMdl)
{
if (pMdl != NULL)
{
// 解锁 MDL 对应的物理页面
MmUnlockPages(pMdl);
}
}
MmMapLockedPagesSpecifyCache
- MmUnmapLockedPages
- MmProtectMdlSystemAddress
- MmGetVirtualForPhysical
- MmAllocateContiguousMemorySpecifyCache
- ExAllocatePoolWithTag
进程线程函数:
- PsLookupProcessByProcessId
- KeStackAttachProcess
- KeUnstackDetachProcess
- ObfDereferenceObject
- KeFlushEntireTb
内存操作函数:
- RtlCopyMemory
- RtlZeroMemory
调试函数:
- DbgPrint
- DbgPrintEx
ntifs.h ✓
系统调用函数:
- NtOpenProcess
- NtCreateFile
intrin.h ✓
CPU指令:
- __readcr3
代码
相信你已经看懂了这些函数,现在要愉快的调用了~
完成一个MDL实现Ptehook吧。
1 | //MDL.h |
1 | //MDL.cpp |
接下来,我们需要调用一些封装好的库。不需要你自己写,直接下载贴上就好,程序员最宝贵的品质是cv,不是么。
ia32源自https://github.com/ia32-doc/ia32-doc,hde我没找到完整出处。
姑且打包点击下载
1 | //HookManager.h |
1 | //HookManager.cpp |
1 | //PageTable.h |
1 | //PageTable.cpp |
1 | //structer.h |
1 | //main.cpp |
