标题: MSDN系列(46)--WinDbg Preview TTD入门 创建: 2022-01-25 15:28 更新: 2022-07-12 15:09 链接: https://scz.617.cn/windows/202201251528.txt -------------------------------------------------------------------------- 目录: ☆ 安装WinDbg Preview ☆ TTD技术简介 ☆ TTDTest_0 1) TTDTest_0.c 2) TTD调试TTDTest_0.exe 2.1) 数据断点+反向执行 2.2) 用TTD.Memory实现数据断点的目的 2.3) GUI中的Timelines ☆ TTDTest_1 1) TTDTest_1.c 2) TTD调试TTDTest_1.exe 3) TTD.Utility.GetHeapAddress 4) TTD.Data.Heap ☆ TTD.exe ☆ tttracer.exe ☆ notepad示例 1) USER32!RegisterClassExW 2) notepad!NPCommand ☆ Calculator示例 ☆ 参考资源 -------------------------------------------------------------------------- ☆ 安装WinDbg Preview WinDbg Preview与传统的WinDbg不是一个东西。二者最大的区别是,前者支持TTD技 术,后者不支持。 可以从Microsoft Store安装WinDbg Preview,可能需要先登录,匿名时可能搜不到 WinDbg Preview。 在cmd中执行windbgx,即可打开WinDbg Preview 尽管官方要求从Microsoft Store安装WinDbg Preview,但安装结束后可以用Process Explorer找出WinDbg Preview安装目录,将之复制到别处仍可使用。比如安装到 C:\Program Files\WindowsApps\Microsoft.WinDbg_1.2206.19001.0_x64__8wekyb3d8bbwe\ 复制到 X:\Green\windbgx\1.2206.19001.0\ 复制出来有好处,想往原始目录放置mex.dll、PDE.dll,权限不足,懒得折腾。 Win10企业版2016 LTSB没有Microsoft Store,但可以使用绿色版WinDbg Preview, 包括TTD技术。据说可以将绿色版"WinDbg Preview"复制到Win8使用,我没试过。 微软这帮缺德货解释为什么只通过Microsoft Store提供WinDbg Preview,官方口径 是,他们在快速迭代,通过Microsoft Store可以获取最新版。但我认为这只是托辞, 实际是强推Microsoft Store。 参看 《不通过Microsoft Store的GUI界面安装Store App》 https://scz.617.cn/windows/202201282230.txt ☆ TTD技术简介 TTD是"Time Travel Debugging"的缩写,可以理解成轻量级、进程级的VMware 7之前 的Record/Replay功能。VMware 7那个功能是系统级、OS级的录制/重放,TTD只针对 单个进程。TTD只能对付用户态进程,无法用于内核态调试。 TTD的背后是Nirvana/iDNA技术,此外可以参[5]。Linux上也有类似的技术,参[6]。 "录制"阶段会生成.run文件,"重放"阶段所有操作都围绕.run文件进行,我称之为" 鞭尸"。若录制时间较长,执行到的代码块较复杂,生成的.run文件可能非常之大。 "鞭尸"时,可以对已被覆盖的缓冲区设置数据断点,反向(逆序)执行,定位向缓冲区 写入数据的代码逻辑。VMware 7的Record/Replay好像不能反向执行?记不清了。 "数据断点+反向执行"是TTD技术最经典的应用,但是,这是一种弱智的应用方式。在 复杂场景中,TTD执行无论顺序、逆序都很耗时,高效作法是用TTD.Memory实现数据 断点的目的,初学者切记之。 ☆ TTDTest_0 本来我只想演示notepad、Calculator的,后来考虑那样入门对小白太难了,就先演 示些更简单的例子吧。 这一段我截了些图,写了个DOC https://scz.617.cn/windows/MSDN_46.docx 1) TTDTest_0.c -------------------------------------------------------------------------- /* * Visual Studio 2019 * * cl TTDTest_0.c /FeTTDTest_0.exe /nologo /Os /GS- /guard:cf- /W3 /WX /D "WIN32" /D "NDEBUG" /D "_CONSOLE" /MD /link /RELEASE * cl TTDTest_0.c /FeTTDTest_0.exe /Zi /FaTTDTest_0.asm /FdTTDTest_0.pdb /nologo /Os /GS- /guard:cf- /W3 /WX /D "WIN32" /D "NDEBUG" /D "_CONSOLE" /MD /link /RELEASE /opt:ref */ #include static void foo ( int a, int b, int c ) { int x = a + b; int y = x / c; } int main ( int argc, char * argv[] ) { int a = 2; int b = 1; int c = 0; foo( a, b, c ); printf( "ok\n" ); return 0; } -------------------------------------------------------------------------- 本例执行时触发除零异常。后面假设没有源码,通过TTD调试了解发生了什么。 2) TTD调试TTDTest_0.exe 开管理员级WinDbg Preview,否则无法使用TTD技术。在管理员级cmd中执行 "X:\Green\windbgx\1.2206.19001.0\DbgX.Shell.exe" 这是假设用绿色版,如果有安装版,在管理员级cmd中执行 windbgx -------------------------------------------------------------------------- File Launch executable (advanced) Executable X:\work\MSDN_46\TTDTest_0.exe Start directory X:\work\MSDN_46\ Target architecture Autodetect Record with Time Travel Debugging (选中) Configure and Record Save location X:\work\MSDN_46 Record -------------------------------------------------------------------------- 这些都是自解释的,不需要写这么细。点击Record之后就开始执行并记录,缺省没有 停在ibp(初始化断点),而是停在ntdll!LdrInitializeThunk,这个点比ibp还要早。 g起来,直至触发除零异常,依次生成TTDTest_001.run、TTDTest_001.idx。 > r rax=0000000000000003 rbx=00000236b75a4ac0 rcx=0000000000000003 ... TTDTest_0+0x1024: 00007ff7`62341024 f77c2430 idiv eax,dword ptr [rsp+30h] ss:00000097`e64ff730=00000000 位于0x97e64ff730的除数为零,idiv指令触发除零异常。 2.1) 数据断点+反向执行 这是个简单示例,用IDA静态看两眼就知道root cause。如果是复杂场景,可以对 0x97e64ff730设置数据断点,从触发异常的现场反向执行。 有调试符号及源码的情况下,反向执行非常直观。现实中可能没有调试符号及源码, 应该以此为前提练习TTD技术。 ba w1 0x97e64ff730;g- "g-"是"g"的逆操作,反向执行。当数据断点命中时查看附近代码 > r rax=00007ffe79e607a8 rbx=00000236b75a4ac0 rcx=0000000000000002 rdx=0000000000000001 rsi=0000000000000000 rdi=00000236b75a8be0 rip=00007ff762341005 rsp=00000097e64ff718 rbp=0000000000000000 r8=0000000000000000 r9=00000097e64ff6d8 r10=0000000000000012 ... > u @rip-5 l 2 TTDTest_0+0x1000: 00007ff7`62341000 4489442418 mov dword ptr [rsp+18h],r8d 00007ff7`62341005 89542410 mov dword ptr [rsp+10h],edx 0x7ff762341000处代码对除数赋零。对于本例,这已经是root cause。 GUI界面上有四种反向执行,可以鼠标操作 Go Back // g- Step Out Back // g-u Step Into Back // t- Step Over Back // p- 我是习惯了命令行,所以用"g-"。 要点在于,所有信息都存储在.run文件中,可以不断正向、反向执行,整个过程不怕 丢失调试的中间状态,可以让反向执行去触发数据断点,这是梦寐以求的功能。显然, 只能以只读模式使用.run文件,不能在调试过程中手工更改寄存器、内存。 2.2) 用TTD.Memory实现数据断点的目的 .idx文件是基于.run事后生成的,可以删除并重新生成。有了.idx文件,可以用dx命 令查询对地址0x97e64ff730进行写操作的所有代码,效率比"数据断点+反向执行"高。 r $t0=0x97e64ff730;r $t1=8;dx -r2 @$cursession.TTD.Memory(@$t0,@$t0+@$t1,"w").Where(m=>m.Value==0) 这是查询对指定内存写入0的代码 EventType : 0x1 ThreadId : 0x32b4 UniqueThreadId : 0x2 TimeStart : 56:30 [Time Travel] AccessType : Write IP : 0x7ff762341000 Address : 0x97e64ff730 Size : 0x4 Value : 0x0 OverwrittenValue : 0x1f OverwrittenValue是原来的值,Value是现在的值,直接定位到0x7ff762341000。 "u 0x7ff762341000 l 1"只能看代码,看不到上下文,可以用!tt切换到那个时间点 > !ttdext.tt 56:30 Setting position: 56:30 TTDTest_0+0x1000: 00007ff7`62341000 4489442418 mov dword ptr [rsp+18h],r8d ss:00000097`e64ff730=0000001f > r rax=00007ffe79e607a8 rbx=00000236b75a4ac0 rcx=0000000000000002 rdx=0000000000000001 rsi=0000000000000000 rdi=00000236b75a8be0 rip=00007ff762341000 rsp=00000097e64ff718 rbp=0000000000000000 r8=0000000000000000 r9=00000097e64ff6d8 r10=0000000000000012 ... TTDTest_0+0x1000: 00007ff7`62341000 4489442418 mov dword ptr [rsp+18h],r8d ss:00000097`e64ff730=0000001f !tt切换过去后就能看到上下文,比如0x97e64ff730处原来的dword是0x1f,在此被赋 值零。!tt相当于上帝模式的g,对每个时间点的情况进行验尸,无论正反向。 下面这两条命令的效果一样,但后者更高效 ba w1 0x97e64ff730;g- r $t0=0x97e64ff730;r $t1=8;dx -r1 @$cursession.TTD.Memory(@$t0,@$t0+@$t1,"w").Last().TimeStart.SeekTo() 2.3) GUI中的Timelines View->Timelines 让GUI左下角出现Timelines区域,从而直观观察对指定内存的各种访问 -------------------------------------------------------------------------- Add timeline Timeline Type Memory Accesses // 有多种类型,比如异常、断点、函数调用、内存访问 Start Address 0x97e64ff730 // 欲监控的内存地址 End Address 0x97e64ff738 // 左闭右开区间 Access Type Write // 只监控写操作 -------------------------------------------------------------------------- 之后Timelines区域新增一条timeline,其上每个褐色棱块对应一次写操作。本例中 双击最后一个褐色棱块,相当于 dx @$cursession.TTD.Memory(0x97e64ff730,0x97e64ff738,"w")[0x1] 本例总共只有两次写操作,所以[1]表示最后一次,其他示例未必如此。 ☆ TTDTest_1 1) TTDTest_1.c -------------------------------------------------------------------------- /* * Visual Studio 2019 * * cl TTDTest_1.c /FeTTDTest_1.exe /nologo /Os /GS- /guard:cf- /W3 /WX /D "WIN32" /D "NDEBUG" /D "_CONSOLE" /MD /link /RELEASE * cl TTDTest_1.c /FeTTDTest_1.exe /Zi /FaTTDTest_1.asm /FdTTDTest_1.pdb /nologo /Os /GS- /guard:cf- /W3 /WX /D "WIN32" /D "NDEBUG" /D "_CONSOLE" /MD /link /RELEASE /opt:ref * * TTDTest_1.exe 4 8 16 0123456789ABCDEF 1 * TTDTest_1.exe 4 8 16 0123456789ABCDEF 6 * TTDTest_1.exe 4 8 16 0123456789ABCDEF 256 */ #include #include #include int main ( int argc, char * argv[] ) { int i, j, k; int len[3]; char* buf[3]; char* sth; int count; if ( argc < 6 ) { fprintf( stderr, "Usage: %s \n", argv[0] ); goto main_exit_0; } sth = argv[4]; count = strtoul( argv[5], NULL, 0 ); k = ( int )strlen( sth ); for ( i = 0; i < 3; i++ ) { buf[i] = NULL; } for ( i = 0; i < 3; i++ ) { len[i] = strtoul( argv[i+1], NULL, 0 ); buf[i] = calloc( len[i], 1 ); if ( NULL == buf[i] ) { goto main_exit_1; } } for ( i = 0; i < 3; i++ ) { for ( j = 0; j < count; j++ ) { memcpy( buf[i]+j*k, sth, k ); } printf( "%s\n", buf[i] ); } printf( "ok\n" ); main_exit_1: for ( i = 0; i < 3; i++ ) { if ( NULL != buf[i] ) { free( buf[i] ); buf[i] = NULL; } } main_exit_0: return 0; } -------------------------------------------------------------------------- 测试 $ TTDTest_1.exe 4 8 16 0123456789ABCDEF 256 2) TTD调试TTDTest_1.exe 开TTD调试TTDTest_1.exe,指定命令行参数。执行有些慢,生成TTDTest_101.run、 TTDTest_101.idx。 已有.run的情况下,不再需要管理员级cmd。在普通cmd中执行 "X:\Green\windbgx\1.2206.19001.0\DbgX.Shell.exe" TTDTest_101.run 或 windbgx TTDTest_101.run 已经有.run,不需要g,直接!tt切换到最后。如果g,慢得要死。 > !tt 100 > r rax=3736353433323150 rbx=0000007a480ce000 rcx=0000000000000010 rdx=3736353433323130 rsi=00000000c0000005 rdi=0000000000000000 rip=00007ffe7c442555 rsp=0000007a48201148 rbp=0000007a48201240 r8=0000000000000002 r9=0000007a48201188 r10=0000000000000013 r11=0000007a482012a0 r12=00007ff683d20000 r13=0000007a482ff9b0 r14=0000000000001504 r15=0000007a48201840 iopl=0 nv up ei pl nz na po nc cs=0033 ss=002b ds=002b es=002b fs=0053 gs=002b efl=00000206 ntdll!RtlFlsGetValue+0x55: 00007ffe`7c442555 488b00 mov rax,qword ptr [rax] ds:37363534`33323150=???????????????? rax被破坏。调用栈乱套了,很多层,不太可信,先看前10层 > kpn 0n10 # Child-SP RetAddr Call Site 00 0000007a`48201148 00007ffe`7a01986b ntdll!RtlFlsGetValue+0x55 01 0000007a`48201150 00007ffe`79d83a81 KERNELBASE!FlsGetValue+0x1b 02 0000007a`48201180 00007ffe`79de0cf1 ucrtbase!__acrt_FlsGetValue+0x41 03 0000007a`482011b0 00007ff6`83d21e3c ucrtbase!seh_filter_exe+0x31 04 0000007a`482011e0 00007ffe`6a77ecd0 TTDTest_1+0x1e3c 05 0000007a`48201210 00007ffe`7c4920cf VCRUNTIME140!_C_specific_handler+0xa0 06 0000007a`48201280 00007ffe`7c441454 ntdll!RtlpExecuteHandlerForException+0xf 07 0000007a`482012b0 00007ffe`7c490bfe ntdll!RtlDispatchException+0x244 08 0000007a`482019c0 00007ffe`7c442555 ntdll!KiUserExceptionDispatch+0x2e 09 0000007a`48202148 00007ffe`7a01986b ntdll!RtlFlsGetValue+0x55 调用栈回溯中出现TTDTest_1+0x1e3c,反向执行到附近 > g- TTDTest_1+0x1e3c-5 Time Travel Position: 4FBDB:286 TTDTest_1+0x1e37: 00007ff6`83d21e37 e828ffffff call TTDTest_1+0x1d64 (00007ff6`83d21d64) IDA里看此处代码 00007FF683D21E37 E8 28 FF FF FF call _seh_filter_exe 位于__scrt_common_main_seh()中,这是SEH的框架代码,离root cause太远了。 应该找第一个异常。本例异常实在太多,GUI中的Timelines已失去意义,Exceptions 铺满了,无法有效点击指定点。可以命令行操作 > dx @$curprocess.TTD.Events.Where(t=>t.Type=="Exception").First().Exception : Exception 0xC0000005 of type Hardware at PC: 0X7FFE7C442555 Position : 64:0 [Time Travel] Type : Hardware // 常见还有Software、CPlusPlus等 ProgramCounter : 0x7ffe7c442555 Code : 0xc0000005 // 内存访问违例 Flags : 0x0 RecordAddress : 0x0 切换至第一个异常处,下列命令任选其一 > dx -s @$curprocess.TTD.Events.Where(t=>t.Type=="Exception").First().Position.SeekTo() > !tt 64:0 > dx -s @$curprocess.TTD.SetPosition("64:0") > dx -s @$create("Debugger.Models.TTD.Position",0x64,0).SeekTo() 查看当前position,下列命令任选其一 > !position > dx @$curprocess.Threads.Select(t=>t.TTD.Position) 查看第一个异常发生时的上下文 > r rax=3736353433323150 rbx=0000007a480ce000 rcx=0000000000000010 rdx=3736353433323130 rsi=0000007a482ff2c0 rdi=0000007a482ff398 rip=00007ffe7c442555 rsp=0000007a482ff168 rbp=00000000fffff000 r8=0000000000000002 r9=0000007a482ff1a8 r10=0000000000000013 r11=00007ffe79e60980 r12=0000000000000001 r13=0000000000000000 r14=0000007a482ff7d8 r15=0000007a482ff7d8 iopl=0 nv up ei pl nz na po nc cs=0033 ss=002b ds=002b es=002b fs=0053 gs=002b efl=00000206 ntdll!RtlFlsGetValue+0x55: 00007ffe`7c442555 488b00 mov rax,qword ptr [rax] ds:37363534`33323150=???????????????? 此时调用栈回溯已能提供相对有意义的信息 > kpn # Child-SP RetAddr Call Site 00 0000007a`482ff168 00007ffe`7a01986b ntdll!RtlFlsGetValue+0x55 01 0000007a`482ff170 00007ffe`79d83a81 KERNELBASE!FlsGetValue+0x1b 02 0000007a`482ff1a0 00007ffe`79d83462 ucrtbase!__acrt_FlsGetValue+0x41 03 0000007a`482ff1d0 00007ffe`79d8b580 ucrtbase!_errno+0x22 04 0000007a`482ff200 00007ffe`79d8bc0e ucrtbase!__crt_deferred_errno_cache::get+0x1c 05 0000007a`482ff230 00007ffe`79d8bdee ucrtbase!__crt_stdio_output::output_adapter_common >::write_string_impl+0x2e 06 0000007a`482ff270 00007ffe`79d8a615 ucrtbase!__crt_stdio_output::output_processor,__crt_stdio_output::standard_base > >::state_case_type+0x102 07 0000007a`482ff2c0 00007ffe`79d8b216 ucrtbase!__crt_stdio_output::output_processor,__crt_stdio_output::standard_base > >::process+0x179 08 0000007a`482ff2f0 00007ffe`79d8b347 ucrtbase!::operator()+0xc2 09 0000007a`482ff810 00007ffe`79d8b3e4 ucrtbase!__crt_seh_guarded_call::operator()<, &, >+0x2b 0a 0000007a`482ff840 00007ff6`83d21267 ucrtbase!__stdio_common_vfprintf+0x74 0b 0000007a`482ff8b0 00007ff6`83d212fe TTDTest_1+0x1267 0c 0000007a`482ff8f0 00007ff6`83d211c1 TTDTest_1+0x12fe 0d 0000007a`482ff930 00007ff6`83d21504 TTDTest_1+0x11c1 0e 0000007a`482ff9b0 00007ffe`7b7e7034 TTDTest_1+0x1504 0f 0000007a`482ff9f0 00007ffe`7c442651 KERNEL32!BaseThreadInitThunk+0x14 10 0000007a`482ffa20 00000000`00000000 ntdll!RtlUserThreadStart+0x21 在IDA中查看TTDTest_1+0x11c1/0x7ff683d211c1附近代码 -------------------------------------------------------------------------- for ( k = 0; k < 3; ++k ) { for ( m = 0; m < v11; ++m ) memcpy((char *)Block[k] + v10 * m, Str, v10); // 抛出异常之前有memcpy() sub_7FF683D212BC("%s\n", (const char *)Block[k]); // 一个printf()之类的函数 } -------------------------------------------------------------------------- 00007ff6`83d211b0 488b54c438 mov rdx, qword ptr [rsp+rax*8+38h] 00007ff6`83d211b5 488d0d701e0000 lea rcx, [TTDTest_1+0x302c (00007ff6`83d2302c)] 00007ff6`83d211bc e8fb000000 call TTDTest_1+0x12bc (00007ff6`83d212bc) 00007ff6`83d211c1 eb8c jmp TTDTest_1+0x114f (00007ff6`83d2114f) -------------------------------------------------------------------------- 回到sub_7FF683D212BC调用点,查看函数形参 > g- TTDTest_1+0x11c1-5 Time Travel Position: 5D:1DC5 TTDTest_1+0x11bc: 00007ff6`83d211bc e8fb000000 call TTDTest_1+0x12bc (00007ff6`83d212bc) > r rax=0000000000000000 rbx=00000252a98c4550 rcx=00007ff683d2302c rdx=00000252a98c4be0 rsi=0000000000000000 rdi=00000252a98c8e20 rip=00007ff683d211bc rsp=0000007a482ff930 rbp=0000000000000000 r8=0000000000000010 r9=0000000000000000 r10=00007ffe6a770000 r11=ff800000000fffff r12=0000000000000000 r13=0000000000000000 r14=0000000000000000 r15=0000000000000000 iopl=0 nv up ei pl zr na po nc cs=0033 ss=002b ds=002b es=002b fs=0053 gs=002b efl=00000246 TTDTest_1+0x11bc: 00007ff6`83d211bc e8fb000000 call TTDTest_1+0x12bc (00007ff6`83d212bc) > da @rcx 00007ff6`83d2302c "%s." > db @rdx 00000252`a98c4be0 30 31 32 33 34 35 36 37-38 39 41 42 43 44 45 46 0123456789ABCDEF 00000252`a98c4bf0 30 31 32 33 34 35 36 37-38 39 41 42 43 44 45 46 0123456789ABCDEF 00000252`a98c4c00 30 31 32 33 34 35 36 37-38 39 41 42 43 44 45 46 0123456789ABCDEF 00000252`a98c4c10 30 31 32 33 34 35 36 37-38 39 41 42 43 44 45 46 0123456789ABCDEF 00000252`a98c4c20 30 31 32 33 34 35 36 37-38 39 41 42 43 44 45 46 0123456789ABCDEF 00000252`a98c4c30 30 31 32 33 34 35 36 37-38 39 41 42 43 44 45 46 0123456789ABCDEF 00000252`a98c4c40 30 31 32 33 34 35 36 37-38 39 41 42 43 44 45 46 0123456789ABCDEF 00000252`a98c4c50 30 31 32 33 34 35 36 37-38 39 41 42 43 44 45 46 0123456789ABCDEF 对比rdx、rsp可知printf()第二形参必不位于stack。简单分析可知rdx取自 poi(rsp+rax*8+0x38) > ? poi(rsp+rax*8+0x38)==@rdx Evaluate expression: 1 = 00000000`00000001 查看所有对rsp+rax*8+0x38的写操作 > r $t0=@rsp+@rax*8+0x38;r $t1=8;dx -g @$cursession.TTD.Memory(@$t0,@$t0+@$t1,"w").Select(m=>new{Time=m.TimeStart,PC=m.IP,Size=m.Size,Old=m.OverwrittenValue,New=m.Value}) ================================================================================================== = = (+) Time = (+) PC = (+) Size = (+) Old = (+) New = ================================================================================================== = [0x0] - 35:48 - 0x7ff683d21bd3 - 0x4 - 0x0 - 0x7ffafbff = = [0x1] - 35:49 - 0x7ff683d21bd7 - 0x4 - 0x0 - 0xbfebfbff = = [0x2] - 35:60 - 0x7ff683d21c4f - 0x4 - 0x7ffafbff - 0x40000000 = = [0x3] - 35:61 - 0x7ff683d21c53 - 0x4 - 0xbfebfbff - 0xbc000400 = = [0x4] - 56:134 - 0x7ff683d210c1 - 0x8 - 0xbc00040040000000 - 0x0 = = [0x5] - 58:60 - 0x7ff683d2112c - 0x8 - 0x0 - 0x252a98c4be0 = ================================================================================================== 最后一次写操作(58:60)值得关注,从0写成0x252a98c4be0,也就是将来的rdx值 > !tt 58:60 > r rax=00000252a98c4be0 rbx=00000252a98c4550 rcx=0000000000000000 ... TTDTest_1+0x112c: 00007ff6`83d2112c 488944cc38 mov qword ptr [rsp+rcx*8+38h],rax ss:0000007a`482ff968=0000000000000000 查看附近代码 ----------------------------------------------------------------------------- 00007ff6`83d21119 ba01000000 mov edx, 1 00007ff6`83d2111e 488bc8 mov rcx, rax /* * 上下文具备时,看得出在调用calloc() */ 00007ff6`83d21121 ff15990f0000 call qword ptr [TTDTest_1+0x20c0 (00007ff6`83d220c0)] ds:00007ff6`83d220c0={ucrtbase!calloc (00007ffe`79d7dce0)} 00007ff6`83d21127 48634c2420 movsxd rcx, dword ptr [rsp+20h] /* * rax是calloc()的返回值 */ 00007ff6`83d2112c 488944cc38 mov qword ptr [rsp+rcx*8+38h], rax ----------------------------------------------------------------------------- 反向执行 > g- 0x7ff683d21121 Time Travel Position: 56:1F0 这里为什么不用!tt呢?因为事先并不知道0x7ff683d21121对应"56:1F0"。 > r rax=0000000000000004 rbx=00000252a98c4550 rcx=0000000000000004 rdx=0000000000000001 rsi=0000000000000000 rdi=00000252a98c8e20 rip=00007ff683d21121 rsp=0000007a482ff930 rbp=0000000000000000 r8=00000252a98c45a8 r9=0000000000000005 r10=0000000000000012 r11=8101010101010100 r12=0000000000000000 r13=0000000000000000 r14=0000000000000000 r15=0000000000000000 iopl=0 nv up ei pl nz na po nc cs=0033 ss=002b ds=002b es=002b fs=0053 gs=002b efl=00000206 TTDTest_1+0x1121: 00007ff6`83d21121 ff15990f0000 call qword ptr [TTDTest_1+0x20c0 (00007ff6`83d220c0)] ds:00007ff6`83d220c0={ucrtbase!calloc (00007ffe`79d7dce0)} printf()的源地址是calloc(4,1)分配的堆区。 3) TTD.Utility.GetHeapAddress 给定一个堆中地址,查找所有影响指定地址的堆操作 > r $t0=0x252a98c4be0;dx -g @$cursession.TTD.Utility.GetHeapAddress(@$t0) ============================================================================================================================ = = Action = Heap = Address = Size = Flags = (+) TimeStart = (+) TimeEnd = ============================================================================================================================ = [0xa] : [object Object] - Alloc - 0x252a98c0000 - 0x252a98c4be0 - 0x4 - 0x8 - 56:206 - 58:59 = ============================================================================================================================ 只有Alloc,没有Free,没来得及Free就已经内存访问违例了。 > !tt 56:206 > kpn # Child-SP RetAddr Call Site 00 0000007a`482ff8f8 00007ffe`79d7dd3e ntdll!RtlAllocateHeap 01 0000007a`482ff900 00007ff6`83d21127 ucrtbase!_calloc_base+0x4e 02 0000007a`482ff930 00007ff6`83d21504 TTDTest_1+0x1127 03 0000007a`482ff9b0 00007ffe`7b7e7034 TTDTest_1+0x1504 04 0000007a`482ff9f0 00007ffe`7c442651 KERNEL32!BaseThreadInitThunk+0x14 05 0000007a`482ffa20 00000000`00000000 ntdll!RtlUserThreadStart+0x21 > u 7ff6`83d21127-6 l 2 TTDTest_1+0x1121: 00007ff6`83d21121 ff15990f0000 call qword ptr [TTDTest_1+0x20c0 (00007ff6`83d220c0)] 00007ff6`83d21127 48634c2420 movsxd rcx,dword ptr [rsp+20h] 4) TTD.Data.Heap 给定一个堆中地址,查找所有影响指定地址的堆操作 > r $t0=0x252a98c4be0;dx -g @$cursession.TTD.Data.Heap().Where(h=>h.Address==@$t0) ============================================================================================================================ = = Action = Heap = Address = Size = Flags = (+) TimeStart = (+) TimeEnd = ============================================================================================================================ = [0xa] : [object Object] - Alloc - 0x252a98c0000 - 0x252a98c4be0 - 0x4 - 0x8 - 56:206 - 58:59 = ============================================================================================================================ 本例不复杂,主要目的是演示dx的用法,到这儿就差不多了。 ☆ TTD.exe "Launch executable (advanced)"功能实际调用的是TTD.exe TTD.exe的很多参数无法通过WinDbg Preview指定,这没关系,可以直接用TTD.exe。 $ tree /A /F . | TTD.exe | TTDInject.exe | TTDLoader.dll | TTDRecord.dll | TTDRecordCPU.dll | \---wow64 TTD.exe TTDInject.exe TTDLoader.dll TTDRecord.dll TTDRecordCPU.dll TTD技术关键在于生成.run文件,为生成它只涉及上面列举的文件,wow64是用于32位 PE的。 在管理员级cmd中执行 $ TTD.exe -ring -maxFile 2048 -out "TTDTest_0%.run" -launch TTDTest_0.exe 生成2GB的TTDTest_002.run,虽然很大,但不会增长,情况可控。 执行TTD.exe需要管理员级cmd,分析.run不需要。在普通cmd中执行 $ windbgx TTDTest_002.run ☆ tttracer.exe 某些版本的Win10自带一个神秘的tttracer.exe,帮助里没有任何参数介绍 $ where tttracer C:\Windows\System32\tttracer.exe tttracer.exe实际就是TTD.exe,其参数完全同TTD.exe。 TTD技术关键在于生成.run文件,为生成它只需要下面列举的文件,这些文件来自非 LTSB版Win10的System32目录,换句话说,全是自带的。 ttdinject.exe ttdloader.dll ttdrecord.dll ttdrecordcpu.dll tttracer.exe ☆ notepad示例 用ResourceHacker查看 C:\Windows\zh-cn\notepad.exe.mui 在Menu项中看到 -------------------------------------------------------------------------- POPUP "格式(&O)" { MENUITEM "自动换行(&W)", 32 MENUITEM "字体(&F)...", 33 } -------------------------------------------------------------------------- $ tttracer.exe -ring -maxFile 2048 -out "notepad%.run" -launch notepad.exe 操作notepad,在菜单中选择"格式->字体" $ tttracer.exe -stop all 用"-stop"可以让notepad保持运行,而不是被杀掉,同时生成.run文件。 $ windbgx notepad01.run 原始需求,找出"格式->字体"对应的代码逻辑。常规思路是条件断点 bp notepad!NPWndProc ".if(0x111==@edx and @r8d==0n33){!position;r rcx,edx,r8,r9}.else{gc}" TTD执行非常慢,断点命中后用pc/t/pc找到 notepad!NPCommand+0x9ee: 00007ff6`9098aa86 e869b90100 call notepad!memset (00007ff6`909a63f4) 附近代码对应"格式->字体"。下面演示更高效的套路 > dx -g @$cursession.TTD.Calls("notepad!NPCommand").Select(c=>new{Time=c.TimeStart,RetAddr=c.ReturnAddress,hWnd=c.Parameters[0],wParam=c.Parameters[1]}) ============================================================================= = = (+) Time = (+) RetAddr = hWnd = wParam = ============================================================================= = [0x0] - 23D55:6C - 0x7ff69098be60 - 0x420566 - 0x200000f = ... = [0x26] - 2787F:62 - 0x7ff69098be60 - 0x420566 - 0x21 = ============================================================================= > r $t0=0n33;dx -g @$cursession.TTD.Calls("notepad!NPCommand").Select(c=>new{Time=c.TimeStart,RetAddr=c.ReturnAddress,hWnd=c.Parameters[0],wParam=c.Parameters[1]}).Where(c=>c.wParam==@$t0) ======================================================================= = = (+) Time = (+) RetAddr = hWnd = wParam = ======================================================================= = [0x26] - 2787F:62 - 0x7ff69098be60 - 0x420566 - 0x21 = ======================================================================= 上述命令比TTD执行快多了,找到"2787F:62",点击或!tt转移过去。pc找到这里 notepad!NPCommand+0x9ee: 00007ff6`9098aa86 e869b90100 call notepad!memset (00007ff6`909a63f4) 本小节的意思是,能dx就不要TTD执行,否则将失去TTD的强大与效率。 1) USER32!RegisterClassExW 后面的内容与TTD本身无关,顺道说说调试Win32 API编写的传统GUI程序。 cdb.exe -noinh -snul -hd -o "C:\Windows\System32\notepad.exe" 参[7],用Win32 API编写的传统GUI程序很可能调用这个函数 -------------------------------------------------------------------------- ATOM RegisterClassExW ( WNDCLASSEXW *unnamedParam1 ) -------------------------------------------------------------------------- 其第一形参类型如下 > dt combase!WNDCLASSEXW +0x000 cbSize : Uint4B +0x004 style : Uint4B +0x008 lpfnWndProc : Ptr64 int64 +0x010 cbClsExtra : Int4B +0x014 cbWndExtra : Int4B +0x018 hInstance : Ptr64 HINSTANCE__ +0x020 hIcon : Ptr64 HICON__ +0x028 hCursor : Ptr64 HICON__ +0x030 hbrBackground : Ptr64 HBRUSH__ +0x038 lpszMenuName : Ptr64 Wchar +0x040 lpszClassName : Ptr64 Wchar +0x048 hIconSm : Ptr64 HICON__ bp USER32!RegisterClassExW "r $t0=poi(@rcx+8);u @$t0 l 1;gc" 用上述断点可知notepad启动时涉及这些窗口过程 notepad!NPWndProc MSCTF!UIWndProc MSCTF!UICompositionCompWndProc MSCTF!CicMarshalWndProc 它们的函数原型都是 -------------------------------------------------------------------------- LRESULT CALLBACK WindowProc ( HWND hwnd, // rcx UINT uMsg, // edx WPARAM wParam, // r8 LPARAM lParam // r9 ) -------------------------------------------------------------------------- #define WM_COMMAND 0x0111 -------------------------------------------------------------------------- bp USER32!RegisterClassExW "bp poi(@rcx+8) \".if(0x111==@edx and @r8d==0n33){u @rip l 1;r rcx,edx,r8,r9};gc\";gc" 该断点当uMsg等于WM_COMMAND、wParam等于0n33时断下,或者说,执行"格式->字体" 时断下 2) notepad!NPCommand 在IDA中静态分析后,找到一个更快的断点拦截"格式->字体"操作 bp notepad!NPCommand ".if(@dx==0n33){r rcx,dx}.else{gc}" ☆ Calculator示例 Win10的calc与Win7的calc不一样,前者好像是个Store App?我用不惯新版,从Win7 复制旧版calc到Win10用。不过,本小节的目标是新版calc,也就是Calculator.exe。 启动新版calc,切换到"程序员模式",切换到16进制模式,然后上TTD。 tttracer.exe -ring -maxFile 2048 -out "calc_%.run" -attach 依次输入 51201314 * 41414141 = 得到乘法运算结果 0x14add2aaaa10ec14 停止TTD,下列命令任选其一 tttracer.exe -stop tttracer.exe -stop all 假设生成calc_04.run 原始需求是,结合TTD技术寻找前述算术运算相关代码。假设前述calc进程仍在活动 中,挂普通调试器进去搜内存,这样方便些。如果直接在.run中搜内存,可能非常慢, 还有可能干脆搜不着。 cdb.exe -noinh -snul -hd -o -p 查看进程空间内存布局 .logopen calc.txt !address -f:stack,heap !mex.grep -cs "Heap" !address -f:stack,heap !address -f:heap .logclose 查看calc.txt,类似这种输出 BaseAddress EndAddress+1 RegionSize Type State Protect Usage -------------------------------------------------------------------------------------------------------------------------- 90`5ca00000 90`5caf7000 0`000f7000 MEM_PRIVATE MEM_RESERVE Stack [~0; 3238.2b4c] ... 90`5e0fe000 90`5e100000 0`00002000 MEM_PRIVATE MEM_COMMIT PAGE_READWRITE Stack [~17; 3238.df0] 21e`f0930000 21e`f0940000 0`00010000 MEM_MAPPED MEM_COMMIT PAGE_READWRITE Heap [ID: 1; Handle: 0000021ef0930000; Type: Segment] ... 21e`fe47e000 21e`fe500000 0`00082000 MEM_PRIVATE MEM_RESERVE Heap [ID: 0; Handle: 0000021ef09a0000; Type: SegmentHeap Segment] 在堆区搜索特征字节流 > s -[w]b 21e`f0930000 21e`fe500000 14 ec 10 aa aa d2 ad 14 0000021e`fe44c2d0 14 ec 10 aa aa d2 ad 14-00 00 00 00 00 00 08 00 ................ 开始鞭尸 windbgx calc_04.run .prompt_allow +reg +ea +dis 如果没有自动生成calc_04.idx,用如下命令手工生成 !ttdext.index -force !ttdext.index -status 移动到.run尾部再查看内存,否则指定内存可能还没被写入期待中的值 !tt 100 理论上当然可以用数据断点加反向执行去找相关代码,但鞭尸时有更快的方案 > r $t0=0x21efe44c2d0;r $t1=4;r $t2=0xaa10ec14;dx -g @$cursession.TTD.Memory(@$t0,@$t0+@$t1,"rw").Where(m=>m.Value==@$t2) ========================================================================================================== = = (+) Time = (+) PC = (+) Size = (+) Old = (+) New = (+) Access = ========================================================================================================== = [0x4c1] - 2FA64F:9F4 - 0x7ff7bc7394d8 - 0x4 - 0x2a10ec14 - 0xaa10ec14 - Write = ========================================================================================================== > !tt 2FA64F:9F4 rax=000000002a10ec14 rbx=0000021efd553600 rcx=000000000000001f rdx=00000000aa10ec14 rsi=0000000000000001 rdi=000000000000001f rip=00007ff7bc7394d8 rsp=000000905d1fcdc0 rbp=000000905d1fcf10 r8=0000021efe44c2d0 r9=0000000000000001 r10=0000000000003730 r11=000000905d1fce00 r12=00007ff7bc95f1b0 r13=0000000000000001 r14=0000000000000000 r15=0000021ef7ccef20 iopl=0 nv up ei ng nz na po nc cs=0033 ss=002b ds=002b es=002b fs=0053 gs=002b efl=00000286 Calculator!VSDesignerDllMain+0x120ae8: 00007ff7`bc7394d8 418910 mov dword ptr [r8],edx ds:0000021e`fe44c2d0=2a10ec14 这个位置在写乘法运算的结果,结合TTD.Memory与反向执行,不断定位源头,直至 ======================================================================================================================== = = (+) Time = (+) PC = (+) Size = (+) Old = (+) New = (+) Access = ======================================================================================================================== = [0x14] - 2EA868:9A - 0x7ff8594c1421 - 0x10 - 0x0 - 0x100000001 - Write = = [0x15] - 2EA868:9B - 0x7ff8594c1425 - 0x10 - 0x100000001 - 0x100000001 - Write = = [0x19] - 2EA8B3:BC - 0x7ff7bc70c218 - 0x4 - 0x0 - 0x2a10ec14 - Write = ... = [0xd3] - 2ED8F7:8F - 0x7ff7bc6f1146 - 0x10 - 0x0 - 0x100000001 - Write = ======================================================================================================================== > !ttdext.tt 2EA8B3:BC rax=00000000295ba555 rbx=0000000000000001 rcx=000000002a10ec14 rdx=000000002a10ec14 rsi=0000021efe461820 rdi=0000000041414141 rip=00007ff7bc70c218 rsp=000000905d1fcd00 rbp=ffffffffffffe6d4 r8=000000002a10ec14 r9=0000000000000000 r10=0000021efe46182c r11=0000000000000001 r12=0000021efe44c2b0 r13=0000021efe45ff00 r14=0000021efe45e3d0 r15=0000021efe461830 iopl=0 nv up ei pl nz na po nc cs=0033 ss=002b ds=002b es=002b fs=0053 gs=002b efl=00000206 Calculator!VSDesignerDllMain+0xf3828: 00007ff7`bc70c218 43890c8a mov dword ptr [r10+r9*4],ecx ds:0000021e`fe46182c=00000000 注意到rdi等于0x41414141,在IDA中简单看看,回TTD中反向执行 > g- 0x7ff7bc70c1d8 Time Travel Position: 2EA8B3:A9 rax=0000000051201314 rbx=0000000000000001 rcx=0000000000000000 rdx=0000000000000002 rsi=0000021efe461820 rdi=0000000041414141 rip=00007ff7bc70c1d8 rsp=000000905d1fcd00 rbp=ffffffffffffe6d4 r8=0000000000000000 r9=0000000000000000 r10=0000021efe46182c r11=0000000000000001 r12=0000021efe44c2b0 r13=0000021efe45ff00 r14=0000021efe45e3d0 r15=0000021efe461830 iopl=0 nv up ei pl zr na po nc cs=0033 ss=002b ds=002b es=002b fs=0053 gs=002b efl=00000246 Calculator!VSDesignerDllMain+0xf37e8: 00007ff7`bc70c1d8 480fafc7 imul rax,rdi 借助TTD调试,相对简单地定位了16进制"51201314*41414141="的现场。 这样验证,普通调试时,设断干扰乘法运算,将GUI输入的0x41414141换成1 ba e1 0x7ff7bc70c1d8 ".if(@rdi==0x41414141){r rax,rdi;r rdi=1};gc" TTD调试比普通调试慢很多,即便是正向执行,前者也慢。二者是互补关系,不要只 依赖TTD调试。 ☆ 参考资源 [2] Time Travel Debugging is now available in WinDbg Preview - [2017-09-27] https://blogs.windows.com/windowsdeveloper/2017/09/27/time-travel-debugging-now-available-windbg-preview/ Time Travel Debugging FAQ - [2017-10-20] https://docs.microsoft.com/zh-cn/archive/blogs/windbg/time-travel-debugging-faq [3] Time Travel Debugging Overview - [2021-12-15] https://docs.microsoft.com/en-us/windows-hardware/drivers/debugger/time-travel-debugging-overview https://github.com/MicrosoftDocs/windows-driver-docs/blob/staging/windows-driver-docs-pr/debugger/time-travel-debugging-overview.md Time Travel Debugging Sample App Walkthrough - [2021-12-15] https://docs.microsoft.com/en-us/windows-hardware/drivers/debugger/time-travel-debugging-walkthrough https://github.com/MicrosoftDocs/windows-driver-docs/blob/staging/windows-driver-docs-pr/debugger/time-travel-debugging-walkthrough.md [4] Introduction to Time Travel Debugging https://www.youtube.com/watch?v=5U73Vxb4Jk8 (Defrag Tools #185) Time Travel Debugging Advanced https://sec.ch9.ms/ch9/fa18/d9f6505c-058a-47dd-b69f-4142122bfa18/DefragTools186_mid.mp4 (Defrag Tools #186) CppCon 2017 Time Travel Debugging https://www.youtube.com/watch?v=l1YJTg_A914 [5] Framework for Instruction-level Tracing and Analysis of Program Executions - [2006-03-31] https://www.usenix.org/legacy/events/vee06/full_papers/p154-bhansali.pdf [6] https://rr-project.org/ (Linux上的TTD功能) [7] RegisterClassExW function (winuser.h) https://docs.microsoft.com/en-us/windows/win32/api/winuser/nf-winuser-registerclassexw WindowProc callback function https://docs.microsoft.com/en-us/previous-versions/windows/desktop/legacy/ms633573(v=vs.85) About Messages and Message Queues https://docs.microsoft.com/en-us/windows/win32/winmsg/about-messages-and-message-queues WM_COMMAND message https://docs.microsoft.com/en-us/windows/win32/menurc/wm-command