Reading user process memory after attaching to process

Hi folks.

Suppose we are at minifilter callback (preCreate for instance) at PASSIVE_LEVEL and this callback you also have valid PEPROCESS to other process then one in context of which callback is executing and you would like to read memory of this process (for instance take something from it's PEB).

I thought about something like:

BOOL ReadUserMemory(PEPROCESS process, LPCVOID address, LPVOID buffer, SIZE_T size)
{
    // some premilinary checks here against null, against non user memory etc.
    
    BOOL status = FALSE;
	KAPC_STATE state;
	KeStackAttachProcess(process, &state); // attach
	
	try
	{
		memcpy(buffer, address, size);    // <- here is a question
        status = TRUE;
	}
	except(EXCEPTION_EXECUTE_HANDLER)
	{
		// notthing to do
	}
    
	KeUnstackDetachProcess(&state); // detach

	return status;
}

(apologize if above code is not working - typing from memory).
My major question is suppose we evaluated address that it is non kernlemode address and we are sure we want to read this particular address from this particular process - is that OK to simply read this memory uner try/except once we attached to this process?
Maybe proper reading should be done via IoAllocateMdl/MmProbeAndLock?
If such thing is not possible/not correct - could you explain me why it would be bad idea?

My doubts comes from documentation:KeStackAttachProcess function (ntifs.h) - Windows drivers | Microsoft Learn

Note Attaching a thread to a different process can prevent asynchronous I/O operations from completing and can potentially cause deadlocks. In general, the lines of code between the call to KeStackAttachProcess and the call to KeUnstackDetachProcess should be very simple and should not call complex routines or send IRPs to other drivers.

I guess such reading woudl be fine in context of caller (then we dont need to attach) and that woudl be fine, but I am not certainly sure doing it same way for other process is ok. Please advise..

Big thank you for all participants.

Reading with a __try/__except should be fine.

The note there is just saying that APCs are disabled so you can't do threaded I/O like you get with ZwReadFile/ZwWriteFile/etc. Paging I/O doesn't use APCs so if the memcpy faults you're good.

Scott thank you for your response.

Just to clarify:

  1. I understand that KeStackAttach that one of things that mentioned api will do behind the scenes will be switching cr3 to process to which we are attaching?
  2. I am reading this great artice: The NT Insider:Master of the Obvious -- MDLs are Lists that Describe Memory and have a question regarding:

This stage is potentially an optional stage. However, for now let's assume your driver wants a new virtual address for accessing the underlying pages.

So we are with IoAllocateMdl did MmProbeAndLock and author wrote that now we can optionally do mapping depending on scenario). I understand that map must be done when we pass buffer down and we dont know in context of which process we will do further processing - so it is better to map it and pass mapped buffer, but I wonder on case when we should do IoAllocateMdl and MmProbeAndLock and dont do mapping? Isn't it scenario I am writing about?

  1. (hopefully last question). MmProbeAndLock needs to be executed under try/except because of probing and fact address of memory can be corrupted/bad/whatever. Suppose we are ok and MmProbeAndLock did its job. If we dont need to do mapping - is there OK to access usermode address (described in MDL) freely without try/except? I guess no, becuase in meatime memory could be corrupted so I imagine code of access this buffer should be also under try/except. Is this correct assumption?

If you are just reading process memory (as noted above) you don't need an mdl, you don't need mmprobeandlock, you just need an exception handler around your code.

Memory corruption is not something you can do anything about, and it is not prevented by locking pageable memory. It is instead a hardware failure. locking pageable memory allows access at >= DISPATCH_LEVEL and prevents deallocation until it is unlocked.

Thank you Mark for this answer.
Appreciate all this information.
Let me dig bit more - now I started to wonder where this Windows-driver-samples/general/ioctl/wdm/sys/sioctl.c at main · microsoft/Windows-driver-samples · GitHub is in fact correct. In this example (windows driver samples) there is MmProbeAndLock called on some user memory, then there is MmGetSystemAddressForMdlSafe call after which instant access to this memory happens.

I thought MmGetSystemAddressForMdlSafe is "just" mapping memory (not copying it) - say on PTE level. If my assumption is correct then it means that we should access this memory under try/except - shouldn't we?

Maybe this general/ioctl sample is not perfect as we are in context of caller so it probably won't be torn down but it is general question about buffer we get from MmGetSystemAddressForMdlSafe.

Thank you for you answers. I know I am mixnig thing, but sometimes questions arise along with answers :slight_smile:

The comment indicates when this is required:

        // If you are accessing these buffers in an arbitrary thread context,
        // say in your DPC or ISR, if you are using it for DMA, or passing these buffers to the
        // next level driver, you should map them in the system process address space.
        // First allocate an MDL large enough to describe the buffer
        // and initilize it

Mark, probably my question is somewhat badly stated. I asked if access to already mapped memory here: Windows-driver-samples/general/ioctl/wdm/sys/sioctl.c at main · microsoft/Windows-driver-samples · GitHub should not be done under try/except?

I know doc does not say anything about it, but I suspect this api is not copying memory to system buffer (then I could assume system under the hood does copy under try/except when dealing with user buffer), but rather doing just mapping (so system PTEs are somehow pointing to user memory). If my assumption is correct then when you access this memory you are touching user memory in fact - dont you? And if so then why not under try/except?

Sorry for confusion :slight_smile:

No, it doesn't copy memory. It creates a new, non-paged virtual mapping of those pages. Once that is completed, access cannot fail. We are guaranteed that there are physical pages backing that virtual address. Even if the user process expires, our mapping ensures the pages will remain viable.

Ignoring the possibility of memory corruption causes by hardware failure (there is nothing that you can do about that)

The contents of this memory can change at any time. There is no lock you can acquire that will prevent some CPU from executing instructions that change the bytes. If you are only reading and are prepared to handle unexpected values, then this is 'safe enough'.

In addition to the contents of the memory changing, the page protection of the memory can also be changed at any time. This is something that you can prevent by creating a mapping. Creating a mapping also verifies that the current page protection is compatible with the access you want (read etc.)

Thank you guys for all valuable responses! :+1: