Synthetic types and tracing syscalls in WinDbg


Recently at work, I needed to trace several syscalls to understand what SQL Server was doing. My usual tool for this purpose on Windows was API Monitor, but, unfortunately, it hasn’t been updated for a few years already and became unstable for me. Thus, I decided to switch back to WinDbg. In the past, my biggest problem with tracing the system API in WinDbg was the missing symbols for the internal NT objects. Moreover, I discovered some messy ways to work around it. Fortunately, with synthetic types in WinDbg Preview it’s no longer a problem. In this post, I will show you how to create a breakpoint that nicely prints the arguments to a sample NtOpenFile syscall.

Creating a custom header file

There are various places where you can look for the syscall signature, but I usually check Microsoft docs and Process Hacker headers. For our example, Process Hacker signature looks as follows:

NTSYSCALLAPI
NTSTATUS
NTAPI
NtOpenFile(
    _Out_ PHANDLE FileHandle,
    _In_ ACCESS_MASK DesiredAccess,
    _In_ POBJECT_ATTRIBUTES ObjectAttributes,
    _Out_ PIO_STATUS_BLOCK IoStatusBlock,
    _In_ ULONG ShareAccess,
    _In_ ULONG OpenOptions
    );

We also need to find definitions of POBJECT_ATTRIBUTES and PIO_STATUS_BLOCK. They are also in Process Hacker headers:

typedef struct _OBJECT_ATTRIBUTES
{
    ULONG Length;
    HANDLE RootDirectory;
    PUNICODE_STRING ObjectName;
    ULONG Attributes;
    PVOID SecurityDescriptor; // PSECURITY_DESCRIPTOR;
    PVOID SecurityQualityOfService; // PSECURITY_QUALITY_OF_SERVICE
} OBJECT_ATTRIBUTES, *POBJECT_ATTRIBUTES;

typedef struct _IO_STATUS_BLOCK
{
    union
    {
        NTSTATUS Status;
        PVOID Pointer;
    };
    ULONG_PTR Information;
} IO_STATUS_BLOCK, *PIO_STATUS_BLOCK;

Let’s save these definitions to a file, for example, c:\temp\nt.h.

