Reversing Windows Defender Exploit Guard - Stack Pivoting (Part I)


exploitguard

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
2
3
4
5
6
7
8
9
10
int main()
{
Sleep(10000);
void* pPage = VirtualAllocEx(GetCurrentProcess(), NULL, 256,
MEM_RESERVE | MEM_COMMIT, PAGE_READWRITE);
DWORD floldProtect = 0;
VirtualProtect(pPage, 256, PAGE_EXECUTE_READ, &floldProtect);
printf("DONE!");
return 0;
}

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.

enable_protection


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

verifier_callstack

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!

verifier_dllmain

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

verifier_mitlibinit

We could start off by putting a breakpoint on the MitLibInitialize function.

1
bp PayloadRestrictions!MitLibInitialize

mitlib_funcs

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

query_info

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 -

mit_flags

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>

registry
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

check_mit


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.

various_var_init
One of the global variable is g_MitLibState and it is being initalized as follows -

1
2
3
4
g_MitLibState+0x1  // 1 byte flag ie. sil (0-6)
g_MitLibState+0x2 // 1 byte flag ie. r14b (6-11), in case of stack pivot, its set
g_MitLibState+0x8 // dword - current mitigation flags ie. 0x40 for stack pivot only
g_MitLibState+0x20 // qword - ImageBaseAddress of exe to be protected

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
2
3
+0x004 TickCountMultiplier : 0xfa00000
...
+0x320 TickCount : _KSYSTEM_TIME

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.

dll_notification

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
2
if ( a1 == 1 )                                // DLL_LOAD_EVENT
return MitLibHandleDllLoadEvent(a2);

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.

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

hooked_func.png

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.

critical_funcs.png

We can define the structure as follows, the other two members are the number of arguments & supported mitigations for the specific function.

1
2
3
4
5
6
7
struct CriticalFunc
{
ULONGLONG DllIndex;
PUCHAR FunctionName;
ULONG NumberOfArguments;
ULONG SupportedMitigations;
}

We can write an IDAPython script to dump all these function names and map it to their respective dlls.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
import idc
import ida_bytes

mitLibHookTable_addr = idc.get_name_ea(idc.BADADDR, "MitLibHookTable")
num_critical_funcs = 0x2f
element_size = 0x18
module_dic = {"kernel32.dll":[], "kernelbase.dll":[], "ntdll.dll":[]}
mod_list = list(module_dic.keys())

for idx in range(1,num_critical_funcs):
curr_elem_addr = mitLibHookTable_addr + (idx * element_size)
dll_index = ida_bytes.get_qword(curr_elem_addr)
string_addr = ida_bytes.get_qword(curr_elem_addr + 8)
function_name = idc.get_strlit_contents(string_addr, -1, idc.STRTYPE_C)
print(dll_index, function_name.decode() if function_name else None)
module_dic[mod_list[dll_index]].append(function_name)

print(module_dic)

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.

resolver2

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 calls detour_is_imported() to determine if the address is imported and updates v25 accordingly.
  • Detects short jumps (EB xx): Adjusts v25 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.

g_mitlibstate_c30

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.

heap_alloc

1
2
3
4
5
6
7
8
9
10
11
class DetourArgs
{
PVOID pre_stub;
ULONGLONG hook_codesize;
ULONGLONG function_index_encoded_ptr_loc;
ULONGLONG shangalcommonstub_ptr_loc;
ULONGLONG mitlibhooksdispatcher_ptr_loc;
ULONGLONG number_of_arguments_loc;
PVOID additional_info;
PVOID mitlibhooksdispatcher_ptr;
}

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.

shangal_prestub

In the end, the installed hook for the critical functions look like the following where I’ve added some patch constants.

precodegen_patch

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.

hook_disasm2

We can now easily deduce the following structure of protected critical function from this information.

1
2
3
4
5
6
7
struct Hook {
PVOID OriginalAddress;
PVOID HookedAddress;
PVOID Trampoline;
ULONGLONG IdxEncodedPtr;
ULONGLONG NumArgs;
};

hooks_dispatcher_call

In summary, these installed hooks invoke MitLibHooksDispatcher, and the execution flow is as follows.

arrowtest

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.

other_hooks

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.

hookapisinmodule

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.

protected_mod

And structure is as follows :

1
2
3
4
5
6
7
struct ProtectedModule {
PVOID DllBaseAddr;
ULONG DllSize;
PUCHAR BaseDllName;
PUCHAR FullDllName;
ULONG IsProtected;
};

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.

stackpivot2

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.

teb_structure

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!

stackpivot1

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