ARTICLE · INTELLIGENCE

战地情报 · 详情页

来自尧图项目组的一线实战观察与深度解析

x64dbg 条件追踪命令 TraceIntoIntoTraceRecord (tiit) 详解:StepInto 回溯到已执行路径内部

x64dbg 条件追踪命令 TraceIntoIntoTraceRecord (tiit) 详解:StepInto 回溯到已执行路径内部 x64dbg 条件追踪命令 TraceIntoIntoTraceRecord (tiit) 详解StepInto 回溯到已执行路径内部【免费下载链接】x64dbgAn open-source user mode debugger for Windows. Optimized for reverse engineering and malware analysis.项目地址: https://gitcode.com/gh_mirrors/x6/x64dbg导读TraceIntoIntoTraceRecord别名TraceIntoIntoTraceCoverage/tiit是 x64dbg 条件追踪Conditional Tracing体系中基于trace coverage追踪覆盖的自动化单步命令它以StepInto逐指令执行直到程序重新进入一条此前已经执行过的指令为止。本文以 TraceIntoIntoTraceRecord.md 为主线结合命令注册、步进回调与 TraceRecord 底层实现完整讲解其语法、参数默认值、执行语义、源码实现原理以及与tibt/toit/tobt/ticnd等姊妹命令的差异和实战用法。读完本文你将能熟练使用tiit定位新路径与旧路径的分叉点并在 fuzz、程序行为对比等逆向场景中直接落地使用。命令概览与语法tiit属于 x64dbg 的 Tracing 命令族在调试器中注册了三个等价命令名src/dbg/x64dbg.cpp#L265dbgcmdnew(TraceIntoIntoTraceCoverage,TraceIntoIntoTraceRecord,tiit, cbDebugTraceIntoIntoTraceRecord, true); //Trace into into trace record完整语法如下TraceIntoIntoTraceCoverage / TraceIntoIntoTraceRecord / tiit [arg1] [arg2]参数含义默认值[arg1]追踪的终止条件break condition。该条件被求值为非 0 时无论当前EIP/RIP位于何处追踪都会提前停止无条件追踪内部为0[arg2]调试器放弃追踪的最大步数50000结果变量该命令不设置任何结果变量result variables。追踪结束时实际暂停的位置需要从日志输出或cip/$tracecounter等系统变量中获取。参数详解arg1追踪终止条件arg1是一个 x64dbg 表达式。当表达式被求值为非 0 时即使程序还没有进入 trace coverage 内部追踪也会立即停止。不指定该参数时追踪是无条件的——只以是否进入 trace coverage为唯一终止判据。从源码看该条件在启动追踪前由dbgsettracecondition解析并保存src/dbg/commands/cmd-tracing.cpp#L32if(!dbgsettracecondition(*argv[1] ? argv[1] : 0, maxCount)) { dprintf(QT_TRANSLATE_NOOP(DBG, Invalid expression \%s\\n), argv[1]); return false; }这里*argv[1] ? argv[1] : 0意味着当省略 arg1 时实际使用的条件表达式是字面量0恒为假即无条件追踪完全符合文档描述。若表达式非法命令会直接返回失败并在日志输出Invalid expression ...。arg2最大步数上限arg2限制追踪的步数上限防止死循环式的无限单步。其取值逻辑位于genericConditionalTraceCommandsrc/dbg/commands/cmd-tracing.cpp#L27-L31duint maxCount; if(!BridgeSettingGetUint(Engine, MaxTraceCount, maxCount) || !maxCount) maxCount 50000; if(argc 2 !valfromstring(argv[2], maxCount, false)) return false;未指定arg2时默认值50000来自配置项Engine/MaxTraceCount若该配置未设置或为 0才回退到代码内置的50000。显式传入arg2时通过valfromstring解析数值并覆盖上述默认值。注意x64dbg 表达式中数字默认按十六进制解释十进制需写作.123形式docs/introduction/ConditionalTracing.md#L28。达到步数上限后调试器会输出Trace finished after %llu steps!并暂停src/dbg/debugger.cpp#L1504-L1513同时把已执行步数写入系统变量$tracecounter。命令执行流程从命令回调到逐指令步进tiit的完整调用链如下命令入口cbDebugTraceIntoIntoTraceRecordsrc/dbg/commands/cmd-tracing.cpp#L112-L121bool cbDebugTraceIntoIntoTraceRecord(int argc, char* argv[]) { if(argc 1) { const char* new_argv[] { tiit, 0 }; return conditionalTraceIntoCommand(cbTraceIntoIntoTraceRecordStep, 2, (char**)new_argv); } else return conditionalTraceIntoCommand(cbTraceIntoIntoTraceRecordStep, argc, argv); }conditionalTraceIntoCommand根据**步进过滤party filter**选择底层步进函数src/dbg/commands/cmd-tracing.cpp#L42-L59auto party dbggettracepartyfilter(); if(party mod_user) stepFunction StepIntoUser; else if(party mod_system) stepFunction StepIntoSystem; else if(party -1) stepFunction StepIntoWow64;tiit使用STEP_FUNCTION(true)即StepIntoWow64见 src/dbg/debugger.cpp#L1536 的宏定义#define STEP_FUNCTION(into) (into ? StepIntoWow64 : StepOverWrapper)。该过滤由TraceSetStepFilter别名SetTraceStepFilter命令配置取值为none-1/user/systemsrc/dbg/commands/cmd-tracing.cpp#L205-L232。每步执行后触发步进回调cbTraceIntoIntoTraceRecordStepsrc/dbg/debugger.cpp#L1558-L1561void cbTraceIntoIntoTraceRecordStep() { cbTraceXXTraceRecordStep(STEP_FUNCTION(true), true, cbTraceIntoIntoTraceRecordStep); }核心原理何时判定进入 trace coveragetiit的核心语义由cbTraceXXTraceRecordStep实现src/dbg/debugger.cpp#L1528-L1534static void cbTraceXXTraceRecordStep(STEPFUNCTION stepFunction, bool bInto, TITANCBSTEP callback) { hActiveThread ThreadGetHandle(GetDebugData()-dwThreadId); auto cip GetContextDataEx(hActiveThread, UE_CIP); auto forceBreakTrace TraceRecord.getTraceRecordType(cip) ! TraceRecordManager::TraceRecordNone (TraceRecord.getHitCount(cip) 0) ^ bInto; cbTraceUniversalConditionalStep(cip, stepFunction, callback, forceBreakTrace); }逐项拆解这条判定表达式TraceRecord.getTraceRecordType(cip) ! TraceRecordManager::TraceRecordNone判断当前cip所在页是否已启用 trace coverage 记录src/dbg/TraceRecord.cpp#L103-L112。未被覆盖的页返回TraceRecordNone。TraceRecord.getHitCount(cip) 0判断当前地址的命中次数是否为 0即该指令此前是否从未被执行过src/dbg/TraceRecord.cpp#L450-L469。命中次数按页粒度以位图1 bit、字节6 bit 计数或字14 bit 计数三种格式存储详见 src/dbg/TraceRecord.h#L41-L47 的类型注释。(命中为 0) ^ bInto当bInto truetiit时命中为 0 的地址0 ^ 1 1不强制中断而命中非 0 的地址1 ^ 1 0触发forceBreakTrace 1。因此对tiit而言只要cip落在已记录且命中次数 ≥ 1的地址上就强制中断追踪——这正是文档所说执行 StepInto 直到程序到达 trace coverage 内部。forceBreakTrace传入cbTraceUniversalConditionalStep后会与用户自定义的 break condition 合并若forceBreakTrace为真且 break condition 尚未命中则强制置为 1src/dbg/debugger.cpp#L1454-L1470。这意味着进入 coverage 内部是最高优先级中断条件arg1 条件只用于在进入之前提前停。与姊妹命令的对比tiit / tibt / toit / tobt / ticndtiit不是孤立命令理解它与同类命令的差异才能真正用好它。下表基于命令注册src/dbg/x64dbg.cpp#L261-L266与步进回调src/dbg/debugger.cpp#L1548-L1566整理命令别名步进方式终止语义bInto 值TraceIntoIntoTraceRecordTraceIntoIntoTraceCoverage,tiitStepInto进入 coverage 内部bInto trueTraceIntoBeyondTraceRecordTraceIntoBeyondTraceCoverage,tibtStepInto离开 coveragebInto false命中为 0 时停TraceOverIntoTraceRecordTraceOverIntoTraceCoverage,toitStepOver进入 coverage 内部TraceOverBeyondTraceRecordTraceOverBeyondTraceCoverage,tobtStepOver离开 coverageInto vs OverInto使用StepIntoWow64会进入被调用函数内部逐步追踪Over使用StepOverWrapper单步越过函数调用。使用 Over 时被跳过的调用内部不会暂停、不会记录、不会执行日志速度更快docs/introduction/ConditionalTracing.md#L44。Into coverage vs Beyond coverage前者在程序回到已执行过的路径时停后者在程序进入从未执行过的新路径时停。两者互补tibt的等价表达式是ticnd tr.hitcount(cip)0arg1, arg2见 TraceIntoBeyondTraceRecord.md。与ticnd/tocnd的关系TraceIntoConditionalticnd是基于任意条件表达式的通用Trace Into until condition命令docs/commands/tracing/TraceIntoConditional.md。tiit/tibt是它基于 trace coverage 的特化版本通过原生代码绕过表达式求值性能更高且天然与 trace coverage 数据联动。前置条件理解并启用 Trace Coveragetiit依赖已存在的 trace coverage 数据作为判断依据。Trace coverage 是 x64dbg 对每条已执行指令的命中记录每次调试器单步或暂停时都会自动更新docs/introduction/ConditionalTracing.md#L26。相关概念与入口Trace recording运行追踪记录可通过菜单Start trace recording或命令StartTraceRecording别名StartRunTrace/opentrace开启将后续逐指令执行历史写入文件并显示在 Trace 视图中StartRunTrace.md。注意只有手动单步或追踪的指令会被记录直接run放行不会记录。Trace 文件格式运行追踪文件以TRAC魔数开头附带 JSON 头部版本、架构、murmurhash、被调试模块路径等实现见 src/dbg/TraceRecord.cpp#L511-L549。表达式函数在自定义条件中可引用tr.enabled(addr)该地址是否启用 coverage、tr.hitcount(addr)命中次数、tr.isrecording()是否在记录注册于 src/dbg/expressionfunctions.cpp#L122-L123实现于 src/dbg/exprfunc.cpp#L454-L467。相关说明见 docs/introduction/Expression-functions.md#L92-L96。清理覆盖数据需要从零开始记录路径时可重新启用 trace recording 以清空旧的 coverage 数据见 TraceIntoBeyondTraceRecord.md 的用法示例。实战场景场景一定位输入 B 相对输入 A 新增的执行路径与tibt配套的经典差分思路同样适用于tiit的反向场景参考 TraceIntoBeyondTraceRecord.md启用 trace recording清空覆盖数据提供输入 A 并执行追踪/单步建立基线路径的 coverage提供输入 B 后执行tiit程序从新路径出发一旦 StepInto 回到输入 A 已执行过的指令就自动暂停——此时cip附近即为两条路径的汇合点可用于定位收敛逻辑或校验代码。场景二确认某段代码是否被二次执行调试循环或重入逻辑时若怀疑当前指令此前已执行过直接运行tiit无条件进入追踪直到某条命中次数 ≥ 1的指令被再次踩中而暂停日志将输出Trace finished after N steps!。场景三组合自定义条件提前刹车在循环体内只想观察进入 coverage 的前若干步时可用 arg1 提前终止例如tiit eax 0x1234, 10000满足eax 0x1234或步数达到 10000 即停避免无谓长跑。场景四借助 GUI 菜单快速触发在 Debug 菜单 的 Trace Coverage 相关入口及 Trace into until condition/Trace over until condition 菜单docs/introduction/ConditionalTracing.md#L40中可交互式发起追踪底层命令即ticnd/tocnd/tiit等亦可在 CPU 视图底部命令行直接输入。注意事项与限制不设置结果变量tiit不写任何 result variables如$result停靠位置请通过暂停后的cip/EIP/RIP、日志或$tracecounter获取。先暂停再追踪若调试对象正在运行命令会直接拒绝执行并提示Cannot start a trace when running, pause execution first.src/dbg/commands/cmd-tracing.cpp#L22-L26。trace 未激活时不可启动若已有 trace 处于激活状态会提示Trace already activesrc/dbg/commands/cmd-tracing.cpp#L17-L21。coverage 数据为空的行为若当前地址所在页从未启用 trace coverageTraceRecordNonetiit不会因 coverage 命中而停只能依靠 arg1 条件或步数上限收尾——这是先录制覆盖数据这一前置条件的重要性所在。不要在 trace 命令文本中使用run等会改变运行状态的命令条件追踪/断点命令中应改用 break condition、command condition 或$tracecondition系统变量docs/introduction/ConditionalTracing.md#L42。数值默认十六进制所有表达式中的数字默认按十六进制解释十进制请使用.123语法docs/introduction/ConditionalTracing.md#L28。参考链接命令文档TraceIntoIntoTraceRecord.md、TraceIntoBeyondTraceRecord.md、TraceOverIntoTraceRecord.md、Tracing 命令索引概念文档条件追踪、表达式、表达式函数、Trace 视图核心实现命令注册、命令处理、步进判定、TraceRecord 类型定义、命中计数实现【免费下载链接】x64dbgAn open-source user mode debugger for Windows. Optimized for reverse engineering and malware analysis.项目地址: https://gitcode.com/gh_mirrors/x6/x64dbg创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考
RELATED READING

延伸阅读

更多一线实战笔记与深度复盘,助您持续精进