SSDT Hooking Reinvented

juillet 23rd, 2007 at 05:27 admin

Il pleut, nous sommes le 23 juillet et il pleut, un temps qui permet d’avoir une bonne escuse pour rester devant le PC, même si en ce moment je post moins souvent cela ne signifie pas que je ne produit plus rien, c’est juste que je prefère me taire plutôt que de dire de la merde comme certains. Puisque c’est les vacances et que personnes n’a envie de se prendre la tête, je vais essayé de vous expliquer de façon claire et simple une des technique de base pour faire du kernel hooking, le hook SSDT (System Service Descriptor Table). Au moins cette fois ci, je vais tenter de me faire comprendre du commum des mortels, ceux qui ne comprendront rien pourront se consoler en mangeant des chocapics halucinèges.

Sous Windows, les appels système ce font avec les fonctions en Zw*, genéralement situées dans ntdll, elles permettent au thread de passer du ring3 (userland) au ring0 (kernelland), ces fontions ont la forme suivante :

ntdll!Zw*
MOV EAX,  ; n0 du syscall
MOV EDX,7FFE0300 ;SharedUserData!SystemCallStub
CALL NEAR DWORD PTR DS:[EDX] ;ntdll!KiFastSystemCall
RET 2C

ntdll!KiFastCallEntry
MOV EDX,ESP
SYSENTER
NOP
NOP
NOP
NOP
NOP
RET

L’appel à sysenter aboutit à la fonction KiFastCallEntry du noyau, qui après diverses opérations va arriver dans KiSystemService, c’est cette dernière qui utilise le numéro du syscall pour retrouver la fonction à appeler. La SSDT est une structure de type KSERVICE_TABLE_DESCRIPTOR :

typedef struct _KSERVICE_TABLE_DESCRIPTOR {
    PULONG_PTR Base;
    PULONG Count;
    ULONG Limit;
    PUCHAR Number;
} KSERVICE_TABLE_DESCRIPTOR, *PKSERVICE_TABLE_DESCRIPTOR;

Le champ Base contient un tableau de pointeurs sur les fonctions en Nt*, qui contiennent le code. Limit est le nombre de ces fonctions (0x11C=284 sous xp)). Number est un tableau de UCHAR contenant la taille des arguments prit par chaque fonction.

kd> dd nt!KeServiceDescriptorTable l 4
80559880  804e26a8 00000000 0000011c 80512ef8

kd> dds 804e26a8
804e26a8  80580115 nt!NtAcceptConnectPort
804e26ac  805702d4 nt!NtAccessCheck
804e26b0  8058b6e6 nt!NtAccessCheckAndAuditAlarm
804e26b4  80589c5b nt!NtAccessCheckByType
804e26b8  80590cfb nt!NtAccessCheckByTypeAndAuditAlarm
[...]

kd> db 80512ef8 l 5
80512ef8  18 20 2c 2c 40

On peut donc voir (si on est pas trop déchiré) que la taille des arguments passé sur la pile est de 0×18 bytes pour NtAcceptConnectPort, 0×20 pour NtAccessCheck, etc …
Maitenant PENSONS (ou pas), sachant que la variable globale KeServiceDescriptorTable est définit dans la section .data de ntoskrnl et que les propriétés de la section sont :

   5. item:
    Name:                  .data
    VirtualSize:           0x00016CA0
    VirtualAddress:        0x00074F80
    SizeOfRawData:         0x00016D00
    PointerToRawData:      0x00074F80
    PointerToRelocations:  0x00000000
    PointerToLinenumbers:  0x00000000
    NumberOfRelocations:   0x0000
    NumberOfLinenumbers:   0x0000
    Characteristics:       0xC8000040
    (INITIALIZED_DATA, NOT_PAGED, READ, WRITE)

On remarque que cette section est non pagée mais surtout quelle est en Read/Write, ce qui veut dire qu’il est autorisé de modifier le champ Base de la structure KeServiceDescriptorTable. Il est donc possible de crée notre propre table de fonctions et d’y rediriger les appels vers celles-ci sans problème. Pour vérifier qu’on a bien le droit d’écrire dans cette zone mémoire il suffit de regarder le PTE associé à notre VA.

kd> !pte 80559880
               VA 80559880
PDE at   C0300804        PTE at C0201564
contains 004001E3      contains 00000000
pfn 400 -GLDA--KWEV    LARGE PAGE 559

