关于调试方面的学习笔记,主要来源于《软件调试》的读书笔记和梦织未来论坛的视频教程

1.调试器使用一个死循环监听调试信息。

DebugActiveProcess(PID);
while(TRUE)
{
DEBUG_EVENT MyDebugInfo;
WaitForDebugEvent(MyDebugInfo,INFINITE);//阻塞
switch (MyDebugInfo.dwDebugEventCode)
{
case CREATE_THREAD_DEBUG_EVENT:
break;
} }

2.什么是调试信息,进程创建、终止,加载模块都是调试信息。dwDebugEventCode说明了调试信息的种类。

dwDebugEventCode

Type: DWORD

The code that identifies the type of debugging event. This member can be one of the following values.

Value Meaning
CREATE_PROCESS_DEBUG_EVENT
3

Reports a create-process debugging event. The value of u.CreateProcessInfo specifies a CREATE_PROCESS_DEBUG_INFO structure.

CREATE_THREAD_DEBUG_EVENT
2

Reports a create-thread debugging event. The value of u.CreateThread specifies a CREATE_THREAD_DEBUG_INFO structure.

EXCEPTION_DEBUG_EVENT
1

Reports an exception debugging event. The value of u.Exception specifies an EXCEPTION_DEBUG_INFO structure.

EXIT_PROCESS_DEBUG_EVENT
5

Reports an exit-process debugging event. The value of u.ExitProcess specifies an EXIT_PROCESS_DEBUG_INFO structure.

EXIT_THREAD_DEBUG_EVENT
4

Reports an exit-thread debugging event. The value of u.ExitThread specifies an EXIT_THREAD_DEBUG_INFO structure.

LOAD_DLL_DEBUG_EVENT
6

Reports a load-dynamic-link-library (DLL) debugging event. The value of u.LoadDll specifies a LOAD_DLL_DEBUG_INFO structure.

OUTPUT_DEBUG_STRING_EVENT
8

Reports an output-debugging-string debugging event. The value of u.DebugString specifies an OUTPUT_DEBUG_STRING_INFO structure.

RIP_EVENT
9

Reports a RIP-debugging event (system debugging error). The value of u.RipInfo specifies a RIP_INFO structure.

UNLOAD_DLL_DEBUG_EVENT
7

Reports an unload-DLL debugging event. The value of u.UnloadDll specifies an UNLOAD_DLL_DEBUG_INFO structure.

3.DEBUG_EVENT中使用共用体来储存具体数据

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;

4.一但附加进程,Windows系统会发送调试信息。包括已经创建过的进程和加载过的模块信息也会发送

5.用户层调试函数的实现

6.调试原理概述

Windows调试系统使用事件驱动,这一点与窗体是很相似的。

WaitForDebugEvent是用来等待调试事件的,调试器处理调试事件时,被调试进程会挂起,所以调试器处理完毕后要调用ContinueDebugEvent来使挂起的被调试进程继续运行。

在系统内核中调试事件的数据结构是DBGKM_APIMSG,而NTDLL也就是原生应用层使用的是DBGUI_WAIT_STATE_CHANGE,而应用层的调试器是使用DEBUG_EVENT。所以要进行一些结构的转换。

在内核创建线程的函数执行过程中会调用一个DbgkCreateThread函数,这个DbgkCreateThread函数会检查新建线程的进程是否正在被调试(通过检查DebugPort的值是否为空)然后会发送调试消息。

注意windows内核函数会根据DebugPort是否为空来判断进程是否处于被调试情况。

内核态下的调试结构DBGKM_APIMSG

 typedef struct _DBGKM_APIMSG
{
PORT_MESSAGE h; // LPC端口消息结构,XP之前使用
DBGKM_APINUMBER ApiNumber; // 消息类型
ULONG ReturnedStatus; // 调试器的回复状态
union // 具体描述消息的共用体,真正的信息在这里面
{
DBGKM_EXCEPTION Exception; // 异常
DBGKM_CREATE_THREAD CreateThread; // 创建线程
DBGKM_CREATE_PROCESS CreateProcess; // 创建进程
DBGKM_EXIT_THREAD ExitThread; // 线程退出
DBGKM_EXIT_PROCESS ExitProcess; // 进程退出
DBGKM_LOAD_DLL LoadDll; // 映射DLl
DBGKM_UNLOAD_DLL UnloadDll; // 反映射Dll
};
} DBGKM_MSG, *PDBGKM_MSG; 复制代码

