深入探索Windbg数据模型、Javascript脚本与x64异常处理

本文详细介绍了Windbg Preview的数据模型、Javascript脚本编程能力及其在x64异常处理解析中的应用,涵盖调试器扩展、时间旅行调试(TTD)和SEH机制分析。

调试器数据模型、Javascript与x64异常处理

引言

本文主要展示最新版Windbg(现称为“WinDbg Preview”)及微软数月前发布的时间旅行调试工具(Time Travel Debugging, TTD)的新功能。Windbg最显著的变化是新UI,但本文不讨论UI。第二大改进是引入了强大的脚本引擎!过去我一直使用pyKD编写自动化脚本,虽然效果不错,但现在很高兴能转向Windbg和Javascript提供的新扩展模型(没错,是Javascript)。使用pyKD时,最大的痛点之一是需要执行多个命令并解析输出以提取所需信息。幸运的是,新的调试器数据模型解决了这个问题(至少部分解决)。第三项新功能是集成了TTD,相关讨论可参考演讲:Time Travel Debugging: Root Causing Bugs in Commercial Scale Software

本文目标是利用所学知识,用Javascript枚举x64的try/except处理程序。请准备好咖啡,继续阅读。

目录

  • 引言
  • 调试器数据模型
    • 概述
    • 第一个查询
  • 用Javascript脚本化数据模型
    • Javascript整数与Int64
    • 访问CPU寄存器
    • 读取内存
    • 执行/评估命令
    • 设置断点
  • TTD
    • TTD.Calls
    • TTD.Memory
    • TTD.Utility.GetHeapAddress
  • 扩展数据模型
    • 步骤1:将节点附加到Process模型
    • 步骤2:向节点添加第一层
    • 步骤3:添加另一层和可迭代类
  • 杂项
    • 在命令窗口中尝试host.* API
    • 如何加载扩展脚本
    • 如何运行命令式脚本
    • Javascript引擎是否仅限Windbg Preview?
    • 如何调试脚本?
    • 如何在Javascript中实现NatVis样式可视化工具?
    • 从类型化对象获取值
    • 评估表达式
    • 如何从模块访问全局变量?
  • x64异常处理与Javascript
    • 关于ImageRuntimeFunctionEntries、UnwindInfos、SehScopeTables和CSpecificHandlerDatas的简要说明
    • 整合所有内容
  • 结束语

调试器数据模型

概述

调试器数据模型是一个对象(方法、属性、值)的层次结构,可直接从调试器命令窗口或通过Javascript API访问。调试器暴露了大量信息:线程相关信息、寄存器值、堆栈跟踪信息等。作为扩展编写者,您可以在层次结构中选择节点来暴露您的功能。一旦插入模型,其他脚本或调试器命令窗口即可使用。

这些暴露信息的一个非常有趣的特性是,它们可以通过受C# LINQ操作符高度启发的操作符进行查询。不熟悉LINQ的读者建议查看基本LINQ查询操作

第一个查询

假设您想查找当前@rip指向的模块,可以通过LINQ操作符和数据模型轻松表达:

1
2
3
0:001> dx @$curprocess.Modules.Where(p => @rip >= p.BaseAddress && @rip < (p.BaseAddress+p.Size))
@$curprocess.Modules.Where(p => @rip >= p.BaseAddress && @rip < (p.BaseAddress+p.Size))                
    [0x8]            : C:\WINDOWS\SYSTEM32\ntdll.dll

甚至可以点击DML链接[0x8]查看该模块的所有信息:

1
2
3
4
5
0:001> dx -r1 @$curprocess.Modules.Where(p => @rip >= p.BaseAddress && @rip < (p.BaseAddress+p.Size))[8]
@$curprocess.Modules.Where(p => @rip >= p.BaseAddress && @rip < (p.BaseAddress+p.Size))[8]                 : C:\WINDOWS\SYSTEM32\ntdll.dll
    BaseAddress      : 0x7ffc985a0000
    Name             : C:\WINDOWS\SYSTEM32\ntdll.dll
    Size             : 0x1db000