GRUZTZ on tombe sur une large page, dans ce cas, le PTE et le PDE sont confondu. Dans le cas présent, le flag W (GLDA–KWEV) signigie que la page est dispo en écriture, c’est plutôt cool pour nous. Pendant que j’y suis, il existe 2 fonctions exportées par le kernel, KeAddSystemServiceTable et KeRemoveSystemServiceTable, je vous laisse lire :

BOOLEAN
KeAddSystemServiceTable (
    IN PULONG_PTR Base,
    IN PULONG Count OPTIONAL,
    IN ULONG Limit,
    IN PUCHAR Number,
    IN ULONG Index
    )

/*++

Routine Description:

    This function adds the specified system service table to the system.

Arguments:

    Base - Supplies the address of the system service table dispatch table.

    Count - Supplies an optional pointer to a table of per system service
        counters.

    Limit - Supplies the limit of the service table. Services greater
        than or equal to this limit will fail.

    Arguments - Supplies the address of the argument count table.

    Index - Supplies index of the service table.

Return Value:

    TRUE - The operation was successful.

    FALSE - the operation failed. A service table is already bound to
        the specified location, or the specified index is larger than
        the maximum allowed index.

--*/        

BOOLEAN
KeRemoveSystemServiceTable (
    IN ULONG Index
    )

/*++

Routine Description:

    This function removes a system service table from the system.

Arguments:

    Index - Supplies index of the service table.

Return Value:

    TRUE - The operation was successful.

    FALSE - the operation failed. A service table is is not bound or is illegal to remove

--*/

Bon même si l’idée de se recrée une nouvelle table de pointeurs de fonctions est plutôt sympa, dans le cadre d’un simple hook, il est tout de même plus pratique de modifier uniquement l’entré qui nous intéresse. On va donc écrire dans la table KiServiceTable qui contient les pointeurs de fonctions pour rediger l’appel système vers notre code. Le petit problème c’est que depuis windows XP cette table à des droits modifiés, si on regarde le moment de l’initialisation au moment de l’appel à KiInitSystem, on peut voir quelle fait partie de la section .text :

KiInitSystem()
mov     ds:_KeServiceDescriptorTable, offset _KiServiceTable
mov     ds:dword_48A504, esi
mov     ds:dword_48A50C, offset _KiArgumentTable

.text:0040D8B0 60 7D 4B 00             _KiServiceTable

Or les droits de la section .text sont :

->Section Header Table
   1. item:
    Name:                  .text
    VirtualSize:           0x00074DB5
    VirtualAddress:        0x00001000
    SizeOfRawData:         0x00074E00
    PointerToRawData:      0x00000600
    PointerToRelocations:  0x00000000
    PointerToLinenumbers:  0x00000000
    NumberOfRelocations:   0x0000
    NumberOfLinenumbers:   0x0000
    Characteristics:       0x68000020
    (CODE, NOT_PAGED, EXECUTE, READ)

EWwWZ la section est lecture seule, pour être sur il suffit de check le PTE associé à la VA de la KiServiceTable :

kd> !pte nt!KiServiceTable
               VA 804e26a8
PDE at   C0300804        PTE at C0201388
contains 0003D163      contains 004E2121
pfn 3d -G-DA--KWEV    pfn 4e2 -G--A--KREV

Dans les droits du PTE (-G–A–KREV) le R signifie read only et le K que c’est une kernel page, si on tente d’écrire dedans on se mangera un joli BSOD. Pour désactiver cette protection, il faut jouer avec le 16 ème bit du registre de control cr0 :

5.6.4  Write Protect  The ability to write to read-only pages is governed by the
(CR0.WP) Bit processor mode and whether write protection is enabled. If
write protection is not enabled, a processor running at CPL 0, 1,
or 2 can write to any physical page, even if it is marked as read-
only. Enabling write protection prevents supervisor code from
writing into read-only pages, including read-only user-level
pages.
A page-fault exception (#PF) occurs if software attempts to
write (at any privilege level) into a read-only page while write
protection is enabled.

Normalement le bit CR0.WP est mit un 1 :
kd> r cr0
cr0=8001003b=10000000000000010000000000111011
Ce qui fait que si un code ring0 tente d’écrire dans un page kernel en read only, un exception sera levé. Par contre si on désactive ce bit « a processor running at CPL 0, 1,or 2 can write to any physical page, even if it is marked as read-
only », on peut écrire dans n’importe quelle page, w00t :) Un code mettant en pratique cette méthode pourrait être :

     __asm
      {
            push eax
            mov  eax, CR0
            and  eax, 0FFFEFFFFh
            mov  CR0, eax
            pop  eax
      }

      // do something
      HOOK_SYSCALL(ZwQuerySystemInformation, NewZwQuerySystemInformation, OldZwQuerySystemInformation );

      // RE-protect memory
   	__asm
      {
            push eax
            mov  eax, CR0
            or   eax, NOT 0FFFEFFFFh
            mov  CR0, eax
            pop  eax
      }

