Developing windows caution

A forum where anything goes. Introduce yourselves to other members of the forums, discuss how your name evolves when written out in the Game of Life, or just tell us how you found it. This is the forum for "non-academic" content.
Post Reply
User avatar
PHPBB12345
Posts: 1096
Joined: August 5th, 2015, 11:55 pm
Contact:

Developing windows caution

Post by PHPBB12345 » June 3rd, 2021, 7:42 am

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

Post Reply