Rédacteur : Heurs
Date de création : 06/08/2009
Section : Sécurité > Failles applicatives
Imprimer cet article : en noir et blanc ou en couleurs
Bon, tout d'abord un stack overflow est un stack overflow, j'entends par là que le principe est le même que l'on soit en user land qu'en kernel land. Des datas posées dans la pile vont écraser la sauvegarde de EIP, et quand on arriver à notre "ret" on dépilera la sauvegarde corompue en question. Le problème en kernel c'est que quand ca crash on se tappe un beau BSOD ! Donc on a pas le droit à l'erreur... En fit si, mais faut rebooter quoi (ca arrive même en audit de faire rebooter un serveur à cause d'un exploit mal dosé).
Tout l'intéret des overflow kernel est que l'on passe en ring0. Pour faire simple on a tous les droits partout :-)
Donc on se tappe toujours des vulns par copie de chaines de caractères, des strctures, etc. Nos méthodes n'ont donc rien de neuf, je vous invites à vous reporter à d'autres articles si vous n'avez pas les bases sur les buffer overflow. Alors c'est parti, on commence avec les sécu lors de la compilation.
On compile un driver (.sys) avec DDK, DDK regarde ce que fait nos fonctions, il nous pose des vérifications de l'intégrité de la stack si il considère que la fonction peut être à risque. Dans le code suivant on voit qu'on place un canary (BugCheckParameter2) dans ebp-4. C'est à dire juste au dessus de l'Ebp empilé. Donc si on a un stack overflow ce DWORD se retrouvera écrasé avant EIP.
Et à la fin de notre fonction on a le code suivant :
Un "Call" tout droit sorti de nulpart ! Bon ben on va regarder ce qu'il fait notre petit call mistérieux :
On voit que si notre [ebp-4] (soit notre canary) est différent du canary original on jump sur loc_10726.
Et BHIM ! BSOD dans notre face. Ca revient au flag GS en user land mais cette fois c'est tout l'OS qui crash. Si vous avec déjà exploité ce type de vuln vous savez que d'habitude on génère une excepetion, cette exception va aller chercher le SEH handle et là on poutre le bordel. Pas de chance pour nous si on déclance une exception à partir du kernel c'estl'OS qui nous fait un BSOD :-(
Fort heureusement pour nous il DDK ne pose pas toujours des security_check, ils en pose si il considère la fonction comme dangereuse. C'est à dire si il y a un "char toto[32]" il protègera la fonction, en prsence de fonction type strcpy() il le fera aussi. Mais avec un memcpy() (par exemple) il ne mettra rien car nous positionons une limite à la copie. Ainsi si nous copions des structure de façons dynamique nous pourrons peut êre déborder quand même ! Par exemple en modifiant quelques peu notre programme nous obtenons l'épilogue suivant :
We win ! Le méchant call su secutity_check a disparu :-D On va donc pouvoir faire correctement déborder notre tampon pour que le ret dépile notre Fake Eip et execute notre Sh3lLc0D3 K3rN3l (Shellcode Kernel) !
Pour lancer des attaques en kernel il nous faut un moyen de passer un mode kernel. Pour cela il existe deux instructions, int xx et sysenter. int xx est une interruption. Cela veut dire que quand on fait un int 2E on passe la main au noyau (on a donc plus le contrôle). Il peut arriver qu'une appli utilise des interruptions non conventionnelles, nous pourrons donc fuzzer cette entrées avec pour objectif trouver un bug ! Bon je l'avoue ce n'est pas la vuln kernel la plus courrante, on ne s'étendra donc pas dessus, mais c'est toujours bien de savoir que c'est possible.
Voilà un point clé ! Nos applis passent constament pas le kernel, un simple printf("toto") devra passer par le kernel à un moment où à un autre s'il veut afficher le message à l'écran. Il utilisera les fonctions que windows met à sa disposition, c'est à dire la liste de fonctions contenue dans la SSDT (table de focntions en noyau). la très grande majorité des appels se font par la fontion KiFastSystemCall :
On retrouve notre SYSENTER évoqué précédement. Une fois le sysenter executé le KiFastSystemCallEntry prendra la valeur contenue dans Eax et appelera la fonction de la SSDT correspondante (pour faire simple), pour plus de détailles le blog de trance vous d'une grande aide ;-) Dans notre code Eax n'est pas positionné, ce qui veut dire que ce sont d'autres fonctions qui affecterons la valeur adéquate (normal puisque FastSystemCall sert à donner la main au noyau et pas à appeler une fonctions particulière)
Le placement de Eax se fera par une des fonctions Zw******. Par exemple ZwLoadDriver fera :
L'index de la fonction LoadDriver sera donc 0xA5. Pour information ce qui est pointé par 7FFE0300 est :
[code] 7FFE0300 90 9A A3 77 [/code]Soit l'adresse 77A39A90, on retrouve bien notre KiFastSystemCall.
Les focntions de windows sont très contrôlées, malgré quelques fuites touvées la robustesse de ces fonctions n'est plus à faire. Donc inutile de perdre du temps à les fuzzer (bien qui si vous trouvez une vuln dessus vous gagnerez pas mal de sousous).
Ce qu'il faut savoir c'est qu'un logiciel (type anti-virus ou logiciel de backup) peut ajouter des entrées dans la SSDT. Ces entrées pourront alors être appelées depuis le user land. Le niveau de sécurité de ces nouvelles entrées ne dépend que de l'éditeur logiciel, ainsi on trouve régulièrement des entrées mal vérifiées. Quelques fuzzings sur ces fonctions peuvent se réveller d'une grande utilité !
Pour communiquer avec un driver en particulié chargé en kernel on peut utiliser les IOCTL, Input Out Control code. Ce sont des messages transmis au kernel par la fonction DeviceIoControl :
Tous les camps sont importants mais IoControlCode l'est encore plus que les autres. C'es ce DWORD qui sélectionnera quel traitement auront nos données. Il peut se représenter de la façon suivante :
[code] [Common |Device Type|Required Access|Custom|Function Code|Transfer Type] 31 30<---->16 15<--------->14 13 12<-------->2 1<-------->0 [/code]On ne va pas s'attarder sur les différent champs (bien qu'ils soient tous très interessents) mais regarderons principalement les deux méthodes ples plus dangeureuses : METHOD_BUFFERED et METHOD_NEITHER.
Ces méthodes font des copies de datas du user land vers le kernel land, pas de problème à ce niveau là. Tout est dans le traitement des datas, si un memcpy se fait d'un buffer d'entrée ver un buffer de la stack kernel on peut avoir le risque d'un débordement de tampon. Les débordements interviennent soit dans des jeux de pointages / copie de strcture, soit directement avec InBufferSize mal dimentionné. Si le GS est activé on aura au pire un BSOD ! C'est pas top, mais déjà pas mal.
Dans notre cas on va appeler le driver avec une InBufferSize trop grande, celui-ci fera un memcpy en prennant en argument notre taille InBufferSize et une adresse de sa stack. On va donc avoir un joli Buffer Overflow, désolé pour le réalisme mais le but est de montrer comment exploiter et non pas comment fuzzer les drivers (qui sait, peut être dans un prochain article).
On charge donc le driver en kernel et on lance notre prog qui fait son appel surdimenssionné (vous trouverez les codes pour utiliser les IOCTL sur le blog d'Overcl0k). En regardant le système avec kd on obtient ca :
OOOOooooo, la belle bleu ! Le BSOD a été produit à cause d'une violation d'accès à l'adresse 0x61616161. Cette adresse équivaut à la chaine de caractères 'aaaa', 0x61 valant 'a'. J'avais mis une longue chaine de 'a' en entrée, nous sommes donc très certainement en présence d'un buffer overflow. En regardant d'un peu plus pret nous pouvons voire que le regitre Eip a été écrasé :
Nous avons donc Ebp qui est écrasé (probablement lors d'un Pop Ebp ou d'un Leave) et Eip (très probablement lors d'un Ret). C'est le signe typique d'un stack overflow. Petite précision, le driver que nous attaquons était une épreuve de la Nuit Du Hack 2009 et permettait de s'élever les privilèges sur les serveurs windows. Rentrons maintenant dans le vif du sujet retrouvons où le buffer overflow se situe dans notre driver :
A la fin de notre fonction nous sautons donc sur DriverEntry, fonction appelée lors du chargement d'un driver. Nous allons donc continuer notre analyse sur DriverEntry :
Sans rentrer dans le détail nous voyons que le driver créer un device "\\DosDevices\\MasterHack", device que l'on utilise pour communiquer avec. Nous voyons aussi qu'on redirige plusieurs IRP, cela nous permettra de comuniquer avec via les IOCTL. L'IRP le plus important ici est le IRP_DEVICE_CONTROL, nous allons donc regarder ce que se passe lorsque nous l'appelons :
Prenons notre code dans l'ordre, nous voyons à la ligne 00010565 l'instrction mov edi, [ebp+arg_4] qui récupère le premier argument. Puis on se déplace dans des structures jusqu'à arriver à l'instruction mov ebx, [edi+0Ch], puis push ebx et enfin call TestMe. En claire on a récupéré une valeur (pourquoi pas un pointeur), l'avons empilé et enfin avons appelé TestMe aveccet argument. Regardons maintenant ce que TestMe va faire de cette valeur :
On se croirait dans un article d'Ivanlef0u avec tous ces codes dans tous les sens lol (je dis bien on se croirait, j'aimerai bien pouvoir écrire les mêmes articles que lui !). On voit ligne 0001050C et ligne 00010513 que des intructions "rep" sont effectuées. Celles-ci copient les datas d'une source vers une destination. Si ces copies sont effectuées dans la stack et qu'il n'y a pas de taille fixe de prédéfinie nous pouvons avoir un beau stack overflow. J'attire votre attention sur le fait qu'il n'y ait pas de security_check dans cette fonction, en cas de débordement de tampon le programme ne sera donc pas à même de crasher l'OS avant que le Ret soit exécuté.
Prennons en considération que cette fonction est la vulnérable, si nous posons un breakpoint à 00010539 nous devrons donc avoir notre 0x61616161 pointé par Esp. Testons donc cela :
On peut dire que tout se passe comme prévu et que le ret va bien dépiler notre 0x61616161, nous avons donc retrouvé notre fonction vulnérable et allons maintenant pouvoir passer à la phase d'exploitation !
Je tiens à préciser que cette phase n'était pas indispansable pour une exploitation du buffer overflow mais necessaire à une bonne compréhension.
Nous pouvons maintenant modifier notre Eip à notre guise (je vous laisses calculer la taille à inserer avant d'écraser Eip, les calculs sont les mêmes qu'un user land donc je les prends pour aquis). On va donc pouvoir faire executer du code à l'adresse que l'on désire. deux choses sont importantes :
Les droits affectés à un processus se font par le Token. Le Token est un pointeur vers une structure de doits, ces droits définissent entre autre l'utilisateur par lequel le process est lancé. Si nous copions le Token d'un processus Administrateur sur l'un de nos processus nous obtiendrons alors les droits Administrateur pour le process courrant. Retrouvons donc cette structure à partir du noyau. Il faut savoir que le registre FS pointe sur la strcture KPCR :
Ici rien d'intéressent pour notre élévation de privilèges, continuons donc avec la structure _KPRCB à l'offset 0x120 :
A l'offset numéro 4 on a une structure _KTHREAD intitulée CurrentThread. C'est donc les infos sur notre thread courrant. Cette structure nous permettra de remonter jusqu'au TOKEN du process.
A l'offset 0x34 nous avons la structure _KAPC_STATE, si on déroule cette sutructure nous avons :
On a l'adresse d'une structure _KPROCESS qui est réalité une structure _EPROCESS. C'est la strucutre même du processus. C'est dans cette structure que nous retrouverons tous les éléments nous permettant de mener à bien notre élévation de privilèges.
Nous trouvons ActiveProcessLinks qui est une liste doublement chainnée des processus actifs du système, UniqueProcessId qui est ni plus ni moins que le PID du processus courrant et enfin Token qui est un pointeur vers la structure des droits alloués au processus. Les process sont chainnés de la façon suivante :
Nous pouvons donc passer de processus en processus juste en utilisant ces listes. De plus comme on a le PID du process affecté à la structure on va pouvoir aisément trouver quel token recopier. Sous windows XP le PID est SYSTEM est toujours 4, nous allons donc recherche la valeur (qui est pointeur) du Token de SYSTEM et la copier à la place de celle de notre processus. Et PAF ca fait des chocapics comme dirait Ivanlef0u ;-)
Maintenant que nous savons comment copier les privilèges nous devons retourner en user land... et oui, souvenez vous, en kernel le moindre bug c'est le BSOD (ou RSOD pour Trance) direct ! Pour se faire on va utiliser l'intruction SYSEXIT. Voyons son fonctionnement :
Intel nous dit en gros qu'on doit placer l'adresse de notre pile dans ECX et mettre l'adresse de la fonction (ou shellecode pour nous) à executer dans EDX. Mais ce que Intel ne nous dit pas (et c'est normal) c'est que nous windows on doit aussi changer la veleur de FS. En effet, FS en kernel et FS en user land n'ont pas la même valeur, en user le FS vaut 0x3B alors qu'en kernel il vaut 0x30. Là on a tout ce qu'il faut pour retourner en userland sans craindre le moindre crash de notre appli :-D
On sort notre MASM et on code tout ca :
Notre dernière ligne droite. On a plus qu'à référencer l'adresse de la stack, celle du shellcode (ou de la fonction à executer), changer l'adresse de retour par celle de notre shellcode qui change les tokens et hop ! on est SYSTEM ;-P Dans le code C le shellcode executé après le changement des droits est un simple appel à cmd.
Ben l'exploitation kernel c'est le bien, bon on trouve rarement ce type du vuln aussi grasse (le stack overflow), mais on a d'autres overflow sympas :-P
Désolé de ne pas mettre en ligne le driver pour le moment... Cela dit avec l'article d'Overcl0k vous devriez être en mesure de recoder tout ca ;-)