NTSTATUS
DbgkpSendApiMessage(
    IN OUT PDBGKM_APIMSG ApiMsg,
    IN BOOLEAN SuspendProcess
    );

调试系统使用DbgkpSuspendProcess和DbgkpResumeProcess这两个函数来控制被调试进程。

DbgkpSuspendProcess会冻结被调试进程中除了调用线程之外的所有线程,执行这个函数后被调试进程中就只有这个发生调试信息的线程还活动着。接着会执行实际的发送消息的函数,

即DbgkpQueueMessage。

windows调试子系统处于CSRSS会话管理器中,调试子系统是以内核对象DebugObject为核心的。

调试对象

来自wrk1.
typedef struct _DEBUG_OBJECT {
KEVENT EventsPresent;
FAST_MUTEX Mutex;
LIST_ENTRY EventList;
ULONG Flags;
} DEBUG_OBJECT, *PDEBUG_OBJECT

EventPresent事件对象是用来同步调试器进程和被调试进程的。函数WaitForDebugEvent等待的其实就这个事件。

快速互斥体Mutex用来处理并发访问,相当于一个锁的作用。

调试器与调试子系统连接时,调试子系统会创建一个调试对象(NtCreateDebugObject),并且将其保存在调试器当前线程的TEB的DbgSsReserved[1]中,而这个线程就是调试器线程。

要建立调试器与被调试进程之间的联系,需要把这个调试对象设置到被调试进程的EPROCESS的DebugPort中。

DbgkpQueueMessage函数用于向一个调试对象的消息队列中增加调试事件

这里EventList链表中每一项都是如下结构

 typedef struct _DEBUG_EVENT {
LIST_ENTRY EventList; // Queued to event object through this
KEVENT ContinueEvent; //用于等待调试器回复的事件对象
CLIENT_ID ClientId; //调试事件所在的线程ID和进程ID
PEPROCESS Process; // 被调试进程的EPROCESS
PETHREAD Thread; // 被调试进程中触发调试事件的线程的ETHREAD
NTSTATUS Status; //调试事件处理结果
ULONG Flags;
PETHREAD BackoutThread; // 产生假消息(Faked)的线程ETHREAD
DBGKM_APIMSG ApiMsg; // 调试事件的真正内容
} DEBUG_EVENT, *PDEBUG_EVENT;

以上来自WRK1.2,注意这个是与用户层同名都是DEBUG_EVENT但是内容完全不同,这是个内核结构。是内核中的调试事件结构。

DbgkpQueueMessage把这个结构插入到调试对象的调试事件链表中。

DbgkpQueueMessage有等待和不等待两种方式,如果指定不等待(异步处理)则函数直接返回。

如果没有指定不等待则设置调试对象的EventPresent,然后再等待(KeWaitForSingleObject)DEBUG_EVENT结构中的ContinueEvent对象用来等待调试器回复。

调试器调用ContinueDebugEvent实际上就是设置这个ContinueEvent对象。

在调试器进程执行的NtDebugActiveProcess中会调用一个函数DbgkpSetProcessDebugObject将一个调试对象设置到要调试的进程中(即EPROCESS的DebugPort)。

这样被调试进程就与调试器进程产生了联系(通过调试对象)

而由于

  1. 这个函数是代表附加进程方式调试(只有这种方式才会调用这个函数)
  2. 这个函数代表刚刚启动对进程的调试
  3. 附加进程方式调试代表目标进程已经运行了一段时间

所以就需要进行虚假调试信息发送。

会通过遍历被调试进程的所有线程,然后在调试内核对象中放置这些线程的虚假调试事件消息。再防止虚假模块加载调试事件消息。

当取消对进程的调试时,会将DebugPort端口清零。

调试器的调试线程的TEB中有特殊结构,这个是区别于普通线程的地方。DbgSsReserved[0]指向一个被调试进程的所有线程的链表,用来描述被调试进程中的每一个线程。

