pause the debugger conditionally

An example of pausing system while a special path is passed to the NtCreateFile

In the previous example, we saw how to create logs from nt!NtOpenFile function. Now, we want to explore nt!NtCreateFile.

This example is written to be used in the Debugger Mode, but you can run it on VMI Mode. In the VMI Mode, you cannot pause the system but you're still able to change the system control flow.

From the MSDN, NtCreateFile is defined like this:

__kernel_entry NTSTATUS NtCreateFile(
  [out]          PHANDLE            FileHandle,
  [in]           ACCESS_MASK        DesiredAccess,
  [in]           POBJECT_ATTRIBUTES ObjectAttributes,
  [out]          PIO_STATUS_BLOCK   IoStatusBlock,
  [in, optional] PLARGE_INTEGER     AllocationSize,
  [in]           ULONG              FileAttributes,
  [in]           ULONG              ShareAccess,
  [in]           ULONG              CreateDisposition,
  [in]           ULONG              CreateOptions,
  [in]           PVOID              EaBuffer,
  [in]           ULONG              EaLength
);

There is no pointer to the file name in the above prototype. The file name is embedded into the ObjectAttributes parameter to this function.

If you want to see how OBJECT_ATTRIBUTES structure is defined, you can see this link from MSDN.

typedef struct _OBJECT_ATTRIBUTES {
  ULONG           Length;
  HANDLE          RootDirectory;
  PUNICODE_STRING ObjectName;
  ULONG           Attributes;
  PVOID           SecurityDescriptor;
  PVOID           SecurityQualityOfService;
} OBJECT_ATTRIBUTES;

From the relative-address point of view, this function is stored in the memory like this:

   +0x000 Length           : Uint4B
   +0x008 RootDirectory    : Ptr64 Void
   +0x010 ObjectName       : Ptr64 _UNICODE_STRING
   +0x018 Attributes       : Uint4B
   +0x020 SecurityDescriptor : Ptr64 Void
   +0x028 SecurityQualityOfService : Ptr64 Void

We can see that there is a UNICODE_STRING field named ObjectName. This is the name of the object that we're trying to open using NtCreateFile.

If we look at the UNICODE_STRING structure. It's defined like this:

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

And the compiler saves it like this:

   +0x000 Length           : Uint2B
   +0x002 MaximumLength    : Uint2B
   +0x008 Buffer           : Ptr64 Wchar

Now, we have all the offsets that we want to observe the file names.

First, the ObjectAttributes parameter is passed as the 3rd parameter to the function, and as the calling convention is Windows fastcall (rcx, rdx, r8, r9, stack), our target parameter is located at the r8 register.

In our case, r8 is a pointer to the OBJECT_ATTRIBUTES, and if we add 0x10 to it, we'll reach the ObjectName field of this structure.

ObjectName is a pointer to the UNICODE_STRING, so we'll dereference this pointer using the poi operator to reach the top of the UNICODE_STRING.

In the UNICODE_STRING, we'll add 0x8 to get the Buffer filed of this structure, and now we dereference it again to get the pointer where the file name string is located. The string is in wide-character format, so for the comparison, we need to use the wcscmp function.

At this point, we want to pause the debugger (halt the system) while Windows passes the C:\folder\test.txt file to the NtCreateFile in the kernel. We'll use the '!epthook' command to execute a script on each execution of the NtCreateFile function, and then compare the file name with \\??\\C:\\folder\\test.txt and if it's equal to this file, then we pause the debugger. Note that, \\?? is added to the start of the file.

The final script is like this:

!epthook nt!NtCreateFile script {
	if(wcscmp(L"\\??\\C:\\folder\\test.txt",poi(poi(@r8+10)+8)) == 0){
		pause();
	}
}

To test it, you need to open a notepad and save the file at the target location (C:\folder\test.txt), and you'll see the debugger is paused.

Last updated