Windows 8 ate my cookie

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.

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


If you would like to learn more about our security audits and explore how we can help you, get in touch with us!

Comments