Read Unknown Kernel Address In A Safe Way

Forum for discussion about kernel-mode development.
Post Reply
User avatar
AxtMueller
Posts: 4
Joined: Thu Dec 27, 2018 11:11 pm

Mon Dec 31, 2018 3:44 pm

Author: Axt Müller

If you are engaged in Windows driver development for many years, I guess you have a nightmare: how to read an unknown address in an absolutely safe way. We all know that, it is useless to test the validity of the address by MmIsAddressValid, even if this function return TRUE, the address may still be invalid. Read address in SEH (I mean put "memcpy" in "try...except") is only valid for user-mode address, only if you read an invalid address of user-mode, you can catch an exception. Some guys on the internet said: you can use IoAllocateMdl, MmProbeAndLockPages and MmGetSystemAddressForMdlSafe to read any kernel address, but this method still crashes system when you read some invalid addresses. Doubt my word? You can write a program to read address from 0x80000000 to 0xFFFFFFFF (32-bit), or from 0xFFFFF800`00000000 to 0xFFFFFFFF`00000000 (64-bit), I think your system will crash in 2 minutes. This problem has plagued me for many years, but I already got 3 good solutions now, let me share to all of you.

On Windows 2000, Windows XP and Windows 2003, you can use NtSystemDebugControl to read any address without crashing system. Code show as below:

Code: Select all

typedef struct _MEMORY_CHUNKS 
{
	PVOID Address;
	PVOID Data;
	ULONG Length;
}MEMORY_CHUNKS, *PMEMORY_CHUNKS;

const ULONG SysDbgCopyMemoryChunks_0 = 8

typedef NTSTATUS (_stdcall *NTSYSTEMDEBUGCONTROL)
(
	IN ULONG Command,
	IN PVOID InputBuffer,
	IN ULONG InputBufferLength,
	OUT PVOID OutputBuffer,
	IN ULONG OutputBufferLength,
	OUT PULONG ReturnLength
);

NTSYSTEMDEBUGCONTROL ZwSystemDebugControl = FUNCTION_ADDRESS;

NTSTATUS SafeReadKrnlAddr(PVOID TargetAddress, PVOID AllocatedBuffer, ULONG LengthYouWantToRead)
{
	MEMORY_CHUNKS mc;
	mc.Address = TargetAddress;
	mc.Length = LengthYouWantToRead;
	mc.Data = AllocatedBuffer;
	return ZwSystemDebugControl(SysDbgCopyMemoryChunks_0, &mc, sizeof(mc), NULL, 0, &NumberOfBytesRead);
}
But unfortunately, the above method is invalid since Windows Vista, So we need to use another method to read kernel memory after Windows Vista. We all knows that, we can get physical address of virtual address by MmGetPhysicalAddress, and we can map physical address by MmMapIoSpace. So the solution is clear now: get physical address of a virtual address, then map it to a new virtual address to read. Code show as below:

Code: Select all

BOOL SafeReadKrnlAddr(PVOID TargetAddress, PVOID AllocatedBuffer, ULONG LengthYouWantToRead)
{
	BOOL b = FALSE;
	PHYSICAL_ADDRESS PA;
	PA = MmGetPhysicalAddress(TargetAddress);
	if(PA.QuadPart)
	{
		NewVA = MmMapIoSpace(PA, LengthYouWantToRead, MmNonCached);
		if(NewVA)
		{
			memcpy(AllocatedBuffer, NewVA, LengthYouWantToRead);
			MmUnmapIoSpace(NewVA, LengthYouWantToRead);
			b = TRUE;
		}
	}
	return b;
}
After Windows 8.1, Microsoft finally provided a function that can read kernel memory safely: MmCopyMemory. This function is easy to use, the only thing to note is that you must use NonPagedPool memory as the buffer address. Code show as below:

Code: Select all

NTSTATUS SafeReadKrnlAddr(PVOID TargetAddress, PVOID AllocatedBuffer, ULONG LengthYouWantToRead)
{
	NTSTATUS status = STATUS_UNSUCCESSFUL;
	PVOID NonPagedBuffer = ExAllocatePool(NonPagedPool, LengthYouWantToRead);
	if(NonPagedBuffer)
	{
		SIZE_T NumberOfBytesTransferred = 0;
		MM_COPY_ADDRESS AddrToRead;
		AddrToRead.VirtualAddress = TargetAddress;
		status = MmCopyMemory(NonPagedBuffer, AddrToRead, LengthYouWantToRead, MM_COPY_MEMORY_VIRTUAL, &NumberOfBytesTransferred);
		if(STATUS_SUCCESS == status)
		{
			memcpy(AllocatedBuffer, NonPagedBuffer, NumberOfBytesTransferred);
		}
		ExFreePool(NonPagedBuffer);
	}
	return status;
}
In my project Windows Kernel Explorer (https://github.com/AxtMueller/Windows-Kernel-Explorer), I used Method 2 and Method 3 to read kernel memory for scanning kernel mode hooks. You can use my tool to scan hooks and let me know if it will crash the system when scanning hooks.
User avatar
Brock
Posts: 213
Joined: Wed Apr 28, 2010 3:13 am
Location: Valparaiso, Florida USA
Contact:

Wed Jan 02, 2019 12:45 am

These methods as well as many others have been shared on this forum for some time now but for those less informed your examples may be informative, so thanks for this. As of 8.1 MmCopyMemory() is imho the best choice because it was designed to do exactly this and performs the underlying PTE validation, shadow range checking etc for you. Your method #2 using MmGetPhysicalAddress() would still need to deal with things like handling addresses in session space (Win32k, CDD, TSDDD, DXG), DMA etc. which has been mentioned here as well in the past. Lastly, your method #3 should allocate non-executable non-paged pool memory (NonPagedPoolNx) since you don't need your allocated memory to be executable, which otherwise would cause framework testing tools like HLK to fail.
Accept nothing less than STATUS_SUCCESS
User avatar
AxtMueller
Posts: 4
Joined: Thu Dec 27, 2018 11:11 pm

Thu Jan 17, 2019 7:36 pm

Brock wrote:
Wed Jan 02, 2019 12:45 am
These methods as well as many others have been shared on this forum for some time now but for those less informed your examples may be informative, so thanks for this. As of 8.1 MmCopyMemory() is imho the best choice because it was designed to do exactly this and performs the underlying PTE validation, shadow range checking etc for you. Your method #2 using MmGetPhysicalAddress() would still need to deal with things like handling addresses in session space (Win32k, CDD, TSDDD, DXG), DMA etc. which has been mentioned here as well in the past. Lastly, your method #3 should allocate non-executable non-paged pool memory (NonPagedPoolNx) since you don't need your allocated memory to be executable, which otherwise would cause framework testing tools like HLK to fail.
yes, you're right, thanks for your suggestion.
Post Reply