
Duas vulnerabilidades de kernel no Razer Lycosa.sys (divulgação de memória CWE-125 + estouro de pilha CWE-121) encadeadas para escalonamento local de privilégios. Materiais de divulgação coordenada, verificados no Windows 11.
Descoberto através de https://github.com/416rehman/DeepZero
Fornecedor: Razer Inc.
Componente: Lycosa.sys, o driver de filtro do teclado Razer Lycosa, x64
SHA-256: a120a6184ab16864e8a5f1dfd0cd178fca541de463b5cef946e18c34b9b6f716
Referência do relator: a120a6184ab16864
Status: ainda não reportado ao fornecedor.
Este diretório reporta dois defeitos distintos na mesma rotina do mesmo driver. Eles têm causas-raiz separadas e correções separadas, então cada um tem sua própria pasta autocontida e pode ser rastreado e receber seu próprio identificador:
| CVE | Defeito | Tipo | Resultado |
|---|---|---|---|
| CVE-01 | O comprimento de saída não é verificado em relação ao buffer, então o driver retorna memória da pilha do kernel | CWE-125 leitura fora dos limites | Divulgação de memória do kernel, derrota a randomização do layout do espaço de endereços |
| CVE-02 | O comprimento de entrada não é verificado em relação ao buffer, então o driver sobrescreve seu próprio endereço de retorno | CWE-121 estouro de buffer de pilha | Execução arbitrária de código no kernel |
Ambos são acessíveis a partir de qualquer conta que consiga fazer login e executar um programa. Sem direitos administrativos, sem elevação, sem privilégio especial. Ambos foram confirmados no Windows 11 25H2 (build 26200.8875) com integridade de código imposta e test signing desativado.
A seção 3 descreve o que os dois representam quando usados juntos, que é a razão pela qual são reportados ao mesmo tempo e por que a prova de conceito encadeada está neste diretório raiz.
Ambos residem no handler IRP_MJ_DEVICE_CONTROL em RVA 0x1270, e ambos atuam
no mesmo buffer de 0x400 bytes na pilha do kernel. O prólogo, lido do
binário distribuído, fixa a geometria para ambos:
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
Um buffer de 0x400 bytes em rsp+0x80, dentro de um frame de 0x498 bytes, sem registradores
não voláteis salvos. Contando a partir do início do buffer:
0x000 .. 0x3FF the buffer, which both defects are supposed to stay inside
0x418 the return address of the dispatch routine
0x420 the saved DeviceObject argument
0x428 the saved Irp argument
0x418 é 0x498 - 0x80. A divulgação (CVE-01) lê além de 0x3FF e devolve
o que encontra; o estouro (CVE-02) escreve além de 0x3FF e o substitui.
DriverEntry cria o dispositivo com nenhum descritor de segurança e publica um
link simbólico para ele, então é acessível em \\.\Lycosa:
IoCreateDevice(param_1, 0x20, L"\\Device\\Lycosa", 0x22, 0, 0, &device);
IoCreateSymbolicLink(L"\\DosDevices\\Lycosa", L"\\Device\\Lycosa");
Todo código de controle afetado decodifica como FILE_DEVICE_UNKNOWN, METHOD_BUFFERED,
FILE_ANY_ACCESS. FILE_ANY_ACCESS significa que nenhum direito de acesso específico precisa ser
mantido no handle, então o descritor de segurança do objeto de dispositivo é o único portão, e
ele concede acesso a todos.
Todos os resultados nestes relatórios foram produzidos a partir de uma conta de usuário padrão cuja
única associação de grupo é o grupo interno Users. A conta não tinha
direitos administrativos, não estava elevada e não possuía privilégios além dos
padrões.
O driver também carrega em máquinas que nunca tiveram hardware Razer conectado, porque o pacote é validamente assinado por catálogo. Esse é o padrão usado em ataques bring-your-own-vulnerable-driver.
Reportados separadamente porque são defeitos separados, mas um fornecedor que os triagem deve saber que cada um piora o outro.
O Windows moderno carrega o kernel em um endereço randomizado. Um atacante que consegue sobrescrever um endereço de retorno ainda precisa saber com o que sobrescrevê-lo, e esse é normalmente o obstáculo. Este driver responde ambas as perguntas por conta própria:
CVE-01 remove a randomização. A divulgação retorna o próprio endereço de retorno da
rotina de dispatch, um endereço de código dentro de ntoskrnl.exe. Subtraindo
seu offset conhecido dentro da imagem obtém-se a base na qual o kernel é carregado,
e a partir daí todo endereço dentro do kernel é conhecido. Isso não custa nada e
não perturba nada.
CVE-01 também fornece o valor que CVE-02 precisa para não falhar. Como
descrito em CVE-02 seção 4.4,
o driver recarrega o Irp do offset 0x428 em sua saída e escreve
através dele. Um estouro ingênuo que alcança o endereço de retorno também destrói esse
ponteiro e falha antes que a rotina retorne. O exploit confiável, em vez disso,
interrompe a cópia exatamente em 0x428, deixando o Irp vivo da requisição
atual no lugar, então o driver completa normalmente.
CVE-02 então redireciona a execução, com a base do kernel já conhecida.
A partir de uma conta sem privilégios, a prova de conceito encadeada lê o frame, calcula a base do kernel e o próprio endereço de kernel do buffer, e envia uma cadeia orientada a retorno:
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.
Esta é a cadeia completa, verificada de ponta a ponta. A conta sem privilégios criou
um arquivo em C:\Windows\System32, um diretório ao qual de outra forma é negado acesso de escrita,
executando nt!ZwCreateFile em modo kernel. accepted=true significa
que a chamada de sistema retornou normalmente: a cadeia retoma o endereço exato para o qual o driver
iria retornar (nt!IopfCallDriver+0x5b, que é add rsp,0x38 ; ret),
então a thread termina e a máquina continua funcionando. O arquivo criado foi
confirmado independentemente a partir de um shell de administrador. A transcrição completa está em
logs/exec_create_file.log, e o método está em
METHODOLOGY.md.
A leitura prática é que este único driver fornece, a qualquer usuário da máquina, ambas as metades do que normalmente é necessário para transformar um defeito de segurança de memória em execução de código no kernel: a divulgação de endereço que remove a randomização, e o sequestro de fluxo de controle que a utiliza. Juntos, foi demonstrado que produzem uma ação privilegiada concreta com a máquina deixada em execução.
As duas correções são independentes e ambas pequenas, declaradas integralmente em cada relatório:
OutputBufferLength, e definir IoStatus.Information para o que foi realmente
produzido.InputBufferLength.Ambos os relatórios também recomendam criar o dispositivo de controle com
IoCreateDeviceSecure e uma string SDDL restringindo-o a administradores e ao
sistema. Isso por si só não corrigiria nenhum dos defeitos, mas removeria o
alcance sem privilégios que dá a ambos sua severidade.
Verificado antes de escrever, porque um relatório duplicado desperdiça o tempo de um fornecedor:
https://aka.ms/VulnerableDriverBlockList e pesquisada em suas 1.713 regras de
negação. Este arquivo não aparece. O único driver Razer nessa lista é
Rzpnk.sys, um componente diferente.Acreditamos que ambos os defeitos são previamente não reportados e agradeceríamos correções.
Vários outros drivers distribuídos no mesmo diretório de pacote compartilham a forma geral deste driver e são examinados separadamente. Nada aqui é uma declaração sobre eles.
pnputil /add-driver Flter2K.inf /install
sc create lycosa_test type= kernel binPath= C:\path\to\Lycosa.sys start= demand
sc start lycosa_test
Cada defeito tem sua própria prova de conceito de propósito único em sua pasta, e esta raiz contém a encadeada que os combina:
# 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
Execute tudo a partir de uma conta de usuário padrão. A PoC encadeada declara as duas
constantes do driver das quais depende (FRAME e BUF_AT) no topo de seu código-fonte,
então ela pode ser apontada para uma build diferente do driver alterando esses dois números.
Os offsets de kernel que seu modo --exec usa são específicos de uma build do Windows; o
programa verifica a base do kernel derivada em tempo de execução e seu modo --calibrate
reporta o único valor que muda entre builds.
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/