|Informative Information for the Uninformed|
Microsoft chose to have the GS implementation generate an image file-specific cookie. This means that each image file (executable or DLL) will have their own unique cookie. When used in conjunction with a stack frame, a function will insert its image file-specific cookie into the stack frame. This will be covered in more detail in the next section. The actual approach taken to generate an image file's cookie lives in a compiler inserted routine called __security_init_cookie. This routine is placed prior to the call to the image file's actual entry point routine and therefore is one of the first things executed. By placing it at this point, all of the image file's code will be protected by the GS cookie.
The guts of the __security_init_cookie routine are actually the most critical part to understand. At a high-level, this routine will take an XOR'd combination of the current system time, process identifier, thread identifier, tick count, and performance counter. The end result of XOR'ing these values together is what ends up being the image file's security cookie. To understand how this actually works in more detail, consider the following disassembly from an application compiled with version 14.00.50727.42 of Microsoft's compiler. Going straight to the disassembly is the best way to concretely understand the implementation, especially if one is in search of weaknesses.
Like all functions, the __security_init_cookie function starts with a prologue. It allocates storage for some local variables and initializes some of them to zero. It also initializes some registers, specifically edi and ebx which will be used later on.
.text:00403D58 push ebp .text:00403D59 mov ebp, esp .text:00403D5B sub esp, 10h .text:00403D5E mov eax, __security_cookie .text:00403D63 and [ebp+SystemTimeAsFileTime.dwLowDateTime], 0 .text:00403D67 and [ebp+SystemTimeAsFileTime.dwHighDateTime], 0 .text:00403D6B push ebx .text:00403D6C push edi .text:00403D6D mov edi, 0BB40E64Eh .text:00403D72 cmp eax, edi .text:00403D74 mov ebx, 0FFFF0000h
As part of the end of the code above, a comparison between the current security cookie and a constant 0xbb40e64e is made. Before __security_init_cookie is called, the global __security_cookie is initialized to 0xbb40e64e. The constant comparison is used to see if the GS cookie has already been initialized. If the current cookie is equal to the constant, or the high order two bytes of the current cookie are zero, then a new cookie is generated. Otherwise, the complement of the current cookie is calculated and cookie generation is skipped.
.text:00403D79 jz short loc_403D88 .text:00403D7B test eax, ebx .text:00403D7D jz short loc_403D88 .text:00403D7F not eax .text:00403D81 mov __security_cookie_complement, eax .text:00403D86 jmp short loc_403DE8
To generate a new cookie, the function starts by querying the current system time using GetSystemTimeAsFileTime. The system time as represented by Windows is a 64-bit integer that measures the system time down to a granularity of 100 nanoseconds. The high order 32-bit integer and the low order 32-bit integer are XOR'd together to produce the first component of the cookie. Following that, the current process identifier is queried using GetCurrentProcessId and then XOR'd as the second component of the cookie. The current thread identifier is then queried using GetCurrentThreadId and then XOR'd as the third component of the cookie. The current tick count is queried using GetTickCount and then XOR'd as the fourth component of the cookie. Finally, the current performance counter value is queried using QueryPerformanceCounter. Like system time, this value is also a 64-bit integer, and its high order 32-bit integer and low order 32-bit integer are XOR'd as the fifth component of the cookie. Once these XOR operations have completed, a comparison is made between the newly generated cookie value and the constant 0xbb40e64e. If the new cookie is not equal to the constant value, then a second check is made to make sure that the high order two bytes of the cookie are non-zero. If they are zero, then a 10 bit left shift of the cookie is performed in order to seed the high order bytes.
.text:00403D89 lea eax, [ebp+SystemTimeAsFileTime] .text:00403D8C push eax .text:00403D8D call ds:__imp__GetSystemTimeAsFileTime@4 .text:00403D93 mov esi, [ebp+SystemTimeAsFileTime.dwHighDateTime] .text:00403D96 xor esi, [ebp+SystemTimeAsFileTime.dwLowDateTime] .text:00403D99 call ds:__imp__GetCurrentProcessId@0 .text:00403D9F xor esi, eax .text:00403DA1 call ds:__imp__GetCurrentThreadId@0 .text:00403DA7 xor esi, eax .text:00403DA9 call ds:__imp__GetTickCount@0 .text:00403DAF xor esi, eax .text:00403DB1 lea eax, [ebp+PerformanceCount] .text:00403DB4 push eax .text:00403DB5 call ds:__imp__QueryPerformanceCounter@4 .text:00403DBB mov eax, dword ptr [ebp+PerformanceCount+4] .text:00403DBE xor eax, dword ptr [ebp+PerformanceCount] .text:00403DC1 xor esi, eax .text:00403DC3 cmp esi, edi .text:00403DC5 jnz short loc_403DCE ... .text:00403DCE loc_403DCE: .text:00403DCE test esi, ebx .text:00403DD0 jnz short loc_403DD9 .text:00403DD2 mov eax, esi .text:00403DD4 shl eax, 10h .text:00403DD7 or esi, eax
Finally, when a valid cookie is generated, it's stored in the image file's __security_cookie. The bit-wise complement of the cookie is also stored in __security_cookie_complement. The reason for the existence of the complement will be described later.
.text:00403DD9 mov __security_cookie, esi .text:00403DDF not esi .text:00403DE1 mov __security_cookie_complement, esi .text:00403DE7 pop esi .text:00403DE8 pop edi .text:00403DE9 pop ebx .text:00403DEA leave .text:00403DEB retn
In simpler terms, the meat of the cookie generation can basically be summarized through the following pseudo code:
Cookie = SystemTimeHigh Cookie ^= SystemTimeLow Cookie ^= ProcessId Cookie ^= ThreadId Cookie ^= TickCount Cookie ^= PerformanceCounterHigh Cookie ^= PerformanceCounterLow