Cependant même si cette méthode est super simple à mettre en place, elle possède un certain inconvénient, imho, dans le cas ou l’on modifit le cr0, rien ne nous garantit que notre routine ne se fasse switcher juste après et qu’un code tente d’écrire dans un page en read-only évitant ainsi l’exception, alors quelle auraît du intervenir. Evidemment, c’est un peu tiré par les cheveux mais il existe un autre moyen pour bypass la protection de la page.

Mais avant, je vais définir le rôle de quelque macros. Lorsque qu’on veut hooker la SSDT il nous faut modifier un des pointeurs de la KiServiceTable. Le problème, c’est que même si les fonctions Zw* sont exportées par le kernel, on n’arrive pas directement sur leur code, par exemple avec la fonction ZwQuerySystemInformation :

kd> u ZwQuerySystemInformation
nt!ZwQuerySystemInformation:
804ddbc0 b8ad000000      mov     eax,0ADh ; 0xAD syscall nbr
804ddbc5 8d542404        lea     edx,[esp+4]
804ddbc9 9c              pushfd
804ddbca 6a08            push    8
804ddbcc e8d5120000      call    nt!KiSystemService (804deea6)
804ddbd1 c21000          ret     10h

HUZ? ce merdier, va faire comme la fonction NtQueryInformationSystem du userland, appeler KiSystemService, pourquoi ? Pour que plus tard, le code de la fonction puisse savoir si il a été appelé du userland ou du kerneland. Bref nous on connaître à quel indice est notre fonction dans la KiServiceTable. Il suffit juste de lire le 2 byte pointé par la fontion en Zw* pour le retrouver:

#define SYSCALL_INDEX(_Function) *(PULONG)((PUCHAR)_Function+1)

Maitenant qu’on a l’indice, on peut lire l’adresse effective de la fonction en Nt* avec un :

#define SYSTEMSERVICE(_function) KeServiceDescriptorTable.ServiceTableBase[ *(PULONG)((PUCHAR)_function+1)]

Enfin, pour hooker, il nous faut l’adresse de la fonction en Zw* pour retrouver l’indice, l’adresse de notre fonction qui va remplacer le pointeur et une variable pour stocker l’adresse orignalle, tout cela est réalisé par les macro suivantes :

#define HOOK_SYSCALL(_Function, _Hook, _Orig )
       _Orig = (PVOID) InterlockedExchange( (PLONG) &MappedSystemCallTable[SYSCALL_INDEX(_Function)], (LONG) _Hook)

#define UNHOOK_SYSCALL(_Function, _Hook, _Orig )
       InterlockedExchange( (PLONG) &MappedSystemCallTable[SYSCALL_INDEX(_Function)], (LONG) _Hook)

Alors, l’astuce permettant de bypass la protection est d’utilser un MDL (Memory Descriptor List) en remappant la SSDT. Le code est le suivant :

 	// Map the memory into our domain so we can change the permissions on the MDL
   	g_pmdlSystemCall=IoAllocateMdl(KeServiceDescriptorTable.ServiceTableBase, KeServiceDescriptorTable.NumberOfServices*4, 0, 0, NULL);
   	if(!g_pmdlSystemCall)
      	return STATUS_UNSUCCESSFUL;

	//The MmBuildMdlForNonPagedPool routine receives an MDL that specifies a virtual memory buffer in nonpaged pool,
	//and updates it to describe the underlying physical pages.
   	MmBuildMdlForNonPagedPool(g_pmdlSystemCall);

	//The MmMapLockedPages routine maps the physical pages that are described by a given MDL.
	MappedSystemCallTable=MmMapLockedPages(g_pmdlSystemCall, KernelMode);

  	//hook system calls
   	OldZwQuerySystemInformation=HOOK_SYSCALL(ZwQuerySystemInformation, NewZwQuerySystemInformation, OldZwQuerySystemInformation );

En fait, une chose BIZARRE, apparaît au moment on l’on fait appel à la la fonction MmMapLockedPages :

typedef struct _MDL {
    struct _MDL *Next;
    CSHORT Size;
    CSHORT MdlFlags;
    struct _EPROCESS *Process;
    PVOID MappedSystemVa;
    PVOID StartVa;
    ULONG ByteCount;
    ULONG ByteOffset;
} MDL, *PMDL;

