WoW64 internals: Unexpected behaviour of NtQueryDirectoryObject

Some time ago I was writing a small class that was supposed to list items from windows objects directory (like WinObj from Sysinternals). Given the fact that there are a lot of examples out there on the internet, it seemed like an easy task. I’ve started coding it without reading any documentation, except required functions definitions:

NTSTATUS WINAPI NtOpenDirectoryObject(
  _Out_ PHANDLE            DirectoryHandle,
  _In_  ACCESS_MASK        DesiredAccess,
  _In_  POBJECT_ATTRIBUTES ObjectAttributes
);
 
NTSTATUS WINAPI NtQueryDirectoryObject(
  _In_      HANDLE  DirectoryHandle,
  _Out_opt_ PVOID   Buffer,
  _In_      ULONG   Length,
  _In_      BOOLEAN ReturnSingleEntry,
  _In_      BOOLEAN RestartScan,
  _Inout_   PULONG  Context,
  _Out_opt_ PULONG  ReturnLength
);

I’ve quickly came up with a working example, which later turned out to work only under WOW64 layer. What went wrong ? I assumed, that calling NtQueryDirectoryObject with Buffer set to nullptr and Length set to 0 would return required size of the buffer in the ReturnLength variable. Unfortunately it doesn’t work that way (except WOW64 of course). Below there are results of such call on native x86 & x64 systems and under WOW64:

Native x86:
Returned NTSTATUS = STATUS_MORE_ENTRIES (0x00000105)
Context           = 0x00000000
ReturnLength      = 0x00000010

Native x64:
Returned NTSTATUS = STATUS_MORE_ENTRIES (0x00000105)
Context           = 0x00000000
ReturnLength      = 0x00000020

WOW64 layer:
Returned NTSTATUS = STATUS_BUFFER_TOO_SMALL (0xC0000023)
Context           = 0x00000000
ReturnLength      = 0x00000A1A

Without WOW64 ReturnLength is set to size of one OBJECT_DIRECTORY_INFORMATION structure and error code is STATUS_MORE_ENTRIES. The ReturnLength value in this scenario is actually set accidentally. Let’s take a look at NtQueryDirectoryObject implementation from WRK (comments in below snippet are original):

// ...
	POBJECT_DIRECTORY_INFORMATION DirInfo;
	// ...
	TotalLengthNeeded = sizeof( *DirInfo );
	// ...
 
	//
	//  If there isn't enough room then take the following error
	//  path.   If the user wanted a single entry then tell the
	//  caller what length is really needed and say the buffer was
	//  too small.  Otherwise the user wanted multiple entries,
	//  so we'll just say there are more entries in the directory.
	//  In both cases we drop down the entry number because we
	//  weren't able to fit it in on this call
	//
	if ((TotalLengthNeeded + LengthNeeded) > Length) 
	{
		if (ReturnSingleEntry) 
		{
			TotalLengthNeeded += LengthNeeded;
			Status = STATUS_BUFFER_TOO_SMALL;
		} else 
		{
			Status = STATUS_MORE_ENTRIES;
		}
		EntryNumber -= 1;
		goto querydone;
	}
	// ...
 
	//
	//  In all cases we'll tell the caller how much space if really needed
	//  provided the user asked for this information
	//
	if (ARGUMENT_PRESENT( ReturnLength )) 
	{
		*ReturnLength = TotalLengthNeeded;
	}

Under WOW64 layer, results are completely different. The difference stems from the implementation of whNT32QueryDirectoryObject inside wow64.dll. To properly convert 64bit version of OBJECT_DIRECTORY_INFORMATION structures to 32bit Microsoft calls 64bit version of NtQueryDirectoryObject in the loop, to obtain all entries before conversion. Then it proceeds to the loop that calculates required size for 32bit structures. After this loop there is very interesting check:

	if ( _input_length < _required_length )
	{
		if ( !_number_of_32bit_structs )
		{
			WriteReturnLengthSilent(returnLength, _required_length);
			return STATUS_BUFFER_TOO_SMALL;
		}
		ret = STATUS_MORE_ENTRIES;
	}

The most notable condition here is the comparison of the _number_of_32bit_structs to 0. It checks if at least one OBJECT_DIRECTORY_INFORMATION structure fits into user supplied buffer and if not, it explicitly returns required size with error code STATUS_BUFFER_TOO_SMALL. Well, it rather looks like intentional behaviour rather than a mistake. I bet that the person that implemented whNT32QueryDirectoryObject made the same assumption about NtQueryDirectoryObject as I’ve made at the beginning. Of course it would be nice if native version of NtQueryDirectoryObject behave also like this, as it would simplify many things, but unfortunately the truth is different.

One Comment

  1. […] How to get system image list icon index of an IShellItem? Information about Desktop and WindowStation objects Enumerating DeviceObjects from user mode WoW64 internals: Unexpected behaviour of NtQueryDirectoryObject […]

    Reply

Leave a Reply

Your email address will not be published. Required fields are marked *