Modern OSes have a feature that mitigates the exploitation of stack based buffer overflows. It basically works by writing a "cookie" value before the return address in the stack in the prologue of a function and checking it before the function returns (for further information, see [1] and [2]). This article talks about how this mitigation has been enforced in Windows 8.
Introduction
On a quiet day of March, we were looking at the new mitigations introduced by Windows 8.1. We were focusing on stack cookie protection. This mitigation inserts a random value on the stack to protect return pointers in case of stack buffer overflows. We put a breakpoint to the entrypoint to take look at our lovely cookie. Thereby we could add a memory breakpoint to follow how it's accessed. The default value of the cookie should be 0xBB40E64E, it's randomized by the function ___security_init_cookie. This function is located at the entrypoint of the executable (if compiled with the Microsoft compiler).
call ___security_init_cookie
jmp ___tmainCRTStartup
We were suprised to realize that the cookie wasn't equal to the default value. Someone has just eaten our cookie.
Who's the culprit?
This first question that arises from this observation is: where is this modification made? Since the cookie is modified before reaching the entrypoint (the one from the PE, not main/WinMain), it could be either the kernel or the loader (ntdll!*Ldr*).
There are many ways to identify the culprit. Unfortunately, one can't simply set an access breakpoint to __security_cookie in user-land mode. So we need to do it from a kernel-land debugger.
Below are the windbg commands we used to track down the code location where the cookie is set. Note that we are using the kernel debugger (kd):
; first set the debugger to break on module load 1: kd> !gflag +ksl New NtGlobalFlag contents: 0x00040000 ksl - Enable loading of kernel debugger symbols ; We want it to break when test.exe is loaded 1: kd> sxe ld test.exe 1: kd> g nt!DbgLoadUserImageSymbols+0x33: 81d7a13d cc int 3 ; check the process 0: kd> !process -1 0 PROCESS 89023700 SessionId: 1 Cid: 0000 Peb: 00000000 ParentCid: 0f04 DirBase: bd361460 ObjectTable: aca3e780 HandleCount: <Data Not Accessible> Image: Test.exe ; The PEB is still not mapped at that point, just put a BP somewhere ; we use InitSecurityCookie() to see when the cookie is set for the ntdll module 0: kd> bp /p 89023700 ntdll!InitSecurityCookie 0: kd> g Breakpoint 0 hit ntdll!InitSecurityCookie: 001b:77269db7 8bff mov edi,edi 0: kd> !process -1 0 PROCESS 89023700 SessionId: 1 Cid: 0b00 Peb: 7f1b7000 ParentCid: 0f04 DirBase: bd361460 ObjectTable: aca3e780 HandleCount: <Data Not Accessible> Image: Test.exe ; show the stack trace for ntdll cookie generation 0: kd> kb ChildEBP RetAddr Args to Child 0021f8c0 771fda85 00000000 00000000 771fda9c ntdll!InitSecurityCookie 0021f8cc 771fda9c 00000000 00000000 0021f8e8 ntdll!LdrpInitialize+0x1f 0021f8d4 00000000 0021f8e8 771c0000 00000000 ntdll!LdrInitializeThunk+0x10 ; At that point the PEB is now mapped ; we want the base address of the main module 3: kd> dt 7f1b7000 _PEB ImageBaseAddress ntdll!_PEB +0x008 ImageBaseAddress : 0x00f60000 Void ; the data section is at VA + 0x12000 from the base address ; the cookie is at the base of the data section 0: kd> ba w 4 /p 89023700 00f60000 + 12000 3: kd> bl 0 e 77269db7 0001 (0001) ntdll!InitSecurityCookie Match process data 89023700 2 e 00f72000 w 4 0001 (0001) Test+0x12000 Match process data 89023700 0: kd> g Single step exception - code 80000004 (first chance) First chance exceptions are reported before any exception handling. This exception may be expected and handled. ntdll!LdrInitSecurityCookie+0x86: 001b:77212168 33c0 xor eax,eax ;show the stack trace when the cookie is set for the main module 3: kd> kb ChildEBP RetAddr Args to Child 0021f6b4 771f3d03 00000000 b05faa75 0021f6e4 ntdll!LdrInitSecurityCookie+0x86 0021f6fc 7726bbe5 52d0f702 7f1b7000 00000000 ntdll!LdrpProcessMappedModule+0x1a5 0021f87c 771fda19 52d0f7b2 00000000 00000000 ntdll!LdrpInitializeProcess+0xe51 0021f8cc 771fda9c 00000000 00000000 0021f8e8 ntdll!_LdrpInitialize+0xad 0021f8d4 00000000 0021f8e8 771c0000 00000000 ntdll!LdrInitializeThunk+0x10
(To tell the truth, we can simply take a peek in the symbol name in NTDLL.dll and look for Cookie : that's what we did, but using a kernel debugger is funnier.)
Below is the broad picture of the call tree leading to the generation of the cookie for the ntdll module:
+ [Kernel] | +--+LdrInitializeThunk() | +--+LdrpInitialize() | +--+InitSecurityCookie() | +--+LdrpGenRandom() | +--+LdrInitSecurityCookie() | +--+LdrpFetchAddressOfSecurityCookie() | +--+LdrpGenSecurityCookie()
ntdll!LdrInitializeThunk is called from the kernel (in a sort of user-mode callback) when a user-mode thread is readied for start. The function calls ntdll!LdrpInitialize: the first thing this function does is to check whether the cookie has already been generated or not. If it hasn't been generated yet, the function calls the ntdll!InitSecurityCookie which is in charge of generating the randomized cookie. To do this, it uses the ntdll!LdrpGenRandom and ntdll!LdrInitSecurityCookie. The later uses the ntdll!LdrpFetchAddressOfSecurityCookie to locate the cookie (see Where is my cookie?), and ntdll!LdrpGenSecurityCookie with the return value of ntdll!LdrpGenRandom to generate the random value, and finally, if required, ZwProtectVirtualMemory to update the cookie value inside the mapped PE.
Below is the same for the main module:
+ [Kernel] | +––+LdrInitializeThunk() | +--+LdrpInitialize() | +--+LdrpInitializeProcess() | +--+LdrpProcessMappedModule() | +--+LdrpGenRandom() | +--+LdrInitSecurityCookie() | +LdrpFetchAddressOfSecurityCookie() | +LdrpGenSecurityCookie()
Notice that this time there's no call to InitSecurityCookie as this function is used solely for the ntdll module.
How does Windows 8 bake the new cookie?
We start with the ntdll!LdrpProcessMappedModule function with the following pseudo-code:
UINT RandomVal = LdrpGenRandom(0);
RandomVal ^= LdrSystemDllInitBlock_GenRandom1; // LdrSystemDllInitBlock_GenRandom1 is init by the Kernel
LdrInitSecurityCookie(NULL, RandomVal, ModuleBase, SizeOfImage);
The ntdll!LdrpGenRandom returns a random integer or 0 depending if the rdrand instruction is supported on the current CPU (this assertion is obviously valid only for the x86 or x86-64 platform):
; .text:6B2D797D
cmp byte ptr ds:7FFE0290h, 0 ; KUSER_SHARED_DATA.ProcessorFeatures[0x1C]
push esi
jnz @@rdrand_supported
The returned random value from ntdll!LdrpGenRandom is then XORed with the ntdll!LdrSystemDllInitBlock_GenRandom1 value (note that this name is not a symbolic name). This value is initalized by the kernel in the nt!PspPrepareSystemDllInitBlock function (actually the whole ntdll!LdrSystemDllInitBlock structure is initialized there) by using the nt!ExGenRandom function:
; PAGE:006B2113
call _ExGenRandom@4 ; ExGenRandom(x)
mov [edi+50h], eax ; [edi+0x50] = LdrSystemDllInitBlock_GenRandom1
The ntdll!LdrSystemDllInitBlock is a structure exported by NTDLL which is initialized by the kernel. This structure was created to mitigate the ntdll!LdrHotpatchRoutine bypass [3].
Next the code calls the ntdll!LdrInitSecurityCookie which performs three operations:
Get the actual address of the cookie using ntdll!LdrpFetchAddressOfSecurityCookie
Generate another round of randomness using ntdll!LdrpGenSecurityCookie
Set the cookie for the module
Where is my cookie?
The __security_cookie is usually located at the first DWORD of .data section. However how does it locate correctly this address? The Loader uses the ntdll!LdrpFetchAddressOfSecurityCookie function, which relies on RtlImageNtHeaders to locate the IMAGE_NT_HEADERS from the PE, then it gathers the address of IMAGE_LOAD_CONFIG_DIRECTORY{32,64} by using the function RtlImageDirectoryEntryToData with the DirectoryEntry as IMAGE_DIRECTORY_ENTRY_LOAD_CONFIG (10).
The structure IMAGE_LOAD_CONFIG_DIRECTORY{32,64} [4] holds an interresting field for us, IMAGE_LOAD_CONFIG_DIRECTORY32::SecurityCookie which contains the address of the cookie.
typedef struct {
DWORD Size;
DWORD TimeDateStamp;
WORD MajorVersion;
WORD MinorVersion;
DWORD GlobalFlagsClear;
DWORD GlobalFlagsSet;
DWORD CriticalSectionDefaultTimeout;
DWORD DeCommitFreeBlockThreshold;
DWORD DeCommitTotalFreeThreshold;
DWORD LockPrefixTable; // VA
DWORD MaximumAllocationSize;
DWORD VirtualMemoryThreshold;
DWORD ProcessHeapFlags;
DWORD ProcessAffinityMask;
WORD CSDVersion;
WORD Reserved1;
DWORD EditList; // VA
DWORD SecurityCookie; // VA
DWORD SEHandlerTable; // VA
DWORD SEHandlerCount;
} IMAGE_LOAD_CONFIG_DIRECTORY32;
Adding some more randomness
The loader uses the function ntdll!LdrpGenSecurityCookie function to generate a random value. It is quite similar to __security_init_cookie (which can be found before main/WinMain), except it relies on the ntdll!KUSER_SHARED_DATA structure instead of using any API. Here is a pseudo-C version:
// Function: LdrpGenSecurityCookie()
// Role: Initalize the SecurityCookie to a random value.
// Returns: A random value for the SecurityCookie.
// Note: (implementation detail) LdrInitSecurityCookie() uses the address of Cookie as the seed.
DWORD LdrpGenSecurityCookie(DWORD Seed)
{
DWORD SysTimeLow = SHARED_USER_DATA->SystemTime.LowPart;
DWORD SysTimeHigh = SHARED_USER_DATA->SystemTime.High1Time;
DWORD CurThrId = Teb->Tib->CurrentThreadId;
DWORD CurPrcId = Teb->Tib->ProcessId;
RandomValue = SysTimeLow ^ SysTimeHigh ^ CurThrId ^ CurPrcId;
DWORD TickCntMul = SHARED_USER_DATA->TickCountMultiplier;
DWORD Tick;
if (TickCntMul < 0x1000000)
{
DWORD TickCntLow = SharedUserData.TickCount.LowPart;
DWORD TickCntHigh1 = SharedUserData.TickCount.High1Time;
DWORD TickCntHigh2 = SharedUserData.TickCount.High2Time;
while (TickCntHigh1 != TickCntHigh2)
{
TickCntLow = SharedUserData.TickCount.LowPart;
TickCntHigh1 = SharedUserData.TickCount.High1Time;
TickCntHigh2 = SharedUserData.TickCount.High2Time;
YieldProcessor();
}
Tick = ((QWORD)((TickCntHigh1 << 8) * TickCntMul) >> 0x18) + TickCntLow * TickCntMul
}
else
{
Tick = (QWORD)((SHARED_USER_DATA->TickCount.LowPart * TickCntMul)) >> 0x18;
}
LARGE_INTEGER PerfCnt;
NtQueryPerformanceCounter(&PerfCnt, NULL); // NOTE: Returned value is not checked
return RandomValue ^ PerfCnt.LowPart ^ PerfCnt.HighPart ^ Seed ^ Tick;
}
Summary
If the module is ntdll, the code goes as follow:
LdrInitializeThunk() → LdrpInitialize() → InitSecurityCookie() → [ LdrpGenRandom() ; LdrInitSecurityCookie() → [LdrpFetchAddressOfSecurityCookie() ; LdrpGenSecurityCookie() ] ]
For a module that is not ntdll, the code goes like this:
LdrInitializeThunk() → LdrpInitialize() → LdrpInitializeProcess() → LdrpProcessMappedModule() → [ LdrpGenRandom() ; LdrInitSecurityCookie() → [LdrpFetchAddressOfSecurityCookie() ; LdrpGenSecurityCookie() ] ]
The cookie generation can be summarized as follow:
SecurityCookie = LdrpGenRandom(0) ^ LdrSystemDllInitBlock_GenRandom1 ^ LdrpGenSecurityCookie();
Conclusion
This mitigation ensures the randomness of the stack cookie regardless of the used compiler. It's pretty useless if your executable was compiled using a modern version of CL (Microsoft Compiler), but reveals itself interesting for older executables. It's worth noting that ntdll!LdrpInitSecurityCookie is also available in Windows 7, but seems to be used only for specific DLL. (see the ReactOS implementation [5] and LDRP_ENTRY_PROCESSED.)
We also found a new structure for the exported data ntdll!LdrSystemDllInitBlock and probably a new version of IMAGE_LOAD_CONFIG_DIRECTORY32:
;6B2D7917
cmp [eax+IMAGE_LOAD_CONFIG_DIRECTORY32.Size], 5Ch ; size is greater than the current IMAGE_LOAD_CONFIG_DIRECTORY32
Acknowledgements
QB Team for the review and feedbacks
References
[1] | http://blogs.technet.com/b/srd/archive/2009/03/16/gs-cookie-protection-effectiveness-and-limitations.aspx |
[2] | http://j00ru.vexillium.org/?p=690 |
[3] | http://blogs.technet.com/b/srd/archive/2013/08/12/mitigating-the-ldrhotpatchroutine-dep-aslr-bypass-with-ms13-063.aspx |
[4] | http://msdn.microsoft.com/en-us/library/windows/desktop/ms680328(v=vs.85).aspx |
[5] | http://doxygen.reactos.org/d8/d6b/ldrinit_8c_source.html |