一 前言
Win10 Calculator是UWP程序,找出UI界面上乘法运算对应的汇编指令所在。要求计算器必须是UWP,且版本不低于11.2210.0.0。非UWP计算器不考虑,更低版本UWP不考虑,可以是Win11上的UWP计算器,只要版本不低于11.2210.0.0。
假设已有run文件,如何在几分钟内定位四则运算的汇编指令所在。这类问题的答案最终都将演变成生产力工具。
二 TTD调试
◆MSDN系列(46)--WinDbg Preview TTD入门^6
◆TTD调试进阶之ttd-bindings^8
◆TTD历史回顾^14
Language-Integrated Query (LINQ) is the name for a set of technologies based on the integration of query capabilities directly into the C# language. Traditionally, queries against data are expressed as simple strings without type checking at compile time or IntelliSense support. Furthermore, you have to learn a different query language for each type of data source: SQL databases, XML documents, various Web services, and so on. With LINQ, a query is a first-class language construct, just like classes, methods, events. You write queries against strongly typed collections of objects by using language keywords and familiar operators. The LINQ family of technologies provides a consistent query experience for objects (LINQ to Objects), relational databases (LINQ to SQL), and XML (LINQ to XML).
三 ttd-bindings
TTD录制的.run文件格式未公开,之前只能在WinDbg Preview中操作.run,很难对之 进行脚本操作,比如,想对position进行大小比较,很费劲。 Bindings for Microsoft WinDBG TTDhttps://github.com/commial/ttd-bindings 上文作者对TTDReplay.dll进行逆向工程,逆出一套编程解析处理.run文件的API。没有完整逆向,但基本功能都有,比windbgx的GUI还多出一些功能。 C++ bindings功能较多,Python bindings功能弱一些,后者无法设置 CallRetCallback、MemCallback。此外,pyTTD.pyd用的是Python 3.10,为了在 Python 3.9中用,必须自己编译出pyTTD.pyd。 作者提供了.cpp、py的使用示例,对于逆向工程人员来说,浅显易懂,可以照猫画虎实现自定义功能。
四 TTD互联网考古
It seems that time-traveling debug or time travel debugging is becoming more common as a term (again). Still, most tools and companies and projects still talk about reverse debugging. Undo, RR, gdb, and Simics all use “reverse” to describe the feature. If I look at the history of the field as far as I have been able to trace it, most people call it reverse until the a 2005 paper introduces “time-traveling virtual machines”. For more on the history of reverse debuggers, see my three-part series of blogs from 2012 that I have since updated as new products and tools have appeared or been found in the archive of history: history 1, history 2, history 3.
Trace-based debugging is based on recording everything a system does into a trace, and once the recording is done, debugger works on the trace rather than using the log to drive an actual system. Reverse debug is implemented by recreating the state of a system by reading the trace, and finding points in the trace where breakpoint conditions are true. This is also known as post-mortem debug, since you debug after the target system has finished executing (typically). A log can be captured by a hardware device, or by software being instrumented to log everything that is going on. The technology can also be used to implement record-replay debugging.
Simics reverse execution and reverse debugging is a unique and very powerful feature of the simulator. In this blog post and accompanying video, we will look at what exactly it is you can do with reverse execution in Simics. It is not just a matter of running backwards to a breakpoint or stepping instructions (pick up my 2012 S4D article for more on reverse debugging). Reverse execution fundamentally is about undoing arbitrary operations on the target and going back to a prior state from a previous point in time. Furthermore, with Simics, you can go back in time and choose to do something different than you did in the original execution.
There is mention of the emulator-based approach in the docs – it is found in a note about “derailment”, where the reverse debugger realizes that it has created an inconsistent current state of the program during reverse. From the TTD docs: “TTD works by running an emulator inside of the debugger, which executes the instructions of the debugged process in order to replicate the state of that process at every position in the recording. Derailments happen when this emulator observes some sort of discrepancy between the resulting state and information found in the trace file.”
The key to making this work efficiently and without gigantic trace files is to only record the memory values that cannot be reconstructed by running the code. Thus, we have a mix of re-execution and trace-based reverse debug, where the re-execution engine is used for short sequences of instructions, before getting the state forced back to the trace as needed.
The “key frames” used in the time concept are more like checkpoints in Simics reverse debugging, in that they represent a complete state across all threads. It makes it easy for the debugger to jump to certain points in the execution and set up a consistent state across the threads there. Neat and useful.
Furthermore, since debugging is the process of tracing effects to their causes, it's much easier if your debugger can execute backwards in time. It's well-known that given a record/replay system which provides restartable checkpoints during replay, you can simulate reverse execution to a particular point in time by restoring the previous checkpoint and executing forwards to the desired point.
五 对scz‘s puzzles的解答
11.2210.0.0版本,用TTD鞭尸找到了 CalcViewModel+0x12cb06: 00007ff9`5eeccb06 4c0fafc5 imul r8,rbp
关于#scz's puzzles#之Win10 Calculator升级版问题的解答: 学了下ttd-bindings,踩了些坑,简单实现了一分钟左右定位到加减乘的汇编和除法的结束现场(除法似乎是间接方式实现的,结束现场由不是输入的数值得到的计算结果)。 实现方式: 先获取到CalcViewModel.dll模块的起始地址和大小,接着设置call、ret回调函数,回调函数中对触发时的地址进行判断,若属于CalcViewModel.dll模块中的地址,则从该时间点往后单步ReplayForward,每执行一步就对常用寄存器值进行判断(加减乘判断输入的两个值,除法判断结果值),若包含相应的值就打印时间点再到windbg里面验证过滤。 如何缩短定位时间: 1.遍历"!tt 0"到"!tt 100"的汇编代码时间花费很长,很多部分和计算无关不需要执行,尝试了挺久,通过设置call、ret回调函数找到第一个属于CalcViewModel.dll模块中的地址,从这个时间点开始单步ReplayForward执行,到达加减乘的汇编现场的时间可以接受。 2.让录制时间尽可能短,如提前打开计算器,先输入好两个数值,然后attach录制,快速点击计算操作,出结果后立马停止录制,使用上面的ttd-bindings编程思路,可在一分钟内定位到加减乘的汇编和除法的结束现场。 踩的坑: 1.ReplayBackward执行极其慢,最初的思路是内存搜索计算结果,查找最早写这个数据的时间点,然后从这个时间点向前执行,但发现ReplayBackward真是龟速,ReplayForward就很快。不过虽然ReplayForward快,但是架不住run文件的时间长,还是要尽可能地想办法缩小时间范围。 2.auto ctxt = ttdcursor.GetContextx86_64()执行后,如果不free(ctxt),内存会一点点被耗尽,仓库代码里面没有free操作,遍历trace文件汇编代码发现内存满了我才意识到这个问题。。。huhuO7O6说的“太耗内存”可能也是没有主动free ctxt。 3.尝试通过开多线程分段跑不同段的时间线,以加快定位速度,但是发现线程里面调用ttd-bindings的函数,程序会直接停止执行,原因不明。。 4.如果callret回调函数中的操作和时间点顺序有关,需要生成run文件的idx文件,不然callret回调函数中获取的时间点,打印出来会发现从前到后是乱序的,即不是从start到last。 5.ttdcursor.ReplayForward(&replayrez, last, -1),第三个形参是-1的话,无法触发call、ret回调函数,这个坑让我十分困惑,不过没多久我在仓库的issues里面发现yara-ttd的作者atxr提了issue描述了这个问题(github.com/commial/ttd-bindings/issues/27)。。。 总结: TTD十分好用,ttd-bindings让TTD变得更好用。
六 puzzles解答细节
问题一
问题二
#include <windows.h> #include <stdio.h> #include <iostream> #include "TTD/utils.h" #include "TTD/TTD.hpp" #define STEP_COUNT 100000 LARGE_INTEGER start, end, freq; uint64_t calcViewModelStart, calcViewModelSize; const wchar_t* path; DWORD64 value1, value2; void logTime() { QueryPerformanceCounter(&end); std::cout << " Elapsed time: " << static_cast<double>(end.QuadPart - start.QuadPart) / freq.QuadPart << " seconds." << std::endl; } bool findValueInRegs(PCONTEXT ctxt, uint64_t val) { if (ctxt->Rax == val or ctxt->Rcx == val or ctxt->Rdx == val or ctxt->Rbx == val or ctxt->Rsp == val or ctxt->Rbp == val or ctxt->Rsi == val or ctxt->Rdi == val or ctxt->R8 == val or ctxt->R9 == val or ctxt->R10 == val or ctxt->R11 == val or ctxt->R12 == val or ctxt->R13 == val or ctxt->R14 == val or ctxt->R15 == val) { return 1; } return 0; } void findCalcAsm(TTD::Position *startPosition) { TTD::ReplayEngine ttdengine = TTD::ReplayEngine(); int result = ttdengine.Initialize(path); if (result == 0) { std::cout << "Fail to open the trace"; exit(-1); } TTD::Cursor ttdcursor = ttdengine.NewCursor(); ttdcursor.SetPosition(startPosition); TTD::Position* last = ttdengine.GetLastPosition(); TTD::TTD_Replay_ICursorView_ReplayResult replayrez; TTD::TTD_Replay_ICursorView_ReplayResult* ret; do { ret = ttdcursor.ReplayForward(&replayrez,last, 1); auto ctxt = ttdcursor.GetContextx86_64(); if (findValueInRegs(ctxt, value1) && findValueInRegs(ctxt,value2) ){ printf("(%llx) [%llx:%llx] Find!n", ttdcursor.GetProgramCounter(), ttdcursor.GetPosition()->Major, ttdcursor.GetPosition()->Minor); logTime(); } free(ctxt); } while (ret->unk3 != 0); printf("over.n"); logTime(); exit(0); } void callCallback_tree(unsigned __int64 callback_value, TTD::GuestAddress addr_func, TTD::GuestAddress addr_ret, struct TTD::TTD_Replay_IThreadView* thread_view) { uint64_t pc = thread_view->IThreadView->GetProgramCounter(thread_view); TTD::Position* position = thread_view->IThreadView->GetPosition(thread_view); if ( pc > calcViewModelStart and pc < calcViewModelStart + calcViewModelSize) { printf("(%llx) [%llx:%llx] Enter CalcViewModel, start stepping-by-steping to search for calc instructions.n", addr_func, position->Major, position->Minor); logTime(); findCalcAsm(position); } return; } bool containsString(const wchar_t* str, const wchar_t* substr) { std::wstring s(str); std::wstring sub(substr); return s.find(sub) != std::wstring::npos; } int wmain(int argc, const wchar_t* argv[]) { if (argc < 4) { wprintf(L"Usage: program.exe <path> <value1> <value2>n"); return 1; } path = argv[1]; value1 = wcstoull(argv[2], nullptr, 0); value2 = wcstoull(argv[3], nullptr, 0); QueryPerformanceFrequency(&freq); QueryPerformanceCounter(&start); TTD::ReplayEngine ttdengine = TTD::ReplayEngine(); TTD::TTD_Replay_ICursorView_ReplayResult replayrez; std::cout << "Openning the tracen"; int result = ttdengine.Initialize(path); if (result == 0) { std::wcerr << "Fail to open the trace"; exit(-1); } const TTD::TTD_Replay_Module* mod_list = ttdengine.GetModuleList(); for (int i = 0; i < ttdengine.GetModuleCount(); i++) { if (containsString(mod_list[i].path, L"CalcViewModel.dll")) { calcViewModelStart = mod_list[i].base_addr; calcViewModelSize = mod_list[i].imageSize; break; } } TTD::Cursor ttdcursor = ttdengine.NewCursor(); TTD::Position* first = ttdengine.GetFirstPosition(); TTD::Position end = *ttdengine.GetLastPosition(); ttdcursor.SetPosition(first); ttdcursor.SetCallReturnCallback((TTD::PROC_CallCallback)callCallback_tree, 0); TTD::Position LastPosition; unsigned long long stepCount; unsigned long long totalStepCount = 0; for (;;) { ttdcursor.ReplayForward(&replayrez, &end, STEP_COUNT); stepCount = replayrez.stepCount; totalStepCount += stepCount; if (replayrez.stepCount < STEP_COUNT) { ttdcursor.SetPosition(&LastPosition); ttdcursor.ReplayForward(&replayrez, &end, stepCount - 1); totalStepCount += stepCount - 1; break; } memcpy(&LastPosition, ttdcursor.GetPosition(), sizeof(LastPosition)); } return 0; }
七 总结
引用链接
1:https://bbs.kanxue.com/thread-277665.htm
2:https://scz.617.cn/windows/202201251528.txt
3:https://bbs.kanxue.com/thread-273055.htm
4:https://learn.microsoft.com/en-us/windows-hardware/drivers/debugger/time-travel-debugging-overview
5:https://weibo.com/1273725432/N8JeKa7df
6:https://scz.617.cn/windows/202201251528.txt
7:https://weibo.com/1273725432/N8VYK1DC2
8:https://scz.617.cn/windows/202207271620.txt
9:https://scz.617.cn/misc/202206221800.txt
10:https://learn.microsoft.com/en-us/windows-hardware/drivers/debugger/time-travel-debugging-troubleshooting
11:https://learn.microsoft.com/en-us/dotnet/csharp/programming-guide/concepts/linq/
12:https://learn.microsoft.com/en-us/windows-hardware/drivers/debugger/time-travel-debugging-object-model
13:https://github.com/commial/ttd-bindings
14:https://scz.617.cn/windows/202207191506.txt
15:https://jakob.engbloms.se/archives/2649
16:https://jakob.engbloms.se/?s=Reverse
17:https://jakob.engbloms.se/archives/1547
18:https://www.windriver.com/blog/back-to-reverse-execution
19:http://www.simulics.com/index_en.php
20:https://rr-project.org/
21:https://jakob.engbloms.se/archives/2788
22:http://www.bitsavers.org/pdf/borland/turbo_debugger/Turbo_Debugger_3.0_for_Windows_Users_Guide_1991.pdf
23:https://jakob.engbloms.se/archives/2306
24:https://jakob.engbloms.se/archives/2350
看雪ID:0x指纹 https://bbs.kanxue.com/user-home-802108.htm
原文始发于微信公众号(看雪学苑):TTD调试与ttd-bindings逆向工程实践
- 左青龙
- 微信扫一扫
-
- 右白虎
- 微信扫一扫
-
评论