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> 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?