Page 1 of 1

Developing windows caution

Posted: June 3rd, 2021, 7:42 am
by PHPBB12345
Memory access:

Code: Select all

Problem #1 (user mode):
Thread A calls VirtualQuery (Address = X) returns "Accessible"
Thread B terminated with release memory (Address = X)
Thread C created with allocate memory (Address = X, Status = Guarded)
Thread A tried access memory (Address = X) failed
Thread C tried expands stack failed, process terminated
Solution:
Call ReadProcessMemory / WriteProcessMemory instead of memory access with SEH

<C styled pseudo code (read access)>
function "Thread A" (<fake arguments>)
{
	MEMORY_BASIC_INFORMATION mbi;
	volatile BOOL bAccessed = FALSE;
	<memory object> captured;

	if (VirtualQuery(<Address X>, &mbi, sizeof(mbi)) == sizeof(mbi) &&
		mbi.State == MEM_COMMIT && !(mbi.Protect & (PAGE_GUARD | PAGE_NOACCESS | PAGE_EXECUTE)))
	{
		/* Interrupted: Thread B terminated */
		/* Interrupted: Thread C created */
		_SEH2_TRY
		{
			/* memcpy may raises Win32 exception and disable guard page, like IsBadReadPtr */
			memcpy(&captured, <Address X>, sizeof(captured));
			bAccessed = TRUE;
		}
		_SEH2_EXCEPT(EXCEPTION_EXECUTE_HANDLER)
		{
			/* Handle exception */
		}
		_SEH2_END;
	}
}

<Solution>
function "Thread A" (<fake arguments>)
{
	MEMORY_BASIC_INFORMATION mbi;
	volatile BOOL bAccessed = FALSE;
	<memory object> captured;

	if (VirtualQuery(<Address X>, &mbi, sizeof(mbi)) == sizeof(mbi) &&
		mbi.State == MEM_COMMIT && !(mbi.Protect & (PAGE_GUARD | PAGE_NOACCESS | PAGE_EXECUTE)))
	{
		/* Interrupted: Thread B terminated */
		/* Interrupted: Thread C created */
		bAccessed = ReadProcessMemory(GetCurrentProcess(), <Address X>, &captured, sizeof(captured), NULL);
		if (!bAccessed)
		{
			/* Handle exception */
		}
	}
}

<C styled pseudo code (write access)>
function "Thread A" (<fake arguments>)
{
	MEMORY_BASIC_INFORMATION mbi;
	volatile BOOL bAccessed = FALSE;
	<memory object> captured = ...;

	if (VirtualQuery(<Address X>, &mbi, sizeof(mbi)) == sizeof(mbi) &&
		mbi.State == MEM_COMMIT && !(mbi.Protect & (PAGE_GUARD | PAGE_NOACCESS | PAGE_READONLY | PAGE_EXECUTE | PAGE_EXECUTE_READ)))
	{
		/* Interrupted: Thread B terminated */
		/* Interrupted: Thread C created */
		_SEH2_TRY
		{
			/* memcpy may raises Win32 exception and disable guard page, like IsBadReadPtr */
			memcpy(<Address X>, &captured, sizeof(captured));
			bAccessed = TRUE;
		}
		_SEH2_EXCEPT(EXCEPTION_EXECUTE_HANDLER)
		{
			/* Handle exception */
		}
		_SEH2_END;
	}
}

<Solution>
function "Thread A" (<fake arguments>)
{
	MEMORY_BASIC_INFORMATION mbi;
	volatile BOOL bAccessed = FALSE;
	<memory object> captured = ...;

	if (VirtualQuery(<Address X>, &mbi, sizeof(mbi)) == sizeof(mbi) &&
		mbi.State == MEM_COMMIT && !(mbi.Protect & (PAGE_GUARD | PAGE_NOACCESS | PAGE_READONLY | PAGE_EXECUTE | PAGE_EXECUTE_READ)))
	{
		/* Interrupted: Thread B terminated */
		/* Interrupted: Thread C created */
		bAccessed = WriteProcessMemory(GetCurrentProcess(), <Address X>, &captured, sizeof(captured), NULL);
		if (!bAccessed)
		{
			/* Handle exception */
		}
	}
}

Problem #2 (kernel mode):
Thread A calls ZwQueryVirtualMemory (Address = X) returns "Accessible"
Thread B terminated with release memory (Address = X)
Thread C created with allocate memory (Address = X, Status = Guarded)
Thread A tried access memory (Address = X) failed
Thread C tried expands stack failed, process terminated (not cause BSOD)
Solution:
Call KeStackAttachProcess

