Click here to Skip to main content
15,614,665 members
Please Sign up or sign in to vote.
5.00/5 (1 vote)
See more:
This question is part C code for the kernel mode driver and part VB.NET on the user mode application. I am using IOCTL (DeviceIoControl) to communicate back and forth to the driver. The driver monitors process creation and assigns the PID and Process Path to variables inside the driver to which I am able to get the PID number in my VB.NET application but I am confused how to also get the Process Path to my application as the data types are different and I use a pinvoke statement in for deviceIocontrol. If I set OutBuffer to Ulong the PID is shown in the application but everything I have tried to send the process path has resulted in BSOD. Can Someone Please give me some insight into sending string,Unicode_String,WCHAR or anything that I can use to get the PID and Process Path to my application? I have been stuck on this for a few months now learning everything up to this point. Do I need another CTL_CODE for the process path? I am really stuck here with the data types and logic of how this should work. Thank you in advance!:)

This is the C code for the driver part(s) I am working on


void OnProcessNotify(PEPROCESS Process, HANDLE ProcessId, PPS_CREATE_NOTIFY_INFO CreateInfo) // Show And Control Created Process

	if (CreateInfo) {
		ProcID = HandleToUlong(ProcessId);	
		ProcPath = CreateInfo->ImageFileName;
		//DbgPrint("%d %wZ", ProcID, ProcPath);

	PIO_STACK_LOCATION irpsp = IoGetCurrentIrpStackLocation(Irp);
	ULONG returnLength = 0;
	PVOID *buffer = Irp->AssociatedIrp.SystemBuffer;
	ULONG inLength = irpsp->Parameters.DeviceIoControl.InputBufferLength;
	ULONG outLength = irpsp->Parameters.DeviceIoControl.OutputBufferLength;

	switch (irpsp->Parameters.DeviceIoControl.IoControlCode) {
		*buffer = ProcID;
		status = STATUS_SUCCESS;
		returnLength = sizeof(*buffer);

	Irp->IoStatus.Status = status;
	Irp->IoStatus.Information = returnLength;
	IoCompleteRequest(Irp, IO_NO_INCREMENT);

	return status;

Here Is the VB.NET Application Part Of Interest:

<DllImport("Kernel32.dll", CharSet:=CharSet.Auto, SetLastError:=True)>
    Public Shared Function DeviceIoControl(hDevice As SafeFileHandle,
                                           dwIoControlCode As UInteger,
                                           ByRef InBuffer As Long,
                                           nInBufferSize As Integer,
                                           <Out> ByRef OutBuffer As ULong,
                                           nOutBufferSize As Integer,
                                           ByRef pBytesReturned As Integer,
                                           <[In]> ByRef lpOverlapped As NativeOverlapped) As Boolean
    End Function

Public Sub Startmon()
       Dim DeviceName = "\\.\\BitMon"
       Dim hFile = CreateFile(DeviceName, FileAccess.ReadWrite, FileShare.ReadWrite, IntPtr.Zero, FileMode.Open, 0, IntPtr.Zero)
       If hFile.IsInvalid Then
           MsgBox("Failed to open file" & "Return Code: " & hFile.DangerousGetHandle.ToString)
           MsgBox("CREATED FILE HANDLE = " & hFile.DangerousGetHandle.ToString)
           OpenFHandle = hFile.DangerousGetHandle
       End If

       Dim Proc_ID As ULong
       Dim Bytes_IO As Integer = 0

       Do While True
           Dim CurrentID As ULong
           If DeviceIoControl(hFile, IO_GET_CLIENT_PROCID, Nothing, 0, Proc_ID, Len(Proc_ID), Bytes_IO, Nothing) Then
               If Not CurrentID = Proc_ID Then
                   CurrentID = Proc_ID
                   Invoke(Sub() RichTextBox1.AppendText(Proc_ID.ToString))
                   Invoke(Sub() RichTextBox1.AppendText(vbCrLf))
               End If
           End If

   End Sub

What I have tried:

I have tried sending only the Process Path with data type WCHAR and using String Char and Char Array but that only prints out garbage or BSOD. I have tried adding both values to one variable of WCHAR but because my DeviceIoControl pinvoke is set to Ulong in the OutBuffer I get back a long number type which is neither the pid or process path as a string. I have checked the CTL Code and its clear everything is working as it should with PID but im still stumped on how to get the Process Path.
Updated 3-May-22 11:47am

... dusting cobwebs off driver knowledge ...

I'm assuming you have the process path in the driver, stored as either ANSI or UNICODE available in the driver.

It looks like you've got the right infrastructure set up for your IOCTL. I'd create a new code to make it easy in the driver to tell the difference between the ulong and the string. The code is just a number and simple code for each type is better than stuffing two different types into a single buffer.

Change your DllImport for DeviceIoctl to not be ulong-specific. Make it a plain pointer instead of the ByRef as ULong; the VB equivalent of C# IntPtr. Your ulong VB code then needs to allocate an unmanaged buffer, call the driver using the pointer to the buffer as the output parameter, then retrieve the ulong value from the pointer. I don't speak VB, so I don't know how to do that, something like C#'s Marshall.AllocHGlobal with a size of 8 and then Marshall.ReadInt64 to get the value.

You'll need to revise your driver code to validate the length of the buffer against the length of a ulong. It's a driver so you should be writing very defensive code in there.

Once that revised code is working, doing the string should be simple. Only difference should be using the new IOCTL code and the size of the buffer, and a new switch case in the driver to fill the buffer with the string value. Once it's back in the VB, something like C# Marshall.PtrToStringUni or Marshall.PtrToStringAnsi.

update: I just noticed that in your OnProcess function in the driver, you do
ProcPath = CreateInfo->ImageFileName;
That is a mistake. You are saving the pointer to the string, not the actual string. That pointer will not be valid once the notification is complete. You need to initialize your own UNICODE_STRING and copy the provided string into that buffer. Then use your UNICODE_STRING as the source when processing your IOCTL.
Share this answer
Draco2013 3-May-22 11:09am    
Thank you so much for this explanation I'm very grateful and will try this once I get home today 😊. This is a breath of fresh air as I feel I'm going crazy 🤪
Draco2013 4-May-22 13:50pm    
I have changed as much as I can as you suggested and have properly initialized my UNICODE_STRING proc_path but I have a few more questions if I can.

You say:
I'd create a new code to make it easy in the driver to tell the difference between the ulong and the string. 

I have both Variables Global as ULONG and UNICODE_STING
is this what you mean? Also what is meant by the code is just a number?

I have changed the DeviceIo Control from Ulong to IntPtr so that the output buffer can hold a pointer to both the PID and Process Path.

You say:
Your ulong VB code then needs to allocate an unmanaged buffer, call the driver using the pointer to the buffer as the output parameter, then retrieve the ulong value from the pointer. 

I have Proc_id in my application as Ulong which is already a integer type value but says implicit Ulong to Intptr which works well without the AllocHglobal stuff which I couldn't seem to make work. I'm not sure either how to allocate a unmanaged buffer from ulong code or how to call the driver with a buffer when it's intptr. You can probably tell by now I'm pretty confused 😕

You say:
You'll need to revise your driver code to validate the length of the buffer against the length of a ulong

I do this with sizeof(*buffer) is this good enough or may have issues? Once I have the new IOCTL and switch statement do I also need to add the length of the PID and the length of the process to buffer? Please help
JudyL_MD 4-May-22 15:24pm    
In general, I'm advocating splitting the retrieval of the PID and the path into two separate calls. I'd advise doing some studying on the different models of memory management used in unmanaged versus managed code. Drivers themselves are a very unique subset of unmanaged code and have their own restrictions and function calls.

Now to specifics ... You asked about my comments. I've listed the big three below.

1) I'd create a new code to make it easy in the driver to tell the difference between the ulong and the string. The code is just a number ...

You were asking if you should create another IOCTL for retrieving the path. Yes you should. Somewhere in your VB, you have IO_GET_CLIENT_PROCID defined; that value gets itself turned into the switch value SEND_TO_USER in the driver. Make a second such "ioctl code" such as IO_GET_CLIENT_PROCPATH, which you must support with a new case in the driver's switch, such as SEND_TO_USER_PATH. Your VB code then calls DeviceIoControl twice, once with each of the two IO control codes. "Just a number" refers to the code value itself -- it's just a UINT with a very specific format to its bits.

2) Your ulong VB code then needs to allocate an unmanaged buffer, call the driver using the pointer to the buffer as the output parameter, then retrieve the ulong value from the pointer

