Edit Template

Microsoft WinDbg Time Travel Debugging versus Intel Processor Trace

Instruction trace is one of the most powerful but underutilized technologies for reverse engineering and malware analysis. In this article, I review the capabilities of WinDbg’s Time Travel Debugging (TTD) feature, and compare it against Intel Processor Trace (Intel PT). Both are excellent tools for cybersecurity research.

WinDbg is of course the “de facto” standard for debugging the Windows kernel and user applications. And debugging is the flip side of the coin of reverse engineering; to really succeed in this craft, one must be proficient in both. With an estimated 1.6 billion devices running Windows globally, and the OS being such a huge attack surface, a deep understanding of Windows internals is crucial to many cybersecurity engineers.

But this deep understanding takes a lot of time and hard work. The Windows codebase is huge; and the learning curve is steep. Just reading the Windows Internals books is not enough; it’s important to get hands-on experience, and to work on specific projects, to burn the needed knowledge into your brain. And this is where the use of tools becomes essential.

Reverse engineering unfamiliar code is hard. A fixed view of the decompiled code (static analysis) provides one level of insight; but seeing the code in operation (dynamic analysis) takes it to another level. This latter methodology often involves the use of trace features that can reconstruct code flow at the source/symbolic and assembly language level. Among tools/technologies that provide these capabilities are Intel PT and TTD.

Intel PT is a near-real-time instruction execution trace capability built into Intel CPUs. It captures all instructions executed into an extremely compact data structure placed into system memory on a target. With the SourcePoint JTAG-based debugger, after a target is halted, the trace is extracted and decoded for presentation to the user. This is raw CPU instruction trace; all executed instructions are captured, with virtually no real-time impact, since everything is happening at the silicon level. Here’s a sample:

I’ve written extensively about Intel PT on ASSET InterTech’s website; Intel PT is a capability built into ASSET’s SourcePoint debugger. Below are a couple of good background reading resources:

WinDbg with Intel Processor Trace

Using ChatGPT on Windows Secure Kernel with Intel Processor Trace

And the ultimate technical reference for Intel PT is the Intel Software Developers Manual (SDM).

Below is an image of decoded and visualized Intel PT within SourcePoint in action:

Above and beyond just being used for debugging UEFI, hypervisors, and kernels, Intel PT is starting to be used within the EDR industry to identify rogue code. Prelude Security recently published the article Introducing Runtime Memory Protection, a research preview of a user-mode Windows agent that detects malicious code execution. For those interested in more detail, the full paper is here: Closing the Execution Gap: Hardware-Backed Telemetry for Detecting Out-Of-Context Execution. Interestingly, this application only uses a tiny fraction of the functionality of Intel PT: it focuses solely on indirect branch targets, with no use of Taken/Not Taken (TNT) packets, which in turn precludes the use of disassembly, one of Intel PT’s most powerful features.

On the other hand, TTD is a form of trace like Intel PT, but it differs significantly in its operation and scope. It operates more like a “time machine”: you can go forwards AND backwards in the trace, and look at the values of registers, memory and the call stack; you can see all the state of the process as you go through its timeline. Intel PT cannot do that; you start at a predetermined location of the code, and capture all instructions leading up to a break. And Intel PT doesn’t capture state (you can easily capture some state such as registers, as referred to in the article Using LLMs to analyze Hyper-V Register State and Instruction Trace, but it doesn’t capture all state, such as memory and call stack). TTD works by injecting a DLL into a target process that provides an emulation layer to track state.

Although it appears to be very well maintained by Microsoft, and it’s built into the WinDbg debugger, there doesn’t seem to be a lot of current documentation on it, and it isn’t referred to all that often in the cybersecurity publications I read. To illustrate its power, I’ll walk us through a typical introductory use case.

I created a small simple example console application that performs a null pointer dereference that will be the subject of this explanation. Here’s the code, NullPtrRead.cpp, in text form and as seen in Visual Studio:

The variable pi is a pointer to an integer, and you can see that it is set to NULL, where NULL is a constant that evaluates to a value that is guaranteed not to be equal to any valid memory address. Then in the last line:

a1 = *p1

we are dereferencing pi, meaning we are trying to access the value at the memory address stored in pi. This is a bug since pi is NULL, and the program will try to read from a memory address that it is not allowed to access. Windows’ memory protection mechanism detects this illegal access and terminates the program, resulting in an exception fault.

In essence, the code is trying to fetch a value from a non-existent location, which is an illegal operation. This is a common programming error that can lead to crashes and security vulnerabilities.