在前两个示例中,有几个有趣的点需要强调:

  1. dx是访问数据模型的操作符,???操作符不可用。
  2. @$name用于访问调试会话中定义的变量。调试器本身定义了几个变量以便查询模型:@$curprocess等效于Javascript中的host.currentProcess@cursession等效于host.currentSession@$curthread等效于host.currentThread。您也可以自定义变量,例如:
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
0:001> dx @$doare = "Diary of a reverse-engineer"
@$doare = "Diary of a reverse-engineer" : Diary of a reverse-engineer
    Length           : 0x1b

0:001> dx "Hello, " + @$doare
"Hello, " + @$doare : Hello, Diary of a reverse-engineer
    Length           : 0x22

0:001> ?? @$doare
Bad register error at '@$doare'

0:001> ? @$doare
Bad register error at '@$doare'
  1. 要查询@$curprocess层次结构中的所有节点(如果想浏览数据模型,可以使用dx Debugger并点击DML链接):
1
2
3
4
5
6
7
0:001> dx @$curprocess
@$curprocess                 : cmd.exe [Switch To]
    Name             : cmd.exe
    Id               : 0x874
    Threads         
    Modules         
    Environment

还可以检查Debugger.State.DebuggerVariables,其中包含我们刚提到的变量定义:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
0:001> dx Debugger.State.DebuggerVariables
Debugger.State.DebuggerVariables                
    cursession       : Live user mode: <Local>
    curprocess       : cmd.exe [Switch To]
    curthread        : ntdll!DbgUiRemoteBreakin (00007ffc`98675320)  [Switch To]
    scripts         
    scriptContents   : [object Object]
    vars            
    curstack        
    curframe         : ntdll!DbgBreakPoint [Switch To]

0:001> dx Debugger.State.DebuggerVariables.vars
Debugger.State.DebuggerVariables.vars                
    doare            : Diary of a reverse-engineer
  1. 最后但同样重要的是,大多数(全部?)可迭代对象都可以通过LINQ风格的操作符进行查询。如果您从未使用过这些,一开始可能会有点奇怪,但一旦掌握,就会觉得非常方便。

以下是数据模型中可迭代对象当前可用的操作符列表:

  • Aggregate
  • All
  • AllNonError
  • Any
  • Average
  • Concat
  • Contains
  • Count
  • Distinct
  • Except
  • First
  • FirstNonError
  • Flatten
  • GroupBy
  • Intersect
  • Join
  • Last
  • LastNonError
  • Max
  • Min
  • OrderBy
  • OrderByDescending
  • Reverse
  • Select
  • SequenceEqual
  • Single
  • Skip
  • SkipWhile
  • Sum
  • Take
  • TakeWhile
  • Union
  • Where

现在您可能想知道,数据模型是否适用于Windbg的所有可能配置?这里的配置是指:实时用户模式附加到进程、离线查看进程崩溃转储、实时内核模式、离线查看系统崩溃转储,或离线查看TTD跟踪。

是的,数据模型在所有上述配置中均可访问,这非常棒。总体而言,只要您挖掘/暴露的信息不依赖于特定配置,就可以编写非常通用的脚本。

用Javascript脚本化数据模型

如前所述,您现在可以通过Javascript以编程方式访问模型中暴露的所有内容。不再需要eval或字符串解析来提取所需信息,只需找到暴露所需信息的节点。如果该节点不存在,可以添加自己的节点来暴露所需信息。

Javascript整数与Int64

使用Javascript时需要注意的第一点是,整数在C双精度浮点数中编码,这意味着您的整数存储在53位中。这绝对是一个问题,因为我们处理的大多数数据都是64位整数。为了解决这个问题,Windbg向Javascript暴露了一个能够存储64位整数的本地类型。该类型称为Int64,数据模型中的大多数(全部?)信息都是通过Int64实例提供的。该类型暴露了各种方法

comments powered by Disqus
使用 Hugo 构建
主题 StackJimmy 设计