-
-
[讨论]WindbgPreview的使用
-
发表于: 2天前 870
-
最近慢慢复习过去的视频,看到了这个的使用,特此复习一下。
一、windbg使用
1.常用指令


2.记忆指南
通过了解 WinDbg 命令背后的英文全称来进行记忆,是非常高效且专业的学习方法。WinDbg 的命令设计其实非常符合英文直觉,掌握了缩写规律,你就不需要死记硬背了。
这主要分为几大系列:
1. 执行与反汇编 (Execution & Unassembly)
| 命令 | 英文全称 / 记忆词 | 含义说明 |
| g | Go | 运行程序,直到遇到断点或结束。 |
| u | Unassemble | 反汇编指定地址的代码。 |
| uf | Unassemble Function | 反汇编整个函数体。 |
2. 内存查看命令 (d 系列 - Display/Dump)
d 代表 Display (显示) 或 Dump (转储)。后面的字母代表数据类型或格式:
| 命令 | 英文全称 / 记忆词 | 含义说明 |
| dt | Display Type | 显示结构体(Type/Struct)的内存布局。 |
| db | Display Byte | 按字节 (Byte, 8位) 显示。 |
| dw | Display Word | 按字 (Word, 16位) 显示。 |
| dd | Display Dword | 按双字 (Double Word, 32位) 显示。 |
| dq | Display Qword | 按四字 (Quad Word, 64位) 显示。 |
| dD | Display Double | 按双精度浮点数 (Double precision float) 显示。 |
| df | Display Float | 按单精度浮点数 (Float) 显示。 |
| da | Display ASCII | 按 ASCII 字符串显示。 |
| du | Display Unicode | 按 Unicode (UTF-16) 字符串显示。 |
| dc | Display Chars (and Dwords) | 同时显示双字值和对应的 ASCII 字符 (Chars)。 |
| ds | Display String | 显示 ANSI_STRING 结构体。 |
| dS | Display String (Unicode) | 显示 UNICODE_STRING 结构体 (大写 S 代表宽字符/Unicode)。 |
3. 内存写入命令 (e 系列 - Enter)
e 代表 Enter (输入/写入)。后面的字母代表写入的数据长度,规律同上:
| 命令 | 英文全称 / 记忆词 | 含义说明 |
| eb | Enter Byte | 写入一个字节。 |
| ew | Enter Word | 写入一个字。 |
| ed | Enter Dword | 写入一个双字。 |
| eq | Enter Qword | 写入一个四字。 |
4. 断点命令 (b 系列 - Breakpoint)
b 代表 Breakpoint (断点)。
| 命令 | 英文全称 / 记忆词 | 含义说明 |
| bp | Breakpoint | 设置软件断点 (通常写入 int 3 指令)。 |
| ba | Breakpoint Access | 设置硬件/访问断点 (依赖 CPU 调试寄存器)。 |
| bl | Breakpoint List | 列出 (List) 所有断点。 |
| be | Breakpoint Enable | 启用 (Enable) 断点。 |
| bd | Breakpoint Disable | 禁用 (Disable) 断点。 |
| bc | Breakpoint Clear | 清除 (Clear) 断点。 |
5. 调用栈与寄存器 (Stack & Registers)
栈回溯命令以 k 开头(来源于 Call Stack 或直接作为回溯的标识)。
| 命令 | 英文全称 / 记忆词 | 含义说明 |
| r | Register | 查看或修改寄存器 (Registers)。 |
| kp | Call Stack with Parameters | 显示调用栈,并尝试打印函数参数 (Parameters)。 |
| kc | Call Stack Clean | 显示干净的 (Clean) 调用栈,只看模块和函数名。 |
6. 环境、进程与模块 (Environment & Modules)
| 命令 | 英文全称 / 记忆词 | 含义说明 |
| lm | List Modules | 列出 (List) 当前加载的所有模块 (Modules, 如 dll/exe)。 |
| !process | Process | 扩展命令,用于查看进程 (Process) 详细信息。 |
| .process | Process Context | 元命令,用于切换当前的进程上下文 (Context)。 |
| !HANDLE | Handle | 扩展命令,查看句柄 (Handle) 信息。 |
| .reload | Reload Symbols | 重新加载 (Reload) 符号文件。 |
| .sympath | Symbol Path | 查看或设置符号路径。 |
| .cls | Clear Screen | 清屏。 |
注:在 WinDbg 中,不带前缀的是内置基本命令;以 . 开头的是控制调试器自身行为的元命令 (Meta-commands);以 ! 开头的是由扩展 DLL 提供的扩展命令 (Extension commands)。
3.注意事项
用这些命令是必须要有符号表的,如果没有符号表的话,里面有些命令是无法使用的。
1. 什么是符号表?(“翻译字典”)
在 C/C++(或任何编译型语言)写代码时,我们会使用人类可读的名称,比如函数名 CreateFile、变量名 ProcessId、结构体名 _EPROCESS。
但是,编译器在把代码编译成 .exe、.dll 或 .sys 文件时,为了让 CPU 执行得更快,会把这些名字全部抹掉,替换成纯粹的内存地址(比如 0x00401020)和偏移量。
符号表(在 Windows 中通常是 .pdb 文件)就是编译器在编译时顺手生成的一本“翻译字典”。 它记录了:
“内存地址
0x00401020对应的函数叫CreateFile”“
_EPROCESS这个结构体总共多大,第 8 个字节代表什么变量,第 16 个字节代表什么变量”甚至包含了哪行汇编代码对应源代码的第几行。
2. 为什么有些命令“没有符号表就无法使用”?
你可以把 WinDbg 想象成一个只认识二进制的瞎子。如果你不给它这本“字典”,很多高级命令它就不知道该怎么执行。结合你上一张图里的命令,我们来看具体的例子:
场景 A:查看结构体 (dt 命令)
有符号表时:你输入
dt _EPROCESS,WinDbg 查阅字典,知道_EPROCESS是个结构体,然后完美地给你列出进程的每一个属性(PID、进程名、Token 等),非常清晰。没有符号表时:你输入
dt _EPROCESS,WinDbg 会报错提示找不到这个类型。因为它根本不知道_EPROCESS是个什么东西,在内存里长什么样、占多大空间。你只能用dd(查看双字)去干看一堆毫无意义的十六进制数字。
场景 B:使用内核扩展命令 (!process、!HANDLE 等)
你图中的
!process 0 0会列出系统中所有的进程。操作系统并没有一个现成的 API 叫“列出所有进程给 WinDbg 看”。WinDbg 是通过在内核内存中顺藤摸瓜(遍历一个叫
ActiveProcessLinks的双向链表)把进程一个个找出来的。找这个链表,必须依赖内核结构体的精确偏移量。如果没有微软提供的内核符号表(如
ntkrnlmp.pdb),WinDbg 就不知道这个链表在内存的哪里,!process命令就会直接罢工报错。
场景 C:查看调用栈和下断点 (kp, bp)
有符号表时:你输入
bp nt!NtCreateFile,WinDbg 知道要在创建文件的地方下断点。调用栈kp也会清晰地显示:A函数 -> B函数 -> C函数。没有符号表时:你只能写具体的硬编码地址
bp 0xfffff80212345678。调用栈也只会显示一堆冰冷的地址,你完全不知道程序正在执行什么功能。
总结与实操建议
讲师的意思是:WinDbg 里的高级分析命令(尤其是涉及结构体解析、内核对象遍历的命令),其底层逻辑严重依赖于知道数据在内存中的具体排布方式,而这些信息只存在于符号表中。
这就是为什么在配置 WinDbg 时,第一件要做的事(对应你图中的 .sympath 命令),就是配置微软的公用符号服务器。
实操小贴士:你在做实验或者调试时,为了让所有命令正常工作,通常需要执行以下操作来自动下载符号表:
设置符号路径(告诉 WinDbg 去微软服务器下载字典并缓存在本地 c 盘):
.sympath SRV*c:\mysymbols*542K9s2c8@1M7s2y4Q4x3@1q4Q4x3V1k6Q4x3V1k6E0M7$3c8D9i4K6u0W2L8h3W2U0M7X3!0K6L8$3k6@1i4K6u0W2j5$3!0E0i4K6u0r3k6r3!0%4L8X3I4G2j5h3c8Q4x3V1k6K6P5h3#2T1L8$3I4K6强制重新加载符号:
.reload /f
等进度条跑完,你就可以畅通无阻地使用 dt 和 !process 等命令了。
二、Windows内核系列:内核下申请虚拟内存
1.关键API讲解
NtOpenProcess是一个