DbgSsReserved[1]指向调试对象。

WaitForDebugEvent和ContinueDebugEvent这两个函数会维护那个线程链表。

一个进程被调试会造成

  • 进程的EPROCESS的DebugPort值不为0
  • 进程的PEB的BeingDebugged值不为0
  • 可能会有调试器建立在被调试进程中的远程线程——RemoteBreakin线程

大名鼎鼎的IsDebuggerPresent就是通过判断BeingDebugged来实现的。

调试器与被调试进程之间的交互被称做“调试会话”

两种调试方式

  • 启动被调试进程
  • 附加到已经运行的被调试进程

1.启动被调试进程

首先调试器线程会调用一个DbgUiConnectToDbg来是调试器线程与调试子系统建立连接(初始化调试器线程),具体做法是新建一个内核调试对象,然后把这个内核调试对象放入调试器线程的DbgSsReserved[1]中,这样调试器线程就初始化好了。

当调用CreateProcess创建进程时指定DEBUG_PROCESS标志即可。系统会把调用这个函数的进程当作调试器进程,把新创建的进程当作被调试的进程。

建立起调试关系

当进程的初始线程创立时会查看自己是否是被调试中(BeingDebugged标志),如果是被调试中会调用DbgBreakPoint来触发一个断点

2.附加到已经运行的被调试进程

通过DebugActiveProcess就可以附加到一个已经运行的进程中。

首先是DbgUiConnectToDbg,这一步与上面是一样的。

打开被调试的进程,因为不打开被调试的进程也就没办法对其进行操作。

调用内核函数(向下分发调用)NtDebugActiveProcess

这个内核函数主要是

1.发送伪造的线程创建、进程创建和模块加载调试消息。

2.设置被调试进程的调试端口,调试对象在DbgUiConnectToDbg调用后就已经创建好了直接拿来用就可以,同时设置被调试进程BeingDebugged字段。

为什么会先发送调试消息,后设置调试端口呢?

这个不是很奇怪吗?发送之后才去设置调试端口?

其实是因为,所谓的发送调试消息实质上指的是创建并设置好一些调试事件,然后把这些调试事件放入调试对象的调试事件链表中,这样一来是否设置好了调试端口也就无关紧要了。

因为这些数据只是储存在调试对象中,还没有人去等待。

WaitForDebugEvent

调试器在用户层的操作接下来就会调用WaitForDebugEvent函数来实现,用来取出一个DEBUG_EVENT结构,这个函数是阻塞的,因而可以设置一个等待时间来防止无限等待。

这个函数会调用底层的等待调试事件函数,然后把等待到的结构转化为用户态的DEBUG_EVENT结构。因为我们前面说过,一个调试事件在不同的层次下的表示的数据结构是不同的。

调试器是如何实现让运行中的被调试进程立刻中断到调试器中的呢?这个功能叫做异步阻停,一种实现方法是使用CreateRemoteThread函数来新建一个触发int 3断点的线程。

系统中已经提供一个用来触发断点的函数,NTDLL的DbgUiRemoteBreakin函数。

windows xp之后系统提供了一个现成的API DebugBreakProcess

 BOOL WINAPI DebugBreakProcess(
_In_ HANDLE Process
);

这个函数会自动创建远程线程

