
CVE-2020-15368, alias « Comment exploiter un pilote vulnérable »
Exploit et Preuve de concept (PoC) pour CVE-2020-15368. Asrock a réemballé le pilote rweverything pour son outil de configuration du contrôleur RGB et l'a signé. Ils le « protègent » en chiffrant leurs ioctls... lol. Nous avons découvert cette CVE par accident l'été dernier, et à ma connaissance, le pilote n'est toujours pas corrigé. L'impact est bien sûr l'exécution de code arbitraire dans le noyau, etc. Alors profitez de ce « 0day » lol.
Si vous voulez discuter avec moi pour savoir si c'est une VRAIE BONA FIDE CVE, n'hésitez pas à me contacter sur Twitter, on peut avoir une énorme dispute sur les réseaux sociaux publics, ce sera vraiment excitant pour tout le monde ! J'achèterai même un domaine pour ce bug si vous le souhaitez. Tout est une question de marketing !!!!
Bref, ce bug est assez merdique, donc je vais l'utiliser comme tutoriel pour pwner un pilote vuln typique. Ce post s'adresse donc aux débutants. Vous apprendrez à exploiter un pilote vulnérable. Il y a des tonnes d'autres pilotes pourris comme celui-ci. Le monde est à vous. Amusez-vous bien.
AVERTISSEMENT : Cette publication est fournie à des fins éducatives uniquement. Il est de la responsabilité du lecteur de respecter toutes les lois locales, étatiques et fédérales applicables. Le(s) auteur(s) de cette publication déclinent toute responsabilité et ne sont pas responsables des mauvais usages ou dommages causés par le logiciel contenu dans cette publication.
Confinés, mes colocataires (Pear0, Codetector) et moi jouions sur la nouvelle carte mère Asrock de Pear0. Les LED rouges vives étaient extrêmement agaçantes et il était impossible de les configurer sous Linux. Notre plan était donc de rétro-concevoir le pilote Windows qui les contrôlait et de reproduire les opérations d'E/S sous Linux.
Bref, il n'a pas fallu longtemps pour se rendre compte que le pilote n'est littéralement qu'un pilote générique accordant un accès en lecture/écriture arbitraire à tout. Cela inclut les registres de contrôle comme CR3, CR4, la mémoire physique, etc. De tels pilotes sont destinés à être utilisés comme outil de débogage et le site du vendeur le dit clairement.

Nous avons trouvé ça extrêmement drôle. C'est assez excitant la première fois que l'on provoque un triple défaut et un redémarrage brutal depuis l'espace utilisateur. (Peut-être moins la 20e fois.) Bref, nous avons signalé le bug puis l'avons oublié pendant un an.
En tant que noob du noyau, je me demandais comment charger et interagir avec le pilote. Il s'avère que c'est extrêmement facile.
Vous pouvez simplement créer un service pour le pilote dans Process Hacker (évidemment, il faut être admin pour charger des pilotes). Ensuite, vous faites un clic droit et le démarrez. Oui, c'est vraiment aussi simple.

Nous pouvons voir notre objet Device dans WinObjEx64.

On peut même jouer avec le périphérique dans FileTest.


Ces trois outils sont tous fantastiques, surtout PH et FileTest. Ils sont comme un couteau suisse et devraient être dans la boîte à outils de tout rétro-ingénieur Windows. Par exemple, d'après ce que j'ai compris, Jonas L a découvert d'innombrables vulnérabilités LPE Windows en trifouillant simplement dans FileTest. Windows a donc vraiment d'excellents outils pour déconner. J'aimerais qu'il y ait ces trucs sous Linux.
Rweverything a un ioctl qui prend un ioctl en paramètre, lequel contrôle l'opération à effectuer (lire la mémoire, écrire la mémoire, lire le MSR, etc.), et une union de paramètres spécifiques à l'opération comme l'adresse source, l'adresse dest, etc. En comparant le code des deux pilotes :

🤔🤔🤔🤔🤔🤔🤔🤔🤔
Néanmoins, le pilote tente une piètre sécurité par l'obscurité en exigeant que tous les appels ioctl soient correctement chiffrés avec une clé AES codée en dur. Le code (après un peu de nettoyage) ressemble à ceci :
if ( IoControlCode == 0x22EC00 )
{
char enc_key[32];
memset(enc_key, 0, sizeof(enc_key));
memmove(enc_key, "C110DD4FE9434147B92A5A1E3FDBF29A", 32ui64);
memcpy(enc_key + 13, ioctl_args->key, 16);
size_t cb_decrypted = 0;
my_decrypted_cmd* decryptedCmd = NULL;
DWORD iv_size = ioctl_args->iv_size;
DWORD input_size = *(DWORD*)(ioctl_args + bufferLen - 6);
// really just calls BCrypt API to get an AES implementation
if ( (unsigned int)decrypt_ioctl_params(enc_key, 32, ioctl_args->iv, iv_size, ioctl_args + bufferLen - input_size - 6, input_size, &decryptedCmd, &cb_decrypted) )
{
// Decryption failed
if ( decryptedCmd )
ExFreePoolWithTag(decryptedCmd, 0);
irp->IoStatus.Status = 0xC000000D;
goto Fail_Out;
}
IoControlCode = decryptedCmd->opcode;
Rweverything_Args = &decryptedCmd->args;
}
else // not 0x22EC00
{
if ( IoControlCode != 0x22E858 &&
IoControlCode != 0x22E860 &&
IoControlCode != 0x22E800 &&
IoControlCode != 0x22E804 )// whitelisted control codes
IoControlCode = 0; // block everything else
}
Le pilote autorise quelques opérations ennuyeuses qui font du PMIO, mais tous les codes de contrôle « amusants » sont verrouillés derrière cette routine de déchiffrement. Malgré cette liste blanche explicite, il inclut toujours toutes les fonctionnalités dangereuses de Rweverything. Au lieu de cacher ces fonctionnalités dangereuses, ils auraient probablement dû les supprimer complètement.
De plus, il est intéressant de noter qu'il permet également à l'utilisateur de spécifier une partie de la clé (???), pour quelle raison, je n'en ai aucune idée. Le code est tout simplement très mal écrit.
Bref, il est relativement facile d'écrire le code client pour utiliser cette API chiffrée bizarre et lui passer les appels ioctl arbitraires que l'on souhaite. Je ne vais pas vous ennuyer avec les détails.
Nous ouvrons un handle vers le pilote et utilisons DeviceIoControl pour appeler l'ioctl, tout à fait standard.
HANDLE hDevice = CreateFileA("\\\\.\\GlobalRoot\\Device\\AsrDrv104", GENERIC_READ | GENERIC_WRITE | SYNCHRONIZE, FILE_SHARE_READ | FILE_SHARE_WRITE, NULL, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, NULL);
// ... set up the encrypted ioctl data
BOOL result = DeviceIoControl(hDevice, 0x22EC00, ioctl_data, in_buf_size, out_buf, sizeof(out_buf), &bytes_returned, NULL);
Maintenant que nous pouvons parler à la partie cachée de Rweverything du pilote, la première chose que je voulais faire était de déclencher un crash pour savoir si mon client pilote fonctionne.