windbgx或WinDbg Preview是一个东西,但与windbg不是一回事。TTD与windbgx相关,与windbg无关。windbgx从Microsoft Store安装,windbg有独立安装包。

关于TTD历史,参看

《TTD历史回顾》
http://scz.617.cn:8/windows/202207191506.txt

TTD录制的.run文件格式未公开,之前只能在WinDbg Preview中操作.run,很难对之进行脚本操作,比如,想对position进行大小比较,很费劲。

Bindings for Microsoft WinDBG TTD
https://github.com/commial/ttd-bindings

上文作者对TTDReplay.dll进行逆向工程,逆出一套编程解析处理.run文件的API。没有完整逆向,但基本功能都有,比windbgx的GUI还多出一些功能。

C++ bindings功能较多,Python bindings功能弱一些,后者无法设置CallRetCallback、MemCallback。

作者提供了.cpp、py的使用示例,对于逆向工程人员来说,浅显易懂,可以照猫画虎实现自定义功能。

TTD::ReplayEngine   ttdengine   = TTD::ReplayEngine();
ttdengine.Initialize( trace_path );

打开.run文件,初始化TTD引擎

TTD::Cursor         ttdcursor   = ttdengine.NewCursor();
ttdcursor.SetPosition( position.Major, position.Minor );

顾名思义,ttdcursor相当于!tt,可以来回切换position

目前支持两种回调函数,一种是call、ret指令触发,另一种就是内存断点。此处的内存断点是TTD的特有支持,不是用硬件断点那套,支持任意长度的内存断点,支持读、写、执行。

MemBreakpoint.addr  = ImageBbase;
MemBreakpoint.size  = imageSize;
MemBreakpoint.flags = TTD::BP_FLAGS::EXEC;
ttdcursor.AddMemoryWatchpoint( &MemBreakpoint );
ttdcursor.SetMemoryWatchpointCallback( ( TTD::PROC_MemCallback )MemCallback, 0 );

static bool MemCallback
(
    unsigned __int64                        callback_value,
    TTD::TTD_Replay_MemoryWatchpointResult *mem,
    struct TTD::TTD_Replay_IThreadView     *thread_info
)
{
    g_mutex.lock();
    g_hit_set.insert( mem->addr );
    g_mutex.unlock();

    return FALSE;
}

这是example_cov中的MemCallback,比较简单,只记录了mem->addr。

TTD::TTD_Replay_ICursorView_ReplayResult    replay_ret;
ttdcursor.ReplayForward( &replay_ret, &end, -1 );

ReplayForward第二形参指定end position;第三形参-1对应stepCount,若为1,就是单步执行。

无论单步、非单步,之前设置的回调函数都会命中,可以在回调函数中做很多动作,相当于交互式调试时条件断点命中后执行的命令。由于可以C++编程,肯定比断点命令强大太多。

TTD不支持wt,会报错

Operation not supported in current debug session

参看example_calltree,可以自己实现wt的效果。要求如下DLL就位

dbghelp.dll
TTDReplay.dll
TTDReplayCPU.dll

example_calltree的输出类似这种

[1] -> Calculator+0x23c080 (ABE36:C6)
| [2] -> Calculator+0x230450 (ABE36:E8)
| | [3] -> ucrtbase!calloc (ABE36:F5)
| | [3] <- ucrtbase!_calloc_base+0x61 (ABE39:87) ret=0x2ca4c833c80
| [2] <- Calculator+0x230484 (ABE39:8B) ret=0x2ca4c833c80
| [2] -> ucrtbase!free (ABE39:EA)
| | [3] -> ntdll!RtlFreeHeap (ABE39:F4)
| | [3] <- ntdll!RtlFreeHeap+0x66 (ABE3C:42) ret=0x1
| [2] <- ucrtbase!_free_base+0x27 (ABE3C:46) ret=0x1
[1] <- Calculator+0x23c292 (ABE3C:4B) ret=0x1

受限于UWP的Mitigation Policies,DynamoRIO缺省无法录制Win10 Calculator。理论上可以改DynamoRIO,使用反射式DLL加载,而非LoadLibrary,使之支持UWP,但我懒得搞。

参看example_cov,可以对UWP生成为Lighthouse所支持的.log文件,从而对UWP进行Coverage Diff。即使不考虑UWP情形,对普通进程而言,DynamoRIO录制也不如TTD录制+example_cov截取,后者可以精确指定position范围,从而让Coverage Diff更有意义。

example_cov的输出类似这种

Calculator+0xacb0
Calculator+0xacb3
Calculator+0xacb7
Calculator+0xacbb

参看example_tenet,生成为Tenet所支持.trace文件。用了一下Tenet,感觉意义不大。

example_tenet的输出类似这种

Rsp=0xd695bfd998,Rip=0x7ff6b3fdc080,mw=0xd695bfd998:9d2efdb3f67f0000
Rip=0x7ff6b3fdc085,mw=0xd695bfd9a8:70dabf95d6000000

mw表示发生内存写操作,等号后面是内存地址,冒号后面是16进制字节流。

example_tenet对小范围的position有效,对全范围使用时有坑,出现漏指令的现象;可能与单步执行有关,可能与多线程有关,总之,极不靠谱。

假设用TTD录制Win10 Calculator的乘法运算,想找出乘法运算的汇编指令。用常规TTD反向执行找出来过,参看

《MSDN系列(46)--WinDbg Preview TTD入门》
http://scz.617.cn:8/windows/202201251528.txt

之前对Win7 calc试过Coverage Diff,Win7 calc有符号,它的算术运算逻辑比较独立,用Coverage Diff很快就定位到乘法运算。参看

《MSDN系列(47)--Lighthouse/DynamoRIO/Coverage Diff入门》
http://scz.617.cn:8/windows/202202101230.txt

对Win10 Calculator测试Coverage Diff,没有符号,它的算术运算逻辑不那么独立,乘法运算位于某个公共函数中,该公共函数被频繁调用,很难制造a.log不覆盖该公共函数、b.log覆盖该公共函数的效果,处理Win7 calc的套路在此不适用。

有ttd-bindings之后,找出乘法运算多了新思路。合理推测应有"imul rega,regb",事先不知道哪两个寄存器,只是逻辑推断可能出现这样的场景。编程遍历"!tt 0"与"!tt 100"之间的代码,当某个通用寄存器值为a、另一个通用寄存器值为b时,记录此时的rip、position。若特征值a、b足够特异,log文件中的记录条数不会太多,人工检视即可定位乘法运算。

可以对着ttd-bindings的源码自己逆一下TTDReplay.dll,或许有新发现。现阶段推荐用C++ bindings,Python bindings实在太弱了。

本文向逆向工程人员推荐ttd-bindings,有用,好用,值得用,将TTD利用方式推进到新高度。