
Доказательство концепции CVE-2025-24990 (драйвер Agere Systems)
ltmdm64.sys). Этот драйвер очень старый и не загружается по умолчанию на моей тестовой машине, поэтому я буду эксплуатировать его в сценарии BYOVD. Интересно, что, согласно моим исследованиям, этот драйвер существовал на Windows 7 и имеет как минимум одну ошибку. здесь
В то время MSRC не предпринял никаких действий 🤡
Некоторые IOCTL в этом драйвере используют METHOD_NEITHER, но не проверяют, находится ли буфер адреса, предоставленный вызывающим абонентом, в пользовательском или режиме ядра. Вот пример кода IOCTL, который я декодировал с помощью OSR:
Это означает, что вы можете передать адрес ядра в API DeviceIoControl, и драйвер обработает его обычным образом.
Обратите внимание, что сначала нужно обойти kASLR, чтобы утечь адрес ядра. Я буду использовать EnumDeviceDrivers (в Windows 24h2 для этого нужен SeDebugPriv).
Проблема в IOCTL 0x802b200f (ud_response). Опять же, этот диспетчер IOCTL не проверяет адрес, который я передаю из пользовательского режима, но я воспользуюсь этим позже.
ud_response вызывает ll_load_diagnostics, и я достигну следующего кода:
В начале глобальная переменная eeprom не инициализирована, поэтому она будет содержать NULL. Вот простой код, который вызовет это.
Я воспользуюсь этим позже.
Этот IOCTL просто преобразует строку версии драйвера "8.36" в число 0x836 (тип DWORD) и записывает его по адресу, предоставленному вызывающим абонентом (благодаря METHOD_NEITHER). Технически я могу записать эти четыре байта (36 08 00 00) по произвольному адресу ядра. Я воспользуюсь этим, чтобы перезаписать глобальные переменные драйвера и изменить поток выполнения.
Я буду называть этот 0x802b2003 IOCTL_GET_VERSION
Произвольный нулевой 1 байт:
Возвращаясь к случаю разыменования NULL, я использую API VirtualAlloc для выделения фиксированного адреса (0x083600000000). Затем я использую IOCTL_GET_VERSION, чтобы записать в *(eeprom + 4) четыре байта, описанные выше. Когда драйвер позже разыменует eeprom, он прочитает данные по адресу, который я выделил.
После исправления разыменования NULL, IOCTL записывает строку по адресу, который я передаю из пользовательского режима, на основе размера буфера.
Этот код просто демонстрирует то, что я описал выше: выделяет буфер и заполняет его 0xAA, исправляет разыменование NULL, затем вызывает драйвер. Обратите внимание, что я выделяю 11 байт, но передаю драйверу размер буфера только 10, чтобы посмотреть, как он себя поведет.
Он записывает фиксированную последовательность байтов в мой буфер, а затем обнуляет последний байт (11-й), даже если я предоставляю размер только 10. Он заменяет последний 0xAA в моем буфере на 0x00. Это указывает на то, что если я укажу размер 0, драйвер все равно запишет один байт 0x00 по целевому адресу.
Произвольное уменьшение
Теперь у меня есть нулевой и произвольный с фиксированными 4 байтами, давайте создадим другой примитив.
Этот IOCTL установит глобальный LtMsgEvent в мой пользовательский буфер, затем проверит, является ли WDM нулевым, и снова установит ноль.
Затем в 0x802b2207 он вызовет API ObfReferenceObject.
В начальном состоянии WDM равен нулю, но с помощью IOCTL_GET_VERSION я могу установить WDM в 0x36 (его размер всего 1 байт), а LtMsgEvent все еще мой буфер. Затем я обнулю WDM и вызову 0x802b2207. Наконец, достигну ObfReferenceObject. Я буду называть эти два ioctl IOCTL_SET_LtMsgEvent и IOCTL_DEREF_LtMsgEvent.
Техника эксплойта с использованием ObfReferenceObject изменяет PreviousMode нашего KTHREAD с UserMode на KernelMode. Вы можете прочитать об этом здесь). Однако Windows исправила этот эксплойт, поэтому мы не можем его использовать.
Но примитив в ObfReferenceObject все еще существует. API вычитает 0x30 из предоставленного нами адреса, приводит результат к 8-байтовому целому числу, а затем вычитает 1.
*(signed long long)(LtMsgEvent-0x30) -= 1
Но проблема в том, что он проверяет, является ли следующее значение 0 или текущее значение < 1 (интерпретируется как 8-байтовое целое число со знаком). Если какое-либо из условий истинно, он переходит к KeBugCheckEx и вызывает сбой системы.
Произвольная запись
Имея в руках произвольное уменьшение, мне нужно найти другое место для записи байта 0xFF, а затем уменьшить его до нужного мне байта, и я нашел этот ioctl 0x802b2243: