Back

IDA Debugger: New Register Subview and More Accurate Call Stacks

IDA Debugger: New Register Subview and More Accurate Call Stacks

In this week’s blog post, we highlight two improvements to the IDA debugger for the upcoming 9.2 release:  

  • A redesigned register subview that provides a much richer context.  
  • A more accurate call stack reconstruction mechanism for certain x64 PE files.  

 

Since a picture is worth a thousand words, here is the IDA 9.1 debugger running cmd.exe on a Windows 10 machine:

ida 9.1 debugger

And here’s the new version, featuring the updated register subview in the bottom left and a more precise call stack reconstruction in the top right:

ida 9.2 debugger

New Register View

If you use IDA for dynamic analysis, you’re already familiar with the Register View, which displays the current state of CPU registers during debugging.  

In the new version, values that can be interpreted as pointers (i.e., addresses falling within a mapped range of the current address space) are automatically dereferenced. If the result is itself a pointer, IDA will recursively follow that chain.  

While walking the pointer chain, the widget applies color-coding to help you immediately recognize the memory type. For example, it’s instantly clear whether a value points to read-only memory, executable code, the stack, or the heap.

You can control the dereference depth through a new context menu entry, “Dereference options”, with the default limit set to 5. Setting it to 0 disables the feature entirely.

More Robust Reconstruction of Call Stacks in x64 PE Binaries

Now, let’s look at a more challenging problem.

For example, one execution path leading to a call to RtlValidSid inside ntdll.dll looks approximately like this:

main 

↳ RegOpenKeyEx

  ↳ RegOpenKeyExInternal

    ↳ MapPredefinedHandleInternal

      ↳ (cfguard protected thunk function)

        ↳ RtlOpenCurrentUser

          ↳ RtlFormatCurrentUserKeyPath

            ↳ RtlLengthSidAsUnicodeString

              ↳ RtlValidSid

 

When you pause execution or hit a breakpoint, IDA attempts to reconstruct this information from CPU registers, values found on the architectural stack, and executable memory.

This process ranges from trivial (when debug information, standard function prologues, or frame pointer chains are present) to very complex (in cases of obfuscated code, dynamically-sized stack frames, non-standard calling conventions/stack organization). As a result, the reconstructed call stack is often only an approximation of the actual execution path.

 

In the first screenshot, the guessed call stack looks like this:

0x7FF7DE898ECD (within cmd.exe, no symbol)

↳ 0x7FF7DE89387C (within cmd.exe, no symbol)

  ↳ 0x7FF7DE89055B (within cmd.exe, no symbol)

    ↳ 0x7FF7DE89055B (within cmd.exe, no symbol)

      ↳ RtlOpenCurrentUser

        ↳ LdrGetProcedureAddressForCaller

          ↳ RtlLookupFunctionEntry

            ↳ RtlFormatCurrentUserKeyPath

              ↳ RtlLengthSidAsUnicodeString

                ↳ RtlValidSid

 

Here, IDA gets confused by the control transfer from MapPredefinedHandleInternal to RtlOpenCurrentUser: The topmost frames don’t make much sense, though the deeper frames closer to RtlValidSid were reconstructed correctly. (Stack reconstruction works in reverse, which explains this behavior.)

Leveraging Exception Handling Metadata

Starting with IDA 9.2, we enhanced the stack reconstruction algorithm by incorporating unwind information stored in exception records.

These records contain metadata used by the runtime to resume execution after exceptions. A critical part of this process is stack unwinding, which resets the stack to the correct state when switching function contexts. The unwind metadata describes how to restore stack frames using a sequence of operations.

This is how the Windows operating system itself (such as the RtlVirtualUnwind API) and its official debugging tools perform stack unwinding, although our implementation does not rely on any Windows operating system functionality.

IDA now detects, parses, and applies this metadata to significantly improve call stack accuracy.

 

Looking at our example, this means that IDA will produce the following call stack:

Example: Improved Stack Trace

In our example, IDA 9.2 produces the following stack trace:

0x7FF7DE893875 (within cmd.exe, no symbol)

↳ RegOpenKeyEx

  ↳ RegOpenKeyExInternal

    ↳ MapPredefinedHandleInternal

      ↳ GetTempPath2

        ↳ RtlOpenCurrentUser

          ↳ RtlFormatCurrentUserKeyPath

            ↳ RtlLengthSidAsUnicodeString

              ↳ RtlValidSid

 

This result is much closer to the actual execution path.  

(Sharp-eyed readers may also notice another subtle improvement in the second screenshot: the displayed addresses now refer to the call instruction in the caller, rather than the instruction right behind the call that the callee would return to.)

With these improvements, IDA 9.2 makes dynamic analysis more reliable, giving you better insights into what’s really happening inside your binaries.