<C styled pseudo code>
function "Thread A" (<fake arguments>)
{
	MEMORY_BASIC_INFORMATION mbi;
	volatile BOOL bAccessed = FALSE;
	<memory object> captured;

	if (ZwQueryVirtualMemory(ZwCurrentProcess(), <Address X>, MemoryBasicInformation, &mbi, sizeof(mbi), NULL) &&
		mbi.State == MEM_COMMIT && !(mbi.Protect & (PAGE_GUARD | PAGE_NOACCESS | PAGE_EXECUTE)))
	{
		_SEH2_TRY
		{
			ProbeForRead(<Address X>, sizeof(captured), 1);
			RtlCopyMemory(&captured, <Address X>, sizeof(captured));
			bAccessed = TRUE;
		}
		_SEH2_EXCEPT(EXCEPTION_EXECUTE_HANDLER)
		{
			/* Handle exception */
		}
		_SEH2_END;
	}
}

<Solution>
function "Thread A" (<fake arguments>)
{
	MEMORY_BASIC_INFORMATION mbi;
	volatile BOOL bAccessed = FALSE;
	<memory object> captured;
	KAPC_STATE ApcState;

	if (ZwQueryVirtualMemory(ZwCurrentProcess(), <Address X>, MemoryBasicInformation, &mbi, sizeof(mbi), NULL) &&
		mbi.State == MEM_COMMIT && !(mbi.Protect & (PAGE_GUARD | PAGE_NOACCESS | PAGE_EXECUTE)))
	{
		KeStackAttachProcess(&PsGetCurrentProcess()->Pcb, &ApcState);
		_SEH2_TRY
		{
			ProbeForRead(<Address X>, sizeof(captured), 1);
			RtlCopyMemory(&captured, <Address X>, sizeof(captured));
			bAccessed = TRUE;
		}
		_SEH2_EXCEPT(EXCEPTION_EXECUTE_HANDLER)
		{
			/* Handle exception */
		}
		_SEH2_END;
		KeUnstackDetachProcess(&ApcState);
	}
}

Problem #3 (user mode, inter-process memory write via WriteProcessMemory):
Assumes X and Y are same memory page
If WriteProcessMemory call NtQueryVirtualMemory first, this is not problem
If WriteProcessMemory call NtProtectVirtualMemory first:
	Thread A changes memory protection (Process = C, Address = X, Protection = PAGE_EXECUTE_READWRITE), OldProtect = undefined
	Thread B changes memory protection (Process = C, Address = Y, Protection = PAGE_EXECUTE_READWRITE), OldProtect = PAGE_EXECUTE_READWRITE (unexpected result)
Solutions:
* Use mutual exclusion
* Create new thread (CreateRemoteThread)
* Call undocumented NtWriteVirtualMemory directly

