Reversing Windows Defender Exploit Guard - Stack Pivoting (Part I)
TLDR
Inspired by @StephenSims, I thought it would be fun to explore the internals of Windows Defender Exploit Guard (formerly EMET) myself. Reversing it proved to be an excellent exercise for learning about various exploitation mitigations, and I highly recommend experimenting with it. In this blog post, we’ll explore how Exploit Guard hooks critical functions. We’ll do some static & dynamic analysis of PayloadRestrictions.dll and see how does it prevent exploitation in user mode processes.
Stack Pivoting - Introduction
Stack pivoting is a technique used in exploitation where an attacker redirects the stack pointer (ESP/RSP) to a controlled memory region, such as the heap, to gain execution control. This is commonly used in ROP (Return-Oriented Programming) chains when the stack is not easily controlled. It could be used to bypass DEP.
Test Environment
I’m analyzing this on the following version of Windows, and the behavior may vary across different versions.
Windows 11 23H2 (OS Build 22631.4890) 64 bit
One could test the stack pivot protection with any application but here I wrote a Demo application as I was analysing other mitigations such as EAF and IAF alongside. It makes the process of debugging really easy as we have full control over our app and know what is happening under the hood. I’ve also included some suspicious API calls like VirtualProtect in this example to see how exploit guard reacts.
1 | int main() |
Applying Protections
Note: There exists a bug that requires at least two protections for Exploit Guard to work, so enable an additional one if needed.
Since this blog post focuses solely on analyzing Stack Pivot Protection, we can enable just that along with “Validate image dependency integrity” just to be sure that exploit guard works.
PayloadRestrictions Huh?
PayloadRestrictions.dll
is the primary DLL responsible for applying process mitigations, as it is loaded only when mitigations are enabled. We can now launch our demo app in WinDbg and set a breakpoint on Dll Load event for PayloadRestrictions.dll and restart the process to trigger it.
1 | sxe ld PayloadRestrictions.dll |
Nice! PayloadRestrictions got loaded into the current process. Taking a quick look at the call stack, we observe that it is the verifier.dll
that is loading the PayloadRestrictions.dll indeed but how?
A quick google search reveals that verifier.dll is related to the Application Verifier utility on Windows. Whenever verifier.dll is loaded into a process, information from NtGlobalFlags member of the PEB is checked. If Application Verifier (FLG_APPLICATION_VERIFIER
) or Page Heap (FLG_HEAP_PAGE_ALLOCS
) is enabled for the process, the mitigation policies are bypassed!
If there are any mitigations found for the current process and those flags are not enabled, it loads PayloadRestrictions.dll and calls its exported function MitLibInitialize
We could start off by putting a breakpoint on the MitLibInitialize function.
1 | bp PayloadRestrictions!MitLibInitialize |
Now we can start analysing MitLibInitialize in IDA alongside as well. With the DLL symbols available, we notice several functions prefixed with “MitLib“ which likely stands for MitigationLibrary. Decompilation is not enough in the case of PayloadRestrictions as it is not accurate for some functions.
ProcessMitigationPolicy
We notice that NtQueryProcessInformation is called with ProcessInformationClass argument as 0x34 ie. ProcessMitigationPolicy (incorrect in decompilation) to query for mitigation policies applied on the current process. The returned value has specific bits set, indicating the applied protections. With some tries in WinDbg, I concluded the following results -
I’ve been unable to turn on the remaining bits, if anyone reading this knows how to do that, please let me know :)
While reviewing previous research on the topic, I discovered that mitigation options can also be retrieved from the following registry key, but this has a slightly different format than the above.
1 | HKLM\Software\Microsoft\Windows NT\CurrentVersion\Image File Execution Options\<appname> |
Furthermore, there is a check that sets some registers based on these bits -
- If any bit from range 0-6 is set then set register
sil
- If any bits from range 6-11 is set then set register
r14b
- Neither 0-6, nor 6-11 is set then dont do anything and exit MitLibInitialize
Global Variables Initialization
Then it goes on to find the .mrdata
section by parsing PEB of PayloadRestrictions.dll which is later used to store some of its global variables.
One of the global variable is g_MitLibState and it is being initalized as follows -
1 | g_MitLibState+0x1 // 1 byte flag ie. sil (0-6) |
Also there is another variable being initialized ie. g_MitLibRandomSeed
which uses the following members of _KUSER_SHARED_DATA to calculate the random seed.
1 | +0x004 TickCountMultiplier : 0xfa00000 |
The TickCount member is being multiplied with TickCountMultiplier value. This technique to generate a random seed was pretty interesting as I didn’t knew about that earlier. We’ll see the use of this value later in this blogpost.
LdrRegisterDllNotification
MitLibInitialize also registers a dll notification hook using LdrRegisterDllNotification
with the handler MitLibDllNotification
.
After the notification is registered everytime a dll is loaded into this process it’ll first call MitLibDllNotification which will in turn call MitLibHandleDllLoadEvent.
1 | if ( a1 == 1 ) // DLL_LOAD_EVENT |
This is useful if a DLL it aims to protect is not yet loaded in the process.
Critical Functions
Currently there are 3 major dlls which will be protected whose names are stored in a global variable named MitLibHookModuleTable
.
MitLibInitialize gets the dll key/index of “ntdll.dll” from MitLibHookModuleTable
. It then makes use of another global variable named MitLibHookTable
which looks like the following
From this, we can depict that this is a array of functions to be protected. It iterates over function names and processes up to 47 entries. We can define a new structure in IDA and clear this up.
We can define the structure as follows, the other two members are the number of arguments & supported mitigations for the specific function.
1 | struct CriticalFunc |
We can write an IDAPython script to dump all these function names and map it to their respective dlls.
1 | import idc |
You can get a list of all these functions here.
Now you might be asking why only these? This could be due to several reasons -
- Hooking every function that manipulates the stack (like mov rsp, rax) would introduce significant overhead and slow down legitimate applications.
- ROP Chains often use specific APIs for eg. VirtualAlloc, VirtualProtect, NtProtectVirtualMemory to disable DEP.
- Not all stack modifications are malicious.
Next it checks if the function belongs to a specific DLL ie. ntdll in this case and retrieves the function’s address via LdrGetProcedureAddress
.
It also checks if the function is hooked or redirected:
- Detects indirect jumps (
FF 25
): This pattern corresponds to a jump through an imported function address. It then callsdetour_is_imported()
to determine if the address is imported and updatesv25
accordingly. - Detects short jumps (
EB xx
): Adjustsv25
based on the offset and checks for further redirections.
Then there exists an array of structures being initialized at g_MitLibState+0xc30
where each structure has a size of 0x28 bytes and stores some information regarding the critical functions. The v25
variable discussed above is also stored here. The following is the structure for NtProtectVirtualMemory.
In a similar manner, it gets the current application address of entrypoint (using Peb → NtHeaders
) and checks the jump, follows redirections, resolves the target address and adds it to the same structure array. Then it calls ShangalTransactionBegin
which looks like a wrapper for DetourTransactionBegin
function.
Captain Hook
Now we are pretty sure that it is using Microsoft Detours to do some hooking. It allocates some heap memory and initializes an object where it stores information about the detour function.
1 | class DetourArgs |
Please take a note of the PreCodeGen
function. It makes use of the DetourArgs object above and patches detour function on the fly. The instructions for the detour function are copied from the following code block.
In the end, the installed hook for the critical functions look like the following where I’ve added some patch constants.
Hooks are being applied by the DetourAttachEx
function and to give you an idea of how a final detour function looks like I’ve included the disassembly of the hooked NtProtectVirtualMemory. Also the previous NtProtectVirtualMemory structure we talked about looks like the following after hooking.
We can now easily deduce the following structure of protected critical function from this information.
1 | struct Hook { |
In summary, these installed hooks invoke MitLibHooksDispatcher, and the execution flow is as follows.
Hooking Other Dlls
As you might notice that these hooks are just being installed for ntdll functions for now but going a little down we could see that the PEB’s InInitializationOrderModuleList
member is being accessed and after some checks MitLibHandleDllLoadEvent
is called.
One could make use of !peb
command on windbg to get the Module List from PEB’s Ldr structure.
MitLibHandleDllLoadEvent is a helper function to protect the dlls. It checks if a loaded dll is in MitLibHookModuleTable with the help of another helper function ie. MitLibResolveHookedApisInModule
. If the dll is found, then it calls MitLibHookApisInModule
to add their critical functions to the list of protected critical functions, at g_MitLibState+c30
as well.
As you can see that the random seed that was calculated before is now being used (as rand_value % 5
) in ShangalTransactionBegin
. Viewing its references, DetourAttachEx uses this random value to calculate number of bytes for copying instructions. The critical functions are also being checked for support of the specific mitigation by utilizing the value encoded in the previously explained critical function structure which is 0x5c for most of them.
To keep track of these protected modules, MitLibHandleDllLoadEvent adds them to an array at g_MitLibState+0x50
which looks like the following.
And structure is as follows :
1 | struct ProtectedModule { |
At this point, we have all of our critical functions hooked and protected.
Stack Pointer Verification
Now lets see how is stack pivot mitigation policy being applied by exploit guard.
Our function of main focus here is MitLibHooksDispatcher. It includes a call to a function named MitLibNotifyStackPivotViolation
which seems relevant.
It checks whether the 6th bit(Stack Pivot mitigation policy) is enabled in the mitigation flags. It uses the TEB to verify whether a value falls within the stack’s boundaries. We can take help of the Vergilius project website to get structure definition for 23H2 in this case.
StackLimit denotes the upper bound of the stack boundary and StackBase is the lower one.
But what are these values being compared to? you might be asking. So lets begin tracking it down!
It all starts in ShangalCommonStub
when rsp
is saved into rcx
register. Afterwards ShangalCStub
loads the effective address of the location rcx+0xf0
which gives the value of rsp
before calling our hook and saves it in the stack as well. Then the rcx
register(which is now pointing to previous rsp) is moved into rsi
in the MitLookHooksDispatcher
function. So r9 is just that saved stack pointer before calling our hook.
If this check fails and the saved rsp doesn’t lie within the stack boundaries, the NotifyStackPivotViolation
function is called which uses NtSetEvent to create an Event Log entry.
Part II - Exploring Other Mitigations
This is just a basic introduction to the inner workings of Exploit Guard, but there’s much more to explore! In the next blog post, we’ll dive into powerful mitigations like Export Address Filtering (EAF) and Import Address Filtering (IAF), examining how they strengthen Windows security. Those posts will be more concise, as many concepts covered here will be referenced there. Stay tuned! :)
References
https://ntdoc.m417z.com/
https://www.vergiliusproject.com/kernels/x64/windows-11/23h2
https://github.com/Microsoft/Detours
https://cqureacademy.com/blog/cqlabs-windows-defender-exploit-guard/
http://0xdabbad00.com/wp-content/uploads/2013/11/emet_4_1_uncovered.pdf