讲师中心 微信公众号
AI工具推荐 视频效率加速

初探Windows用户态调试机制

轻宇小哥_5892

轻宇小哥_5892

发布时间:2025-09-25 09:35:22

|

854人浏览过

|

来源于php中文网

原创

我们在感叹onlydbg强大与便利的同时,是否考虑过它实现的原理呢?

作为一个技术人员知其然必知其所以然,这才是我们追求的本心。

最近在学习张银奎老师的《软件调试》,获益良多。熟悉Windows调试机制,对我们深入理解操作系统以及游戏保护的原理有着莫大好处。

0X01

初探调试原理

初探Windows用户态调试机制

调试系统的实现思路如图所示:

调试器与被调试程序建立联系,程序像调试器发送调试信息,调试器暂停程序处理完调试信息后再恢复程序运行,如此周而复始。

下面我们看看如何用操作系统提供的API去实现一个简单的调试器。

初探Windows用户态调试机制
代码语言:javascript代码运行次数:0运行复制
//启动要调试的进程或挂接调试器到已运行的进程上CreateProcess(..., DEBUG_PROCESS, ...) or DebugActiveProcess(dwProcessId)DEBUG_EVENT de;BOOL bContinue = TRUE;DWORD dwContinueStatus;while(bContinue){  bContinue = WaitForDebugEvent(&de, INFINITE);  switch(de.dwDebugEventCode)  {  ...  default:    {      dwContinueStatus = DBG_CONTINUE;      break;    }  }  ContinueDebugEvent(de.dwProcessId, de.dwThreadId, dwContinueStatus);}
初探Windows用户态调试机制

在调试器开始调试的时候,会启动被调试程序的新进程或者挂接(attach)到一个已运行进程上,此时Win32系统会启动调试接口的服务器端;然后调试器调用WaitForDebugEvent函数等待调试服务器端的调试事件被引发;调试器根据调试事件进行相应的处理;最后调用ContinueDebugEvent函数请求调试服务器继续执行被调试进程,以等待并处理下一个调试事件。

0X02

抽茧剥丝看调试机制

要想深入了解Windows调试机制,对着三个函数的深入分析是必不可少的。

1.DebugActiveProcess

