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