这里我们需要记住一个点
但是没人知道这

我突然有个疑问
2.代码
NTSTATUS KeAllocateMemory()
{
NTSTATUS status = 0;
HANDLE hProcess = NULL;
OBJECT_ATTRIBUTES Object = { sizeof(OBJECT_ATTRIBUTES), 0, NULL, NULL };
CLIENT_ID Cid;
LPVOID Address = NULL;
SIZE_T dwSize = 1024;
Cid.UniqueProcess = (HANDLE)0;//在这里填写要申请的进程的PID(到任务管理器里面去找)
Cid.UniqueThread = 0;
status = NtOpenProcess(&hProcess, PROCESS_ALL_ACCESS, &Object, &Cid);
if (!NT_SUCCESS(status))
{
DbgPrint("NtOpenProcess Error:%X\n", status);
goto Exit;
}
status = NtAllocateVirtualMemory(hProcess, &Address, NULL, &dwSize, MEM_COMMIT, PAGE_EXECUTE_READWRITE);
if (!NT_SUCCESS(status))
{
DbgPrint("NtAllocateVirtualMemory Error:%X\n", status);
goto Exit;
}
__try
{
DbgPrint("Address:%llX\n", *(PULONG64)Address);
}
except(1)
{
DbgPrint("Address:%p\n",Address);
}
Exit:
return status;
}或者可以用apc的方式附加,代码如下:
NTSTATUS KeAllocateMemory()
{
NTSTATUS status = 0;
HANDLE hProcess = NULL;
LPVOID Address = NULL;
SIZE_T dwSize = 1024;
PEPROCESS epGame = NULL;
KAPC_STATE apc = { 0 };
// 1. 通过PID获取目标进程的PEPROCESS结构
PsLookupProcessByProcessId((HANDLE)8812, &epGame);
// 2. 将当前线程附加到目标进程的地址空间
KeStackAttachProcess(epGame, &apc); // 附加到目标进程
// 被注释的传统NtOpenProcess方式(已改用Attach方案)
/*
OBJECT_ATTRIBUTES Object = { sizeof(OBJECT_ATTRIBUTES), 0, NULL, NULL };
CLIENT_ID Cid;
Cid.UniqueProcess = (HANDLE)8812;
Cid.UniqueThread = 0;
status = NtOpenProcess(&hProcess, PROCESS_ALL_ACCESS, &Object, &Cid);
*/
if (!NT_SUCCESS(status))
{
DbgPrint("NtOpenProcess Error:%X\n", status);
goto Exit;
}
// 3. 在当前进程(已Attach到目标进程上下文)分配内存
status = NtAllocateVirtualMemory(NtCurrentProcess(), &Address, NULL, &dwSize, MEM_COMMIT, PAGE_EXECUTE_READWRITE);
if (!NT_SUCCESS(status))
{
DbgPrint("NtAllocateVirtualMemory Error:%X\n", status);
goto Exit;
}
// 4. 结构化异常处理,防止访问无效地址导致蓝屏
__try
{
DbgPrint("Address:%llX\n", Address);
}
except(1)
{
DbgPrint("Error Point Address:%p\n", Address);
}
// 5. 解除进程附加,恢复原地址空间
KeUnstackDetachProcess(&apc);
Exit:
return status;
}三、内核下实现远线程操作
比如注入、远程Call、远程代码,在用户层用到的函数:
1.API讲解
我们知道的

况且

和

这个是一个未公开的Api,需要

2.代码
typedef NTSTATUS(NTAPI* _RtlCreateUserThread)(
HANDLE ProcessHandle,
PSECURITY_DESCRIPTOR SecurityDescriptor,
BOOLEAN CreateSuspended,
ULONG StackZeroBits,
SIZE_T StackReserved,
SIZE_T StackCommit,
PVOID StartAddress,
PVOID StartParanter,
PHANDLE ThreadHandle,
PCLIENT_ID ClientId
);
NTSTATUS KeCreateRemoteThread()
{
NTSTATUS status = 0;
PEPROCESS epGame = NULL;
HANDLE hThread = NULL;
UNICODE_STRING RtlCreateThread;
CLIENT_ID Cid = { 0, 0 };
KAPC_STATE apc = { 0 };
PsLookupProcessByProcessId((HANDLE)0, &epGame);
KeStackAttachProcess(epGame, &apc);
RtlInitUnicodeString(&RtlCreateThread, L"RtlCreateUserThread");
_RtlCreateUserThread RtlCreateUserThread = (_RtlCreateUserThread)MmGetSystemRoutineAddress(&RtlCreateThread);
__try
{
status = RtlCreateUserThread(NtCurrentProcess(), NULL, FALSE, 0, 0, 0, (PVOID)0x0, (PVOID)0x0, hThread, &Cid);
if (!NT_SUCCESS(status))
{
DbgPrint("RtlCreateUserThread Error:%X\n", status);
goto Exit;
}
}
except(1)
{
DbgPrint("Error Pointer\n");
goto Exit;
}
KeUnstackDetachProcess(&apc);
Exit:
return status;
}下一步新建一个项目,把这里的活动平台改为Win32


