
Две уязвимости ядра в Razer Lycosa.sys (CWE-125 раскрытие памяти + CWE-121 переполнение стека), объединённые в цепочку для локального повышения привилегий. Материалы скоординированного раскрытия, проверено на Windows 11.
Обнаружено с помощью https://github.com/416rehman/DeepZero
Производитель: Razer Inc.
Компонент: Lycosa.sys, драйвер-фильтр клавиатуры Razer Lycosa, x64
SHA-256: a120a6184ab16864e8a5f1dfd0cd178fca541de463b5cef946e18c34b9b6f716
Ссылка для отчёта: a120a6184ab16864
Статус: производителю ещё не сообщено.
В этом каталоге описаны два различных дефекта в одной и той же процедуре одного и того же драйвера. У них разные первопричины и разные исправления, поэтому каждый имеет собственную самодостаточную папку и может отслеживаться и получать собственный идентификатор:
| CVE | Дефект | Тип | Последствие |
|---|---|---|---|
| CVE-01 | Длина вывода не проверяется относительно буфера, поэтому драйвер возвращает память стека ядра | CWE-125 чтение за границами | Раскрытие памяти ядра, обход рандомизации адресного пространства |
| CVE-02 | Длина ввода не проверяется относительно буфера, поэтому драйвер перезаписывает собственный адрес возврата | CWE-121 переполнение буфера стека | Выполнение произвольного кода в ядре |
Оба дефекта достижимы из любой учётной записи, которая может войти в систему и запустить программу. Без административных прав, без повышения привилегий, без особых полномочий. Оба подтверждены на Windows 11 25H2 (сборка 26200.8875) при включённой проверке целостности кода и выключенной проверке подписи тестовых драйверов.
В разделе 3 описано, что эти два дефекта дают при совместном использовании, — именно поэтому они описываются одновременно и почему объединённое proof of concept находится в этом корневом каталоге.
Оба находятся в обработчике IRP_MJ_DEVICE_CONTROL по RVA 0x1270, и оба
работают с одним и тем же буфером размером 0x400 байт на стеке ядра. Пролог,
прочитанный из поставляемого бинарника, задаёт геометрию для обоих:
Lycosa+0x1270 48 89 54 24 10 mov [rsp+10h], rdx ; Irp
Lycosa+0x1275 48 89 4c 24 08 mov [rsp+8], rcx ; DeviceObject
Lycosa+0x127a 48 81 ec 98 04 00 00 sub rsp, 498h ; the frame
Lycosa+0x1291 ba 00 04 00 00 mov edx, 400h ; the buffer size
Lycosa+0x1296 48 8d 8c 24 80 00 00 00 lea rcx, [rsp+80h] ; the buffer
Буфер размером 0x400 байт по адресу rsp+0x80 внутри кадра размером 0x498 байт,
без сохранения энергонезависимых регистров. Считая от начала буфера:
0x000 .. 0x3FF буфер, внутри которого должны оставаться оба дефекта
0x418 адрес возврата диспетчерской процедуры
0x420 сохранённый аргумент DeviceObject
0x428 сохранённый аргумент Irp
0x418 — это 0x498 - 0x80. Раскрытие (CVE-01) читает за пределами 0x3FF и
возвращает то, что находит; переполнение (CVE-02) пишет за пределами 0x3FF и
заменяет это.
DriverEntry создаёт устройство без дескриптора безопасности и публикует
для него символьную ссылку, поэтому оно доступно по \\.\Lycosa:
IoCreateDevice(param_1, 0x20, L"\\Device\\Lycosa", 0x22, 0, 0, &device);
IoCreateSymbolicLink(L"\\DosDevices\\Lycosa", L"\\Device\\Lycosa");
Каждый затронутый управляющий код декодируется как FILE_DEVICE_UNKNOWN,
METHOD_BUFFERED, FILE_ANY_ACCESS. FILE_ANY_ACCESS означает, что для дескриптора
не требуется наличие какого-либо конкретного права доступа, поэтому дескриптор
безопасности объекта устройства является единственной преградой, а он
предоставляет доступ всем.
Все результаты в этих отчётах получены из учётной записи обычного пользователя,
единственным членством в группе которой является встроенная группа Users. У
учётной записи не было административных прав, она не была повышена и не имела
полномочий сверх значений по умолчанию.
Драйвер также загружается на машинах, к которым никогда не подключалось оборудование Razer, поскольку пакет корректно подписан каталогом. Это тот самый шаблон, который используется в атаках bring-your-own-vulnerable-driver.
Они описаны отдельно, потому что это отдельные дефекты, но производитель, занимающийся их разбором, должен знать, что каждый усугубляет другой.
Современная Windows загружает ядро по рандомизированному адресу. Атакующий, который может перезаписать адрес возврата, всё ещё должен знать, чем его перезаписать, и обычно именно это является препятствием. Этот драйвер сам отвечает на оба вопроса:
CVE-01 устраняет рандомизацию. Раскрытие возвращает собственный адрес
возврата диспетчерской процедуры — адрес кода внутри ntoskrnl.exe. Вычитание
его известного смещения внутри образа даёт базовый адрес, по которому загружено
ядро, а оттуда становится известен каждый адрес внутри ядра. Это ничего не
стоит и ничего не нарушает.
CVE-01 также предоставляет значение, необходимое CVE-02, чтобы не вызвать
сбой. Как описано в разделе 4.4 CVE-02,
драйвер на выходе перезагружает Irp из смещения 0x428 и пишет через него.
Наивное переполнение, достигающее адреса возврата, также уничтожает этот
указатель и вызывает сбой до возврата из процедуры. Надёжный эксплойт вместо
этого останавливает копирование точно на 0x428, оставляя на месте живой Irp
текущего запроса, так что драйвер завершается нормально.
CVE-02 затем перенаправляет выполнение, при уже известном базовом адресе ядра.
Из непривилегированной учётной записи объединённое proof of concept читает кадр, вычисляет базовый адрес ядра и собственный адрес буфера в ядре и отправляет return-oriented цепочку:
step 1, read what is above the buffer on the kernel stack:
+0x418 return address 0xFFFFF807D565CABB
+0x498 frame pointer 0xFFFFFD042D313750 (read twice, must match)
step 2, turn those into the two addresses the payload needs:
kernel base = 0xFFFFF807D565CABB - 0x25CABB = 0xFFFFF807D5400000
buffer on stack = 0xFFFFFD042D313750 - 0x500 = 0xFFFFFD042D313250
step 4, overflow with a chain that:
pivots the stack onto the buffer, calls nt!ZwCreateFile, and
resumes nt!IopfCallDriver+0x5b
sending 0x428 bytes
call returned: accepted=true error=0
PROOF: C:\Windows\System32\dz_lycosa_kernel_exec.txt now exists.
Это полная цепочка, проверенная от начала до конца. Непривилегированная учётная
запись создала файл в C:\Windows\System32 — каталоге, на запись в который ей
иначе отказано, — путём выполнения nt!ZwCreateFile в режиме ядра. accepted=true
означает, что системный вызов вернулся нормально: цепочка возобновляет выполнение
точно с того адреса, на который драйвер собирался вернуться (nt!IopfCallDriver+0x5b,
то есть add rsp,0x38 ; ret), поэтому поток завершается и машина продолжает
работать. Созданный файл был независимо подтверждён из административной оболочки.
Полная расшифровка находится в
logs/exec_create_file.log, а метод — в
METHODOLOGY.md.
Практический вывод таков: этот один драйвер предоставляет любому пользователю машины обе половины того, что обычно требуется, чтобы превратить дефект безопасности памяти в выполнение кода в ядре: раскрытие адресов, устраняющее рандомизацию, и перехват потока управления, который его использует. Вместе они, как было показано, дают конкретное привилегированное действие при продолжающей работать машине.
Оба исправления независимы и оба невелики, полностью изложены в каждом отчёте:
OutputBufferLength и устанавливать IoStatus.Information в
фактически полученное значение.InputBufferLength.Оба отчёта также рекомендуют создавать управляющее устройство с помощью
IoCreateDeviceSecure и строки SDDL, ограничивающей его администраторами и
системой. Это само по себе не исправило бы ни один из дефектов, но устранило бы
непривилегированную достижимость, которая придаёт обоим их серьёзность.
Проверено перед написанием отчёта, поскольку дублирующий отчёт тратит время производителя:
https://aka.ms/VulnerableDriverBlockList и проверенный по всем его 1 713
правилам запрета. Этот файл в нём не встречается. Единственный драйвер Razer в
этом списке — Rzpnk.sys, другой компонент.Мы полагаем, что оба дефекта ранее не описывались, и будем признательны за исправления.
Несколько других драйверов, поставляемых в том же каталоге пакета, имеют ту же общую форму, что и этот драйвер, и рассматриваются отдельно. Ничто здесь не является утверждением о них.
pnputil /add-driver Flter2K.inf /install
sc create lycosa_test type= kernel binPath= C:\path\to\Lycosa.sys start= demand
sc start lycosa_test
У каждого дефекта есть собственное узкоспециализированное proof of concept в его папке, а в этом корневом каталоге находится объединённое, которое их сочетает:
# CVE-01, reads only, safe to run anywhere, quickest confirmation of the report
rustc -O CVE-01-kernel-memory-disclosure/poc/lycosa_disclosure.rs -o disc.exe
disc.exe
# CVE-02, stops the machine by design
rustc -O CVE-02-kernel-stack-overflow/poc/lycosa_overflow.rs -o ovf.exe
ovf.exe --yes-crash-this-machine
# the chain: CVE-01 + CVE-02 into a file created in System32, machine left running
rustc -O poc/lycosa_chain.rs -o chain.exe
chain.exe --exec # default target under System32
chain.exe --exec C:\Users\Public\proof.txt # or any path you choose
Запускайте всё из учётной записи обычного пользователя. Объединённое PoC
указывает две константы драйвера, от которых зависит (FRAME и BUF_AT), в
начале своего исходного кода, поэтому его можно нацелить на другую сборку
драйвера, изменив эти два числа. Смещения ядра, используемые его режимом
--exec, специфичны для одной сборки Windows; программа проверяет вычисленный
базовый адрес ядра во время выполнения, а её режим --calibrate сообщает
единственное значение, которое меняется между сборками.
README.md this overview and the chaining analysis
METHODOLOGY.md how both were found and confirmed, in order
poc/lycosa_chain.rs the CHAINED proof of concept (both defects)
evidence/ the binary, its package, decompiled sources, dumps
logs/exec_create_file.log transcript of the chained run in section 3
CVE-01-kernel-memory-disclosure/ standalone disclosure for the out-of-bounds read
README.md, poc/lycosa_disclosure.rs, evidence/, logs/
CVE-02-kernel-stack-overflow/ standalone disclosure for the stack overflow
README.md, poc/lycosa_overflow.rs, evidence/, logs/