Windows 驱动开发 之 WinDbg调试(一)

课程链接:https://www.bilibili.com/video/BV1r7411A7hq?vd_source=4f5979757af4551dfc8d2f504918a338

Windows 驱动开发 之 WinDbg调试(一)

一、序言

  1. 相关特性

    • 软件调试正在逐渐地变成一门学科,其目标为使用调试器或其他工具定位软件错误的过程

    • 重现>定位错误根源>解决>验证 的循环过程

    • 其为一种特别的搜索问题,具备以下特点

      • 关键词不明确

      • 目标空间很庞大,4GB/16TB,整个计算机系统,庞杂的代码,软件的复杂化和大型化

  2. 调试比写代码困难两倍

  3. 软件调试时一项系统工程

  4. 课程目录

    image-20220612190737937

二、CPU的调试支持

2.1 x86的主要调试设施
  1. int 3 指令,8086引入,是软件断点的基础

  2. 追踪标志(TF),8086引入,是单步追踪技术的基础

    • 标志寄存器TF,一旦置1将会触发CPU异常
  3. 调试寄存器(DR0~DR7),80386引入

    • 硬件断点的基础,监视变量、IO访问,也可以针对代码
  4. 分支监视和记录, Pentium Pro 引入,是按分支单步的基础,记录软件的执行流程(TF一次只能单步一条分支指令,会很累)