优化改为已禁用
在这个RemoteThread项目中,我们写下代码:
#include <iostream>
#include <Windows.h>
int main()
{
LPVOID Address = VirtualAllocEx(OpenProcess(PROCESS_ALL_ACCESS, FALSE, 1932), NULL, 1024, MEM_COMMIT, PAGE_EXECUTE_READWRITE);
printf("Address:%p\n", Address);
wchar_t DllPath[] = { L"C:\\test.dll" };
WriteProcessMemory(OpenProcess(PROCESS_ALL_ACCESS, FALSE, 1932), Address, DllPath, sizeof(DllPath), 0);
system("pause");
CreateRemoteThread(OpenProcess(PROCESS_ALL_ACCESS, FALSE, 1932), NULL, 0, (LPTHREAD_START_ROUTINE)LoadLibraryW, Address, NULL, NULL);
system("pause");
}三、内核下释放虚拟内存
1.关键API

2.代码
NTSTATUS KeFreeVirtualMemory()
{
NTSTATUS status = 0;
HANDLE hProcess = NULL;
LPVOID Address = NULL;
SIZE_T dwSize = 0;
OBJECT_ATTRIBUTES Object = { sizeof(OBJECT_ATTRIBUTES), 0, NULL, NULL };
CLIENT_ID Cid;
Cid.UniqueProcess = (HANDLE)0;
Cid.UniqueThread = 0;
status = NtOpenProcess(&hProcess, PROCESS_ALL_ACCESS, &Object, &Cid);
if (!NT_SUCCESS(status))
{
DbgPrint("NtOpenProcess Error:%X\n", status);
goto Exit;
}
status = NtFreeVirtualMemory(hProcess, &Address, &dwSize, MEM_RELEASE);
if (!NT_SUCCESS(status))
{
DbgPrint("NtFreeVirtualMemory Error:%X\n", status);
goto Exit;
}
DbgPrint("释放成功!\n");
Exit:
return status;
}或者:
NTSTATUS KeFreeVirtualMemory()
{
NTSTATUS status = 0;
HANDLE hProcess = NULL;
LPVOID Address = (LPVOID)0x2F10000;
SIZE_T dwSize = 0;
PEPROCESS epGame = NULL;
PsLookupProcessByProcessId((HANDLE)7436, &epGame);
KAPC_STATE apc;
KeStackAttachProcess(epGame, &apc);
/*OBJECT_ATTRIBUTES Object = { sizeof(OBJECT_ATTRIBUTES),0,NULL,NULL };
CLIENT_ID Cid;
Cid.UniqueProcess = (HANDLE)7436;
Cid.UniqueThread = 0;
status = NtOpenProcess(&hProcess, PROCESS_ALL_ACCESS, &Object, &Cid);*/
if (!NT_SUCCESS(status))
{
DbgPrint("NtOpenProcess Error:%X\n", status);
goto Exit;
}
status = NtFreeVirtualMemory(NtCurrentProcess(), &Address, &dwSize, MEM_RELEASE);
if (!NT_SUCCESS(status))
{
DbgPrint("NtFreeVirtualMemory Error:%X\n", status);
goto Exit;
}
DbgPrint("释放成功!\n");
KeUnstackDetachProcess(&apc);
Exit:
return status;
}四、Windows内核系列:驱动开发进程回调
1.进程回调前置概念


