One of the popular techniques is based on the usage of a pair of functions:
- PsLookupProcessByProcessId (for retrieving EPROCESS structure)
- KeStackAttachProcess with KeUnstackDetachProcess
This technique is used by both Antiviruses and Rootkits. So the final snippet code could be as follows
NTSTATUS
ReadProcessMemorySafe(
HANDLE targetProcessId,
LARGE_INTEGER memoryOffset,
PVOID destinationBuffer,
ULONG bufferLength,
PULONG bytesRead
)
{
if (!targetProcessId || !destinationBuffer || bufferLength == 0)
return STATUS_INVALID_PARAMETER;
if (bytesRead)
*bytesRead = 0;
PEPROCESS targetProcess = NULL;
NTSTATUS status = PsLookupProcessByProcessId(targetProcessId, &targetProcess);
if (!NT_SUCCESS(status))
return status;
SIZE_T localBytesRead = 0;
status = MmCopyVirtualMemory(
targetProcess, // Source process
(PVOID)(memoryOffset.QuadPart), // Source address
PsGetCurrentProcess(), // Target is the kernel-mode caller
destinationBuffer, // Target buffer
bufferLength, // Number of bytes to read
KernelMode, // Running in kernel mode
&localBytesRead // Out: actual number of bytes copied
);
ObDereferenceObject(targetProcess);
if (NT_SUCCESS(status) && bytesRead)
*bytesRead = (ULONG)localBytesRead;
return status;
}
The careful reader could notice that, unlike ReadProcessMemory(), which is subject to user-mode process protections and access control, kernel-mode code operates at a higher privilege level and can read any address in a user-mode process.