The right way to update a UNICODE_STRING's value

Hey Everyone,

Long time lurker, first time poster. I’m a hobbyist dev so I apologize if this is a dumb post. I am working on a project that has a requirement which has left me scratching my head. I would greatly appreciate any advice!

I am using a process creation callback (via PsSetCreateProccessNotifyRoutineEx2()) to monitor for the creation of a specific process. The name of the target process, a UNICODE_STRING, is initialized in DriverEntry as a test value via RtlInitUnicodeString(). I would like to give the user of my application to change the name of the application being monitored via an IRP. While implementing this, however, I have run into significant issues when trying to update the global UNICODE_STRING despite my best efforts at mitigating what I believe to be the core issue.

Here’s my code stripped of irrelevant lines:

// header.h  
UNICODE_STRING g_TargetFileName;
// driver.c  
NTSTATUS DriverEntry(PDRIVER_OBJECT pDrObj, PUNICODE_STRING pRegPath)  
{  
    <...>
    // Initialize the global variable to track the target process
    RtlInitUnicodeString(&g_szTargetImage, L"my_test_application.exe");
    <...>
}
// ioctl.c
static NTSTATUS UpdateTargetProcess(PDEVICE_OBJECT pDevObj, PIRP pIrp)
{
    <...>
    PWSTR szNewTargetImage = (PWSTR)pIrp->AssociatedIrp.SystemBuffer;
    <...>
    // This is where the bug exists
    RtlInitUnicodeString(&g_TargetFileName, szNewTargetImage);
    <...>
}

Here is what I’ve tried:

Attempt 1: Use RtlInitUnicodeString() to “reinitialize” the value.
Result: No crash but the value placed in the global variable is not populating properly (i.e. the process creation callback never sees processes spawning with a matching name).

Attempt 2: Use RtlInitUnicode() string to initialize a new UNICODE_STRING with the data sent from the user via their IRL. Then use RtlUnicodeString() to copy the new string into the global.
Result: System crash (ATTEMPTED_WRITE_TO_READONLY_MEMORY).

Attempt 3: Use the same technique as Attempt 2, but manually create the global UNICODE_STRING with a call to ExAllocatePoolWithTag() to put the sting in the non-paged pool.
Result: System crash (ATTEMPTED_WRITE_TO_READONLY_MEMORY).

Attempt 4: Take the new target filename from the user and place it into a PWSTR. Then update the global variable’s string pointer (PUNICODE_STRING->Buffer) to point to this new value.
Result: No crash but the value copied now pointed to be the buffer appears corrupted. Nearly the same result as Attempt 1, but the value comes back as an invalid string when printing it with KdPrintEx().

I could use some help figuring out what the “right” way to update a global UNICODE_STRING via an IRP would be.

Thanks in advance!

Irp->AssociatedIrp.SystemBuffer points to a buffer inside your usermode process, which eventually get freed (and is locked to that context).

You need to allocate memory and copy its content instead.
For example, make a global buffer which can hold, let’s say 256 characters, like this:

WCHAR g_szTargetImageBuffer[256] = { };
UNICODE_STRING g_szTargetImage = { };

In your driver entry, you initialize the UNICODE_STRING to point to the buffer we’ve allocated in .data:

NTSTATUS DriverEntry(...)
{
...
RtlInitEmptyUnicodeString(&g_szTargetImage, &g_szTargetImage[0], sizeof(g_szTargetImage));
UNICODE_STRING InitialName = RTL_CONSTANT_STRING(L"my_test_application.exe");
RtlCopyUnicodeString(&g_szTargetImage, &InitialName);
...
}

And then in your update function, whenever you want to update the process name:

NTSTATUS UpdateTargetProcess(...)
{
...
PWSTR szNewTargetImage = (PWSTR)pIrp->AssociatedIrp.SystemBuffer;
RtlUnicodeStringCopyString(&g_TargetFileName, szNewTargetImage);
...
}

Irp->AssociatedIrp.SystemBuffer points to a buffer inside your usermode process, which eventually get freed

No, it’s not. If it’s a “buffered” ioctl or the first buffer for a “direct” ioctl, then this is a locked-down kernel buffer. The only way to get a user pointer is with METHOD_NEITHER, which no sane person uses.

However, it IS a temporary buffer, which will be freed with the I/O operation is completed. So he does need to copy the data.

Thanks ThatsBerkan. That was very helpful. I was able to implement your suggestion with minor modifications (RtlInitEmptyUnicodeString()'s second parameter should point to g_szTargetImageBuffer for anyone who comes across this thread in the future) and I am no longer crashing :smile:

I put in a few debug prints to track the initialization and change to the UNICODE_STRING and I see that only 8 characters (16+1? bytes) are being initialized and copied over from the IRP. Here’s what I have in WinDbg.

Global initialized: my_test_
Global updated: testing.

Any insight into what might be causing this?

Answering my own question. I just expanded the third parameter passed to RtlInitEmptyUnicodeString() to the size I needed and we’re in business.

1 Like

Oh yes, sorry, it should have been the sizeof the wchar buffer, not the unicode_string!
Happy it works for you now thought.