2.2 软件断点
  1. 特点

    • INT 3指令,即0xCC
    • 机器码为1字节

    • 没有数量限制

    • 局限性

      • 属于代码类断点,即可以让CPU执行到代码段 内的某个地址时停下来,不适用于数据段和 I/O空间。

      image-20220612191755013

      • 对于在ROM(只读存储器)中执行的程序 (比如BIOS或其它固件程序),无法动态增加软件断点。因为目标内存是只读的无法动态写入断点指令。这时就要使用后面介绍的硬件断点
  2. 举例(winmine.exe)

    step 1 查看ntdll著名的函数readfile

    注意打开时要使用运行可执行文件的open executable,不要直接拖入代码框,运行指令x ntdll!*readfile

    [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-hiVK1TgQ-1655386785124)(https://cdn.jsdelivr.net/gh/YOURLEGEND/PictureBedForCSDN@main//img/202206122035532.png)]ntdll是每个windows程序都有的著名的特殊用户态模块,nt表示windows操作系统的别名,ntdll可以表示windows内核

    随后使用指令u将地址上的二进制反汇编为代码输出,即指令u ntdll!NtReadFile

    [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-JG1aE6dh-1655386785124)(https://cdn.jsdelivr.net/gh/YOURLEGEND/PictureBedForCSDN@main//img/202206141134639.png)]

    step 2 使用bp指令设置软件断点

    使用指令bp对某地址下断点,bl指令看所有断点地址

    image-20220612213238167

    此时可以使用bl指令来查看所有断点

    step 3 使用g指令运行(go)

    go指令之后可以看到程序加载了一些模块

    [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-bxQG0ZVU-1655386785125)(https://cdn.jsdelivr.net/gh/YOURLEGEND/PictureBedForCSDN@main//img/202206122137680.png)]

    可以使用k指令来查看当前线程函数堆栈,查看为什么触发断点

    image-20220612214155637

    同样可以使用u指令查看反汇编

    image-20220612214320662

​ 值得注意的是,触发断点时并没有int 0x03,这里是调试器故意做的事情,先使用int 0x03代替对应的指令,触发后将替换的内容恢复回来,如果想要看可以打开另外一个windbg,使用noninvasive模式进行调试,一般情况下两个调试程序不能调试同一个程序,但是强大的windbg使用非入侵模式可以使用只读模式读另一个程序的内存

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-8JWDCx0v-1655386785126)(https://cdn.jsdelivr.net/gh/YOURLEGEND/PictureBedForCSDN@main//img/202206122155229.png)]

​ 我们对同一个地址进行反汇编,发现指令int 3出现

image-20220613144612804

​ 需要注意的是,由于int 3指令只是CC一个字节,后面出现的add指令是因为未被替换的指令未对齐解析生成的结果,因为0x00正好是add指令操作符的译码结果。

2.3 硬件断点、陷阱和JTAG
  1. 硬件断点的实现

    硬件断点的基础就是调试寄存器,著名的X86架构的调试寄存器(Debug Registers,简称DR)如下

    image-20220613145344225

    • DR0~DR3可以放四个线性地址,即如果需要下硬件断点,CPU将会将断点地址放在其中一个线性地址。对应DRx寄存器中线性地址的断点的长度LEN信息和读写R/W信息对应DR7中的高16位标志位,即LENx和R/Wx。

    • DR6用于断点地址的检测,在执行每条指令前CPU都会检测当前地址是否与DR0~DR3中的地址匹配,如果匹配则设置DR6中的标志位,然后设置异常报告给操作系统。操作系统检查标志位时就知道哪一个断点匹配命中了。

    • 这些标志位支持4个硬件断点,因此DR4~DR5目前没有用处,理论来讲每个CPU最多可以劫持4个硬件断点地址(软件层可以理解为每个线程最多4个地址)

  2. 硬件断点特点

    • 基于CPU的调试寄存器
    • 可以对代码、 数据访问和IO访问设置断点
    • 断点被触发时, CPU产生的是1号异常(设置硬件断点不需要改自己的程序)
    • 受调试寄存器的数量限制
    • WinDbg的ba命令设置的便是硬件断点
    • 在多处理器系统中,硬件断点是与CPU相关的,也就是说针对一个CPU设置的硬件断点并适用于其它CPU
  3. 陷阱标志

    • 单步陷阱标志(TF),位于Flags寄存器中

    • 任务状态段(TSS)陷阱标志,位于每个线程的任务状态段(TSS)内

    • 分支到分支单步执行标志(BTF),位于MSR寄存器中(P6的DebugCtlMSR,P4的DebugCtlA,Pentium M的DebugCtlB)

    使用r指令观察寄存器的值

    [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-Koo9QlnP-1655386785126)(https://cdn.jsdelivr.net/gh/YOURLEGEND/PictureBedForCSDN@main//img/202206131520862.png)]

​ 其中efl的bit8(pf位),如果为单步调试执行时会被置1,CPU在执行任何一条指令时如果发现efl寄存器的第八位置1,则执行一步即触发异常。注意CPU检测pf位置1会自动清pf位,因此pf位很难调试时看到置1的。

  1. 异常

​ x86的终端向量表(IDT)如下,前32个是留给CPU保留使用的,目前使用了19个,后面留作用户或者特定操作系统定义。如触发断点时的int 0x03即调用异常中的3号表项breakpoint进行处理,同理单步调试对应1号表项,页故障对应14号表项

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-QINmv9Cw-1655386785126)(https://cdn.jsdelivr.net/gh/YOURLEGEND/PictureBedForCSDN@main//img/202206131526884.png)]

  1. JTAG调试标准

    ​ [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-mhsNlP4P-1655386785127)(https://cdn.jsdelivr.net/gh/YOURLEGEND/PictureBedForCSDN@main//img/202206131531373.png)]

​ 标准适用于所有的集成电路,其基于一个边界扫描条,对每个管脚的信号进行扫描,通过控制器移位输出出去。通过上位机发送特殊信号给JTAG调试接口以测试被调试系统。X86著名的硬件调试架构叫做ITT。

image-20220613153533352

三、操作系统的调试支持

3.1 Windows 用户态调试模型

1.Windows XP用户态调试模型

image-20220613154444285

​ step 1 为了访问内存,待调试程序调用调试API

​ step 2 调试事件通过内核沟通一般通过类似Int 0x03指令触发异常进内核态,调用IDT内核函数

​ step 3 通过raise和dispatch分发异常,其会通知调试子系统

​ step 4 dbgk判断是否有调试器

​ step 5 如果有调试器,向调试器发送信息

​ step 6 将调试事件放入队列

​ step 7 调试器进程会等待队列中下一个事件

​ step 8 当等待完成时调试器会取出和处理事件(如断点),能看到调试界面了

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-hhgCUyzp-1655386785127)(https://cdn.jsdelivr.net/gh/YOURLEGEND/PictureBedForCSDN@main//img/202206132036281.png)]

​ WINDOWS XP的调试模型是由调试事件驱动的,主要分为以下几类

调试事件功能
EXCEPTION_DEBUG_EVENT异常调试事件(如断点就是特殊的异常)
CREATE_THREAD_DEBUG_EVENT创建线程调试事件
CREATE_PROCESS_DEBUG_EVENT创建进程调试事件
EXIT_THREAD_DEBUG_EVENT线程退出调试事件
EXIT_PROCESS_DEBUG_EVENT进程退出调试事件
LOAD_DLL_DEBUG_EVENTDLL加载调试事件
UNLOAD_DLL_DEBUG_EVENTDLL卸载调试事件
OUTPUT_DEBUG_STRING_EVENT输出调试信息事件

2.XP之前的用户态调试模型

image-20220613204806127

​ 注意其不支持分离调试会话,一旦开始调试,则调试器和被调试器"生死与共"

3.工作线程机制

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-7cUqmdNy-1655386785128)(https://cdn.jsdelivr.net/gh/YOURLEGEND/PictureBedForCSDN@main//img/202206132055421.png)]

image-20220613205803910

4.异常的来源

  • CPU产生

    • 执行指令时检测到的错误,除O,GP,无效指令

    • Machine Check Exceptions, 总线错误,ECC错误, Cache错误

    • 预先埋伏的,Int 3,调试异常

  • 程序产生

    • RaiseException,Win32 API

    • C++,throw E, 编译器会翻译为对RaiseException 调用

    • C#,throw,最终仍是调用RaiseException

5.理解用户态调试

step 1 打开记事本notepad.exe,将该进程attach到相应程序上

image-20220613213441690

​ 用户态进程调试的特点——当将进程attach到调试器时,进程处于freeze状态

step 2 使用~*命令列出当前进程中的所有线程的详细信息,使用~k查看第k个子线程

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-snMgwxuo-1655386785128)(https://cdn.jsdelivr.net/gh/YOURLEGEND/PictureBedForCSDN@main//img/202206132138644.png)]

​ 使用~0看0号线程的栈回溯,0号线程即主线程(注意要切换线程要使用~0s,提示符会变为0:000>,即提示符冒号后的数表示线程号)

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-GREtMlmJ-1655386785128)(https://cdn.jsdelivr.net/gh/YOURLEGEND/PictureBedForCSDN@main//img/202206141134251.png)]

​ 结合k指令查看当前线程的堆栈

image-20220613214336193

​ 可能需要联网下载符号表,可以看到堆栈02号函数调用的notepad!wWinmain即notepad.exe的wWinMain函数,堆栈01号函数的USER32!GetMessageW表示等待消息队列的函数GetMessage是从调用线程的消息队列里取得一个消息并将其放于指定的结构。

step 3 下断点

​ 系统下断点的方法:

  1. 在被调试程序中新建一个remote breakin线程,触发int 0x03处理异常事件。如下面的下标为8的DbgUiRemoteBreakin线程本来程序是没有的,但是调试时其新建并加入了线程

image-20220614104837189

​ 注意int 3 进入内核态时,通过分发异常,调试器会把所有线程都freeze掉继续执行g指令就可以正常运行了。

​ 通过k指令能看到相关函数堆栈,通过u指令看函数反汇编结果,能看到syscall指令(陷入内核态),由于这里用户态调试看不见,所以只能

image-20220614151840144

​ 使用以下指令可以在断点时继续执行多条指令,这里为执行echo指令,打印堆栈并继续

bp ntdll!NtReadFile ".echo hello from shanghai, readfile is being invoked;k;gc"

​ 有的时候windbg在下载符号表或者继续运行速度较慢,一直显示Debugee is running,建议进行等待

step 2 sxe加载模块事件

​ 使用指令sxe ld加载一个ld模块,每一个模块加载时都会报告。 实现原理即触发加载模块(LOAD_DLL_DEBUG_EVENT)的内核事件,放入调试事件队列并通知调试子系统,最终报告给调试器

3.2 linux用户态调试模型

1.linux进程跟踪

  • Process Trace
  • Ptrace 最早实现在1979发布的Unix V7
  • Unix/Linux用户态调试的主要依据

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-yf4yEVAf-1655386785129)(https://cdn.jsdelivr.net/gh/YOURLEGEND/PictureBedForCSDN@main//img/202206141539138.png)]

2.ptrace函数

​ linux下面没有专门的调试事件,都是接收Signal(如Page Fault、Segmentation Fault)

image-20220614154159609

​ ptrace相当于万能接口,其中有attach/detach进程的步骤、访问和写入代码数据、KILL进程等全部通过这一个函数完成

3.waitpid函数

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-7W9elTh5-1655386785129)(https://cdn.jsdelivr.net/gh/YOURLEGEND/PictureBedForCSDN@main//img/202206141546368.png)]

​ 总体上讲,linux下的调试机制依然过于简陋,比如子进程中有多进程,操作系统没有主动记录和freeze这些进程,而是需要调试器进行进程操作

3.3 Windows操作系统的异常分发过程

1.windows中的异常

  1. Win32异常:包括CPU异常及 Windows操作系统所定义的异常
  2. CLR异常:CLR及.Net程序定义的异常,异常代码为e0434f4d,即.COM 异常
  3. C++异常:代码为Oxe06d7363即.msc

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-mi21DaT8-1655386785129)(https://cdn.jsdelivr.net/gh/YOURLEGEND/PictureBedForCSDN@main//img/202206141552884.png)]

2.windows 异常分发函数详解

​ 异常分发函数(KiDispatchException)总共分为两轮,FirstChance为第一轮分发,注意没有用户态调试器但可能有内核态调试器(KD)。

Step 1 在第一轮分发中,如果没有用户态调试器或者需要把异常分发给内核调试器的情况,将其分发给内核调试器

Step 2 如果内核调试器没有处理,则分发给用户态调试器(Dbgk相关分发函数),并执行异常后返回用户态

step 3 第二轮分发中,分别对处理普通和异常端口的情况进行分发,如果还不能分发则在内核态kill掉进程

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-PgBfPo48-1655386785130)(https://cdn.jsdelivr.net/gh/YOURLEGEND/PictureBedForCSDN@main//img/202206142015075.png)]

img

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-Ky9rJAlO-1655386785130)(https://cdn.jsdelivr.net/gh/YOURLEGEND/PictureBedForCSDN@main//img/202206151556765.png)]

​ 以上为KiDispatchException的流程,注意没有包含所有细节,并且因版本不同会略有不同。在使用WinDBG的内核调试中,不能对这个函数设置断点——死循环,可以使用ITP来跟踪。

​ 举例,小程序抛出的C++异常

image-20220615160133719

3.寻找异常处理器

​ 通过结构化异常处理器的FS0链条,windows的每个线程都有一个特殊链条,即段寄存器指向的特殊信息块。链条的每个节点都是一个异常的注册结构,每个异常注册结构指向handle(句柄)函数,再指向自己的一个前向指针。

image-20220615202317918

​ 结构化异常处理SEH,通过运行保护代码块,看是否触发异常,分发异常过程中处理过滤表达式,过滤表达式的返回值决定是否执行异常代码块。返回值有3中情况,如下图

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-1yyt1cwL-1655386785130)(https://cdn.jsdelivr.net/gh/YOURLEGEND/PictureBedForCSDN@main//img/202206152026544.png)]

image-20220615203028191

4.调试实例

​ 加入try…except语句,在VC++6.0中查看汇编代码,会看到如下的部分,即先建立异常处理结构,然后再移入FS链表节点。注册了异常处理结构之后,等下发生异常就会找到异常处理结构,后面在函数的末尾会有相关的代码来反注册。

image-20220615203522071

​ 这里故意使用除0样例,并在try…catch中将被除数改为1。当触发异常时,内核态会经过两轮分发异常,第一轮内核态调试不予分发,然后转到用户态分发函数,将异常信息复制到用户态栈,找当前线程的异常处理链条(FS0链条),并且找到了相应的异常处理器SEH,在SEH中执行过滤表达式(过滤表达式可以认为是一个特殊函数,编译器会将其认为成一个特殊函数)。

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-UIS61xH2-1655386785131)(https://cdn.jsdelivr.net/gh/YOURLEGEND/PictureBedForCSDN@main//img/202206152040826.png)]

​ 断点已经下在了SEH执行过滤表达式的位置,使用r指令查看寄存器的值,使用dd指令查看内存信息(重点查找由内核态复制到用户态的异常信息和线程上下文 )

image-20220615205532010

​ 异常结构体结构如下,第一个字段是指示异常的异常代码(这里除0异常即c0000094),后面有导致异常的地址(这里是0040108b),随后是context(线程上下文结构体),其具备典型标识符。

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-UMTlMStn-1655386785131)(https://cdn.jsdelivr.net/gh/YOURLEGEND/PictureBedForCSDN@main//img/202206162103506.png)]

​ 使用.cxr指令回到除0错误代码,注意其转入内核态时把出错指令的地址压入栈,这也是我们知道哪条指令出错的原因,由于内核态将寄存器上下文复制到用户态,因此我们可以在用户态中看到寄存器上下文,使用指令dt _CONTEXT (address)即使用_CONTEXT结构解析address处的数据(dt指令用来显示数据类型以及按照类型来显示数据)。Windows也公开了一些API可以从栈上取到异常代码。

​ 当在内核态执行完SEH过滤表达式后,程序会回到用户态处理异常,如下图栈回溯,最后通过ZwContinue()函数回到用户态继续执行

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-zP24ugdz-1655386785132)(https://cdn.jsdelivr.net/gh/YOURLEGEND/PictureBedForCSDN@main//img/202206162134519.png)]

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值