初探Windows用户态调试机制
代码语言:javascript代码运行次数:0运行复制
BOOL WINAPI DebugActiveProcessSelf(    _In_  DWORD dwProcessId    ){    NTSTATUS    status;    HANDLE        TargProcessHandle;    status = DbgUiConnectToDbg();  //DebugObject    if (!NT_SUCCESS(status))    {        BaseSetLastNTError(status);        return false;    }    TargProcessHandle = GetTargProcessHandle(dwProcessId);    if (TargProcessHandle == 0)    {        return false;    }    //调试目标进程    status = DbgUiDebugActiveProcess(TargProcessHandle);        //不管调试是否成功都关闭目标进程句柄    ZwClose(TargProcessHandle);    if (!NT_SUCCESS(status))    {        BaseSetLastNTError(status);        return false;    }    return true;}
初探Windows用户态调试机制

DbgUiConnectToDbg函数内部主要调用ZwCreateDebugObject创建一个调试对象,并将调试对象句柄保存在调试器当前线程的TEB结构的DbgSsReserved[1]中。

其中TEB可以通过FS:[0x18]获得,DbgSsReserved字段在不同操作系统版本中也不相同,在Win732位中处于TEB结构的0xF20中。那么我们可以通过一下汇编得到DbgSsReserved。

初探Windows用户态调试机制
代码语言:javascript代码运行次数:0运行复制
    __asm{        push    eax        mov        eax,FS:[0x18]        lea        eax,[eax+0xF20]        mov        DbgSsReserved,eax        pop        eax    }
初探Windows用户态调试机制

那么到底什么是调试对象呢?

调试任务的顺利进行在于调试器与调试程序两者间的事件交互,一开始的图里已经很好的表示了。既然是两个进程间的交互,那么必定涉及进程间通信的问题,我在Windows进程通信中已经总结的很明白了,进程间通信靠的是所有进程共享高2G内核空间中的内核对象,

比如事件对象,管道对象等。由此可以推断出调试对象就是调试器与被调试程序间通讯的桥梁! 调试对象保存在调试器TEB线程环境变量块的DbgSsReserved[1]中,保存在被调试进程的DebugPort字段中。(这点下文做详细分析)所以判断一个进程是否被调试可

以看这个进程的DebugPort字段。游戏保护其中的一种保护手段就是通过不断抹除DebugPort,从而达到反调试的目的,所以我们发现用OD无法附加游戏,当然我们可以通过端口移位的方法绕过这种保护方法,这里暂且不做讨论。

GetTargProcessHandle函数主要就是运用ZwOpenProcess函数获得了下进程句柄,在此不作分析,我们下面主要看看最后这个DbgUiDebugActiveProcess函数。

初探Windows用户态调试机制
代码语言:javascript代码运行次数:0运行复制
NTSTATUS DbgUiDebugActiveProcess(HANDLE hTargProcess){    NTSTATUS    status;    HANDLE        hDebugObject;    hDebugObject = (GetThreadDbgSsReserved())[1];    status = ZwDebugActiveProcess(hTargProcess,hDebugObject);    if (!NT_SUCCESS(status))    {        return status;    }        status = DbgUiIssueRemoteBreakin(hTargProcess);  //创建远程线程 设置远程断点    if (!NT_SUCCESS(status))    {        DbgUiStopDebugging(hTargProcess);    }    return status;}
初探Windows用户态调试机制

我们先来看看DbgUiIssueRemoteBreakin函数

这个函数比较简单的主要作用是创建远程线程下远程断点,如果没有断点进行拦截,那还怎么调试。

到此DebugActiveProcess函数在Ring3下分析的就差不多了,剩下我们可以看见把被调试程序和调试对象作为参数调用系统函数ZwDebugActiveProcess

我结合上面所说的是不是很清晰这个系统调用在内核做了些什么事情呢? 显然在内核把调试对象放到被调试进程的Debugport字段中去了!

Windows 图片打印
Windows 图片打印

通过 Python 使用 mspaint /pt 在 Windows 上打印图像——绕过 bash 中文路径编码问题。

下载

但是ZwDebugActiveProcess在内核中所做的事情可不止这么一点哦,这个函数主要做三件事:

(1)取得被调试进程EPROCESS和调试对象的指针。

(2)向调试对象发送杜撰的调试事件。(当调试器附加到一个已经运行的进程时,为了向调试器报告以前发生的但目前仍有意义的调试事件,调试子系统会“捏造”一些调试事件来模拟过去的调试事件,这样的调试消息被称为杜撰的调试消息)。

(3)调用DbgSetprocessDebugObject将调试对象设置到被调试进程的Debug字段,并调用DbgkpmarkprocessPeb设置PEB中的BeingDebugged字段。

我觉得学习新知识就应该从大体入手,千万不能太抠细节,在有了清晰的框架后再逐渐了解细节的实现问题。看到这里肯定有了很多疑问,比如调试事件结构是什么,它又是如何获得的,又是怎么通过调试对象进行传递的?下面我们再来一探究竟。

调试事件的采取

首先我们应该明白什么算调试事件:被调试进程创建了一个进程、创建了一个线程、加载了一个模块......这些都是调试事件,那么调试器又是如何知道的呢?

在操作系统中有一组Dbgk开头的一组函数它们就是采集例程。以创建线程为例,我们看一下调试消息传递过程。

当我们调用CreateThread函数时,函数建立了线程必要的内核对象和数据结构,做了必要的登记后,最终会调用PspUserthreadStartup函数,准备启动该线 程。为了支持调试,PspUserThreadStartup函数总是会调用DbgkCreateThread,以便采集调试事件。DbgkCreateThread函数会检查自己的DebugPort字段是否为空来判断自己是否被调试,如果被调试,则采集调试信息调用DbgkpSendApiMessage函数向DebugPort发送消息。同理可得LoadLibrary会调用系统函数NtMapViewOfSection然后会调用采集函数DbgkMapViewOfMapSection,最后判断自己是否被调试决定是否采集调试事件来调用DbgkpSendApiMessage。

我们看到采集调试事件中最后都是调用DbgkpSendApiMessage,那么这个函数到底做了些什么呢?

我们先来看看这个函数的定义

代码语言:javascript代码运行次数:0运行复制
NTSTATUS DbgkpSendApiMessage(    IN OUT PDBGKM_APIMSG ApiMsg,    IN PVOID Port,    IN BOOLEAN SuspendProcess)

其中ApiMsg用来描述消息的,Port用来指定要发送的端口,大多数时候就是EPROCESS结构的DebugPort字段的值,偶尔是进程中的异常端口,即ExceptionPort字段。

初探Windows用户态调试机制
代码语言:javascript代码运行次数:0运行复制
//消息结构typedef struct _DBGKM_APIMSG {     PORT_MESSAGE h;                                //+0x0    DBGKM_APINUMBER ApiNumber;                    //+0x18    NTSTATUS ReturnedStatus;                    //+0x1c    union {         DBGKM_EXCEPTION Exception;              //异常        DBGKM_CREATE_THREAD CreateThread;       //创建线程        DBGKM_CREATE_PROCESS CreateProcessInfo; //创建进程        DBGKM_EXIT_THREAD ExitThread;           //线程退出        DBGKM_EXIT_PROCESS ExitProcess;         //进程退出        DBGKM_LOAD_DLL LoadDll;                 //映射DLL        DBGKM_UNLOAD_DLL UnloadDll;             //反映射DLL    } u;                                        //0x20} DBGKM_APIMSG, *PDBGKM_APIMSG;
初探Windows用户态调试机制

其中DBGKM_APINUMBER是个枚举常量。

初探Windows用户态调试机制
代码语言:javascript代码运行次数:0运行复制
//枚举类型,指定是哪种事件typedef enum _DBGKM_APINUMBER {     DbgKmExceptionApi,     DbgKmCreateThreadApi,     DbgKmCreateProcessApi,     DbgKmExitThreadApi,     DbgKmExitProcessApi,     DbgKmLoadDllApi,     DbgKmUnloadDllApi,     DbgKmMaxApiNumber } DBGKM_APINUMBER; 
初探Windows用户态调试机制

上面说道DbgkpSendApiMessage把调试消息发送给调试对象,那么调试对象又是如何管理这些调试消息的呢?

初探Windows用户态调试机制
代码语言:javascript代码运行次数:0运行复制
//调试对象typedef struct _DEBUG_OBJECT{    KEVENT EventsPresent;    FAST_MUTEX Mutex;    LIST_ENTRY EventList;    union    {        ULONG Flags;        struct        {            UCHAR DebuggerInactive:1;            UCHAR KillProcessOnExit:1;        };    };} DEBUG_OBJECT, *PDEBUG_OBJECT;
初探Windows用户态调试机制

这个就是调试对象的数据结构,里面可以清晰的看见有个LIST_ENTRY的双向链表。

到这里可能已经有点迷糊了,我们需要个图来整理整理。

初探Windows用户态调试机制

这里需要注意的是有个KEVENT的内核事件对象,我们回忆下应用层有个WaitForDebugEvent函数在阻塞着,这个事件就是通知调试器有调试事件到达。

2.WaitForDebugEvent

代码语言:javascript代码运行次数:0运行复制
BOOL WINAPI WaitForDebugEvent(    _Out_  LPDEBUG_EVENT lpDebugEvent,    _In_   DWORD dwMilliseconds    )

WaitForDebugEvent用于等待和接收调试事件,收到调试事件后,调试器便根据事件的类型(事件ID)来分发和处理,并根据情况决定是否要通知用户并进入交互式调试。在处理调试事件的过程中,被调试进程时处于挂起状态的。处理调试事件后,调试器调用ContinueDebugEvent将处理结果回复给调试子系统。到这里细心的似乎已经发现这个调试事件和内核中的调试事件的结构不一样。

初探Windows用户态调试机制
代码语言:javascript代码运行次数:0运行复制
typedef struct _DEBUG_EVENT {  DWORD dwDebugEventCode;  DWORD dwProcessId;  DWORD dwThreadId;  union {    EXCEPTION_DEBUG_INFO      Exception;    CREATE_THREAD_DEBUG_INFO  CreateThread;    CREATE_PROCESS_DEBUG_INFO CreateProcessInfo;    EXIT_THREAD_DEBUG_INFO    ExitThread;    EXIT_PROCESS_DEBUG_INFO   ExitProcess;    LOAD_DLL_DEBUG_INFO       LoadDll;    UNLOAD_DLL_DEBUG_INFO     UnloadDll;    OUTPUT_DEBUG_STRING_INFO  DebugString;    RIP_INFO                  RipInfo;  } u;} DEBUG_EVENT, *LPDEBUG_EVENT;
初探Windows用户态调试机制

在内核中调试事件使用DBGKM_APIMSG的结构来描述。在发送调试器时,调试API使用的是DEBUG_EVENT结构。所以之间必定有一个转换过程。简单的说,DBGKM_APIMSG转换成DBGUI_WAIT_STATE_CHANGE然后在转换成DEBUG_EVENT。

我们再来画张图整理一下

初探Windows用户态调试机制

0X03 总结

初探Windows用户态调试机制

热门AI工具

更多
UP简历
UP简历 Hot

一款AI办公效率工具,主要用于基于AI技术的免费在线简历制作工具,适合需要提升相关任务效率的用户。

PixPix
PixPix Hot

PixPix是一款面向电商视觉生产的AI商品图生成工具。

Atoms
Atoms Hot

Atoms是一款AI智能体工具,第一支自动构建真实业务的 AI 团队。

WorkBuddy

一款AI办公效率工具,主要用于腾讯云推出的AI原生桌面智能体工作台,适合需要提升相关任务效率的用户。

DeepSeek

DeepSeek是一款面向对话、写作、编程和推理场景的AI大模型工具。

VibeKnow
VibeKnow Hot

一款AI视频创作工具,主要用于全球首个AI知识视频创作平台,文档、文章、网页,一键生成视频,适合需要提升相关任务效率的用户。

二狗PPT
二狗PPT Hot

一款AI演示文稿工具,主要用于专为中式职场打造的AI PPT生成工具,适合需要提升相关任务效率的用户。

豆包大模型

豆包大模型是一款由字节跳动推出的企业级大语言模型服务平台。

超级简历WonderCV

一款AI办公效率工具,主要用于免费求职简历模版下载制作,应届生职场人必备简历制作神器,适合需要提升相关任务效率的用户。

相关专题

更多
java基础知识汇总
java基础知识汇总

java基础知识有Java的历史和特点、Java的开发环境、Java的基本数据类型、变量和常量、运算符和表达式、控制语句、数组和字符串等等知识点。想要知道更多关于java基础知识的朋友,请阅读本专题下面的的有关文章,欢迎大家来php中文网学习。

6284

2023.10.24

C++ 智能指针与现代内存管理
C++ 智能指针与现代内存管理

深入讲解 C++ 现代内存管理的核心工具——智能指针,涵盖 unique_ptr 独占所有权语义、shared_ptr 引用计数机制与循环引用问题、weak_ptr 弱引用的应用场景、make_unique/make_shared 工厂函数的性能优势、自定义删除器的编写、RAII 资源管理思想的实践,以及从裸指针迁移到智能指针的重构策略,帮助开发者编写安全无泄漏的现代 C++ 代码。

379

2026.04.23

treenode的用法
treenode的用法

​在计算机编程领域,TreeNode是一种常见的数据结构,通常用于构建树形结构。在不同的编程语言中,TreeNode可能有不同的实现方式和用法,通常用于表示树的节点信息。更多关于treenode相关问题详情请看本专题下面的文章。php中文网欢迎大家前来学习。

2781

2023.12.01

C++ 高效算法与数据结构
C++ 高效算法与数据结构

本专题讲解 C++ 中常用算法与数据结构的实现与优化,涵盖排序算法(快速排序、归并排序)、查找算法、图算法、动态规划、贪心算法等,并结合实际案例分析如何选择最优算法来提高程序效率。通过深入理解数据结构(链表、树、堆、哈希表等),帮助开发者提升 在复杂应用中的算法设计与性能优化能力。

396

2025.12.22

深入理解算法:高效算法与数据结构专题
深入理解算法:高效算法与数据结构专题

本专题专注于算法与数据结构的核心概念,适合想深入理解并提升编程能力的开发者。专题内容包括常见数据结构的实现与应用,如数组、链表、栈、队列、哈希表、树、图等;以及高效的排序算法、搜索算法、动态规划等经典算法。通过详细的讲解与复杂度分析,帮助开发者不仅能熟练运用这些基础知识,还能在实际编程中优化性能,提高代码的执行效率。本专题适合准备面试的开发者,也适合希望提高算法思维的编程爱好者。

437

2026.01.06

C++ 数据结构与算法实现教程合集
C++ 数据结构与算法实现教程合集

以 C++ 为实现语言,系统讲解核心数据结构与算法,涵盖链表(单链表/双链表/环检测)、栈与队列(单调栈/优先队列)、二叉树(遍历/BST/AVL/红黑树)、哈希表(开地址法/链地址法)、图(邻接表/BFS/DFS/Dijkstra/拓扑排序)、常见排序算法(快排/归并/堆排/计数排序)的实现与复杂度分析,同时分享 LeetCode 刷题技巧、竞赛编程常用模板(二分/前缀和/滑动窗口/动态规划),帮助开发者夯实算法基础。

492

2026.05.09

硬盘接口类型介绍
硬盘接口类型介绍

硬盘接口类型有IDE、SATA、SCSI、Fibre Channel、USB、eSATA、mSATA、PCIe等等。详细介绍:1、IDE接口是一种并行接口,主要用于连接硬盘和光驱等设备,它主要有两种类型:ATA和ATAPI,IDE接口已经逐渐被SATA接口;2、SATA接口是一种串行接口,相较于IDE接口,它具有更高的传输速度、更低的功耗和更小的体积;3、SCSI接口等等。

3208

2023.10.19

PHP接口编写教程
PHP接口编写教程

本专题整合了PHP接口编写教程,阅读专题下面的文章了解更多详细内容。

5269

2025.10.17

DBeaver怎么导入数据
DBeaver怎么导入数据

DBeaver数据导入专题,整理Excel、CSV、SQL、TXT等常见文件的数据导入方法,涵盖目标数据库选择、数据表创建、字段映射、数据类型设置及导入参数配置等操作,帮助用户将本地文件中的数据写入数据库,并了解批量导入、重复数据处理和导入失败等常见问题的解决方法。

0

2026.11.05

热门下载

更多
网站特效
/
网站源码
/
网站素材
/
前端模板

精品课程

更多
相关推荐
/
热门推荐
/
最新课程
Gradle Kotlin DSL API 参考
Gradle Kotlin DSL API 参考

共0课时 | 0人学习

JDK 27官方文档
JDK 27官方文档

共0课时 | 0人学习

LLVM Programmer’s Manual
LLVM Programmer’s Manual

共0课时 | 0人学习

关于我们 免责申明 举报中心 意见反馈 讲师合作 广告合作 最新更新
php中文网:公益在线php培训,帮助PHP学习者快速成长!
关注服务号
PHP中文网订阅号
每天精选资源文章推送

Copyright 2014-2026 https://www.php.cn/ All Rights Reserved | php.cn