Fortinet FortiClient Local Privilege Escalation
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.
6 comments février 28th, 2008