Got the Handle
juin 22nd, 2007 at 10:04 admin
Réveil tardif et difficile aujourd’hui, nous sommes le lendemain de la fête de musique et je ne me suis toujours pas remit de mon concert d’éclatage de tympans avec Korn. Ma tête me fait mal et mes oreilles sifflent toujours à cause de la basse de Fieldy mais j’arrive encore à penser correctement (du moins à faire semblant). Je vais profiter de mon état comateux larvaire pour vous faire découvrir le rôle des handles dans l’Object Manager de Windows. Vous l’avez surement déjà expérimenté, pour pouvoir injecter du code dans un process, il vous faut obtenir un handle sur celui ci, alors vous faites un OpenProcess, comme vous curieux vous regardez la valeur du handle et vous tombez sur un 0xC1 qui ne vous parle absolument pas :], aucun rapport avec le PID du process, si vous regardez le winNT.h vous remarquez qu’un handle est définit par un void*, or la valeur que vous avez trouvé ne pointe vers rien, bref le handle est un objet vraiment mystérieux.
Après un peu de lecture des Windows Internals sur l’Object Manager, on apprend que chaque process possède sa propre table de handles, celle-ci se situe à l’offet 0xC4 de la structure EPROCESS et porte le nom de ObjectTable. Prennons celle du process explorer.exe
kd> !process 0 0 [...] PROCESS ffb45030 SessionId: 0 Cid: 0488 Peb: 7ffdb000 ParentCid: 0468 DirBase: 00ed1000 ObjectTable: e1552f50 HandleCount: 307. Image: explorer.exe [...]
Ok, on y va tranquillement l’ObjectTabble de explorer.exe se trouve en 0xe1552f50, la structure situé à cette adresse est de type HANDLE_TABLE, dumpons tout cela.
kd> dt nt!_HANDLE_TABLE e1552f50 +0x000 TableCode : 0xe1558000 +0x004 QuotaProcess : 0xffb45030 _EPROCESS +0x008 UniqueProcessId : 0x00000488 +0x00c HandleTableLock : [4] _EX_PUSH_LOCK +0x01c HandleTableList : _LIST_ENTRY [ 0xe1426fd4 - 0xe152fcb4 ] +0x024 HandleContentionEvent : _EX_PUSH_LOCK +0x028 DebugInfo : (null) +0x02c ExtraInfoPages : 0 +0x030 FirstFree : 0x4d0 +0x034 LastFree : 0 +0x038 NextHandleNeedingPool : 0x800 +0x03c HandleCount : 307 +0x040 Flags : 0 +0x040 StrictFIFO : 0y0
Déjà plusieurs choses intéressantes, on voit que explorer.exe à ouvert 307 handles, on retrouve son PID de 0×488, l’EPROCESS qui correspond à cette ObjectTable, HandleTableList est une double liste chaînée pointant sur les ObjectTable des autres process, permettant ainsi au kernel de calculer plus rapidement par exemple le nombre de handles ouvert, enfin le champ HandleTableLock permet de verrouiller la structure, évitant ainsi que plusieurs thread manipulent cette structure en même temps, c’est l’équivalent d’un mécanisme de type Mutex. Le champ TableCode est celui qui nous intéresse le plus mais on jouera avec plus tard.
Pour l’instant on fait mumuse tranquillement (on est toujours la phase de réveil post gueule de bois). On va demander au KD de dumper tout les handles ouvert de explorer.exe, on utilise !handle en lui balançant en premier paramètre, la valeur du handle, nous on veut tous les voir alors on met 0, ensuite le type d’info qu’on veut obtenir, en mettant la valeur 3 on aura des infos sur le nom et le type du handle et enfin le PID du process à analyser :
kd> !handle 0 3 488 processor number 0, process 00000488 Searching for Process with Cid == 488 PROCESS ffb45030 SessionId: 0 Cid: 0488 Peb: 7ffdb000 ParentCid: 0468 DirBase: 00ed1000 ObjectTable: e1552f50 HandleCount: 307. Image: explorer.exe Handle table at e1558000 with 307 Entries in use 0004: Object: e1007e38 GrantedAccess: 000f0003 Entry: e1558008 Object: e1007e38 Type: (80e6be70) KeyedEvent ObjectHeader: e1007e20 (old version) HandleCount: 14 PointerCount: 15 Directory Object: e1004d10 Name: CritSecOutOfMemoryEvent 0008: Object: e149baa8 GrantedAccess: 00000003 Entry: e1558010 Object: e149baa8 Type: (80e99b68) Directory ObjectHeader: e149ba90 (old version) HandleCount: 14 PointerCount: 49 Directory Object: e1004ec8 Name: KnownDlls [...]
J’ai arrêté le dump aux 2 premiers handles, mais déjà je peux vous dire que la valeur d’un handle est forcément un multiple de 4. Le premier handle, de valeur 4, fait référence à un objet de type KeyedEvent dont l’ObjectHeader se trouve en 0xe1007e38. En fait, en mémoire, chaque objet est précédent d’une structure OBJECT_HEADER, permettant au noyau de connaître le nombre de handles et de pointeurs faisant référence à l’objet (handle!=pointeur). Dans le cas d’un thread, qui est lui aussi un objet, on retrouve juste au dessus de l’ETHREAD une structure OBJECT_HEADER.
Il temps de comprendre à quoi correspondent les valeurs 4 et 8 des handles. En fait c’est simple, ce sont tout simplement des indices dans un tableau de structures HANDLE_TABLE_ENTRY et ce tableau est pointé par le champ TableCode de la structure HANDLE_TABLE.
On est toujours avec notre petit explorer.exe, chez lui TableCode vaut 0xe1558000, un petit dump pour voir à quoi ressemble le bordel.
kd> dd e1558000 e1558000 00000000 fffffffe e1007e21 000f0003 e1558010 e149ba91 00000003 ffb50313 00100020 e1558020 80cd3019 021f0003 e125ed81 000f000f e1558030 e1552d29 021f0001 e1533679 020f003f e1558040 80ca5031 000f037f e13696b1 0002000f e1558050 80e3c411 000f01ff 80ca5031 000f037f e1558060 80db7fd1 001f0003 80da6369 00100001 e1558070 80cb6eb1 00100020 80cda919 00100000
On observe des valeurs qui pourraient être des pointeurs valides dans le kernel space mais aussi d’autres qui nous parraissent un peu étranges. Bref il faut savoir que les symbols du KD nous donnent la définition suivante d’une HANDLE_TABLE_ENTRY :
kd> dt nt!_HANDLE_TABLE_ENTRY +0x000 Object : Ptr32 Void +0x000 ObAttributes : Uint4B +0x000 InfoTable : Ptr32 _HANDLE_TABLE_ENTRY_INFO +0x000 Value : Uint4B +0x004 GrantedAccess : Uint4B +0x004 GrantedAccessIndex : Uint2B +0x006 CreatorBackTraceIndex : Uint2B +0x004 NextFreeTableEntry : Int4B
De cette définition on déduit qu’une HANDLE_TABLE_ENTRY est une structure avec 2 unions (regardez les offets des différents champs), la première pointant sur l’objet et la seconde contenant les droits d’accès du handle. Cette définition est légèrement inexacte et le schéma suivant montre ce qu’il en est vraiment :
La première union ne pointe pas directement sur l’objet. Ce qui se passe c’est que, comme les kernel développeurs de chez MS ne sont pas trop des buses, ils se sont dit que comme un objet est forcément définit dans le noyau, son adresse sera donc supérieure à 0×80000000 et qu’ainsi le MSB du champ Object serait forcément à 1. Le champ lock mit à 0 permet en gros de dire que le handle de ce process n’est pas manipulé par un thread. De plus les 3 derniers bits ne sont pas prit en compte dans le calcul de l’adresse, ce qui donne l’opération suivant pour passer de valeur du champ Object à un pointeur valide :
pObject=(Object|=0×80000000)&=0xfffffff8
Aussi, comme je l’ai déjà dit, les handles sont forcément multiple de 4, je ne sais pas du tout pourquoi MS à implémenté de cette façon, il doit y avoir une bonne raison mais elle m’échappe encore. Bref, si on regarde notre handle de valeur 4 du process explorer.exe, cela ne veut justement pas dire que la structure HANDLE_TABLE_ENTRY est l’indice 3 de la liste, mais bien à l’indice 1. Sachant que ces structures font 8 bytes, on arrive en 0xe1558000+8 et la valeur Object est de 0xe1007e21. On effectue le petit calcul pour translater, (0xe1007e21|=0×80000000)&=0xfffffff8 = 0xe1007e20. Cette valeur pointe directement sur l’OBJECT_HEADER précédent le process, mais la commande !object du KD prennant en argument un pointeur sur un objet, on ajoute donc à notre pointeur la taille d’une struct OBJECT_HEADER, 0×18:
kd> !object 0xe1007e20+18 Object: e1007e38 Type: (80e6be70) KeyedEvent ObjectHeader: e1007e20 (old version) HandleCount: 14 PointerCount: 15 Directory Object: e1004d10 Name: CritSecOutOfMemoryEvent
Cool on retrouvé notre KeyedEvent :}
On continue, l’exemple que j’ai prit avec explorer.exe ne possédait qu’un faible nombre de handles ouvert (307). Le problème c’est que la granularité d’allocation (je kiff cette expression) est par pages, chaque page fait 0×1000 bytes, c’est à dire que si vous avez besoin d’allouer 0×1024 et bien le système vous filera 0×2000. Dans le cas de notre table de handles, celle-ci fait au départ la taille d’une page, ce qui nous fait 0×1000/sizeof(HANDLE_TABLE_ENTRY)=512 handles dans la table. Evidemment vous vous doutez bien qu’un process peut ouvrir plus de 512 handles et qu’il existe donc un mécanisme sous-jacent à l’allocation des handles que je n’ai point encore développé (ca aussi je kiff à donf :p).
Alors on va se trouver à process avec plus de 512 handles et on va disséquer son ObjectTable.
kd> !process 0 0
[...]
PROCESS 80d19ab8 SessionId: 0 Cid: 03b0 Peb: 7ffd5000 ParentCid: 0224
DirBase: 026ae000 ObjectTable: e1679128 HandleCount: 955.
Image: svchost.exe
[...]
Cool notre ami svchost.exe est volontaire, regardons sa HANDLE_TABLE :
kd> dt nt!_HANDLE_TABLE e1679128 +0x000 TableCode : 0xe162a001 +0x004 QuotaProcess : 0x80d19ab8 _EPROCESS +0x008 UniqueProcessId : 0x000003b0 +0x00c HandleTableLock : [4] _EX_PUSH_LOCK +0x01c HandleTableList : _LIST_ENTRY [ 0xe152fcb4 - 0xe166516c ] +0x024 HandleContentionEvent : _EX_PUSH_LOCK +0x028 DebugInfo : (null) +0x02c ExtraInfoPages : 0 +0x030 FirstFree : 0xaa8 +0x034 LastFree : 0 +0x038 NextHandleNeedingPool : 0x1000 +0x03c HandleCount : 955 +0x040 Flags : 0 +0x040 StrictFIFO : 0y0
svchost.exe à ouvert 955 handles, comment donc est foutu sa table des handles ?! He bien le champ TableCode n’est pas définit comme un pointeur direct sur un tableau de structures HANDLE_TABLE_ENTRY. En fait comme on alloue par pages, les adresses des tables sont des multiples de 0×1000. Ainsi on est sur de trouvé des adresses finissant par 000, cependant pour notre svchost, la valeur de Tablecode est 0xe162a001, WTF ?! Après avoir un peu lu les Windows Internals, on se rend compte de la magnificence de la chose, en fait TableCode ne pointe plus sur un tableau de HANDLE_TABLE_ENTRY mais sur un tableau de pointeurs qui pointent sur nos HANDLE_TABLE_ENTRY … Et justement le fameux 1 permet de dire que nous avons une table de niveau 1, un tableau de pointeurs sur des tables d’HANDLE_TABLE_ENTRY. Mais je crois qu’un exemple est plus parlant.
Si on dump à l’adresse du TableCode, en mettant le 1 à 0 on peut voir :
kd> dd 0xe162a000 e162a000 e167d000 e162b000 00000000 00000000 e162a010 00000000 00000000 00000000 00000000 e162a020 00000000 00000000 00000000 00000000 e162a030 00000000 00000000 00000000 00000000 e162a040 00000000 00000000 00000000 00000000 e162a050 00000000 00000000 00000000 00000000 e162a060 00000000 00000000 00000000 00000000 e162a070 00000000 00000000 00000000 00000000 kd> dd e167d000 e167d000 00000000 fffffffe e1007e21 000f0003 e167d010 e149ba91 00000003 80cc713b 00100020 e167d020 80cc4af9 021f0003 e125ed81 000f000f e167d030 e167b499 021f0001 e167b871 020f003f e167d040 e13696b1 0002000f 80db8469 001f0001 e167d050 80e2de79 000f016e 80cc2ad9 000f00cf e167d060 80e2de79 000f016e 80cc9669 001f0003 e167d070 80cc9621 00100003 80cb59b9 00100003
On a bien 2 pointeurs, 0xe167d000 et 0xe162b000, chacun pointant ainsi sur un tableau d’HANDLE_TABLE_ENTRY (regardez le dd e167d000). On peut vérifier en mattant la position du premier handle de valeur 4.
kd> !handle 4 3 03b0 processor number 0, process 000003b0 Searching for Process with Cid == 3b0 PROCESS 80d19ab8 SessionId: 0 Cid: 03b0 Peb: 7ffd5000 ParentCid: 0224 DirBase: 026ae000 ObjectTable: e1679128 HandleCount: 955. Image: svchost.exe Handle table at e162a000 with 955 Entries in use 0004: Object: e1007e38 GrantedAccess: 000f0003 Entry: e167d008 Object: e1007e38 Type: (80e6be70) KeyedEvent ObjectHeader: e1007e20 (old version) HandleCount: 14 PointerCount: 15 Directory Object: e1004d10 Name: CritSecOutOfMemoryEvent
L’adresse de sa HANDLE_TABLE_ENTRY est bien e167d000+8. J’ajoute que le premier pointeur (0xe167d000) pointe sur une table remplie de 512 HANDLE_TABLE_ENTRY et que le second pointeur (e162b000) vise une table avec 443 structures.
Ce que je n’ai pas dit c’est qu’avant explorer.exe était un process avec un ObjectTable de niveau 0, ici svchost à une ObjectTable de niveau 1 mais il peut exister des tables de niveau 2 ! Ce qui permet d’avoir 1024*1024*512=536 870 912 de handles ouvert par process, ca laisse de la marge :]
Résumons un peu, chaque process possède sa propre handle table, celle-ci peut être organisé de manière hiérarchique en fonction du nombre de handles. Au sein d’un process les handles ne sont que des indices (à divisé par 4) de structures HANDLE_TABLE_ENTRY contenant un pointeur sur l’objet référencé et les droits que possède l’handle sur l’objet. Lorsqu’on ouvre un handle sur un objet, le système va donc crée une entré dans la table des handles et nous renvoyé l’indice de celle ci. Quand on ferme un handle, l’entré est effacée.
Par contre je n’ai vu aucune API exportées par le kernel permettant de manipuler cette table directement, ce qui veut dire que si vous codez un rootkit, soit vous vous débrouillez à récup les différentes adresses des fonctions suivantes :
ExInitializeHandleTablePackage
ExSetHandleTableStrictFIFO
ExUnlockHandleTableEntry
ExCreateHandleTable
ExRemoveHandleTable
ExDestroyHandleTable
ExEnumHandleTable
ExDupHandleTable
ExSnapShotHandleTables
ExCreateHandle
ExDestroyHandle
ExChangeHandle
ExMapHandleToPointer
ExMapHandleToPointerEx
ExpAllocateHandleTable
ExpFreeHandleTable
ExpAllocateHandleTableEntry
ExpAllocateHandleTableEntrySlow
ExpFreeHandleTableEntry
ExpLookupHandleTableEntry
ExSweepHandleTable
ExpAllocateMidLevelTable
ExpAllocateTablePagedPool
ExpAllocateTablePagedPoolNoZero
ExpFreeTablePagedPool
ExpAllocateLowLevelTable
ExSetHandleInfo
ExpGetHandleInfo
ExSnapShotHandleTablesEx
ExpFreeLowLevelTable
ExpBlockOnLockedHandleEntry
ExpMoveFreeHandles
ExEnableHandleTracing
ExDereferenceHandleDebugInfo
ExReferenceHandleDebugInfo
ExpUpdateDebugInfo
Ou soit vous codez vous même votre propre gestionnaire de handles et je peux vous dire que c’est funny
Pour finir, un petit bonus, n’avez vous jamais remarqué vous aussi que les PID et TID (Thread ID) étaient des multiples de 4 ? :] Moi perso, j’aimerais savoir, comment fait le noyau, à partir d’un PID (ou TID), pour retrouver l’objet EPROCESS (ou ETHREAD). Allay soyons fous, désassemblons NtOpenProcess :
kd> uf nt!NtOpenProcess [...] nt!NtOpenProcess+0x1fb: 80579196 8d45dc lea eax,[ebp-24h] 80579199 50 push eax 8057919a ff75d4 push dword ptr [ebp-2Ch] 8057919d e867feffff call nt!PsLookupProcessByProcessId (80579009) [...]
Bon c’est assez parlant comme appel, NtOpenProcess fait appel à PsLookupProcessByProcessId (fonction documentée en plus) en pushant en premier le PID du process puis un pointeur vers la variable qui va récup l’adresse de l’EPROCESS.
Continuons avec PsLookupProcessByProcessId :
kd> uf nt!PsLookupProcessByProcessId nt!PsLookupProcessByProcessId: 8057436d 8bff mov edi,edi 8057436f 55 push ebp 80574370 8bec mov ebp,esp 80574372 53 push ebx 80574373 56 push esi 80574374 64a124010000 mov eax,dword ptr fs:[00000124h] 8057437a ff7508 push dword ptr [ebp+8] ; PID 8057437d 8bf0 mov esi,eax 8057437f ff8ed4000000 dec dword ptr [esi+0D4h] 80574385 ff35400a5680 push dword ptr [nt!PspCidTable (80560a40)] 8057438b e84b1cffff call nt!ExMapHandleToPointer (80565fdb) 80574390 8bd8 mov ebx,eax 80574392 85db test ebx,ebx [...]
Ok, on est IN, on est HYPE, on lâche rien !, on voit un superbe ExMapHandleToPointer qui prend en arg, une table (devinez de quel type …) et le PID tu process. Bien sur, la PspCidTable est une HANDLE_TABLE, explorons la
kd> dd nt!PspCidTable l 1 80560a40 e1000198 kd> dt nt!_HANDLE_TABLE e1000198 +0x000 TableCode : 0xe1003000 +0x004 QuotaProcess : (null) +0x008 UniqueProcessId : (null) +0x00c HandleTableLock : [4] _EX_PUSH_LOCK +0x01c HandleTableList : _LIST_ENTRY [ 0xe10001b4 - 0xe10001b4 ] +0x024 HandleContentionEvent : _EX_PUSH_LOCK +0x028 DebugInfo : (null) +0x02c ExtraInfoPages : 0 +0x030 FirstFree : 0x764 +0x034 LastFree : 0x754 +0x038 NextHandleNeedingPool : 0x800 +0x03c HandleCount : 252 +0x040 Flags : 1 +0x040 StrictFIFO : 0y1
Cette table sert à la fois pour les PID et les TID, ici il y a 252 valeurs, ce qui signifie qu’on a 252 objets de type process et thread tournant à ce moment là avec chacun leur PID et TID. Dumpons la TableCode :
kd> dd e1003000 e1003000 00000000 fffffffe 80e6fa01 00000000 e1003010 80e6f779 00000000 80e6f321 00000000 e1003020 80e6e031 00000000 80e6eda9 00000000 e1003030 80e6eb21 00000000 80e6e899 00000000 e1003040 80e6e611 00000000 80e6e389 00000000 e1003050 80e6d031 00000000 80e6dda9 00000000 e1003060 80e6db21 00000000 80e6d899 00000000 e1003070 80e6d611 00000000 80e6d389 00000000
On retrouve bien le même schéma que précédemment, sauf qu’ici les access mask sont tous NULL, normal on veut juste récup l’adresse de l’objet. Si on prend par exemple, le process de PID 4 (system), on tombe sur l’entrée à 0xe1003000+8 dont la valeur est 0x80e6fa01.
kd> ?? (0x80e6fa01|0x80000000)&0xfffffff8 unsigned int 0x80e6fa00 kd> !object 0x80e6fa00 Object: 80e6fa00 Type: (80e6f040) Process ObjectHeader: 80e6f9e8 (old version) HandleCount: 2 PointerCount: 57
W00T on a bien notre pointeur sur le processus, à noté qu’ici on ne pointe par sur l’OBJECT_HEADER mais directement sur le corps de l’objet, c’est à dire l’EPROCESS ou l’ETHREAD.
Ensuite on peut s’amuser à regarder ce que vaut l’entrée suivante :
kd> ?? (0x80e6f779|0x80000000)&0xfffffff8 unsigned int 0x80e6f778 kd> !object 0x80e6f778 Object: 80e6f778 Type: (80e6fe70) Thread ObjectHeader: 80e6f760 (old version) HandleCount: 0 PointerCount: 2
Cool on est tombé sur le thread primaire du process system (TID=8) :] Il suffit juste de vérif :
kd> !process 80e6fa00 4 PROCESS 80e6fa00 SessionId: none Cid: 0004 Peb: 00000000 ParentCid: 0000 DirBase: 00039000 ObjectTable: e1000b78 HandleCount: 214. Image: System THREAD 80e6f778 Cid 0004.0008 Teb: 00000000 Win32Thread: 00000000 WAIT THREAD 80e6e030 Cid 0004.0010 Teb: 00000000 Win32Thread: 00000000 WAIT THREAD 80e6eda8 Cid 0004.0014 Teb: 00000000 Win32Thread: 00000000 WAIT THREAD 80e6eb20 Cid 0004.0018 Teb: 00000000 Win32Thread: 00000000 WAIT [...]
Nickel tout correspond parfaitement.
Cette table permet aussi à un anti-rookit de detecter les process dont on a retiré l’EPROCESS de la double liste PsActiveProcessHead. Il suffit de recherché dedans en comparant les PID et TID à ceux des processes/threads existant.
Ouf, j’ai finit, j’espère que vous avez kiffer et que vous comprenez vraiment maintenant ce qu’est un handle. Evidemment si vous codez des softs « normaux » connaître se mécanisme ne vous apportera pas grand chose. Par contre si vous être rootkit designer ca peut servir :p
Références :
Windows Internals Fourth Edition : Chapter 3. System Mechanisms -> Object Manager
Rootkit Futo :
http://www.uninformed.org/?v=3&a=7&t=sumry
Entry Filed under: Non classé
6 Comments
1. Baboon | juin 23rd, 2007 at 13:08
Eheh
Tres simpa
J’avais failli explorer NtOpenProcess mais ayant effacés toutes mes VM sur un coup de tete ….
Sinon eh bien bravo , article clair avec des zoulis n’exemple , que demander de plus
Et en plus un lendemain de fete de la musique ….
Moi qui ce jour la me suis levé a 16h30 …
Chapeau
2. overdose | juin 30th, 2007 at 11:03
si j’ai bien comprit les pid/tips ont juste des aoffset dans une table de pointeur.
Est ce que ca veut dire ke les pid et tId sont completement previsible ?
3. admin | juin 30th, 2007 at 14:54
En effet les PID/TID sont prévisibles. Ils seront crée s dans les entrées libres de la table, cependant pour connaitre ces entrées il faut regarder le champ FirstFree de la PspCidTable. Par exemple, quand on crée un process :
Le parametre pHandle pushé est un pointeur qui va recevoir la valeur du PID. Si on regarde son contenu après le call, on voit bien que c’est la valeur du champ FirstFree. Après l’appel, la valeur est mise a jour :
4. philou | juillet 10th, 2007 at 11:59
Hum… je crois que je suis très loin du sujet du billet mais…. Korn, à la fête de la musique, tu viens de la ville où on fait du bon vin toi aussi :p ?
(Non, non, je suis pas tombé ici par hasard XD)
5. Ivanlef0u’s Blog &r&hellip | novembre 24th, 2007 at 16:24
[...] temps. On va ruser comme des renards, sachant qu’un handle n’est qu’au final un indice dans une table, il suffit de crée un handle, de récup sa valeur puis de le ferme, en théorie le handle suivant [...]
6. Geo | septembre 23rd, 2010 at 20:43
Hello, Ivan.
Je sais pas si tout ça c’est encore frais dans ta tête, mais que se passe-t-il lorsqu’un handle est ouvert par une DLL ? La table est gérée par la DLL elle-même – une fois mappée en mémoire – ou c’est le processus qui importe la DLL – IAT, LoadLibrary, … – qui s’occupe de tout ça ?
Merci !
Geo
Trackback this post