To see this in action and how TTD can debug it, first just do a Build on this code and run it from within Visual Studio. I’m using VS2022, Community edition. Create it as a standard Windows console application. You should see the following when you run it:

And this is the displayed text within the window:

Hello World!

C:\Users\alans\source\repos\NullPtrRead\x64\Debug\NullPtrRead.exe (process 18364) exited with code -1073741819 (0xc0000005).

Press any key to close this window . . .

NTSTATUS 0xc0000005 (STATUS_ACCESS_VIOLATION) is an access violation, as expected.

Now, you can debug this with any debugger, but only WinDbg supports TTD, so we’ll show how this works. To begin with the end in mind, below is a sample screenshot of WinDbg with TTD live in the NullPtrRead.exe application:

To get here, start by launching WinDbgX as Admin, and going to:

File > Launch Executable (advanced)

This is the entry that supports TTD.

Find your .exe file, and click on the checkbox enabling TTD. Then hit Configure and Record. You’ll see something like the below. Yours will be different, but it helps to have at least the Command, Disassembly, Registers, and Local windows open:

The program will be launched, it crashes with the exception, and the trace file will be recorded and written to disk. And then the application will be re-launched to ntdll!LdrInitializeThunk so you can explore it. Cool!

The Command console window will look something like this:

A word for people new to WinDbg: if you look carefully in the above output, you’ll see that I’d already set the symbol path to point to the PDB file generated by the Visual Studio Build:

Symbol search path is: srv*C:\\symbols*http://msdl.microsoft.com/download/symbols; ;C:\Users\alans\source\repos\NullPtrRead\x64\Debug

You’ll want to do the same, either by using the sympath command, as in:

.sympath+ C:\Users\alans\source\repos\NullPtrRead\x64\Debug

Or by adding it explicitly into:

File > Settings > Debug Settings

Since we have source and symbols, it helps to visualize everything by opening the source file as well, with:

File > Open source file

Given that we have source and symbols, we can operate on it just as in a regular debugging session, but this time on steroids! Let’s set a breakpoint at the application’s entry point:

>bp NullPtrRead!main

And the breakpoint shows as being set in the WinDbg source code window:

And you’ll see that now the Ribbon displays additional buttons for stepping not only forwards, but backwards in time, with Step Into Back, Step Over Back, Step Out Back, and even a Go Back button, in the Reverse Flow Control section:

From now on, it’s smooth sailing. Hit Go and we hit the breakpoint at the entry point NullPtrRead!main:

Do a Step Out and you jump to the instruction that created the access violation. You can see this in the Source window and the Disassembly window for additional insight:

Note the last three instructions at the instruction pointer in the Disassembly window:

00007ff6`062c22e0 48c7452800000000 mov     qword ptr [pi (rbp+28h)], 0

00007ff6`062c22e8 488b4528         mov     rax, qword ptr [pi (rbp+28h)]

00007ff6`062c22ec 8b00             mov     eax, dword ptr [rax]

and the value of the locals and the RAX register. Do some Step Into Back’s, and Step Into’s to see how pi and a1 get changed, and RAX gets set to the bad value creating the access violation from the null pointer dereference.

Cool, huh? There are a ton more capabilities of TTD that you can find in the Microsoft tutorial content.

Check out this video to see the whole TTD session live:

Demo of WinDbg Time Travel Debugging

To wrap up, here’s a table summarizing a stare-and-compare between TTD versus Intel PT:

FeatureWinDbg Time Travel Debugging (TTD)Intel Processor Trace (Intel PT)
TechnologySoftware-based emulatorHardware-based CPU feature
Performance OverheadHigh (5x-20x slowdown)Very Low (typically < 5% slowdown)
Trace File SizeVery large (many GBs)Small to medium (decoded trace is large)
Data CapturedComplete instruction, memory, and register stateControl flow (branches taken/not taken) and limited events
Debugging Use CaseDebugging complex, non-reproducible bugs and memory corruptionDebugging performance issues and code coverage analysis
PortabilityHigh; trace files are portable across machinesLow; relies on specific hardware (Intel CPUs)
AnalysisRich querying with dx and LINQRequires post-processing to decode the trace stream
Interactive DebuggingPost-mortem only; cannot alter program statePost-mortem only; cannot alter program state
Key LimitationsHigh overhead and large file sizes. User-mode only.Incomplete data (no memory/register state).

Given the pros and cons above, a combination of the two tools might yield the most insight for malware research and threat analysis.

What other features of WinDbg TTD are useful for cybersecurity research and engagements? I’ll explore these in a future article.

Edit Template