Пожалуйста, обратите внимание, что пользователь заблокирован
Статью рекомендуется читать на темной теме или pdf из-за того, что схемы на светлой плохо читаются.
Ссылка на pdf - http://**************************************************************/b/DPYGPuhUGduurEWaZf7K2
(отдельное спасибо ordinaria1 за создание pdf-файла)
Введение
В этой небольшой статье я бы хотел поделиться опытом эксплуатации 1-day уязвимостей в нативных компонентах Windows - ее драйверах. За основу были взяты 4 уязвимости в
Для чего разрабатывать 1-day эксплойты?
По сравнению с поиском 0-day разработка 1-day эксплойтов менее трудозатратна и позволяет атакующим расширить свой арсенал, повысив тем самым эффективность.
Работая с 1-day уязвимостями, исследователь совершенствует свои навыки статического, динамического анализа, а также навыки программирования.
В случае с разработкой проактивных решений для эксплойта исследователь также совершенствует техники обхода зищитных систем, что позволяет минимизировать риски обнаружения и повысить эффективность эксплойта. При анализе 1-day не исключена возможность обхода патча или обнаружения 0-day.
Согласно недавнему отчету Mandiant 30% эксплуатируемых уязвимостей in-the-wild - это 1-day уязвимости, успешная эксплуатация которых может происходить даже спустя полгода после выхода исправлений.
Таким уязвимостям подвержены также End-Of-Life системы, информации о потенциальном количестве которых не много. Например, согласно информации из блога Государственного Университета Портлэнда40% их систем к октябрю 2025 года перестанут получать обновления для Windows 10 из-за устаревшего железа.
CVE-2023-21768
0-day для этой уязвимости использовался в дикой природе, а сама уязвимость была подробно описана в блоге IBM X-Force. PoC также был опубликован на GitHub. В оригинале авторы эксплуатировали подсистему I/O Ring для получения примитивов чтения и записи. Мы же доработали эксплойт и применили два других метода, один из которых был обнаружен совсем недавно и отлично вписывается в контекст уязвимости.
Начнем с того, что уязвимость позволяет осуществлять запись DWORD по произвольному указателю при удалении из очереди пакета для объекта I/O Completion Port.
Происходит это в цепочке вызовов
Для вызова уязвимости необходимо:
На Рисунке 2 показано как происходит добавление пакетов в очередь.
Чтобы обнулить
Для повышения привилегий мы будем использовать технику DKOM и чтобы завершить эксплойт нам потребуются ядерные адреса объектов
На Рисунке 4 схематически изображен процесс подмены токена.
CVE-2024-21338 & CVE-2024-38041
В феврале 2024 года вышла статья от Avast, которая описывала техники Lazarus и в частности 0-day уязвимость, которую использовал руткит. Уязвимость довольно простая, хотя все еще требует реверс-инжиниринга и обхода ряда проверок. Основной сложностью было найти подходящий kCFG-гаджет для эксплуатации.
kCFG позволяет отслеживать, верифицировать и прерывать непрямые вызовы.
Параллельно несколько человек уже разрабатывало PoC и чтобы не повторяться было решено использовать не kCFG гаджет.
Получается, что мы можем точно обнулить
CVE-2024-26229
Уязвимость изначально обнаружена Эриком Эгсгардом и описана в докладе на конференции OffensiveCon. Microsoft в бюллетени безопасности пометил уязвимость следующим образом.
Из самого доклада становится ясно, что уязвимость - это CWE-781: Improper Address Validation in IOCTL with METHOD_NEITHER I/O Control Code. При этом методе передачи данных входящий адрес не валидируется I/O Manager'ом. Сделано это в целях повышения производительности, а использование этого метода можно встретить, например, в сетевых драйверах.
На изображении ниже PoC, который продемонстрировал Эрик. При компиляции и запуске кода вызвать уязвимость не получится из-за ошибки в коде.
Уязвимость была обнаружена Angelboy из DEVCORE, продемонстрирована на Pwn2Own Vancouver 2024 и описана в блогекомпании в рамках одного большого исследования, посвященного логическим уявимостям в Kernel Streaming. Из данного источника об уязвимости было известно следующее.
Далее оно еще раз инициализируется как
Вернемся к псевдокоду фукнции
Нам снова необходимо найти гаджеты для обхода kCFG. В оригинальном исследовании Angelboy использовал гаджет
Для разнообразия была добавлена поддержка перезаписи
Ссылка на pdf - http://**************************************************************/b/DPYGPuhUGduurEWaZf7K2
(отдельное спасибо ordinaria1 за создание pdf-файла)
Введение
В этой небольшой статье я бы хотел поделиться опытом эксплуатации 1-day уязвимостей в нативных компонентах Windows - ее драйверах. За основу были взяты 4 уязвимости в
appid.sys, csc.sys, afd.sys, ks.sys. Для CVE-2024-26229, CVE-2024-35250, CVE-2024-21338 мною были опубликованы PoC, которые добавлены или находятся в очереди на добавление во фреймворки постэксплуатации, такие как Cobalt Strike, Brute Ratel, Metasploit Framework и другие инструменты. Для CVE-2023-21768 PoC был опубликован другими исследователями, но я решил включить эту уязвимость в доклад из-за различий в подходах к эксплуатации. На ее примере будут описаны три техники LPE, которые будут использоваться в дальнейшем. Следующие типы уязвимостей будут представлены.- CWE-822: Untrusted Pointer Dereference
- CWE-781: Improper Address Validation in IOCTL with METHOD_NEITHER I/O Control Code
Для чего разрабатывать 1-day эксплойты?
По сравнению с поиском 0-day разработка 1-day эксплойтов менее трудозатратна и позволяет атакующим расширить свой арсенал, повысив тем самым эффективность.
Работая с 1-day уязвимостями, исследователь совершенствует свои навыки статического, динамического анализа, а также навыки программирования.
В случае с разработкой проактивных решений для эксплойта исследователь также совершенствует техники обхода зищитных систем, что позволяет минимизировать риски обнаружения и повысить эффективность эксплойта. При анализе 1-day не исключена возможность обхода патча или обнаружения 0-day.
Согласно недавнему отчету Mandiant 30% эксплуатируемых уязвимостей in-the-wild - это 1-day уязвимости, успешная эксплуатация которых может происходить даже спустя полгода после выхода исправлений.
Рисунок 1 - Окно эксплуатации N-Day уязвимостей по данным Mandiant
Таким уязвимостям подвержены также End-Of-Life системы, информации о потенциальном количестве которых не много. Например, согласно информации из блога Государственного Университета Портлэнда40% их систем к октябрю 2025 года перестанут получать обновления для Windows 10 из-за устаревшего железа.
CVE-2023-21768
0-day для этой уязвимости использовался в дикой природе, а сама уязвимость была подробно описана в блоге IBM X-Force. PoC также был опубликован на GitHub. В оригинале авторы эксплуатировали подсистему I/O Ring для получения примитивов чтения и записи. Мы же доработали эксплойт и применили два других метода, один из которых был обнаружен совсем недавно и отлично вписывается в контекст уязвимости.
Начнем с того, что уязвимость позволяет осуществлять запись DWORD по произвольному указателю при удалении из очереди пакета для объекта I/O Completion Port.
Происходит это в цепочке вызовов
AfdNotifySock->AfdNotifyRemoveIoCompletion->IoRemoveIoCompletion.
C:
NTSTATUS __fastcall AfdNotifySock(
_FILE_OBJECT *FileObject,
__int64 a2,
KPROCESSOR_MODE PreviousMode,
AFD_NOTIFISOCK_DATA *user_UnkStruct, // 1. Attacker controlled input data
int nInBufferSize,
__int64 lpOutBuffer,
int nOutBufferSize)
// Skipped
status = AfdNotifyRemoveIoCompletion(PreviousMode, CompletionObject, user_UnkStruct);
// Skipped
}
NTSTATUS __fastcall AfdNotifyRemoveIoCompletion(
KPROCESSOR_MODE PreviousMode,
_KQUEUE *IoCompletion,
AFD_NOTIFISOCK_DATA *user_UnkStruct) // 2. Attacker controlled input data
{
nCompletionPck = 0; // 3. Initialization of nCompletionPck
// Skipped
RemovePackets:
status = IoRemoveIoCompletion(
IoCompletionObject,
OutBuf,
Pool,
dwLen,
&nCompletionPck, // 4. Saving value to the nCompletionPck
PreviousMode,
Timeout,
0);
// Skipped
*(_DWORD *)user_UnkStruct->pAcidPtr = nCompletionPck; // 5. Writing nCompletionPck to the attacker controlled pointer
// Skipped
}
Листинг 1 - Цепочка передачи пользовательских данных
На листинге выше мы можем видеть запись значения переменной nCompletionPck по указателю в структуре user_unkStruct, которую мы передаем драйверу. Откуда берется значение nCompletionPck?
C:
NTSTATUS __fastcall IoRemoveIoCompletion(
struct _KQUEUE *IoCompletionObject,
__int64 OutBuf,
PLIST_ENTRY *EntryArray,
ULONG dwLen,
ULONG *nCompletionPck,
KPROCESSOR_MODE PreviousMode,
LARGE_INTEGER *Timeout,
BOOLEAN somebool)
{
// Skipped
retVal = KeRemoveQueueEx(IoCompletionObject, PreviousMode, somebool, Timeout, EntryArray, dwLen);
// Skipped
*nCompletionPck = retVal;
//
Листинг 2 - Получение значения nCompletionPck
Значение nCompletionPck является результатом выполнения функции KeRemoveQueueEx и возвращает кол-во пакетов, убранных из очереди.Для вызова уязвимости необходимо:
- Cоздать объект I/O Completion и ассоциированный с ним IPv4 TCP сокет.
- Добавить в очередь N пакетов при помощи
PostQueuedCompletionStatus - Инициировать входящий буфер
AFD_NOTIFYSOCK_DATA - Отправить запрос драйверу с
IOCTL_AFD_NOTIFY_SOCK
NtCreateFile.
Листинг 3 - Получение описателя для TCP сокета
Объект I/O Completion Port cоздается при помощи функции CreateIoCompletionPort или NtCreateIoCompletion.На Рисунке 2 показано как происходит добавление пакетов в очередь.
Рисунок 2 - Добавление пакетов в очередь объекта I/O Completion
В оригинальном исследовании авторы производили запись значения 0x1, т.е. в очередь добавлялся всего один пакет. На первый взгляд сложно представить как можно такой ограниченный примитив "докрутить" до LPE, но данный подход был уже апробирован и отлично накладывается на технику с использование I/O Ring. Нам же изначально было любопытно возможно ли переписать PreviousMode на значение 0x0 и получить простой, мощный примитив на чтение и запись ядерной памяти. Для этого пришлось отредактировать код, добавив цикл с PostQueuedCompletionStatus, чтобы получить произвольный инкремент.
Листинг 4 - Цикл для добавления пакетов в очередь
Добавив цикл в оригинальный код мы можем приступить к эксплуатации.
Листинг 5 - Реализация интерфейса ArbitraryKernelWrite
Что нам дает перезапись PreviousMode? При вызове Nt* функций ядро производит валидацию входных параметров, поступающих из режима пользователя, основываясь на значении PreviousMode для текущего потока описываемого структурой KTHREAD. У этого поля есть два значения - KernelMode и UserMode которые равны 0 и 1 соответственно. Если значение PreviousMode == KernelMode, то проверки пользовательских указателей опускаются. В эксплойтах чаще всего используются функции NtReadVirtualMemory, NtWriteVirtualMemory для произвольнго чтения/записи виртуальной памяти. Возможно использование и других интересных функций, например, получение описателя устройства \Device\PhysicalMemory и вызов NtOpenSection, NtMapViewOfSection для чтения и записи физической памяти.Чтобы обнулить
PreviousMode мы должны записать значение 0x100 по смещению 0x232 структуры KTHREAD для текущего потока. Следовательно, мы должны добавить 0x100 пакетов в очередь.
Рисунок 3 - перезапись KTHREAD.PreviousMode
Стоит отметить, что это было не так очевидно на первый взгляд, т.к. не удалось найти примеров использования функции NtSetIoCompletion. С задачей помог справиться ChatGPT, который написал цикл с оберткой PostQueuedCompletionStatus и PoC сработал как от него и ожидалось.Для повышения привилегий мы будем использовать технику DKOM и чтобы завершить эксплойт нам потребуются ядерные адреса объектов
EPROCESS для системного и текущего процессов, KTHREAD для текущего потока. Получить их мы можем при помощи системной функции NtQuerySystemInformation с идентификатором SystemHandleInformation. Данная техника давно известна и широко применяется, поэтому не будем на ней акцентировать внимание.
Листинг 6 - Получение ядерных адресов объектов EPROCESS, KTHREAD
GetObjPtr в данном случае - это просто обертка над NtQuerySystemInformation, которая возвращает адрес объекта по идентификатору (PID) и описателю.
Листинг 7 - Перезапись значения токена текущего процесса на системный
Перезапись производится при помощи обертки для NtWriteVirtualMemory. В обязательном порядке следует восстановить значение PreviousMode, в противном случае при создании нового процесса произойдет краш системы. Восстановление значения BasePriority в данном случае необязательно и оно ни на что не влияет.
Листинг 8 - Реализация обертки Write64
На Рисунке 4 схематически изображен процесс подмены токена.
Рисунок 4 - Схема подмены токена
Рисунок 5 - Результат выполнения эксплойта
Еще два метода построены вокруг манипуляций с привилегиями токена. Рассмотрим их по порядку. В структуре токена есть поле Privileges, которое описывается другой структурой - _SEP_TOKEN_PRIVILEGES и имеет вид.
C:
typedef struct _SEP_TOKEN_PRIVILEGES
{
UINT64 Present;
UINT64 Enabled;
UINT64 EnabledByDefault;
} SEP_TOKEN_PRIVILEGES, *PSEP_TOKEN_PRIVILEGES;
Листинг 9 - Структура _SEP_TOKEN_PRIVILEGES
Зачастую для повышения привилегий используют добавление токену привилегии SeDebugPrivilege, т.к. эта привилегия позволяет атакующему [обойти любую проверку доступа при открытии объекта процесса или потока.](Windows Security Internals (James Forshaw)) Для успешной эксплуатации мы должны установить двадцатый бит полей Present и Enabled т.к. именно он ответственен за эту привилегию.
Рисунок 6 - Схема перезаписи полей Present/Enabled для текущего токена
Листинг 10 - Повышение привилегий при помощи перезаписи полей Present/Enabled
Следующая техника была изобретена недавно специалистами из DEVCORE и все также основана на получении привилегий SeDebugPrivilege. Как было уже показано в публикации DEVCORE SeDebugPrivilege - это константа типа LUID. Хранится она в секции ядра PAGEDATA, имеющей права на запись.
Рисунок 7 - Вывод декомпилятора функции SepVariableInitialization
Т.к. стандартный пользователь в системе имеет права SeChangeNotifyPrivilege (см. Изображение 4), то мы можем изменить значение SeDebugPrivilege с 0x14 на 0x17, тем самым пройдя проверку при открытии описателя привилегированного процесса и аналогично предыдущему способу получить шелл с правами nt authority/system.
Листинг 11 - Повышение привилегий при помощи перезаписи константы SeDebugPrivilege
Немаловажным фактором при использовании этого метода является то, что мы можем многократно вызывать ArbitraryKernelWrite без BSOD или других явных внешних признаков вмешательства, что обеспечивает эксплойту стабильность и возможность его модернизации.CVE-2024-21338 & CVE-2024-38041
В феврале 2024 года вышла статья от Avast, которая описывала техники Lazarus и в частности 0-day уязвимость, которую использовал руткит. Уязвимость довольно простая, хотя все еще требует реверс-инжиниринга и обхода ряда проверок. Основной сложностью было найти подходящий kCFG-гаджет для эксплуатации.
Листинг 12 - Arbitrary Pointer Dereference в драйвере appid.sys
kCFG позволяет отслеживать, верифицировать и прерывать непрямые вызовы.
Рисунок 7 - Схема kCFG
Когда указатель не проходит проверку kCFG в рантайме, то Windows завершает выполнение программы, таким образом прерывая попытки эксплуатации непрямых вызовов.
Рисунок 9 - Вывод секции GFID в утилите PEAnatomist
Параллельно несколько человек уже разрабатывало PoC и чтобы не повторяться было решено использовать не kCFG гаджет.
Рисунок 10 - Твит в одной запрещенной социальной сети
Критерии для гаджета были следующие:- Размер функции < 100 байт
- В функции должен быть вызов
memmove
PopEtDataSectionCopyData и здесь нужно остановиться для более детального рассмотрения уязвимости.
Рисунок 10 - Интерфейс плагина FindFunc
Листинг 13 - Псевдокод функции PopEtDataSectionCopyData при наложении данных для эксплуатации
В Листинге 13 указатель vuln_callback_addr принимает два аргумента, где controlled_arg - это указатель на копию пользовательского буфера в пространстве ядра - SMART_HASH_IMAGE_FILE, который мы передаем драйверу через интерфейс IoDeviceControl. Во втором аргументе находится указатель переменной some_val, значение которой равно 0x0. Это состояние мы и будем эксплуатировать.
C:
//
// Main exploit structures definition
//
typedef struct _VULN_CALLBACK_STRUCT
{
PVOID vuln_callback_addr;
}VULN_CALLBACK_STRUCT;
typedef struct _SMART_HASH_IMAGE_FILE
{
int64_t field0; // 0x00
unsigned __int64 dummy_f_obj; // 0x8 should containt a valid FILE_OBJECT pointer to avoid BSOD
VULN_CALLBACK_STRUCT *ptr; // 0x10 points to the struct which contain malicious callback function pointer
void *prev_mode; // 0x18 KTHREAD->PreviousMode address
}SMART_HASH_IMAGE_FILE;
Листинг 14 - Объявление структуры SMART_HASH_IMAGE_FILE
Чтобы успешно проэксплуатировать этот гаджет, мы должны уточнить состояние третьего аргумена на момент вызова коллбека, т.е. регистра r8, который равен 0x1.Получается, что мы можем точно обнулить
PreviousMode.Data->prev_modeсодержит указатель наKTHREAD.PreviousModeтекущего потока- По указателю
InBufнаходится значение 0x0 InBufSize= 0x1
0x100000000 и таким образом старший DWORD будет равен 0x1. В сумме с размером InBufSize значение всегда будет меньше младшего DWORD ядерного объекта.
Листинг 15 - Инициализация входящего буфера и вызов уязвимости
Рисунок 12 - Результат выполнения эксплойта
Недостатком этого гаджета помимо того, что эксплойт не будет работать с включенным HVCI является то, что код применим только на Windows 11, т.к. в Windows 10 размер структуры меньше и мы не можем контролировать адрес по смещению 0x18.CVE-2024-26229
Уязвимость изначально обнаружена Эриком Эгсгардом и описана в докладе на конференции OffensiveCon. Microsoft в бюллетени безопасности пометил уязвимость следующим образом.
- CWE-122: Heap-based buffer overflow
- Exploitation Less Likely
Из самого доклада становится ясно, что уязвимость - это CWE-781: Improper Address Validation in IOCTL with METHOD_NEITHER I/O Control Code. При этом методе передачи данных входящий адрес не валидируется I/O Manager'ом. Сделано это в целях повышения производительности, а использование этого метода можно встретить, например, в сетевых драйверах.
На изображении ниже PoC, который продемонстрировал Эрик. При компиляции и запуске кода вызвать уязвимость не получится из-за ошибки в коде.
Рисунок 13 - Листинг с PoC от Эрика
Передача пользовательских аргументов происходит через цепочку драйверов и структуру RX_CONTEXT. Валидация входящих аргументов происходит в драйвере rdbss.sys и показана на рисунке ниже.
Рисунок 14 - Схема передачи пользовательских данных и триггер уязвимости
Чтобы обойти проверку необходимо при вызове NtFsControlFile указать следующие аргументы.InputBufferLength!= 0InputBuffer!= 0OutputBufferLength= 0UnkVal= 0
OutputBufferLength. После этого эксплуатация становится тривиальной. Из Рисунка 14 следует, что мы можем записать 0 по произвольному адресу. Наиболее очевидным решением для эксплуатации уязвимости является обнуление PreviousMode.
Рисунок 15 - Повышение привилегий при помощи обнуления PreviousMode
Рисунок 16 - Результат выполнения эксплойта
CVE-2024-35250Уязвимость была обнаружена Angelboy из DEVCORE, продемонстрирована на Pwn2Own Vancouver 2024 и описана в блогекомпании в рамках одного большого исследования, посвященного логическим уявимостям в Kernel Streaming. Из данного источника об уязвимости было известно следующее.
- Уязвимость CWE-822: Untrusted Pointer Dereference
- Работа ограничена в среде Hyper-V
- Уязвимая функция
UnserializePropertySet - Непрямой вызов происходит в функции
CKSThunkDevice::CheckIrpForStackAdjustmentNative
Код:
1: kd> k
# Child-SP RetAddr Call Site
00 ffffea85`d3bdb1e8 fffff806`8b711f74 ksthunk!CKSThunkDevice::CheckIrpForStackAdjustmentNative
01 ffffea85`d3bdb1f0 fffff806`8b7113c6 ksthunk!CKSThunkDevice::DispatchIoctl+0xc4
02 ffffea85`d3bdb230 fffff806`8b711133 ksthunk!CKernelFilterDevice::DispatchIrp+0xa6
03 ffffea85`d3bdb290 fffff806`802cb875 ksthunk!CKernelFilterDevice::DispatchIrpBridge+0x13
04 ffffea85`d3bdb2c0 fffff806`8bf2a15a nt!IofCallDriver+0x55
05 ffffea85`d3bdb300 fffff806`8bf04a41 ks!KsSynchronousIoControlDevice+0x11a
06 ffffea85`d3bdb3a0 fffff806`8bf1e32b ks!UnserializePropertySet+0x165
07 ffffea85`d3bdb420 fffff806`8bf1d8de ks!KspPropertyHandler+0x6db
08 ffffea85`d3bdb490 fffff806`8bf1d0f7 ks!KspHandleAutomationIoControl+0xce
09 ffffea85`d3bdb530 fffff806`8bed5fca ks!KsDispatchIrp+0xf7
0a ffffea85`d3bdb5f0 fffff806`802cb875 ks!CKsDevice::PassThroughIrp+0x6a
0b ffffea85`d3bdb630 fffff806`8b711415 nt!IofCallDriver+0x55
0c ffffea85`d3bdb670 fffff806`8b711133 ksthunk!CKernelFilterDevice::DispatchIrp+0xf5
0d ffffea85`d3bdb6d0 fffff806`802cb875 ksthunk!CKernelFilterDevice::DispatchIrpBridge+0x13
0e ffffea85`d3bdb700 fffff806`806c2c70 nt!IofCallDriver+0x55
0f ffffea85`d3bdb740 fffff806`806c123c nt!IopSynchronousServiceTail+0x1d0
10 ffffea85`d3bdb7f0 fffff806`806bf516 nt!IopXxxControlFile+0x72c
11 ffffea85`d3bdba00 fffff806`8043d1e5 nt!NtDeviceIoControlFile+0x56
12 ffffea85`d3bdba70 00007ffb`afe2eee4 nt!KiSystemServiceCopyEnd+0x25
13 00000001`9b7cfb08 00007ffb`ad1ebc5b ntdll!NtDeviceIoControlFile+0x14
14 00000001`9b7cfb10 00007ffb`aefc27f1 KERNELBASE!DeviceIoControl+0x6b
15 00000001`9b7cfb80 00007ff6`e70e1b0a KERNEL32!DeviceIoControlImplementation+0x81
16 00000001`9b7cfbd0 000001a6`5f3dde70 CVE_2024_35250+0x1b0a
Листинг 16 - Цепочка вызова уязвимости
На рисунке ниже изображен псевдокод функции UnserializePropertySet, который отчасти дает представление о данных, которые мы должны передать драйверу для вызова уязвимости.
Рисунок 17 - Фрагмент псевдокода функции UnserializePropertyset
Логическая уязвимость заключается в некорректном установлении значения RequestorMode для Irp при вызове функции KsSynchronousIoControlDevice.
Рисунок 17 - Фрагмент псевдокода функции KsSynchronousIoControlDevice
При инциализации Irp в IoBuildDeviceIoControlRequest поле RequestorMode по умолчанию устанавливается как KernelMode.Далее оно еще раз инициализируется как
KernelMode и это проблема, т.к. в дальнейшем при вызове IofCallDriver драйвер будет оперировать недоверенными указателями, а проверки на RequestorMode будут опущены.
Код:
3: kd> dt nt!_IRP @rax
+0x000 Type : 0n6
+0x002 Size : 0x358
+0x004 AllocationProcessorNumber : 3
+0x006 Reserved : 0
+0x008 MdlAddress : (null)
+0x010 Flags : 0x60000
+0x018 AssociatedIrp : <unnamed-tag>
+0x020 ThreadListEntry : _LIST_ENTRY [ 0xffff800b`ea7cd3b0 - 0xffff800b`e970e580 ]
+0x030 IoStatus : _IO_STATUS_BLOCK
+0x040 RequestorMode : 0 ''
Листинг 17 - Поле RequestorMode установлено как 0 после вызова IoBuildDeviceIoControlRequest
После вызова IofCallDriver мы попадаем в обработчик IOCTL CKSThunkDevice::DispatchIoctl, где происходит валидация запроса, IOCTL из входящих параметров IRP и вызов обработчика CKSThunkDevice::CheckIrpForStackAdjustmentNative.
Рисунок 18 - Псевдокод функции CKSThunkDevice:
ispatchIoctl
Здесь цепочка уязвимости завершается. И снова, все проверки на RequestorMode опускаются и происходит непрямой вызов по недоверенному указателю, который мы контролируем во входящем буфере, также как и первый аргумент при условии, что InputBufferLenth >= 0x18.
Рисунок 19 - Псевдокод функции CKSThunkDevice::CheckIrpForStackAdjustmentNative
Немаловажной деталью здесь является проверка с KSPROPSETID_DrmAudioStream. Устройство, которому мы будем посылать запрос должно поддерживать этот набор свойств. Как узнать описатель какого устройства мы должны получить? Есть два неочевидных способа.- Программно через функцию
KsOpenDefaultDevicecKSCATEGORY_DRM_DESCRAMBLE - Найти полное имя устройства через утилиту
KsStudio
Рисунок 20 - Получение описателя устройства с набором свойств KSPROPSETID_DrmAudioStream
KsStudio имеет графический интерфейс и позволяет получить большое количество информации об устройствах, логгировать IRP и т.д. Категорий устройств, содержащих в своем названии DRM было не так много - всего одно.
Рисунок 21 - Интерфейс утилиты KsStudio
Рисунок 22 - Интерфейс утилиты KsStudio
На рисунке ниже утилита показывает какой набор свойств поддерживает устройство. Это устройство и будет целью при эксплуатации.
Рисунок 23 - Интерфейс утилиты KsStudio
Полное имя устройства, которому необходимо передать запрос - \\?\root#system#0000#{ffbb6e3f-ccfe-4d84-90d9-421418b03a8e}\{eec12db6-ad9c-4168-8658-b03daef417fe}&{abd61e00-9350-47e2-a632-4438b90c6641}. Это можно сделать напрямую через CreateFileW или, как упоминалось ранее, через KsOpenDefaultDevice.Вернемся к псевдокоду фукнции
UnserializePropertySet. В SystemBuffer хранятся данные не из входящего буфера, как это бывает зачастую. Перед вызовом UnserializePropertySet данные в SystemBuffer копируются из исходящего буфера.
Рисунок 24 - Фрагмент псевдокода функции
Для запроса с флагом свойства KspPropertyHandlerKSPROPERTY_TYPE_UNSERIALIZESET валидный исходящий и входящий буферы должны выглядеть следующим образом.
Рисунок 25 - Стукртура буферов UnserializePropertySetRequest и InBuffer
Структура буфера сериализации.- Заголовок
KSPROPERTY_SERIALHDR - Набор свойств для сериализации
KSPROPERTY_SERIAL - Какие-то данные
Рисунок 26 - Инициализация UnserializePropertySetRequest
Структура входящего буфера.- Структура
KSPROPERTY - Какие-то данные
Рисунок 27 - Инициализация InBuffer
То, как будут инициализироваться поля структур EXPLOIT_DATA1, EXPLOIT_DATA2 во входящем и исходящем буфере зависит от того, какую технику для повышения привилегий мы будем использовать.Нам снова необходимо найти гаджеты для обхода kCFG. В оригинальном исследовании Angelboy использовал гаджет
RtlSetAllBits для перезаписи SEP_TOKEN_PRIVILEGES для текущего токена.Для разнообразия была добавлена поддержка перезаписи
PreviousMode и гаджет RtlClearAllBits.
Рисунок 28 - Инициализация FakeBitmap и ptr_ArbitraryFunCall
Поле FakeBitmap описывается структурой RTL_BITMAP и содержит два поля - SizeOfBitMap и указатель Buffer. Т.к. мы полностью контролируем содержимое структуры, то можем осуществить запись по произвольному адресу в Buffer.
C:
typedef struct _RTL_BITMAP
{
DWORD SizeOfBitMap;
PVOID Buffer;
}RTL_BITMAP, *PRTL_BITMAP;
Листинг 18 - Структура RTL_BITMAP
Код повышения привилегий шаблонный и похож на то, что мы уже проделывали ранее.
Рисунок 29 - Повышение привилегий двумя техниками
По итогу при корректном вызове DeviceIoControl суммарно 10 проверок должно быть удовлетворено, не считая указания валидных гаджетов. Это был самый сложный 1-day, который мне приходилось реверсить из-за отсутствия примеров кода, запутанной работы самой технологии, сложностях при динамическом анализе. Например, мне так и не удалось поймать в системе вызовы обработчика UnserializePropertySet, чтобы подсмотреть стек вызовов и проанализировать аргументы. Основную часть работы пришлось делать в статике.
Последнее редактирование: