TTD调试进阶之ttd-bindings - scz
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利用方式推进到新高度。
本作品采用 知识共享署名-相同方式共享 4.0 国际许可协议 进行许可。
评论已关闭