#define MDL_MAPPED_TO_SYSTEM_VA     0x0001
#define MDL_PAGES_LOCKED            0x0002
#define MDL_SOURCE_IS_NONPAGED_POOL 0x0004
#define MDL_ALLOCATED_FIXED_SIZE    0x0008
#define MDL_PARTIAL                 0x0010
#define MDL_PARTIAL_HAS_BEEN_MAPPED 0x0020
#define MDL_IO_PAGE_READ            0x0040
#define MDL_WRITE_OPERATION         0x0080
#define MDL_PARENT_MAPPED_SYSTEM_VA 0x0100
#define MDL_FREE_EXTRA_PTES         0x0200
#define MDL_DESCRIBES_AWE           0x0400
#define MDL_IO_SPACE                0x0800
#define MDL_NETWORK_HEADER          0x1000
#define MDL_MAPPING_CAN_FAIL        0x2000
#define MDL_ALLOCATED_MUST_SUCCEED  0x4000

avant le lock
kd> dt nt!_MDL 0xffbd8670
   +0x000 Next             : (null)
   +0x004 Size             : 32
   +0x006 MdlFlags         : 12 ; MDL_ALLOCATED_FIXED_SIZE|MDL_SOURCE_IS_NONPAGED_POOL
   +0x008 Process          : (null)
   +0x00c MappedSystemVa   : 0x804e26a8
   +0x010 StartVa          : 0x804e2000
   +0x014 ByteCount        : 0x470
   +0x018 ByteOffset       : 0x6a8

kd> !pte 0x804e26a8
               VA 804e26a8
PDE at   C0300804        PTE at C0201388
contains 0003D163      contains 004E2121
pfn 3d -G-DA--KWEV    pfn 4e2 -G--A--KREV

Avant l’appel, le champ MappedSystemVa du MDL contient la VA « normale » qui représente les frames physiques contenant la KiServiceTable.

Après le lock
kd> dt nt!_MDL 0xffbd8670
   +0x000 Next             : (null)
   +0x004 Size             : 32
   +0x006 MdlFlags         : 13 ; MDL_ALLOCATED_FIXED_SIZE|MDL_SOURCE_IS_NONPAGED_POOL|MDL_MAPPED_TO_SYSTEM_VA
   +0x008 Process          : (null)
   +0x00c MappedSystemVa   : 0xfd3f76a8 ; !!!!
   +0x010 StartVa          : 0x804e2000
   +0x014 ByteCount        : 0x470
   +0x018 ByteOffset       : 0x6a8

kd> !pte 0xfd3f76a8
               VA fd3f76a8
PDE at   C0300FD0        PTE at C03F4FDC
contains 01031163      contains 004E2163
pfn 1031 -G-DA--KWEV    pfn 4e2 -G-DA--KWEV

Après l’appel à MmMapLockedPages, le MappedSystemVa a changé et le pte associé possède un magnique W dans ses flags, indiquant qu’on a le droit d’écriture sur la page ! On peut vérifier que cette VA 0xfd3f76a8 est bien associdé à la même frame que la VA 0x804e26a8 en dumpant le contenu de la physical memory :

kd> !dd 4e2*1000+6a8
#  4e26a8 80580115 805702d4 8058b6e6 80589c5b
#  4e26b8 80590cfb 80636c94 80638e25 80638e6e
#  4e26c8 8057833f 806476ab 80636453 8057ae00
#  4e26d8 8062e598 80578d91 8058cb5e 8062569d
#  4e26e8 805db60c 8056819d 805d81ad 805a1290
#  4e26f8 804e2cb4 80647697 805c88e8 804ecfac
#  4e2708 80568849 80566f49 805906f0 8064d6bb
#  4e2718 8058f858 8057f4a1 8064d929 8058b738

Bon, alors bug ou pas bug ? Je n’en sait trop rien, normalement le fait de remapper une page ne devrait pas permettre de pouvoir y accéder de façon différente, d’après ce que j’ai vu c’est la fonction MiReserveSystemPtes qui va allouer le PTE durant l’appel à MmMapLockedPages.

Enfin le code d’exemple. Il montre un exemple d’un hook SSDT sur la fonction NtQuerySystemInformation, cachant tout les process commençant par _root_. Je vous laisse jouer avec :

http://ivanlef0u.fr/repo/HookSsdtMdl.rar

SPLITZ D4 MEG4HZURTZ