The IOCTL handling function in a driver only takes one type as input and one type as output -- a pointer to a chunk of unmanaged memory. In your old code, hid those details from you. When you had "<out> ByRef OutBuffer As ULong" describing the OutBuffer parameter, VB created an unmanaged chunk of memory and got a pointer to it, copied the value of your ulong into that memory ... or more likely just locked the memory location of your ulong variable and got a pointer to the locked location ... then passed that pointer through to the DeviceIoControl function in Kernel32.DLL.

To use DeviceIoControl properly, you're going to need to do that hidden stuff yourself. (I'm speaking C# pseudo-code here):
IntPtr p = Marshall.AllocHGlobal (sizeof (ulong))
DeviceIoControl (hFile, IO_GET_CLIENT_PROCID, Nothing, 0, p, sizeof (ulong), Bytes_IO, Nothing)
check return value from DeviceIoControl
ulong value = Marshall.ReadInt64 (p);
Marshall.FreeHGlobal (p);

For the string case, you need to allocate a string of the max possible size and then use that in your call to the driver. I've got "* 2" below since you have unicode in the driver and that takes 2 bytes per character, and "+ 1" to leave room for the NULL terminator.
numStringBytes = (MAX_STRING_SIZE + 1) * 2
IntPtr p = Marshall.AllocHGlobal (numStringBytes)
DeviceIoControl (hFile, IO_GET_CLIENT_PROCPATH, Nothing, 0, p, numStringBytes, Bytes_IO, Nothing)
check return value from DeviceIoControl
string = Marshall.PtrToStringUni (p)
Marshall.FreeHGlobal (p);