NOTE: You may be wondering why not use windows.h or phnt.h files and have all the types parsed. Unfortunately, the Synthetic Types extension (we will use it in the next paragraph) is quite limited and can’t parse such complex files : (

Load the custom header file into WinDbg

To load and parse the header file into WinDbg, we will use Andy Luhrs’ Synthetic Types extension. Checkout the repo and load the script as stated in the README:

0:000> .scriptload c:\repos\WinDbg-Samples\SyntheticTypes\SynTypes.js
JavaScript script successfully loaded from 'c:\repos\WinDbg-Samples\SyntheticTypes\SynTypes.js'

Next, let’s read our header file:

0:000> dx Debugger.Utility.Analysis.SyntheticTypes.ReadHeader("c:\\temp\\nt.h", "ntdll")
Debugger.Utility.Analysis.SyntheticTypes.ReadHeader("c:\\temp\\nt.h", "ntdll")                 : C:\Windows\SYSTEM32\ntdll.dll(nt.h)
    Module           : C:\Windows\SYSTEM32\ntdll.dll
    Header           : nt.h
    Types  

Place the breakpoint

It’s time to set a breakpoint on the NtOpenFile and decode its parameters:

0:000> bp ntdll!NtOpenFile
0:000> g
Breakpoint 0 hit
Time Travel Position: 95DD:153
ntdll!NtOpenFile:
00007ffa`4f22fdb0 4c8bd1          mov     r10,rcx

As I’m debugging a 64-bit version of SQL Server, AMD64 calling convention applies:

/* WinDbg output with my comments */
0:003> r
...
rcx=000000ca651faa20 <- FileHandle : PHANDLE
rdx=0000000000100080 <- DesiredAccess : ACCESS_MASK
r8=000000ca651faa50  <- ObjectAttributes : POBJECT_ATTRIBUTES
r9=000000ca651faa80  <- IoStatusBlock : PIO_STATUS_BLOCK
...

0:003&gt; dps @rsp
000000ca`651fa9e8  00007ffa`4bd76761 KERNELBASE!GetDriveTypeW+0x1e1 <- return address
000000ca`651fa9f0  00000000`ffffffff |
000000ca`651fa9f8  00007ffa`024723f8 |<- shadow space
000000ca`651faa00  000002a7`018b68c8 |
000000ca`651faa08  000002a7`018aac58 |
000000ca`651faa10  000002a7`00000003 <- ShareAccess : ULONG (take 4 low bytes) = 00000003
000000ca`651faa18  000002a7`00000060 <- OpenOptions : ULONG (take 4 low bytes) = 00000060
000000ca`651faa20  00000000`00000002

As we are interested in decoding ObjectAttributes, let’s use our custom header type:

0:003> dx -r2 Debugger.Utility.Analysis.SyntheticTypes.CreateInstance("_OBJECT_ATTRIBUTES", @r8)
Error: Unable to get property 'size' of undefined or null reference [at SynTypes (line 102 col 5)]

Eek! We hit an error. After some experimenting, I learned that this error signifies that the extension was not able to calculate the size of the object instance. The most probable reason is that WinDbg did not correctly decode some of the types from our custom header.

When things do not work

When you hit an error like the one above, you need to perform a mundane task of finding the faulting type. To do that, read the header file once again and click on the DML Types link. That’s also a way to know what is the name of your type in WinDbg, in case you have many typedefs. You should see something similar to the output below:

0:003> dx Debugger.Utility.Analysis.SyntheticTypes.ReadHeader("d:\\temp\\nt.h", "ntdll")
Debugger.Utility.Analysis.SyntheticTypes.ReadHeader("d:\\temp\\nt.h", "ntdll")                 : C:\Windows\SYSTEM32\ntdll.dll(nt.h)
    Module           : C:\Windows\SYSTEM32\ntdll.dll
    Header           : nt.h
    Types           
0:003> dx -r1 Debugger.Utility.Analysis.SyntheticTypes.ReadHeader("d:\\temp\\nt.h", "ntdll").Types
Debugger.Utility.Analysis.SyntheticTypes.ReadHeader("d:\\temp\\nt.h", "ntdll").Types                
    length           : 0x3
    [0x0]            : _OBJECT_ATTRIBUTES
    [0x1]            : __UNNAMED_TYPE_0
    [0x2]            : _IO_STATUS_BLOCK

Next, click [0x0] and then Description:

0:003> dx -r1 Debugger.Utility.Analysis.SyntheticTypes.ReadHeader("d:\\temp\\nt.h", "ntdll").Types[0]
Debugger.Utility.Analysis.SyntheticTypes.ReadHeader("d:\\temp\\nt.h", "ntdll").Types[0]                 : _OBJECT_ATTRIBUTES
    IsUnion          : false
    Name             : _OBJECT_ATTRIBUTES
    Make             [Make(address)- Constructs an instance of this type]
    Description     
0:003> dx -r1 Debugger.Utility.Analysis.SyntheticTypes.ReadHeader("d:\\temp\\nt.h", "ntdll").Types[0].Description
Debugger.Utility.Analysis.SyntheticTypes.ReadHeader("d:\\temp\\nt.h", "ntdll").Types[0].Description                
    [0x0]            : unsigned long Length
    [0x1]            : void * RootDirectory
    [0x2]            : PUNICODE_STRING ObjectName
    [0x3]            : unsigned long Attributes
    [0x4]            : void * SecurityDescriptor
    [0x5]            : void * SecurityQualityOfService

All simple types are good, so click on the one complex field available, which is [0x2]:

0:003> dx -r1 Debugger.Utility.Analysis.SyntheticTypes.ReadHeader("d:\\temp\\nt.h", "ntdll").Types[0].Description[2]
Debugger.Utility.Analysis.SyntheticTypes.ReadHeader("d:\\temp\\nt.h", "ntdll").Types[0].Description[2]                 : PUNICODE_STRING ObjectName
    Size             : Unable to get property 'size' of undefined or null reference [at SynTypes (line 102 col 5)]
    Align            : Unable to get property 'align' of undefined or null reference [at SynTypes (line 65 col 5)]

Here we go. PUNICODE_STRING is an unknown type to WinDbg, and we need to define it. There are few more structures we need to add, so below is the final working header for NtOpenFile:

typedef wchar_t* PWCH;
typedef ULONG* ULONG_PTR;

typedef LONG NTSTATUS;

typedef struct _UNICODE_STRING
{
    USHORT Length;
    USHORT MaximumLength;
    PWCH Buffer;
} UNICODE_STRING, *PUNICODE_STRING;

typedef struct _OBJECT_ATTRIBUTES
{
    ULONG Length;
    HANDLE RootDirectory;
    PUNICODE_STRING ObjectName;
    ULONG Attributes;
    PVOID SecurityDescriptor; // PSECURITY_DESCRIPTOR;
    PVOID SecurityQualityOfService; // PSECURITY_QUALITY_OF_SERVICE
} OBJECT_ATTRIBUTES, *POBJECT_ATTRIBUTES;

typedef struct _IO_STATUS_BLOCK
{
    union
    {
        NTSTATUS Status;
        PVOID Pointer;
    };
    ULONG_PTR Information;
} IO_STATUS_BLOCK, *PIO_STATUS_BLOCK;

NOTE: Once you change the definition, you need to restart the debugging session. Reloading the changed header file did not take any effect in my version of WinDbg (1.0.1906.12001 / 10.0.18914.1001).

Producing nice output

With the final header file read, it is time to create the final breakpoint:

0:000> bp ntdll!NtOpenFile "r @rcx,@rdx; dx -r2 Debugger.Utility.Analysis.SyntheticTypes.CreateInstance(\"_OBJECT_ATTRIBUTES\", @r8); r @r9; dq @rsp+0x28 L1; dq @rsp+0x30 L1"
breakpoint 0 redefined

0:003> g
rcx=000000ca651faa20 rdx=0000000000100080
Debugger.Utility.Analysis.SyntheticTypes.CreateInstance("_OBJECT_ATTRIBUTES", @r8)                
    Length           : 0x30
    RootDirectory    : 0x0 [Type: void *]
    ObjectName       : 0xca651faa28 [Type: _UNICODE_STRING *]
        [+0x000] Length           : 0x2e [Type: unsigned short]
        [+0x002] MaximumLength    : 0x32 [Type: unsigned short]
        [+0x008] Buffer           : 0x2a712fdccc0 : "\??\c:\test2\testdb.mdf\" [Type: wchar_t *]
    Attributes       : 0x40
    SecurityDescriptor : 0x0 [Type: void *]
    SecurityQualityOfService : 0x0 [Type: void *]
r9=000000ca651faa80
000000ca`651faa10  000002a7`00000003
000000ca`651faa18  000002a7`00000020

You may improve the output with .printf or other meta-functions, but it looks nice already, doesn’t it?

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out /  Change )

Google photo

You are commenting using your Google account. Log Out /  Change )

Twitter picture

You are commenting using your Twitter account. Log Out /  Change )

Facebook photo

You are commenting using your Facebook account. Log Out /  Change )

Connecting to %s

This site uses Akismet to reduce spam. Learn how your comment data is processed.