http://book.itzero.com/read/microsoft/0507/Microsoft.Press.Microsoft.Windows.Internals.Fourth.Edition.Dec.2004.internal.eBook-DDU_html/0735619174/ch07lev1sec5.html
http://ivanlef0u.fr/repo/windoz/Nt_vs_Zw.txt
http://www.amd.com/us-en/assets/content_type/DownloadableAssets/dwamd_24593.pdf

Entry Filed under: Non classé

14 Comments

  • 1. wo`  |  juillet 23rd, 2007 at 17:39

    yeah nice maintenant que je suis sous win je vais trippoter le ringue levalz zeero.

    bonne continuation


  • 2. rootkited  |  juillet 24th, 2007 at 10:46

    superbe la demonstration de ton hook.
    j’ai testé certains code et marche nickel.

    y’a un truc que je comprend pas
    pourquoi dans ce macro :

    #define SYSCALL_INDEX(_Function) *(PULONG)((PUCHAR)_Function+1)

    on a _Function+1 . pourquoi le +1 ?

    et pourqoi tous ces changements de types de PUCHAR puis PULONG.

    merci encore pour ces merveilleux codes.


  • 3. admin  |  juillet 24th, 2007 at 11:01

    Les fonctions Zw* qui sont exportées par le kernel sont de la forme suivante :

    kd> u ZwQuerySystemInformation
    nt!ZwQuerySystemInformation:
    804ddbc0 b8ad000000      mov     eax,0ADh ; 0xAD syscall nbr
    804ddbc5 8d542404        lea     edx,[esp+4]
    804ddbc9 9c              pushfd
    804ddbca 6a08            push    8
    804ddbcc e8d5120000      call    nt!KiSystemService (804deea6)
    804ddbd1 c21000          ret     10h
    

    lkd> db nt!zwquerysysteminformation
    804dd440 b8 ad 00 00 00

    Si tu regardes, le second byte de la fonction ZwQuerySystemInformation tu retrouves le syscall index. Ensuite en castant la fonction en tant que pointeur de type PUCHAR on est sur d’incrémenter le pointeur de 1 (et non de 4 si le pointeur était de type PULONG), ensuite on recast ce pointeur sur PULONG pour récuperer les 4 bytes (ad 00 00 00) et donc l’index de la fonction dans la KiServiceTable.


  • 4. rootkited  |  juillet 24th, 2007 at 17:32

    thanks.
    sans l’illustration avec le desassemblage j’aurais rien compris.

    Merci pour tout. j’ai compris youpi!

    R+


  • 5. newsoft  |  juillet 26th, 2007 at 12:40

    Pour éviter le context switch lors de la modification du CR0, tu dois pouvoir jouer avec RaiseIrql() non ?


  • 6. admin  |  juillet 26th, 2007 at 13:01

    Ouais en faisant peter l’IRQL à DISPATCH_LEVEL avec KeRaiseIrql ca marche car les pages qui contiennent la KiServiceTable sont dans la non-paged pool.


  • 7. Taron  |  août 1st, 2007 at 17:19

    fine


  • 8. Paton  |  septembre 22nd, 2007 at 01:03

    Question de néophyte : est-ce que ça marche sous vista ?


  • 9. admin  |  septembre 22nd, 2007 at 01:42

    wi


  • 10. SSDT Hooking | Nibbles mi&hellip  |  décembre 17th, 2008 at 18:34

    [...] ce qui ne connaisse pas du tout le sujet, je vous renvoi ici(KPCR) ou ici(Ivanlef0u). Santabug a également fais un code [...]


  • 11. [r00tkit] SSDT Hook pour &hellip  |  octobre 10th, 2010 at 17:52

    [...] compris l’intérêt de cette méthode mais si vous êtes curieux je vous renvoie à l’article d’Ivanlef0u à ce [...]


  • 12. Hel0ck  |  novembre 24th, 2010 at 00:03

    J’ai un petit soucis, quand je fait
    lkd> dd nt!KeServiceDescriptorTable l 4
    82d7f9c0 82c86700 00000000 00000191 82c86d48
    Sa marche, mais aprés quand je tape :
    lkd>db 82c86d48 1 5
    Sa m’affiche ^ Range error in ‘db 82c86d48 1 5′ :s


  • 13. admin  |  novembre 26th, 2010 at 21:16

    @Helock
    C’est pas ’1′ mais ‘l’ :p


  • 14. API hooking sous Windows &hellip  |  mai 30th, 2012 at 08:31

    [...] Hook SSDT : http://www.ivanlef0u.tuxfamily.org/?p=63 , [...]


Trackback this post


Calendar

mars 2024
L Ma Me J V S D
« fév    
 123
45678910
11121314151617
18192021222324
25262728293031

Most Recent Posts