ZwQueryInformationProcess Return Length

Hi everyone.
First of all, I have to say that I am a novice! :smile:
I was looking for a way to get the file name in my PsSetCreateProcessNotifyRoutine.
In my research, I came across this link:
https://www.osronline.com/article.cfm^article=472.htm
But I do not understand a subject here.
Why does it reduce the size of the Unicode_String from the returnedLength?

status = ZwQueryInformationProcess( NtCurrentProcess(),
ProcessImageFileName,
NULL, // buffer
0, // buffer size
&returnedLength);

bufferLength = returnedLength - sizeof(UNICODE_STRING);

if (ProcessImageName->MaximumLength < bufferLength) {

    ProcessImageName->Length = (USHORT) bufferLength;

    return STATUS_BUFFER_OVERFLOW;
   
}

It reduces the size by sizeof(UNICODE_STRING) because the buffer includes two things, the UNICODE_STRING (at offset 0x00) that describes the process name and its length, and then after that structure, you have the actual string/file path, which is also pointed to by UNICODE_STRING->Buffer.

So the Buffer looks something like this :
[
0x00 - UNICODE_STRING
0x10 - The path…
]

/// <summary>
/// Gets the image file path of the given process.
/// </summary>
/// <param name="Process">The process object.</param>
/// <param name="OutProcessName">The returned process image file path.</param>
NTSTATUS ProcessUtils::GetProcessImageFilePath(CONST PEPROCESS Process, OUT WCHAR OutProcessName[MAXIMUM_FILENAME_LENGTH])
{
	NTSTATUS Status = { };

	// 
	// Verify the passed arguments.
	// 

	if (Process == nullptr)
	{
		return STATUS_INVALID_PARAMETER_1;
	}

	if (OutProcessName == nullptr)
	{
		return STATUS_INVALID_PARAMETER_2;
	}

	// 
	// Open a handle to the process.
	// 

	auto* ProcessHandle = ZwCurrentProcess();

	if (PsGetCurrentProcess() != Process)
	{
		if (!NT_SUCCESS(Status = ObOpenObjectByPointer(Process, OBJ_KERNEL_HANDLE, NULL, GENERIC_ALL, *PsProcessType, KernelMode, &ProcessHandle)))
		{
			return Status;
		}
	}

	// 
	// Retrieve the file path of the main module of the process.
	// 

	ULONG ReturnedLength = 0;
	BYTE Buffer[MAXIMUM_FILENAME_LENGTH + sizeof(UNICODE_STRING)] = { };
	UNICODE_STRING* ProcessName = (UNICODE_STRING*) Buffer;

	Status = ZwQueryInformationProcess(ProcessHandle, ProcessImageFileName, &Buffer, sizeof(Buffer), &ReturnedLength);

	// 
	// Close the handle to the process.
	// 

	if (ProcessHandle != ZwCurrentProcess())
	{
		ZwClose(ProcessHandle);
	}

	// 
	// If the query was successful, copy the image filename to the caller's buffer.
	// 

	if (NT_SUCCESS(Status))
	{
		RtlZeroMemory(&OutProcessName[0], max(sizeof(OutProcessName), ProcessName->Length + sizeof(WCHAR)));
		RtlCopyMemory(&OutProcessName[0], ProcessName->Buffer, ProcessName->Length);
	}
	
	return Status;
}

@ThatsBerkan said:
It reduces the size by sizeof(UNICODE_STRING) because the buffer includes two things, the UNICODE_STRING (at offset 0x00) that describes the process name and its length, and then after that structure, you have the actual string/file path, which is also pointed to by UNICODE_STRING->Buffer.

So the Buffer looks something like this :
[
0x00 - UNICODE_STRING
0x10 - The path…
]

/// <summary>
/// Gets the image file path of the given process.
/// </summary>
/// <param name="Process">The process object.</param>
/// <param name="OutProcessName">The returned process image file path.</param>
NTSTATUS ProcessUtils::GetProcessImageFilePath(CONST PEPROCESS Process, OUT WCHAR OutProcessName[MAXIMUM_FILENAME_LENGTH])
{
	NTSTATUS Status = { };

	// 
	// Verify the passed arguments.
	// 

	if (Process == nullptr)
	{
		return STATUS_INVALID_PARAMETER_1;
	}

	if (OutProcessName == nullptr)
	{
		return STATUS_INVALID_PARAMETER_2;
	}

	// 
	// Open a handle to the process.
	// 

	auto* ProcessHandle = ZwCurrentProcess();

	if (PsGetCurrentProcess() != Process)
	{
		if (!NT_SUCCESS(Status = ObOpenObjectByPointer(Process, OBJ_KERNEL_HANDLE, NULL, GENERIC_ALL, *PsProcessType, KernelMode, &ProcessHandle)))
		{
			return Status;
		}
	}

	// 
	// Retrieve the file path of the main module of the process.
	// 

	ULONG ReturnedLength = 0;
	BYTE Buffer[MAXIMUM_FILENAME_LENGTH + sizeof(UNICODE_STRING)] = { };
	UNICODE_STRING* ProcessName = (UNICODE_STRING*) Buffer;

	Status = ZwQueryInformationProcess(ProcessHandle, ProcessImageFileName, &Buffer, sizeof(Buffer), &ReturnedLength);

	// 
	// Close the handle to the process.
	// 

	if (ProcessHandle != ZwCurrentProcess())
	{
		ZwClose(ProcessHandle);
	}

	// 
	// If the query was successful, copy the image filename to the caller's buffer.
	// 

	if (NT_SUCCESS(Status))
	{
		RtlZeroMemory(&OutProcessName[0], max(sizeof(OutProcessName), ProcessName->Length + sizeof(WCHAR)));
		RtlCopyMemory(&OutProcessName[0], ProcessName->Buffer, ProcessName->Length);
	}
	
	return Status;
}

So you mean the ZwQueryInformationProcess adds the real length value to the Unicode_String structure and then return?

Yes, the UNICODE_STRING structure contains a ‘->Length’ field that specifies the length (in bytes) of the string.

1 Like

I have a couple of random comments.

First, I can’t believe how ambiguous the docs for ZwQueryInformationProcess/ProcessImageFileName are. Sure… you should be able to “figure it out” given what’s written – but the docs just need to be explicit that when ProcessInformationClass is ProcessImageFileName that the buffer at ProcessInformation contains a UNICODE_STRING (at offset zero) that describes the processes image file name.

Second, the code in that 15-year-old example (the function “GetProcessImageName”) is needlessly complicated and kinda… sucks, and some of the comments in it suck even worse. The caller (of the GetProcessImageName) supplies a fixed-length buffer… so, if that’s the case, why go through the little game of calling ZwQueryInformationProcess with null/0? Instead, just ExAllocate a buffer that’s the same size as that supplied by the user plus sizeof(UNICODE_STRING) and just use that. If the supplied buffer is too small, you can still return the required size as the example does.

These things conspire together to make understanding these functions more difficult for Mr. @zinoberdevelop than it should be.

While I’m here, I need to also mention a common issue in the code that Mr. @ThatsBerkan provided: It’s a buffer overflow problem waiting to happen. PLEASE don’t write code like this. First, MAXIMUM_FILENAME_LENGTH is not the right value to use (it hasn’t in fact been correct for years… the max file path is 32K plus 4 characters). Second, you really HAVE to pass the length parameter to avoid the buffer overflow. And if you’re gonna DO that, why not just pass in PUNICODE_STRING as the argument?

Anyhow… your very fundamental question turned out to be a lot more interesting in terms of details than I expected.

Peter

>

First, MAXIMUM_FILENAME_LENGTH is not the right value to use (it hasn’t in
fact been correct for years… the max file path is 32K plus 4 bytes).

32k + 4?

@“Peter_Viscarola_(OSR)” said:
I have a couple of random comments.

First, I can’t believe how ambiguous the docs for ZwQueryInformationProcess/ProcessImageFileName are. Sure… you should be able to “figure it out” given what’s written – but the docs just need to be explicit that when ProcessInformationClass is ProcessImageFileName that the buffer at ProcessInformation contains a UNICODE_STRING (at offset zero) that describes the processes image file name.

Second, the code in that 15-year-old example (the function “GetProcessImageName”) is needlessly complicated and kinda… sucks, and some of the comments in it suck even worse. The caller (of the GetProcessImageName) supplies a fixed-length buffer… so, if that’s the case, why go through the little game of calling ZwQueryInformationProcess with null/0? Instead, just ExAllocate a buffer that’s the same size as that supplied by the user plus sizeof(UNICODE_STRING) and just use that. If the supplied buffer is too small, you can still return the required size as the example does.

These things conspire together to make understanding these functions more difficult for Mr. @zinoberdevelop than it should be.

While I’m here, I need to also mention a common issue in the code that Mr. @ThatsBerkan provided: It’s a buffer overflow problem waiting to happen. PLEASE don’t write code like this. First, MAXIMUM_FILENAME_LENGTH is not the right value to use (it hasn’t in fact been correct for years… the max file path is 32K plus 4 characters). Second, you really HAVE to pass the length parameter to avoid the buffer overflow. And if you’re gonna DO that, why not just pass in PUNICODE_STRING as the argument?

Anyhow… your very fundamental question turned out to be a lot more interesting in terms of details than I expected.

Peter

Thank you for your very interesting and good explanation.

32k + 4?

That’s 32K+4 characters (I edited immediately after writing it) — It’s the max possible path spec PLUS the preceding “\\?\”

Peter

>> 32k + 4?

That’s 32K+4 characters (I edited immediately after writing it) — It’s the
max possible path spec PLUS the preceding “?\”

I thought the 32767 limit (+NULL character for 32768 chars total)
included the preceeding "\?"?

Can’t remember, because I tried it almost 20 years ago (never saw
anything over 500 characters used).
Good thing I never hardcode buffer sizes and presume I will never get more.

@Dejan_Maksimovic… You may be right. I just went Googling for this, and it seems that the whole issue is a complete mess. Different system components have differing maximum path sizes, and enforce differing size expectations. Plus there are the Win10 1607 changes that the system owner can optionally opt-in for.

Some day, in my copious free time, I’ll have to write a native user-mode program that tries to create a file with a Very Long Path Name (something on the order of 32K). Until that time, your policy is the best one: Don’t just assume the max length possible; Check for overflow.

Peter