
Preuve de concept CVE-2025-24990 (pilote d'Agere Systems)
Pilote du modem Agere Windows (ltmdm64.sys). Ce pilote est très ancien et n'est pas chargé par défaut sur ma machine de test, je vais donc l'exploiter dans un scénario BYOVD. Fait intéressant, selon mes recherches, ce pilote existe depuis Windows 7 et possède au moins un bug. ici
À cette époque, le MSRC n'a pris aucune mesure 🤡
Certains IOCTL de ce pilote utilisent METHOD_NEITHER mais ne vérifient pas si le tampon d'adresse fourni par l'appelant provient du mode utilisateur ou du mode noyau. Voici un exemple de code IOCTL que j'ai décodé avec OSR :
Cela signifie que vous pouvez fournir une adresse noyau à l'API DeviceIoControl et le pilote la traitera normalement.
Notez que vous devez d'abord contourner kASLR pour divulguer l'adresse noyau. J'utiliserai EnumDeviceDrivers (sur Windows 24h2, vous avez besoin de SeDebugPriv pour cela).
Le problème se trouve dans l'IOCTL 0x802b200f (ud_response). Encore une fois, ce dispatch d'IOCTL ne valide pas l'adresse que je fournis depuis le mode utilisateur, mais je l'exploiterai plus tard.
Le ud_response appelle ll_load_diagnostics, et j'atteindrai le code suivant :
Au début, la variable globale eeprom n'est pas initialisée, elle contiendra donc NULL. Voici un code simple qui déclenchera cela.
Je l'exploiterai plus tard.
Cet IOCTL convertit simplement la chaîne de version du pilote "8.36" en nombre 0x836 (un DWORD) et l'écrit à l'adresse fournie par l'appelant (grâce à METHOD_NEITHER). Techniquement, je peux écrire ces quatre octets (36 08 00 00) à une adresse noyau arbitraire. J'exploiterai cela pour écraser les variables globales du pilote et modifier le flux d'exécution.
J'appellerai cet IOCTL 0x802b2003 IOCTL_GET_VERSION.
Octet nul arbitraire :
En revenant au cas du déréférencement nul, j'utilise l'API VirtualAlloc pour allouer une adresse fixe (0x083600000000). J'utilise ensuite IOCTL_GET_VERSION pour écrire dans *(eeprom + 4) les quatre octets décrits ci-dessus. Lorsque le pilote déréférence ensuite eeprom, il lira depuis l'adresse que j'ai allouée.
Après avoir corrigé le déréférencement nul, l'IOCTL écrit une chaîne à l'adresse que je fournis depuis le mode utilisateur, en fonction de la taille du tampon.
Ce code démontre simplement ce que j'ai décrit ci-dessus : allouer un tampon et le remplir avec 0xAA, corriger le déréférencement nul, puis appeler le pilote. Notez que j'alloue 11 octets mais ne fournis qu'une taille de tampon de 10 au pilote pour observer son comportement.
Il écrit une séquence fixe d'octets dans mon tampon puis met à zéro le dernier octet (le 11ᵉ), même si je ne fournis qu'une taille de 10. Il remplace le dernier 0xAA de mon tampon par 0x00. Cela indique que si je fournis une taille de 0, le pilote écrit quand même un seul octet 0x00 à l'adresse cible.
Décrémentation arbitraire
Maintenant que j'ai l'octet nul et l'écriture arbitraire avec 4 octets fixes, construisons une autre primitive.
Cet IOCTL définira la variable globale LtMsgEvent sur mon tampon utilisateur, puis vérifiera si WDM est nul et le remettra à zéro.
Ensuite, dans 0x802b2207, il appellera l'API ObfReferenceObject.
À l'état initial, WDM est nul, mais avec l'aide de IOCTL_GET_VERSION, je peux définir WDM sur 0x36 (sa taille n'est que d'un octet) et LtMsgEvent reste mon tampon. Ensuite, je mets WDM à zéro et j'appelle 0x802b2207. Enfin, j'atteins ObfReferenceObject. J'appellerai ces deux IOCTL IOCTL_SET_LtMsgEvent et IOCTL_DEREF_LtMsgEvent.
La technique d'exploitation utilisant ObfReferenceObject modifie le PreviousMode de notre KTHREAD de UserMode à KernelMode ; vous pouvez en lire plus ici. Cependant, Windows a corrigé cet exploit, nous ne pouvons donc pas l'utiliser.
Mais la primitive dans ObfReferenceObject existe toujours. L'API soustrait 0x30 de l'adresse que nous fournissons, convertit le résultat en entier de 8 octets, puis soustrait 1.
*(signed long long)(LtMsgEvent-0x30) -= 1
Mais le problème est qu'elle vérifie si la valeur suivante est 0 ou si la valeur actuelle est < 1 (interprétée comme un entier signé de 8 octets). Si l'une ou l'autre condition est vraie, elle saute vers KeBugCheckEx et fait planter le système.
Écriture arbitraire
Avec la décrémentation arbitraire en main, je dois trouver un autre endroit pour écrire l'octet 0xFF, puis le décrémenter jusqu'à l'octet souhaité, et j'ai trouvé cet IOCTL 0x802b2243 :