飘云阁

 找回密码
 加入我们

QQ登录

只需一步,快速开始

查看: 3464|回复: 5

[原创] Windbg新手入坑指南

[复制链接]
  • TA的每日心情
    擦汗
    2021-3-13 19:12
  • 签到天数: 254 天

    [LV.8]以坛为家I

    发表于 2019-4-8 22:23:07 | 显示全部楼层 |阅读模式
    前言

    这篇文章是我学习windbg的一个笔记和总结,通过和OD的功能来对比学习windbg的一些理论和命令,达到能调试一个exe或者sys文件的目的。大神请飘过~

    熟悉理论 提高调试效率

    在开始调试之前,了解以下理论知识可以帮助你大大提高调试效率

    定制自己的Windbg界面

    windbg默认打开就只有一个Command窗口,但是这样调试效率很低,所以可以先设置好自己的用户界面,下面是我的Windbg界面,因为用惯了OD,所以这个完全就是仿制的OD的界面,

    1554539588077.png

    所有窗口都可以状态栏里调出来

    设置完成之后,你可以保存到工作空间,这样下次再次打开时这个界面就会保留

    1554539784517.png

    关于工作空间

    工作空间保存有断点 用户定义的别名 调试器的设置 图形界面信息 调试会话状态等等信息,类似VS的项目文件,PS的工作区。

    命令概述

    WinDBG主要是以命令方式工作的,WinDBG共支持三类命令:标准命令、元命令和扩展命令

    标准命令

    标准命令通常是一两个字符(version除外)或者符号,用来提供适用于各种调试目标的最基本调试功能。标准命令是不分大小写的。比如:

    - g 运行
    - t 单步步入
    - p 单步步过
    - r 查看和修改寄存器

    元命令

    元命令用来提供标准命令没有提供的调试功能,与标准命令一样,元命令也是内建在调试器引擎或者WinDBG程序文件中的。
    所有元命令都以一个点(.)开始,所以元命令也被称为点命令,例如:

    - .reload 重新载入符号
    - .reboot 重启目标机器
    - .restart 重启调试器
    - .logfile 显示信息

    扩展命令

    扩展命令用于扩展某一方面的调试功能。与标准命令和元命令是内建在WinDBG程序文件中不同,扩展命令是实现在动态加载的扩展模块(DLL)文件中的。

    所有的扩展命令都以!开头

    通过WinDBG的SDK,用户可以编写自己的扩展模块和扩展命令,例如漏洞测试常用的一个mona插件

    !mona

    调试技巧

    在开始调试之前,有几个要点,记住以下几个点对于调试事半功倍

    - 直接按回车可以执行上一条命令
    - 使用分分号作为分隔符,可以在同一行输入多条命令
    - 按上下方向键可以浏览和选择以前输入过的命令
    - 当命令提示符显示为*BUSY*时,即使命令编辑框可以输入命令,但是这个命令也不会被马上执行,要等WinDBG恢复到空闲状态才能执行
    - 使用Ctrl+Break 来终止一个长时间未完成的命令。如果使用KD或则CDB,那么用Ctrl+C
    - 选择菜单->Edit_>Write Window Text to File可以把之前敲过的所有命令记录到文件

    伪寄存器

    WinDBG自动定义了很多伪寄存器。在命令行和命令文件中都可以使用伪寄存器。WinDBG会自动将其替换(展开)为合适的值。例如下面这个@$scopeip就是一个伪寄存器,它代表当前的eip指针

    1554541014139.png

    下表列出了windbg所定义的部分寄存器(字典型知识,需要时查阅即可)

    伪寄存器.png

    开始实战

    控制调试目标

    控制调试目标是调试器的一个核心任务。其宗旨就是使调试目标始终处于调试器的控制之下,让调试人员可以可以随心所欲的控制程序的执行状态。在OD可以通过图形界面和各种快捷键随心所欲地控制程序,相比windbg就没那么方便了。但是WinDBG提供了强大的机制和丰富的命令来控制调试目标,这些命令要比OD的功能丰富的多。

    单步步入和单步步过

    - t 单步步入
    - p 单步步过

    命令格式如下:

    p|t [r] [=StartAddress] [count] ["Command"]

    - r表示禁止显示寄存器内容
    - 默认情况下,调试器总是让目标从当前位置开始单步执行,但是也可以通过等号(=)来指定一个新的起始地址,让程序从这个地址开始单步
    - count用来指定单步执行的次数
    - Command用来指定每次单步执行后要执行的命令

    例如:

    1554552826202.png

    首先查看一下当前的反汇编,我想从77e40d8e这个位置开始执行单步 单步两次 不显示寄存器 单步执行完成之后显示调用堆栈,就可以执行这么一条命令,执行完成之后如图:

    tr =77e40d8e 2 "kb"

    1554552930272.png

    单步执行到指定地址

    WinDBG提供了pa和ta命令用来执行到指定的代码地址,其命令格式为:

    pa|ta [r] [=StartAddress] StopAddress

    - pa是Step To Address的缩写,即单步执行到StopAddress参数所代表的地址处
    - pa和ta的区别在于 ta在遇到函数时会进入函数,windbg的执行结果会显示函数内容;而pa则是直接步过函数,windbg的执行结果不显示函数内容

    例如:

    查看一下当前的反汇编

    1554553648314.png

    如果想直接单步到77e9f137的位置,就可以输入下面这条命令,执行结果如下

    par 77e9f137

    1554553702649.png

    单步执行到下一个函数调用

    与pa和ta命令类似,pc和tc命令用来单步执行到下一个函数调用指令(call)

    pc|tc [r] [=StratAddress] [Count]

    - pc或tc命令都是让调试目标从当前地址或者StartAddress指定的地址恢复执行,直到遇到函数调用时停下来
    - Count用来指定遇到的函数调用指令个数
    - 这两个的差别依然是在进入和不进入函数时windbg显示的结果上有区别

    例如:

    首先来查看一下当前的反汇编

    1554554139677.png

    如果想直接单步到77e9f137这个位置的call,就可以直接用下面一条命令

    pcr

    1554554204372.png

    单步执行到下一个分支

    CPU有分支的监视和记录功能,利用这一功能可以实现单步执行到分支,但是这个命令有一个缺陷就是不能在x86的用户态模式下使用,命令格式如下:

    tb [r] [=StartAddress] [Count]

    继续运行

    g(go)命令的一般形式为:

    g[a][=StartAddress] [BreakAddress][;BreakCommands]

    - 其实StartAddress用来指定开始执行的起始地址 这个功能有点像OD的此处为新的EIP
    - BreakAddress用来指定一个断点地址
    - BreakCommands用来指定断点命中后所指定命令
    - 如果不带任何参数,那么g命令就是恢复目标运行 相当于OD的F9
    - 可以用gu命令来执行到返回 相当于OD的Ctrl+F9

    追踪并监视

    如果我们想了解一个函数的执行路径和它调用了哪些其它函数,每个函数包含了多少条指令,但我们又不想一步步的跟踪执行,那么可以使用wt命令让它帮我跟踪执行并生成一份报告给我

    下面通过一个例子来解释wt命令的用法,**注意:wt命令必须在函数的起始位置处,也就是步入call之后的第一条指令时执行**

    首先单步步入函数。然后使用wt命令生成报告

    1554555175540.png

    可以把wt命令的结果分为六个部分

    - 第一个部分是标题 显示了追踪的函数名和追踪的结束地址
    - 第二部分是详细的执行情况 包括如下四列:
    - 第一列为指令数,这一列的数字就是这个函数从入口进入到下一行所对应的函数入口所执行的指令
    - 第二列用来显示本行所对应的函数调用其他函数时所执行的总指令数
    - 第三列表示函数的调用深度 没进入一个函数深度加一
    - 第四列为函数名称 名称前的缩进用来表示深度

    停止调试

    - q 停止调试
    - .detach 分离调试器

    区别在于停止调试时调试器和目标程序的偶停止运行,而分离调试器则是调试器显示No Target,而目标程序继续运行

    总结

    控制调试器.png

    使用断点
    软件断点

    WinDBG设计了三条命令来设置软件断点,分别是bp、bu和bm。其中bp是基本的而且最常用的,其命令格式如下:

    bp[ID] [Options] [Address [Passes]] ["Command String"]

    - ID用来指定断点编号 不指定默认从0开始编排
    - Options用来指定选项
    - Address用来指定断点地址
    - Passes用来指定经过断点的次数 默认经过一次断下
    - CommandString用来指定设置断点后执行的命令 用双引号包围命令,多个命令用分号分隔

    bu命令用来设置一个延迟的以后再求解的断点,用于对尚未加载模块中的代码设置断点。当指定的模块被加载时,WinDBG会真正落实这个断点。所以bu命令对于调试动态加载模块的入口函数或者初始化代码特别有用

    bm命令用来设置一批断点,相当于帮我们自动执行很多次bp或者bu命令。比如以下命令对于msvcr80d模块中的所有print开头的函数设置断点:

    bm msvcr80d!print*

    bm和bu是命令格式如下:


    bu[ID] [Options] [Address [Passes]] ["Command String"]
    bm[Options]  SymbolPattern [Passes] ["Command String"]


    其中的Options可以为以下内容

    - /1如果指定此选项,那么这个断点命中一次后便会便自动从断点列表中删除。这种断点被称为一次命中断点
    - /p这个开关只能用在内核调试中,/p后跟一个进程的EPROCESS结构,作用是只有当前进程是指定进程时才触发这个断点
    - /t与/p开关类似,只能用在内核调试中,用来指定一个ETHREAD结构,作用是只有在执行指定的线程访问断点地址时才触发断点
    - /c和/C这两个开关后面可以带一个数字,用来指定中断给用户的最大函数调用深度和最小函数调用深度。举例来说,使用命令bp/c5msvcr80d!printf设置的断点只有当函数调用深度浅于5时才中断给用户

    硬件断点

    WinDBG的ba命令用来设置硬件断点,其格式如下:

    ba [ID] Access Size [Option] [Address[Passes]] ["Command String"]

    - ID用来指定断点序号
    - Access用来指定触发断点的访问方式 可以为以下几个字母之一
      - e 读取和执行时触发断点
      - r 读取和写入时触发断点
      - w 写入时触发断点
      - i 有IO操作时触发断点

    - Size用来指定访问的长度 x86系统可以为1 2 4三种值
    - Passes参数和CommandString参数的用法与设置软件断点命令中的一样

    例如:

    ba r1 0x401000

    那么对内存地址0041717c的一字节访问、字访问、双字访问(读写)都会触发这个断点

    如果想要查看硬件断点和状态可以直接看寄存器窗口的DR0-DR7寄存器

    条件断点

    对 没有错!windbg也支持条件断点,但是这玩意有点复杂,而且实用性好像不大,反正我在OD里从来没用过,直接PASS吧。另外windbg好像是没有内存断点的

    地址表达式

    可以使用以下三种方法来指定断点命令中的地址参数

    - 直接使用内存地址,比如bp 00411390

    - 使用模块名加函数符号的方式,比如bp dbgee!wmain代表对dbgee模块中的wmain函数设置断点。也可以在符号后增加一个地址偏移,比如bp dbgee!wmain+3

    - 如果是使用完全的调试符号,调试符号中包含源代码行信息,那么可以使用如下形式:


      [[Module!]Filename][:LineNumber]


    其中Module为模块名,Filename为源程序文件名,LInenumber为行号。整个表达式要用两个波浪号包起来``,要有调试符号才能实现,对于逆向来说没什么用 这个也pass

    - 对于C++的类方法,也可以使用类名双冒号(::)或者双下划线(__)来连接类名和方法名,比如:


      bp MyClass__MyMethod
      bp MyClass:MyMethod
      bp@@(MyClass::MyMethod)


    断点

    使用bl命令可以列出当前已经设置的所有断点,例如:

    1554560702285.png

    - 第一列是断点的序号
    - 第二列是断点的状态
      - e代表启用(enable)
      - d代表禁用(disable)
      - 对于bu设置的断点还可能有字母u,表示尚未解决(unresolved)

    - 第三列是断点的地址
    - 第四列是断点触发剩余次数
    - 第五列是断点的初始计数
    - 第六列是断点所关联的进程和线程,冒号前是进程号,冒号后是线程号
    - 第七列是断点地址的符号表示

    命令bc、bd、be分别用来删除、禁止和启用断点,它们的格式都是:

    bc|bd|be 断点号

    其中断点号可以使用*来通配所有断点,使用-来表示一个范围,或者使用逗号来指定多个断点号。例如以下命令都是有效的:


    bd 0-2,4  禁止0 1 2 4号断点
    be *      启用所有断点


    总结

    断点.png

    观察栈

    显示栈回溯

    WinDBG的k系列命令就是用来帮助我们进行栈回溯的,先来看一个例子

    1554561435896.png

    - 其中的每一行代表栈上的一个栈帧 也就是一个函数
    - 最上面一行表示的是当前正在执行的函数 每个函数下面一行是上一行的父函数
    - 第一列是栈帧的基地址EBP
    - 第二列是函数的返回地址
    - 第三列是函数名以及执行位置

    K命令显示了函数名信息,但是没有显示每个函数的参数。命令kb可以显示放在栈上的前三个参数,例如(L是不显示源文件信息):

    1554561861693.png
    - 前两列以及最后一列的内容与k命令结果是一样的
    - 中间三列是函数的参数 只显示三个,如果要观察第四个参数可以用  dd ebp+x014
    - 这只是栈上的前三个参数,如果函数的调用约定为fastcall,那么前两个参数还是在ecx和edx

    其他K系列的命令:

    - kp命令可以把参数和参数值都以函数原型格式显示出来,但是需要有符号,对于逆向来说没什么用
    - kv命令可以在kb命令的基础上增加显示FPO信息和调用约定
    - kn命令会在每行前显示栈帧的序号

    总结

    观察栈.png

    分析内存

    显示内存

    WinDBG的d系列命令用来显示指定内存区域的数据内容。这些命令的格式为:


    d{a|b|c|d|D|f|p|q|u|w|W} [Options] [Range]
    dy{b|d} [Options] [Range]
    d [Options] [Range]


    其中大括号中的字母(区分大小写)用来指定数据的显示方式,含义如下:

    - a表示ASCII码
    - b表示字节和ASCII码
    - c表示DWORD和ASCII码
    - d表示DWORD
    - D表示双精度浮点数
    - f表示单精度浮点数
    - p表示按指针宽度显示
    - q表示四字(8字节)
    - u表示UNICODE字符
    - w表示字
    - W表示字和ASCII码
    - yb表示二进制和字节
    - yd表示二进制和双字

    Range参数用来指定要显示的内存范围。可以有以下几种表示方法:

    - 第一种方法是起始地址加空格加终止地址,比如dd 0012fd9c 0012fda8命令以双字格式显示从0012fd9c开始到0012fda8结束的16字节内存数据
    - 第二种方法是起始地址加空格加L(或者1)和对象个数,比如上面的命令可以等价的写为:dd 0012fd9c L4
    - 第三种方式是结束地址加空格加L(或者1)加负号和对象个数。使用这种方式可以把上面的命令写为:dd 0012fdac L-4

    显示字符串

    可以以0结尾的简单字符串,可以使用da或者du命令来显它的内容,前者用于使用单字节字符集的字符串,后者用于采用UNICODE字符集的字符串。当遇到字符串末尾的0时,会自动停止显示。例如:du 003a2e9c

    1554562807596.png

    显示数据类型

    WinDBG的dt命令用来显示数据类型以及按照类型来显示数据。Dt的含义是Dump symbolic Type information。Dt是个比较复杂的命令,下面我们按照用法分别来介绍

    首先,可以使用dt来显示一个数据类型(数据结构)。这种用法的典型格式是:
    dt[模块名!]类型名
    其中模块名部分可以省略,如果省略,那么调试器会自动搜索所有模块。类型名即程序中定义数据结构或者通过typedef定义的类姓名。类型名中可以包含通配符,比如以下命令会列出NTDLL模块中的所有类型:

    dt ntdll!*

    如果类型名是确定的类型,那么dt便会显示这个类型的定义,如果类型中还包含子类型,那么可以用-b开关来递归式显示所有子类型,也可以使用-r开关来指定显示深度。-r0表示不显示子类型,-r1表示显示1级子类型,依此类推,例如:

    dt -r1 _TEB

    如果不想显示整个结构,而只显示某些字段,那么可以在类型名后使用-ny开关附加字段搜索选项,比如以下命令只显示TEB结构的LastError开始的字段:

    dt _TEB -ny LastError

    Dt命令的第二种用法是在上一种方法的基础上增加内存地址,让dt 按照类型显示指定地址的变量。例如,以下命令使用_PEB结构来显示内存地址7ffdd000出的数据:

    dt _PEB 7ffdd000

    Dt命令的第三种用法是显示类型的实例,包括全局变量、静态变量和函数。比如以下命令显示dbgee程序的g_szGlobal全局变量:

    dt dbgee!*wmain*

    搜索内存

    S命令用于搜索内存,有三种使用方法。

    - 第一种用法是在指定的内存范围内搜索任何ASCⅡ字符或者UNICODE字符串,其格式如下:

      - s-[[Flags]]sa|su Range
      - Range用来指定内存范围
      - sa用来搜索ASCII字符串 su用来搜索unicode字符串
      - Flag可以指定搜索选项

      例如,以下命令搜索nt!PsInitialSystemProcess变量所指向地址开始的512个字节范围内任何长度不小于5的ASCIⅡ字符串:

      s-[l5]sa poi(nt!)PsInitialSystemProcessl200

    - 第二种用法是在指定内存地址范围内搜索与指定对象相同类型的对象,这里的对象是指包含虚拟函数表的使用面向对象语言(如C++)编写的类(Class)对象。其格式为:

      - s[[Flags]]v Range Object

    - S命令的第三种用法是在指定范围内搜索某一内容模式,其语法格式为:
      - s[-[[Flags]]Type] Range Pattern
      - 其中类型表示搜索的内容的数据类型(宽度),可以为b(字节) d(双字)
      - Pattern参数用来指定要搜索的内容

    如果你觉得上面一段话理解起来有点费劲,不妨直接看下面的例子


    s 0012ff40 L20 'H' 'e' 'l' 'l' 'o'
    s 0012ff40 L20 48 65 6c 6c 6f
    s -a 0012ff40 L20 "Hello"


    它们都是等效的。意为在0012ff40到0012ff60之间搜索hello字符,-a参数指定以ACSII的方式搜索字符,类似的还有-u,它指定以UNICODE的方式搜索字符

    修改内存

    命令e用来修改指定内置地址或者区域的内容,

    - 第一种是按字符串编辑,其命令格式为:
      - `e{a|u|za|zu}Address "String"`
      - 其中Address是要修改的内存的起始地址
      - za代表以0结尾的ASCII字符 zu代表以0结尾的Unicode字符
      - a和u分别代表不是以0结尾的ASCII和Unicode字符

    - 第二种用法是以数值方式编辑,其格式为:
      - `e{b|d|D|f|p|q|w} Address [Values]`
      - 其中大括号中的字母用来表示要修改的数据类型,也决定要修改的内存方式
      - Address用来指定要修改的内存起始地址
      - Values用来指定新的值

    例如:

    Eb 00100000 01 02 03 04      数据类型为BYTE
    Ed 00100000 0201 0403        数据类型为DWORD
    Ea 00100000 ‘hello’          数据类型为ASCII
    Eu 00100000 ‘你好’            数据类型为UNICODE


    其他命令

    下表记录了我个人认为在调试时经常会用到的一些命令,记录的不全,欢迎补充

    其他命令.png

    参考资料

    《windbg用法详解》

    WinDbg命令三部曲

    从Ollydbg说起-----WinDbg用户态调试教程:<https://bbs.pediy.com/thread-34379.htm>

    PYG19周年生日快乐!
  • TA的每日心情
    郁闷
    2021-7-31 10:39
  • 签到天数: 67 天

    [LV.6]常住居民II

    发表于 2019-4-9 11:29:50 | 显示全部楼层
    一脸蒙蔽的来,一脸蒙蔽的去。。。。。。
    PYG19周年生日快乐!
    回复 支持 反对

    使用道具 举报

  • TA的每日心情
    开心
    昨天 08:43
  • 签到天数: 1460 天

    [LV.10]以坛为家III

    发表于 2019-4-9 21:05:12 | 显示全部楼层
    没有用过,正好学习学习。
    PYG19周年生日快乐!
    回复 支持 反对

    使用道具 举报

  • TA的每日心情
    开心
    21 小时前
  • 签到天数: 1489 天

    [LV.10]以坛为家III

    发表于 2019-4-10 09:00:33 | 显示全部楼层
    感谢分享如此精彩的教学
    PYG19周年生日快乐!
    回复 支持 反对

    使用道具 举报

  • TA的每日心情
    开心
    2019-4-21 20:15
  • 签到天数: 2 天

    [LV.1]初来乍到

    发表于 2019-4-21 20:44:30 | 显示全部楼层
    楼主分享的不错,希望能够进一步卸下去
    PYG19周年生日快乐!
    回复 支持 反对

    使用道具 举报

  • TA的每日心情

    2020-1-9 10:13
  • 签到天数: 136 天

    [LV.7]常住居民III

    发表于 2019-5-17 09:29:35 | 显示全部楼层
    很详细的教学 适合windbg新手入门
    PYG19周年生日快乐!
    回复 支持 反对

    使用道具 举报

    您需要登录后才可以回帖 登录 | 加入我们

    本版积分规则

    快速回复 返回顶部 返回列表