A forum for reverse engineering, OS internals and malware analysis 

Ask your beginner questions here.
 #27646  by GSnake
 Thu Jan 14, 2016 8:32 pm
Hello guys.

I'm trying to read some user space memory but I can't due to an exception while using ProbeForRead (ACCESS_VIOLATION).
These are the steps I follow to read the memory:

1) I open a through ZwOpenProcess
2) I open a system handle through PsLookupProcessByProcessId
3) I use ZwQueryVirtualMemory to get the virtual addresses pages and their sizes
4) KeStackAttach to switch context
5) ProbeForRead -> ACCESS_VIOLATION (on addresses got before with ZwQueryVirtualMemory)

What am I doing wrong?

Thank you very much.
 #27649  by Vrtule
 Thu Jan 14, 2016 8:59 pm
Hello,

use KeStackAttachProcess/KeUnstackDeatchProcess instead of KeStackAttachProcess.

Show us your code.

Actually, I am not sure whether ProbeForRead will help you when your previous mode is KernelMode. In that case, an attempt to copy invalid user memory usually caused me a BSOD regardless of a try/excpet block wrapping the copy operation.
 #27650  by GSnake
 Thu Jan 14, 2016 9:12 pm
I used KeDetachStackProcess. I didn't include in the list since I thought it wasn't important for the topic. Sorry.

This is my code.
Code: Select all
	HANDLE hProcess;
	NTSTATUS status;
	MEMORY_BASIC_INFORMATION mbi;
	CLIENT_ID client_id;
	OBJECT_ATTRIBUTES objAttr;
	LONG currentAddress = 0;
	PVOID buffer;
	SIZE_T returnLength = 0;
	KAPC_STATE apcState;
	PEPROCESS peProcess;

	ZwOpenProcess(&hProcess, PROCESS_ALL_ACCESS, &objAttr, &client_id);
	PsLookupProcessByProcessId((HANDLE)buf->pid, &peProcess);
	do {
		ZwQueryVirtualMemory(hProcess, (PVOID)currentAddress, MemoryBasicInformation, &mbi, sizeof(mbi), &returnLength);
		KeStackAttachProcess(peProcess, &apcState);
		if (mbi.State == MEM_COMMIT && (mbi.Protect & (PAGE_READONLY | PAGE_READWRITE))) {
			buffer = ExAllocatePoolWithTag(PagedPool, mbi.RegionSize, POOL_TAG);
			if (buffer == NULL) {
				DEBUG_KPRINT(("[-] Failed to allocate buffer (%d bytes) to store memory.\n", (LONG)mbi.RegionSize));
			}
			else {
				__try {
					KeStackAttachProcess(peProcess, &apcState);
					ProbeForRead((PVOID)currentAddress, 4096, 0);
					RtlCopyMemory(buffer, (PVOID)currentAddress, mbi.RegionSize);
					KeUnstackDetachProcess(&apcState);

				}
				__except (EXCEPTION_EXECUTE_HANDLER) {
					DbgPrint(("Address 0x%x isn't accessible.\n", currentAddress));
				}
				ExFreePoolWithTag(buffer, POOL_TAG);
			}
		}
		currentAddress += (LONG)mbi.RegionSize;
	} while (currentAddress <= 0x7FF'FFFFFFFF);
I did not include the error handling just to keep the code short.

Thank you again.
 #27654  by Vrtule
 Fri Jan 15, 2016 9:37 am
Your call to KeStackAttachProcess just after ZwQueryVirtualMemory has no pairing KeUnstackDetachProcess call. I think it also makes no sense to call the routine there.

There is also a problem with the KeStackAttachProcess/KeUnstackDetachProcess pair within the try/except block. If either ProbeForRead or RtlCopyMemory raises an exception, no KeUnstackDetachProcess is called (but you probably handle this within the except block that is just stripped from the code).

Why do you set the Alignment parameter of the ProbeForRead call to zero? I am unsure whether this value is valid. Specify 1 if alignemnt does not matter to you.

Also, according to MSDN, ProbeForRead does the following:
The ProbeForRead routine checks that a user-mode buffer actually resides in the user portion of the address space, and is correctly aligned.
It is not clear whether it checks that the given buffer is readable (which is not so important since this condition may change after the call finishes). It checks propper alignment of the buffer (which seems not to be of any importance to you) and whether the buffer resides in user mode portion of the address space (which you know is true because ZwQueryVirtualMemory returned the information to you).
 #27655  by GSnake
 Fri Jan 15, 2016 9:58 am
Vrtule wrote:Your call to KeStackAttachProcess just after ZwQueryVirtualMemory has no pairing KeUnstackDetachProcess call. I think it also makes no sense to call the routine there.
I'm sorry, my bad. I forgot to remove that line of code. I used that only in the try/except block.
Vrtule wrote: There is also a problem with the KeStackAttachProcess/KeUnstackDetachProcess pair within the try/except block. If either ProbeForRead or RtlCopyMemory raises an exception, no KeUnstackDetachProcess is called (but you probably handle this within the except block that is just stripped from the code).

Why do you set the Alignment parameter of the ProbeForRead call to zero? I am unsure whether this value is valid. Specify 1 if alignemnt does not matter to you.

Also, according to MSDN, ProbeForRead does the following:
The ProbeForRead routine checks that a user-mode buffer actually resides in the user portion of the address space, and is correctly aligned.
It is not clear whether it checks that the given buffer is readable (which is not so important since this condition may change after the call finishes). It checks propper alignment of the buffer (which seems not to be of any importance to you) and whether the buffer resides in user mode portion of the address space (which you know is true because ZwQueryVirtualMemory returned the information to you).
Thank you. That was the problem.

After having set to 1 the alignment, the code started working.
I used 0 since I thought: "The buffer starts exactly where the address points to".
There's no reference to 1 in the documentation (or at least I can't see it...): https://msdn.microsoft.com/en-us/librar ... s.85).aspx

Two more questions:
1) how do I prevent a process kill while I do scan the user space process? I thought using ExAcquireRundownProtection but I don't have a clue on how I can acquire the EX_RUNDOWN_REF for my target process.
2) how do I get the maximum virtual address I should scan for? I think its undocumented. In user mode you can use lpMaximumApplicationAddress from the SYSTEM_INFO structure.

Thank you very much, again.
 #27656  by Brock
 Fri Jan 15, 2016 10:09 am
GSnake why reinvent the wheel instead of using the exported MmCopyVirtualMemory? IIRC it's exported on Vista+. Anyhow, it does all the copying to/from for you and even acquires process rundown protection in case the target up and terminates during the read/write operation
 #27657  by Brock
 Fri Jan 15, 2016 10:17 am
EX_RUNDOWN_REF field is located in each process' EPROCESS block. This offset changes per OS version (maybe SP?). You can get the maximum usermode application virtual address from ZwQuerySystemInformation(SystemBasicInformation). Refer to my previous post as well
 #27658  by GSnake
 Fri Jan 15, 2016 10:40 am
Brock wrote:GSnake why reinvent the wheel instead of using the exported MmCopyVirtualMemory? IIRC it's exported on Vista+. Anyhow, it does all the copying to/from for you and even acquires process rundown protection in case the target up and terminates during the read/write operation
Great, thanks.
Brock wrote: EX_RUNDOWN_REF field is located in each process' EPROCESS block. This offset changes per OS version (maybe SP?). You can get the maximum usermode application virtual address from ZwQuerySystemInformation(SystemBasicInformation). Refer to my previous post as well
Thanks again.