时光旅行调试:从过去引爆调试革命
微软安全响应中心(MSRC)通过公开"时光旅行调试"(TTD)工具,使安全研究人员能够提供完整的漏洞复现记录。该工具采用代码仿真技术记录程序执行的每个事件,包括内存读取、寄存器值、线程创建等关键数据。
理解时光旅行调试
无论称为"无时间调试"、“记录-回放调试"还是"时光旅行调试”,核心思想都是记录程序执行过程。这种执行轨迹是确定性的记录,所有人都能看到相同时间点的相同行为。
TTD包含三个关键组件:
- 记录器(类似摄像机)
- 轨迹文件(类似录像文件)
- 回放器(类似播放器)
传统调试的局限
传统调试过程通常需要:
- 在调试器中观察行为
- 反复重现问题理解原因
这种方式效率低下,需要大量重复操作。
TTD技术架构
微软开发的TTD技术基于2006年微软研究院的成果,通过代码仿真记录程序执行的每个必要事件:
-
记录过程:TTDRecordCPU.dll被注入目标进程,劫持线程控制流。仿真器将本地指令解码为内部中间语言,缓存并执行它们。
-
回放过程:TTDReplayCPU.dll直接从轨迹文件加载数据,无需运行原程序即可高保真回放执行过程。
轨迹文件格式
轨迹文件使用.run扩展名,采用自定义文件格式和压缩算法优化大小。首次打开时,WinDbg Preview会创建索引文件(通常是原文件的1-2倍大小)。
使用WinDbg Preview记录轨迹
- 从Microsoft Store获取WinDbg Preview
- 按照"时光旅行调试-记录轨迹"教程操作
TTD自动化分析
通过JavaScript脚本可以构建强大的自动化分析:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
|
// 示例:重建控制台输出
'use strict';
function initializeScript() {
return [new host.apiVersionSupport(1, 3)];
}
function invokeScript() {
const logln = p => host.diagnostics.debugLog(p + '\n');
const CurrentSession = host.currentSession;
const Memory = host.memory;
const Bytes = [];
for(const Call of CurrentSession.TTD.Calls('msvcrt!write').OrderBy(p => p.TimeStart)) {
Call.TimeStart.SeekTo();
const [_, Address, Count] = Call.Parameters;
}
logln(Bytes.filter(p => p != 0).map(
p => String.fromCharCode(p)
).join(''));
}
|
TTD.Memory API的强大功能
TTD.Memory允许查询轨迹文件中的内存访问情况:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
|
// 示例:追踪LastErrorValue修改
'use strict';
function initializeScript() {
return [new host.apiVersionSupport(1, 3)];
}
function invokeScript() {
const logln = p => host.diagnostics.debugLog(p + '\n');
const CurrentThread = host.currentThread;
const CurrentSession = host.currentSession;
const Teb = CurrentThread.Environment.EnvironmentBlock;
const LastErrorValueOffset = Teb.targetType.fields.LastErrorValue.offset;
const LastErrorValueAddress = Teb.address.add(LastErrorValueOffset);
const Callstacks = new Set();
for(const Access of CurrentSession.TTD.Memory(
LastErrorValueAddress, LastErrorValueAddress.add(8), 'w'
)) {
Access.TimeStart.SeekTo();
const Callstack = Array.from(CurrentThread.Stack.Frames);
Callstacks.add(Callstack);
}
for(const Callstack of Callstacks) {
for(const [Idx, Frame] of Callstack.entries()) {
logln(Idx + ': ' + Frame);
}
logln('----');
}
}
|
常见问题解答
-
能否在回放时编辑内存?
不能,记录器只保存回放特定执行路径所需的信息。
-
是否需要私有符号或源代码?
不需要,但如果有可以提供更好的调试体验。
-
是否支持内核模式记录?
目前仅支持用户模式执行。
-
是否支持自修改代码?
完全支持。
TTD是安全软件工程师的强大工具,也可用于恶意软件分析、漏洞挖掘和性能分析。生成的轨迹文件压缩率很高(使用7zip可压缩至原大小的10%)。