2.关键概念




#include <ntddk.h>
#include <ntstrsafe.h>
// 全局句柄:用于后续注销进程回调
PVOID g_ProcessNotifyHandle = NULL;
// 回调函数必须放在非分页内存,避免蓝屏
#pragma code_seg("NONPAGE")
/**
* 进程创建/退出回调函数(Win8+ 专用)
* @param ParentProcessId 父进程PID
* @param ProcessId 目标进程PID
* @param CreateInfo 进程创建信息(创建时有效,退出时为NULL)
*/
VOID MyProcessNotify(
_In_ HANDLE ParentProcessId,
_In_ HANDLE ProcessId,
_In_ PPS_CREATE_NOTIFY_INFO CreateInfo
)
{
UNREFERENCED_PARAMETER(ParentProcessId);
if (CreateInfo != NULL)
{
// 进程创建事件
DbgPrint("[进程回调] 创建进程: PID=%d, 父PID=%d\n",
(ULONG)ProcessId, (ULONG)ParentProcessId);
// 安全读取进程路径(必须加__try/__except,防止用户态内存异常)
__try
{
if (CreateInfo->ImageFileName != NULL && CreateInfo->ImageFileName->Buffer != NULL)
{
DbgPrint("[进程回调] 进程路径: %wZ\n", CreateInfo->ImageFileName);
}
}
__except (EXCEPTION_EXECUTE_HANDLER)
{
DbgPrint("[进程回调] 进程名读取异常,PID=%d\n", (ULONG)ProcessId);
}
}
else
{
// 进程退出事件
DbgPrint("[进程回调] 退出进程: PID=%d\n", (ULONG)ProcessId);
}
}
#pragma code_seg() // 恢复默认代码段
// 驱动卸载函数:必须注销回调,否则会蓝屏
VOID DriverUnload(_In_ PDRIVER_OBJECT DriverObject)
{
UNREFERENCED_PARAMETER(DriverObject);
// 注销进程回调
if (g_ProcessNotifyHandle != NULL)
{
NTSTATUS status = PsRemoveCreateProcessNotifyRoutineEx(g_ProcessNotifyHandle);
if (NT_SUCCESS(status))
{
DbgPrint("进程回调注销成功\n");
g_ProcessNotifyHandle = NULL;
}
else
{
DbgPrint("进程回调注销失败: 0x%X\n", status);
}
}
DbgPrint("进程回调驱动卸载完成\n");
}
// 驱动入口:注册进程回调
NTSTATUS DriverEntry(_In_ PDRIVER_OBJECT DriverObject, _In_ PUNICODE_STRING RegistryPath)
{
UNREFERENCED_PARAMETER(RegistryPath);
NTSTATUS status = STATUS_SUCCESS;
// 设置驱动卸载函数(必须!)
DriverObject->DriverUnload = DriverUnload;
// 注册进程创建/退出回调(Win8+ 推荐)
status = PsSetCreateProcessNotifyRoutineEx(
MyProcessNotify, // 回调函数
NULL, // 上下文参数(可选)
&g_ProcessNotifyHandle // 输出句柄,用于注销
);
if (!NT_SUCCESS(status))
{
DbgPrint("进程回调注册失败: 0x%X\n", status);
goto Exit;
}
DbgPrint("进程回调注册成功,句柄: 0x%p\n", g_ProcessNotifyHandle);
Exit:
return status;
}
// 链接内核库
#pragma comment(lib, "ntoskrnl.lib")五、Windows内核系列:驱动开发线程回调
2.代码:
#include <ntddk.h>
// 回调函数必须放在非分页内存,避免蓝屏
#pragma code_seg("NONPAGE")
/**
* 线程创建/退出回调函数(全Windows版本兼容)
* @param ProcessId 线程所属进程的PID
* @param ThreadId 线程自身的TID
* @param Create TRUE=线程创建,FALSE=线程退出
*/
VOID MyThreadNotify(
_In_ HANDLE ProcessId,
_In_ HANDLE ThreadId,
_In_ BOOLEAN Create
)
{
if (Create)
{
DbgPrint("[线程回调] 创建线程: PID=%d, TID=%d\n",
(ULONG)ProcessId, (ULONG)ThreadId);
}
else
{
DbgPrint("[线程回调] 退出线程: PID=%d, TID=%d\n",
(ULONG)ProcessId, (ULONG)ThreadId);
}
}
#pragma code_seg() // 恢复默认代码段
// 驱动卸载函数:必须注销回调,否则会蓝屏
VOID DriverUnload(_In_ PDRIVER_OBJECT DriverObject)
{
UNREFERENCED_PARAMETER(DriverObject);
// 注销线程回调
PsRemoveCreateThreadNotifyRoutine(MyThreadNotify);
DbgPrint("线程回调注销成功\n");
DbgPrint("线程回调驱动卸载完成\n");
}
// 驱动入口:注册线程回调
NTSTATUS DriverEntry(_In_ PDRIVER_OBJECT DriverObject, _In_ PUNICODE_STRING RegistryPath)
{
UNREFERENCED_PARAMETER(RegistryPath);
NTSTATUS status = STATUS_SUCCESS;
// 设置驱动卸载函数(必须!)
DriverObject->DriverUnload = DriverUnload;
// 注册线程创建/退出回调(全版本兼容)
status = PsSetCreateThreadNotifyRoutine(MyThreadNotify);
if (!NT_SUCCESS(status))
{
DbgPrint("线程回调注册失败: 0x%X\n", status);
goto Exit;
}
DbgPrint("线程回调注册成功\n");
Exit:
return status;
}
// 链接内核库
#pragma comment(lib, "ntoskrnl.lib")六、Windows内核系列:用户层代码实现 内核下保护进程
1.API