读书笔记|Windows 调试原理学习|持续更新的更多相关文章

  1. <读书笔记>软件调试之道 :从大局看调试-零容忍策略

    声明:本文档的内容主要来源于书籍<软件调试修炼之道>作者Paul Butcher,属于读书笔记.欢迎转载! ---------------------------------------- ...

  2. Windows内核读书笔记——Windows异常分发处理机制

    本篇读书笔记主要参考自<深入解析Windows操作系统>和<软件调试>这两本书. IDT是处理异常,实现操作系统与CPU的交互的关口. 系统在初始化阶段会去填写这个结构. ID ...

  3. <读书笔记>软件调试之道 :问题的核心-修复后的反思

    声明:本文档的内容主要来源于书籍<软件调试修炼之道>作者Paul Butcher,属于读书笔记.欢迎转载! ---------------------------------------- ...

  4. <读书笔记>软件调试之道 :问题的核心-诊断

    声明:本文档的内容主要来源于书籍<软件调试修炼之道>作者Paul Butcher,属于读书笔记. 不要急于动手! 尽管可以利用各种工具和技术以及软件自身查找缺陷,但是你最重要的财富是你的智 ...

  5. Key Technologies Primer 读书笔记,翻译 --- Struct 学习 1

    原文链接:https://struts.apache.org/primer.html 本来想写成读书笔记的,结果还是变成翻译,谨作记录,学习.   1.HTML -- 见我前面文章 2.Interne ...

  6. <读书笔记>软件调试之道 :从大局看调试-理想的调试环境

    声明:本文档的内容主要来源于书籍<软件调试修炼之道>作者Paul Butcher,属于读书笔记.欢迎转载! ---------------------------------------- ...

  7. <读书笔记>软件调试之道 :从大局看调试-发现代码存在问题

    声明:本文档的内容主要来源于书籍<软件调试修炼之道>作者Paul Butcher,属于读书笔记.欢迎转载! ---------------------------------------- ...

  8. <读书笔记>软件调试之道 :问题的核心-如何修复缺陷

    声明:本文档的内容主要来源于书籍<软件调试修炼之道>作者Paul Butcher,属于读书笔记.欢迎转载! 修复缺陷 对于一个好的修复来说,不仅仅是让软件运行正确,还需要为将来奠定基础.一 ...

  9. <读书笔记>软件调试之道 :问题的核心-重现问题

    声明:本文档的内容主要来源于书籍<软件调试修炼之道>作者Paul Butcher,属于读书笔记. 重现第一,提问第二 问题重现是实证过程的最强大武器,如果不能重现问题,你也无法证明修复了它 ...

随机推荐

  1. Flash 0day CVE-2018-4878 漏洞复现

      0x01 前言 Adobe公司在当地时间2018年2月1日发布了一条安全公告: https://helpx.adobe.com/security/products/flash-player/aps ...

  2. 【Python简介】

    一.Python的简介 1.什么是python? Python(发音:[ 'paiθ(ə)n; (US) 'paiθɔn ]),是一种面向对象的解释性的计算机程序设计语言,也是一种功能强大而完善的通用 ...

  3. 【SQL优化】MySQL官网中可优化的层次结构

    正如上一篇中我翻译的那篇文章,关于MySQL数据库优化的宏观介绍,了解到了从大体上来讲,优化MySQL可以从3个角度来讲.那么这一篇文章,则从一个个优化点出发,统计出究竟有多少个地方我们可以来优化My ...

  4. CDOJ--1404

    原题链接:http://acm.uestc.edu.cn/problem.php?pid=1404 分析:定义dp[i][j]表示i位时最左边为j时的情况,那么dp[i][[j]可以由dp[i-1][ ...

  5. Messenger 进程间通信

    Messenger 使用 Messenger 可以在进程间传递数据, 实现一对多的处理. 其内部实现, 也是基于 aidl 文件, 这个aidl位于: frameworks/base/core/jav ...

  6. python学习(十五) 内建模块学习

    介绍python的几个內建模块,原文链接 1 python的时间模块datetime 取现在时间 from datetime import datetime now = datetime.now() ...

  7. C语言 两个小知识点

    strlen 函数原型 extern unsigned int strlen(char *s); 在Visual C++ 6.0中,原型为size_t strlen(const char *strin ...

  8. python---Scrapy模块的使用(二)

    出处:http://www.cnblogs.com/wupeiqi/ 一:去除重复URL scrapy默认使用 scrapy.dupefilter.RFPDupeFilter 进行去重,相关配置有: ...

  9. SpringCloud学习(1)——SpringCloud概述

    微服务架构: 微服务架构是一种架构模式或者说是一种架构风格, 他提倡将单一应用程序划分成一组小的服务, 每个服务运行在其独立的进程中, 服务之间互相协调,互相配合, 为用户提供最终价值.服务之间采用轻 ...

  10. Mongodb 备份 还原 导出 导入 等批量操作

    mongodb数据备份和还原主要分为二种,一种是针对于库的mongodump和mongorestore,一种是针对库中表的mongoexport和mongoimport. 一,mongodump备份数 ...