首页
社区
课程
招聘
[讨论]WindbgPreview的使用
发表于: 2天前 870

[讨论]WindbgPreview的使用

2天前
870

最近慢慢复习过去的视频,看到了这个的使用,特此复习一下。

一、windbg使用

1.常用指令



2.记忆指南

通过了解 WinDbg 命令背后的英文全称来进行记忆,是非常高效且专业的学习方法。WinDbg 的命令设计其实非常符合英文直觉,掌握了缩写规律,你就不需要死记硬背了。

这主要分为几大系列:

1. 执行与反汇编 (Execution & Unassembly)

命令英文全称 / 记忆词含义说明
gGo运行程序,直到遇到断点或结束。
uUnassemble反汇编指定地址的代码。
ufUnassemble Function反汇编整个函数体。

2. 内存查看命令 (d 系列 - Display/Dump)


d 代表 Display (显示) 或 Dump (转储)。后面的字母代表数据类型或格式:

命令英文全称 / 记忆词含义说明
dtDisplay Type显示结构体(Type/Struct)的内存布局。
dbDisplay Byte字节 (Byte, 8位) 显示。
dwDisplay Word (Word, 16位) 显示。
ddDisplay Dword双字 (Double Word, 32位) 显示。
dqDisplay Qword四字 (Quad Word, 64位) 显示。
dDDisplay Double双精度浮点数 (Double precision float) 显示。
dfDisplay Float单精度浮点数 (Float) 显示。
daDisplay ASCIIASCII 字符串显示。
duDisplay UnicodeUnicode (UTF-16) 字符串显示。
dcDisplay Chars (and Dwords)同时显示双字值和对应的 ASCII 字符 (Chars)。
dsDisplay String显示 ANSI_STRING 结构体。
dSDisplay String (Unicode)显示 UNICODE_STRING 结构体 (大写 S 代表宽字符/Unicode)。

3. 内存写入命令 (e 系列 - Enter)


e 代表 Enter (输入/写入)。后面的字母代表写入的数据长度,规律同上:

命令英文全称 / 记忆词含义说明
ebEnter Byte写入一个字节。
ewEnter Word写入一个字。
edEnter Dword写入一个双字。
eqEnter Qword写入一个四字。

4. 断点命令 (b 系列 - Breakpoint)


b 代表 Breakpoint (断点)。

命令英文全称 / 记忆词含义说明
bpBreakpoint设置软件断点 (通常写入 int 3 指令)。
baBreakpoint Access设置硬件/访问断点 (依赖 CPU 调试寄存器)。
blBreakpoint List列出 (List) 所有断点。
beBreakpoint Enable启用 (Enable) 断点。
bdBreakpoint Disable禁用 (Disable) 断点。
bcBreakpoint Clear清除 (Clear) 断点。

5. 调用栈与寄存器 (Stack & Registers)


栈回溯命令以 k 开头(来源于 Call Stack 或直接作为回溯的标识)。

命令英文全称 / 记忆词含义说明
rRegister查看或修改寄存器 (Registers)。
kpCall Stack with Parameters显示调用栈,并尝试打印函数参数 (Parameters)。
kcCall Stack Clean显示干净的 (Clean) 调用栈,只看模块和函数名。

6. 环境、进程与模块 (Environment & Modules)

命令英文全称 / 记忆词含义说明
lmList Modules列出 (List) 当前加载的所有模块 (Modules, 如 dll/exe)。
!processProcess扩展命令,用于查看进程 (Process) 详细信息。
.processProcess Context元命令,用于切换当前的进程上下文 (Context)。
!HANDLEHandle扩展命令,查看句柄 (Handle) 信息。
.reloadReload Symbols重新加载 (Reload) 符号文件。
.sympathSymbol Path查看或设置符号路径。
.clsClear 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 命令),就是配置微软的公用符号服务器。

实操小贴士:你在做实验或者调试时,为了让所有命令正常工作,通常需要执行以下操作来自动下载符号表:

  1. 设置符号路径(告诉 WinDbg 去微软服务器下载字典并缓存在本地 c 盘):.sympath SRV*c:\mysymbols*542K9s2c8@1M7s2y4Q4x3@1q4Q4x3V1k6Q4x3V1k6E0M7$3c8D9i4K6u0W2L8h3W2U0M7X3!0K6L8$3k6@1i4K6u0W2j5$3!0E0i4K6u0r3k6r3!0%4L8X3I4G2j5h3c8Q4x3V1k6K6P5h3#2T1L8$3I4K6

  2. 强制重新加载符号:.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;
}














传播安全知识、拓宽行业人脉——看雪讲师团队等你加入!

最后于 1天前 被BOSC叛忍编辑 ,原因:
收藏
免费 1
打赏
分享
最新回复 (0)
游客
登录 | 注册 方可回帖
返回