2.代码实现
0环代码:
#include <ntddk.h>
// =========================================================
// 1. 控制码定义 (IOCTL)
// =========================================================
#define 控制码_保护进程 CTL_CODE(FILE_DEVICE_UNKNOWN, 0xC00, METHOD_NEITHER, FILE_ANY_ACCESS)
#define 控制码_分配线程 CTL_CODE(FILE_DEVICE_UNKNOWN, 0xC10, METHOD_NEITHER, FILE_ANY_ACCESS)
#define 控制码_创建远程线程 CTL_CODE(FILE_DEVICE_UNKNOWN, 0xC20, METHOD_NEITHER, FILE_ANY_ACCESS)
// =========================================================
// 2. 字符串与结构体定义
// =========================================================
#define 物理设备名 L"\\Device\\MTest"
#define 符号链接名 L"\\??\\Test"
typedef struct _保护进程结构体
{
ULONG 进程ID;
BOOLEAN 是否保护;
} 保护进程结构体, * P保护进程结构体;
// =========================================================
// 3. 全局变量与函数前置声明
// =========================================================
PDEVICE_OBJECT 全局_设备对象指针 = NULL;
UNICODE_STRING 全局_物理设备名称;
UNICODE_STRING 全局_符号链接名称;
NTSTATUS 派遣函数(PDEVICE_OBJECT 设备对象指针, PIRP IRP指针);
VOID 驱动卸载函数(PDRIVER_OBJECT 驱动对象指针);
// 模拟的设置回调函数 (供测试编译通过使用)
NTSTATUS SetProtectCallBack(HANDLE 进程句柄) {
UNREFERENCED_PARAMETER(进程句柄);
// 此处未来应补充真实的 ObRegisterCallbacks 逻辑
return STATUS_SUCCESS;
}
// =========================================================
// 4. 驱动卸载逻辑
// =========================================================
VOID 驱动卸载函数(PDRIVER_OBJECT 驱动对象指针)
{
UNREFERENCED_PARAMETER(驱动对象指针);
IoDeleteSymbolicLink(&全局_符号链接名称);
if (全局_设备对象指针 != NULL) {
IoDeleteDevice(全局_设备对象指针);
}
DbgPrint("[MTest] 驱动已卸载.\n");
}
// =========================================================
// 5. 驱动入口点
// =========================================================
extern "C" NTSTATUS DriverEntry(PDRIVER_OBJECT 驱动对象指针, PUNICODE_STRING 注册表路径指针)
{
NTSTATUS 执行状态码 = STATUS_SUCCESS;
UNREFERENCED_PARAMETER(注册表路径指针);
驱动对象指针->DriverUnload = 驱动卸载函数;
RtlInitUnicodeString(&全局_物理设备名称, 物理设备名);
RtlInitUnicodeString(&全局_符号链接名称, 符号链接名);
执行状态码 = IoCreateDevice(驱动对象指针, 0, &全局_物理设备名称, FILE_DEVICE_UNKNOWN, 0, FALSE, &全局_设备对象指针);
if (!NT_SUCCESS(执行状态码)) {
DbgPrint("[MTest] 创建设备失败! 状态码: 0x%08X\n", 执行状态码);
return 执行状态码;
}
执行状态码 = IoCreateSymbolicLink(&全局_符号链接名称, &全局_物理设备名称);
if (!NT_SUCCESS(执行状态码)) {
DbgPrint("[MTest] 创建符号链接失败!\n");
IoDeleteDevice(全局_设备对象指针);
return 执行状态码;
}
for (ULONG 索引 = 0; 索引 <= IRP_MJ_MAXIMUM_FUNCTION; 索引++) {
驱动对象指针->MajorFunction[索引] = 派遣函数;
}
DbgPrint("[MTest] 驱动加载成功.\n");
return 执行状态码;
}
// =========================================================
// 6. IRP 派遣函数 (核心通信逻辑)
// =========================================================
NTSTATUS 派遣函数(PDEVICE_OBJECT 设备对象指针, PIRP IRP指针)
{
UNREFERENCED_PARAMETER(设备对象指针);
NTSTATUS 执行状态码 = STATUS_SUCCESS;
ULONG 返回字节数 = 0;
PIO_STACK_LOCATION IRP栈单元指针 = IoGetCurrentIrpStackLocation(IRP指针);
if (IRP栈单元指针->MajorFunction != IRP_MJ_DEVICE_CONTROL) {
IRP指针->IoStatus.Status = STATUS_SUCCESS;
IRP指针->IoStatus.Information = 0;
IoCompleteRequest(IRP指针, IO_NO_INCREMENT);
return STATUS_SUCCESS;
}
ULONG 输入长度 = IRP栈单元指针->Parameters.DeviceIoControl.InputBufferLength;
ULONG 控制码 = IRP栈单元指针->Parameters.DeviceIoControl.IoControlCode;
P保护进程结构体 缓冲区指针 = NULL;
switch (控制码) {
case 控制码_保护进程:
if (输入长度 < sizeof(保护进程结构体)) {
执行状态码 = STATUS_INVALID_PARAMETER;
break;
}
缓冲区指针 = (P保护进程结构体)ExAllocatePoolWithTag(PagedPool, sizeof(保护进程结构体), 'torP');
if (缓冲区指针 == NULL) {
执行状态码 = STATUS_INSUFFICIENT_RESOURCES;
break;
}
__try {
// 1. 读取 Ring 3 传来的数据
RtlCopyMemory(缓冲区指针, IRP栈单元指针->Parameters.DeviceIoControl.Type3InputBuffer, sizeof(保护进程结构体));
DbgPrint("[MTest] 收到保护请求 - PID: %d\n", 缓冲区指针->进程ID);
// 2. 执行保护逻辑 (注意 64 位系统强转 HANDLE 的安全写法)
if (!NT_SUCCESS(SetProtectCallBack((HANDLE)(ULONG_PTR)缓冲区指针->进程ID))) {
缓冲区指针->是否保护 = FALSE;
}
else {
缓冲区指针->是否保护 = TRUE;
}
// 3. 将结果写回 Ring 3
// 必须验证 UserBuffer 不为空,防止蓝屏
if (IRP指针->UserBuffer != NULL) {
RtlCopyMemory(IRP指针->UserBuffer, 缓冲区指针, sizeof(保护进程结构体));
返回字节数 = sizeof(保护进程结构体); // 极其关键:告诉系统返回了多少字节
}
}
__except (EXCEPTION_EXECUTE_HANDLER) {
DbgPrint("[MTest] 内存访问异常!\n");
执行状态码 = STATUS_ACCESS_VIOLATION;
}
// 4. 清理内存
if (缓冲区指针 != NULL) {
ExFreePoolWithTag(缓冲区指针, 'torP');
缓冲区指针 = NULL;
}
break;
case 控制码_分配线程:
DbgPrint("[MTest] 控制码_分配线程 触发\n");
break;
case 控制码_创建远程线程:
DbgPrint("[MTest] 控制码_创建远程线程 触发\n");
break;
default:
执行状态码 = STATUS_INVALID_DEVICE_REQUEST;
break;
}
// 统一完成 IRP 请求
IRP指针->IoStatus.Status = 执行状态码;
IRP指针->IoStatus.Information = 返回字节数;
IoCompleteRequest(IRP指针, IO_NO_INCREMENT);
return 执行状态码;
}3环代码:
#include <windows.h>
#include <iostream>
// =========================================================
// 通信协议必须与内核保持绝对一致
// =========================================================
#define 控制码_保护进程 CTL_CODE(FILE_DEVICE_UNKNOWN, 0xC00, METHOD_NEITHER, FILE_ANY_ACCESS)
typedef struct _保护进程结构体 {
ULONG 进程ID;
BOOLEAN 是否保护;
} 保护进程结构体, * P保护进程结构体;
int main()
{
// 1. 打开驱动设备 (注意符号链接的写法:内核里是 \??\Test,在 R3 要写成 \\.\Test)
HANDLE 设备句柄 = CreateFileW(
L"\\\\.\\Test",
GENERIC_READ | GENERIC_WRITE,
0,
NULL,
OPEN_EXISTING,
FILE_ATTRIBUTE_NORMAL,
NULL
);
if (设备句柄 == INVALID_HANDLE_VALUE) {
std::cout << "[!] 打开驱动失败,错误码: " << GetLastError() << std::endl;
std::cout << "[!] 请确认驱动已加载,并且是用管理员权限运行本程序。" << std::endl;
system("pause");
return 1;
}
std::cout << "[+] 成功连接到内核驱动!" << std::endl;
// 2. 准备要发送的数据 (这里以保护本进程为例)
保护进程结构体 进程数据 = { 0 };
进程数据.进程ID = GetCurrentProcessId();
进程数据.是否保护 = FALSE; // 初始状态
DWORD 实际返回字节数 = 0;
std::cout << "[*] 发送请求前状态 -> PID: " << 进程数据.进程ID
<< ", 是否保护: " << (int)进程数据.是否保护 << std::endl;
// 3. 发送 DeviceIoControl 请求
BOOL 执行结果 = DeviceIoControl(
设备句柄, // CreateFile 拿到的句柄
控制码_保护进程, // 约定的控制码
&进程数据, // 输入缓冲区指针 (Type3InputBuffer)
sizeof(进程数据), // 输入缓冲区大小 (InputBufferLength)
&进程数据, // 输出缓冲区指针 (UserBuffer)
sizeof(进程数据), // 输出缓冲区大小 (OutputBufferLength)
&实际返回字节数, // 内核返回的实际长度 (IoStatus.Information)
NULL
);
// 4. 解析内核返回的结果
if (执行结果) {
std::cout << "[+] 请求成功! 内核返回字节数: " << 实际返回字节数 << std::endl;
std::cout << "[+] 内核回传状态 -> 是否保护: " << (int)进程数据.是否保护 << std::endl;
} else {
std::cout << "[-] 请求失败,系统错误码: " << GetLastError() << std::endl;
}
// 5. 释放句柄
CloseHandle(设备句柄);
system("pause");
return 0;
}赞赏
- [原创] CE 句柄提权内核驱动源代码 120
- [原创]Ini解析器 176
- [讨论]Windbg学习使用2 439
- [讨论]WindbgPreview的使用 871
- [原创]DLL注入技术大全(持续更新,欢迎插眼) 6878