3) (Now the driver code) You'll need to revise your driver code to validate the length of the buffer against the length of a ulong

sizeof (*buffer) is not the right way to either validate input length or set return length. Remember, the driver is pure C code. For the IO_GET_CLIENT_PROCID that already exists to retrieve the process ID, your switch case should be something like:

if (outLength == sizeof (ULONG))
*((ULONG *) buffer) = ProcID;
returnLength = sizeof(ULONG);

for your new ioctl code
if (outLength <= (myUnicodeString.Length / 2) - 1) // leave space for the NULL terminator
RtlFillMemory (buffer, nOutBufferSize, 0);
RtlCopyBytes (buffer, myUnicodeString.buffer, myUnocdeString.Length * 2);
returnLength = myUnocdeString.Length * 2;
Randor 5-May-22 5:16am    
You never checked to make sure it's safe to write to the address. You just created a driver that can patch any memory address. Before writing to usermode addresses you must call MmGetSystemAddressForMdlSafe. :)
JudyL_MD 5-May-22 9:46am    
LOL ... seriously though, based on the OP's initial "working" code, I think we can safely assume he is using METHOD_BUFFERED. In that case, no MDL mapping is needed and I added the needed length validation in my comments.

The IOCTL code sample[^] will demonstrate how to do I/O with a device driver. JudyL_MD gave great advice, you should initialize (RtlInitUnicodeString[^]) a UNICODE_STRING and make a copy (RtlCopyBytes[^]) of the file name.

The IOCTL code sample shows how to copy the buffer back to the usermode caller. When looking through the code sample, you want to look at the METHOD_OUT_DIRECT[^] control code.

Direct link to code sample section, IOCTL line 615[^]

Best Wishes,
-David Delaune
Share this answer
Dale Seeley 4-May-22 22:11pm    
Thank you very much for this!
Randor 5-May-22 5:48am    
Use the IOCTL sample as a starting point. Don't forget about potential security issues. Further reading:

This content, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)

CodeProject, 20 Bay Street, 11th Floor Toronto, Ontario, Canada M5J 2N8 +1 (416) 849-8900