<C styled pseudo code (write access)>
function "Thread A" (<fake arguments>)
{
	<memory object> captured;
	if (!WriteProcessMemory(hProcessC, <Address X>, &captured, sizeof(captured), NULL)
	{
		/* Handle exception */
	}
}

function "Thread B" (<fake arguments>)
{
	<memory object> captured;
	if (!WriteProcessMemory(hProcessC, <Address Y>, &captured, sizeof(captured), NULL)
	{
		/* Handle exception */
	}
}
UI Processing (may cause unresponsive):

Code: Select all

EndDeferWindowPos is blocking function
i386(?) Exception handling:

Code: Select all

If termination handling code jump out of _SEH2_FINALLY block, behavior is undefined (MSVC: C4532)
If invalidates SEH chain (e.g. set fs:[0] to 0xFFFFFFFF or stack buffer overflow), future exception cause undefined behavior (even if SEHOP is enabled)
	* For program keep SEH chain validity, call NtRaiseException to raise second chance exception
		* For user mode, NtRaiseException calls TerminateProcess
		* For kernel mode, NtRaiseException calls KeBugCheckEx (KMODE_EXCEPTION_NOT_HANDLED)

Call stack (User mode, first chance, debugger is attached):
	ntoskrnl!KiSwapContext (symbol)
	ntoskrnl!KiSwapThread (symbol)
	ntoskrnl!KeWaitForSingleObject (exported)
	ntoskrnl!DbgkpSendApiMessage (symbol)
	ntoskrnl!DbgkForwardException (symbol)
	ntoskrnl!KiDispatchException (symbol)
	ntoskrnl!KiRaiseException (symbol)
	ntoskrnl!NtRaiseException (symbol)
	ntdll!ZwRaiseException (exported)
	ntdll!RtlRaiseException (exported)
	kernel32!RaiseException (documented)

Call stack (User mode, first chance, VEH is handling):
	< VEH Handler >
	ntdll!RtlpCallVectoredHandlers (symbol)
	ntdll!RtlCallVectoredExceptionHandlers (symbol)
	ntdll!RtlDispatchException (exported)
	ntdll!KiUserExceptionDispatcher (exported)

Call stack (User mode, first chance, SEH is handling):
	< SEH Handler >	
	ntdll!RtlpExecuteHandler2 (symbol)
	ntdll!RtlpExecuteHandler (symbol)
	ntdll!RtlpExecuteHandlerForException (symbol)
	ntdll!RtlDispatchException (symbol)
	ntdll!KiUserExceptionDispatcher (exported)
	
Call stack (User mode, first chance, VCH is handling):
	< VCH Handler >
	ntdll!RtlpCallVectoredHandlers (symbol)
	ntdll!RtlCallVectoredContinueHandlers (symbol)
	ntdll!RtlDispatchException (exported)
	ntdll!KiUserExceptionDispatcher (exported)

Call stack (User mode, second chance, debugger is attached):
	ntoskrnl!KiSwapContext (symbol)
	ntoskrnl!KiSwapThread (symbol)
	ntoskrnl!KeWaitForSingleObject (exported)
	ntoskrnl!DbgkpSendApiMessage (symbol)
	ntoskrnl!DbgkForwardException (symbol)
	ntoskrnl!KiDispatchException (symbol)
	ntoskrnl!KiRaiseException (symbol)
	ntoskrnl!NtRaiseException (symbol)
	ntdll!NtRaiseException (exported)

Call stack (User mode, last resort):
	ntoskrnl!KiSwapContext (symbol)
	ntoskrnl!KiSwapThread (symbol)
	ntoskrnl!KeTerminateThread (symbol)
	ntoskrnl!PspExitThread (symbol)
	ntoskrnl!PspTerminateThreadByPointer (symbol)
	ntoskrnl!NtTerminateProcess (symbol)
	ntoskrnl!ZwTerminateProcess (exported)
	ntoskrnl!KiDispatchException (symbol)
	ntoskrnl!KiRaiseException (symbol)
	ntoskrnl!NtRaiseException (symbol)
	ntdll!NtRaiseException (exported)
	
Call stack (Kernel mode, first chance, SEH is handling):
	< SEH Handler >	
	ntoskrnl!RtlpExecuteHandler2 (symbol)
	ntoskrnl!RtlpExecuteHandler (symbol)
	ntoskrnl!RtlpExecuteHandlerForException (symbol)
	ntoskrnl!RtlDispatchException (symbol)
	ntoskrnl!KiDispatchException (symbol)
	ntoskrnl!NtRaiseException (symbol)
	ntoskrnl!ZwRaiseException (symbol)
	ntoskrnl!RtlRaiseException (exported)
	
Call stack (Kernel mode, last resort):
	ntoskrnl!RtlpBreakWithStatusInstruction (symbol)
	ntoskrnl!KiBugCheckDebugBreak (symbol)
	ntoskrnl!KeBugCheckWithTf (symbol)
	ntoskrnl!KeBugCheckEx (exported)
	ntoskrnl!KiDispatchException (symbol)

Call stack (x86 hardware trap, first fault (e.g. dereference an invalid pointer)):
	ntoskrnl!KiDispatchException (symbol)
	ntoskrnl!KiDispatchExceptionFromTrapFrame (symbol)
	ntoskrnl!KiTrapXXHandler (symbol)

Call stack (x86 hardware trap, double fault (e.g. kernel-mode stack overflow)):
	ntoskrnl!RtlpBreakWithStatusInstruction (symbol)
	ntoskrnl!KiBugCheckDebugBreak (symbol)
	ntoskrnl!KeBugCheckWithTf (symbol)
	ntoskrnl!KiTrap08Handler (symbol)

For triple fault, processor is restarted without call stack.
How to get (S)ROP in Windows:

Code: Select all

i386: NtContinue, popad
x86-64: NtContinue, RtlRestoreContext