A rather short blog post today, as I am currently on my vacations. After publishing two, quite extensive write-ups regarding vulnerabilities in the Windows “CSRSS” component at Microsoft July Patch Tuesday:
- CVE-2011-1281: A story of a Windows CSRSS Privilege Escalation vulnerability
- CVE-2011-1282: User-Mode NULL Pointer Dereference & co.
I would like to shortly discuss the details about another bug in the Windows Subsystem, which was NOT patched due to low severity, and can be used to force a reboot of a Windows-driven machine. The result can be accomplished by exploiting a flaw in the winsrv!SrvGetConsoleTitle routine – a member of the Console Management services’ group. All Windows NT-family system editions up to Windows XP / 2003 are affected; on Windows 7, making use of the bug would crash the corresponding CONHOST.EXE process, at most. Even though it is also theoretically possible to turn the issue into an “Information Disclosure” class, we consider it highly unlikely to avoid an unhandled exception during the exploitation process.
Note: Upon publishing the CVE-2011-1281 (AllocConsole EOP) article, a few people contacted me asking for some minor advices related to the vulnerability exploitation. One of them – a French security researcher Mysterie – managed to create a fully functional exploit and even made it available to the public audience. If you’re interested, see his post and PoC source code.
Vulnerability Details
As previously mentioned, the considered security issue resides in winsrv!SrvGetConsoleTitle, a CSRSS operation handler associated with the official kernel32!GetConsoleTitle API. The routine’s functionality is implied by its name: it should be used to retrieve the current console title (a single string, displayed on the console window title bar). The function takes two parameters: a pointer to the output buffer, and the size of the buffer. Simple enough, so far :-)
After a short investigation of the flawed function, we can see the following assembly snippet:
.text:75B328DF push edi
.text:75B328E0 push 1
.text:75B328E2 push dword ptr [esi+LPC_MSG.nSize]
.text:75B328E5 lea edi, [esi+LPC_MSG.lpConsoleTitle]
.text:75B328E8 push edi
.text:75B328E9 push esi
.text:75B328EA call ds:__imp__CsrValidateMessageBuffer@16; CsrValidateMessageBuffer(x,x,x,x)
.text:75B328F0 test al, al
.text:75B328F2 jz loc_75B3F50D
.text:75B328F8 cmp byte ptr [esi+34h], 0
.text:75B328FC jz loc_75B3F517
.text:75B32902 mov edx, [ebp+msg]
.text:75B32905 mov ax, [edx+CONSOLE.TitleLength]
.text:75B3290C cmp [esi+LPC_MSG.nSize], ax
.text:75B32910 jbe short outputLengthLessThanActual
.text:75B32912 movzx eax, ax
.text:75B32915 mov [esi+LPC_MSG.nSize], eax
.text:75B32918
.text:75B32918 outputLengthLessThanActual: ; CODE XREF:SrvGetConsoleTitle(x,x)+4Aj
.text:75B32918 mov ecx, [esi+LPC_MSG.nSize]
.text:75B3291B mov esi, [edx+CONSOLE.Title]
.text:75B32921 mov edi,[edi]
.text:75B32923 mov eax, ecx
.text:75B32925 shr ecx, 2
.text:75B32928 rep movsd
.text:75B3292A mov ecx, eax
.text:75B3292C and ecx,3
.text:75B3292F rep movsb
The above listing is equivalent to the following C-like pseudocode:
if(!CsrValidateMessageBuffer(msg, &msg->lpConsoleTitle, msg->nSize, sizeof(BYTE)) { // Bail out with STATUS_INVALID_PARAMETER; } if(msg->UnicodeFlag) { if((USHORT)msg->nSize > Console->TitleLength) { msg->nSize = Console->TitleLength; } memcpy(msg->lpConsoleTitle, Console->Title, msg->nSize); }
A single glimpse is enough to spot the apparent error – a 16-bit truncated output buffer size is used during the comparison with the actual title length; after that, a full 32-bit number is used as the “memcpy” function operand. Therefore, it should be possible to get CSRSS to copy more bytes than desired, by setting the nSize parameter to a value larger than the current title, but with the lower 16 bits below the string length (e.g. nSize = 0x10002). However, if you simply try to call:
#define CONSOLE_TITLE_LENGTH (0x10002) CHAR ConsoleTitle[CONSOLE_TITLE_LENGTH]; GetConsoleTitle(lpConsoleTitle, CONSOLE_TITLE_LENGTH);
the operating system will definitely not crash. Why?
The reason of this specific behavior, is the fact that the “msg->nSize” value is used to validate the correctness of the msg->lpConsoleTitle
pointer. Since the output of the funtion can be as large as tens of kilobytes, the output buffer is expected to be stored inside the shared client-csrss section. The section – created during the process initialization – has a constant size of 0x10000, hard-coded in the ntdll!CsrpConnectToServer routine:
.text:7C92291F mov [ebp+var_38], 2
.text:7C922926 mov [ebp+var_34], 1
.text:7C92292A mov [ebp+var_33], 1
.text:7C92292E mov [ebp+var_24], 10000h
.text:7C922935 mov [ebp+var_20], ebx
.text:7C922938 call _NtCreateSection@28 ; NtCreateSection(x,x,x,x,x,x,x)
Consequently, it is not possible to use the shared heap for a valid allocation of 0x10000 or more bytes. In order to address the limitation, one can close the CSRSS connection at run-time (primarily through closing the (A)LPC port handle, established during PE loading), and re-connect to the server, specifying a custom-sized shared section. The above is made possible thanks to the fact that CSRSS performs no validation regarding the size of the shared section, whatsoever.
After following the above steps, a potential attacker can specify an overlong output buffer as the kernel32!GetConsoleTitle parameter, and either read an excessive amount of CSRSS heap bytes following the original console title string, or generate an unhandled READ Access Violation exception.
An example crash log from Windows XP SP3 32-bit is as follows:
*** An Access Violation occurred in C:\WINDOWS\system32\csrss.exe ObjectDirectory=\Windows SharedSection=1024,3072,512 Windows=On SubSystemType=Windows ServerDll=basesrv,1 ServerDll=winsrv:UserServerDllInitialization,3 ServerDll=winsrv:ConServerDllInitialization,2 ProfileControl=Off MaxRequestThreads=16: The instruction at 75B62928 tried to read from an invalid address, 01148000 *** enter .exr 006AFBE0 for the exception record *** enter .cxr 006AFBFC for the context *** then kb to get the faulting stack Break instruction exception - code 80000003 (first chance) 001b:7c90120e cc int 3 kd> kb ChildEBP RetAddr Args to Child 006afa38 7c9652ae 006afaec 00000000 00000001 ntdll!DbgBreakPoint 006afa78 7c9659c1 006afaec 7c9659c6 006afac4 ntdll!RtlUnhandledExceptionFilter2+0x27b 006afa88 75b4324c 006afaec 00000000 00000000 ntdll!RtlUnhandledExceptionFilter+0x12 006afac4 75b44aea 006afaec 75b468b1 006afaf4 CSRSRV!CsrUnhandledExceptionFilter+0x48 006afacc 75b468b1 006afaf4 00000001 006afaf4 CSRSRV!CsrApiRequestThread+0x4d4 006afaf4 7c9032a8 006afbe0 006affe4 006afbfc CSRSRV!_except_handler3+0x61 006afb18 7c90327a 006afbe0 006affe4 006afbfc ntdll!ExecuteHandler2+0x26 006afbc8 7c90e46a 00000000 006afbfc 006afbe0 ntdll!ExecuteHandler+0x24 006afbcc 00000000 006afbfc 006afbe0 006afbfc ntdll!KiUserExceptionDispatcher+0xe kd> .exr 006AFBE0 ExceptionAddress: 75b62928 (winsrv!SrvGetConsoleTitle+0x00000065) ExceptionCode: c0000005 (Access violation) ExceptionFlags: 00000000 NumberParameters: 2 Parameter[0]: 00000000 Parameter[1]: 01148000 Attempt to read from address 01148000 kd> .cxr 006AFBFC eax=00040002 ebx=00000026 ecx=0000ed22 edx=004f23b0 esi=01148000 edi=0018b134 eip=75b62928 esp=006afec8 ebp=006afed0 iopl=3 nv up ei pl nz ac pe cy cs=001b ss=0023 ds=0023 es=0023 fs=003b gs=0000 efl=00013217 winsrv!SrvGetConsoleTitle+0x65: 001b:75b62928 f3a5 rep movs dword ptr es:[edi],dword ptr [esi]
And… that’s pretty much it! To those who are spending their week on BlackHat / DEFCON – have fun! I am going to drop some details about one or more CSRSS issues, as long as you’re not completely fed up with the Windows Subsystem posts :-)
Take care,
that’s a nice article, for someone who should be AFK ;)
careful, stupid html encodings ! & g t ; etc…
“All Windows NT-family system editions up to Windows XP / 2003 are affected; on Windows 7, making use of the bug would crash the corresponding CONHOST.EXE process, at most. ”
What about Vista?