
Deux vulnérabilités du noyau dans Razer Lycosa.sys (divulgation de mémoire CWE-125 + dépassement de pile CWE-121) enchaînées pour une élévation de privilèges locale. Documents de divulgation coordonnée, vérifiés sur Windows 11.
Découvertes via https://github.com/416rehman/DeepZero
Éditeur : Razer Inc.
Composant : Lycosa.sys, le pilote de filtre du clavier Razer Lycosa, x64
SHA-256 : a120a6184ab16864e8a5f1dfd0cd178fca541de463b5cef946e18c34b9b6f716
Référence du rapporteur : a120a6184ab16864
Statut : pas encore signalé à l'éditeur.
Ce répertoire documente deux défauts distincts dans la même routine du même pilote. Ils ont des causes racines et des correctifs distincts, chacun dispose donc de son propre dossier autonome et peut être suivi et se voir attribuer son propre identifiant :
| CVE | Défaut | Type | Conséquence |
|---|---|---|---|
| CVE-01 | La longueur de sortie n'est pas vérifiée par rapport au tampon, le pilote renvoie donc de la mémoire de pile du noyau | CWE-125 lecture hors limites | Divulgation de mémoire du noyau, contourne la randomisation de l'espace d'adressage |
| CVE-02 | La longueur d'entrée n'est pas vérifiée par rapport au tampon, le pilote écrase donc sa propre adresse de retour | CWE-121 débordement de tampon de pile | Exécution de code arbitraire dans le noyau |
Les deux sont accessibles depuis tout compte capable de se connecter et d'exécuter un programme. Aucun droit administratif, aucune élévation, aucun privilège particulier. Les deux ont été confirmés sur Windows 11 25H2 (build 26200.8875) avec l'intégrité du code appliquée et la signature de test désactivée.
La section 3 décrit ce que les deux représentent lorsqu'ils sont utilisés ensemble, ce qui explique pourquoi ils sont signalés en même temps et pourquoi la preuve de concept chaînée se trouve dans ce répertoire racine.
Les deux se trouvent dans le gestionnaire IRP_MJ_DEVICE_CONTROL à RVA 0x1270, et tous deux agissent sur le même tampon de 0x400 octets sur la pile du noyau. Le prologue, lu depuis le binaire livré, fixe la géométrie pour les deux :
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
Un tampon de 0x400 octets à rsp+0x80, dans une trame de 0x498 octets, sans aucun registre non volatil sauvegardé. En comptant depuis le début du tampon :
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 correspond à 0x498 - 0x80. La divulgation (CVE-01) lit au-delà de 0x3FF et renvoie ce qu'elle y trouve ; le débordement (CVE-02) écrit au-delà de 0x3FF et le remplace.
DriverEntry crée le périphérique sans descripteur de sécurité et publie un lien symbolique pour celui-ci, il est donc accessible à \\.\Lycosa :
IoCreateDevice(param_1, 0x20, L"\\Device\\Lycosa", 0x22, 0, 0, &device);
IoCreateSymbolicLink(L"\\DosDevices\\Lycosa", L"\\Device\\Lycosa");
Chaque code de contrôle affecté se décode comme FILE_DEVICE_UNKNOWN, METHOD_BUFFERED, FILE_ANY_ACCESS. FILE_ANY_ACCESS signifie qu'aucun droit d'accès particulier ne doit être détenu sur le handle, le descripteur de sécurité de l'objet périphérique est donc la seule barrière, et il accorde l'accès à tout le monde.
Tous les résultats de ces rapports ont été produits depuis un compte utilisateur standard dont la seule appartenance à un groupe est le groupe intégré Users. Le compte n'avait aucun droit administratif, n'était pas élevé et ne détenait aucun privilège au-delà des valeurs par défaut.
Le pilote se charge également sur des machines qui n'ont jamais eu de matériel Razer connecté, car le paquet est signé par catalogue de manière valide. C'est le schéma utilisé dans les attaques de type bring-your-own-vulnerable-driver.
Signalés séparément parce qu'il s'agit de défauts distincts, mais un éditeur qui les trie devrait savoir que chacun aggrave l'autre.
Les Windows modernes chargent le noyau à une adresse randomisée. Un attaquant capable d'écraser une adresse de retour doit encore savoir par quoi l'écraser, et c'est normalement là l'obstacle. Ce pilote répond lui-même aux deux questions :
CVE-01 supprime la randomisation. La divulgation renvoie l'adresse de retour de la routine de dispatch elle-même, une adresse de code à l'intérieur de ntoskrnl.exe. En soustrayant son décalage connu dans l'image, on obtient la base à laquelle le noyau est chargé, et à partir de là, toutes les adresses à l'intérieur du noyau sont connues. Cela ne coûte rien et ne perturbe rien.
CVE-01 fournit aussi la valeur dont CVE-02 a besoin pour ne pas planter. Comme décrit dans CVE-02 section 4.4,
le pilote recharge l'Irp depuis l'offset 0x428 sur son chemin de sortie et écrit à travers lui. Un débordement naïf qui atteint l'adresse de retour détruit aussi ce pointeur et plante avant que la routine ne retourne. L'exploit fiable arrête au contraire la copie exactement à 0x428, laissant l'Irp vivant de la requête en cours en place, de sorte que le pilote se termine normalement.
CVE-02 redirige ensuite l'exécution, la base du noyau étant déjà connue.
Depuis un compte non privilégié, la preuve de concept chaînée lit la trame, calcule la base du noyau et l'adresse noyau du tampon lui-même, et envoie une chaîne orientée retour :
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'est la chaîne complète, vérifiée de bout en bout. Le compte non privilégié a créé un fichier sous C:\Windows\System32, un répertoire auquel l'accès en écriture lui est autrement refusé, en exécutant nt!ZwCreateFile en mode noyau. accepted=true signifie que l'appel système est retourné normalement : la chaîne reprend exactement à l'adresse à laquelle le pilote allait retourner (nt!IopfCallDriver+0x5b, qui est add rsp,0x38 ; ret), le thread se termine donc et la machine continue de fonctionner. Le fichier créé a été confirmé indépendamment depuis un shell administrateur. La transcription complète se trouve dans logs/exec_create_file.log, et la méthode dans METHODOLOGY.md.