Archive for juin, 2007
Imaginez vous êtes sur une b0x mais il y a méchant HIDS (Host Intrusion Detection System) qui vous empêche d’ouvrir un handle sur un process et sur tout ses threads, en utilisant par exemple un hook SSDT sur les API NtOpenProcess et NtOpenThread. Nous, comme on est des méchants, on voudrait bypass cette mesure (évidemment on ne peut pas loader de driver). Dans ce post je propose une méthode permettant d’injecter du code dans un process, sans utiliser de NtOpen* sur celui-ci, je préviens quand même, il ne s’agit pas d’un hack, mais plutôt de jongleries avec les différentes API et particularités de Windows qui de plus requiert certains privilèges alors n’espérer rien de révolutionnaire. Mais d’abord regardons comment fonctionne les API NtOpenProcess et NtOpenThread :
NTSTATUS
NtOpenProcess (
__out PHANDLE ProcessHandle,
__in ACCESS_MASK DesiredAccess,
__in POBJECT_ATTRIBUTES ObjectAttributes,
__in_opt PCLIENT_ID ClientId
);
NTSTATUS
NtOpenThread (
__out PHANDLE ThreadHandle,
__in ACCESS_MASK DesiredAccess,
__in POBJECT_ATTRIBUTES ObjectAttributes,
__in_opt PCLIENT_ID ClientId
);
Les 2 API’s demandent en paramètre l’ID de l’objet que souhaite ouvrir, le champ ClientId étant un pointeur sur une structure CLIENT_ID qui est définit par :
typedef struct _CLIENT_ID {
HANDLE UniqueProcess;
HANDLE UniqueThread;
} CLIENT_ID;
typedef CLIENT_ID *PCLIENT_ID;
En fonction de ce qu’on veut faire on spécifie ou bien le champ UniqueProcess avec le PID du process visé ou le TID du thread dans UniqueThread. Ensuite le paramètre DesiredAccess contient les droits que le handle devra avoir sur l’objet. Enfin le champ ObjectAttributes n’est quasiment jamais utilisé.
On peut alors se dire que le driver du HIDS va vérifier si on tente d’effectuer un Open* sur une liste de PID/TID, si il detecte une tentative alors il nous envoie balader. Tout le problème est là, comme on ne peut obtenir de handles ni sur le process ni sur ses threads, on est un peu bloqué pour l’injection de code.
Cependant lors de la création d’un process, la fonction CreateProcessInternalW de kernel32 va notifier le subsystem qu’un nouveau process à été crée en appelant CsrClientConnectToServer de ntdll qui va utiliser un LPC sur le port \\Windows\Api. On peut trouver un pseudo-implémentation de ce mécanisme dans le bouquin de Garry Nebbet (Windows 2000 Native Api Reference) au chapitre 6, Processes -> Example 6.1 : Forking a Win32 Process.
/Informs CSRSS about new win32-process
VOID InformCsrss(HANDLE hProcess, HANDLE hThread, ULONG pid, ULONG tid)
{
// _asm int 3;
struct CSRSS_MESSAGE {
ULONG Unknown1;
ULONG Opcode;
ULONG Status;
ULONG Unknown2;
};
struct {
NT::PORT_MESSAGE PortMessage;
CSRSS_MESSAGE CsrssMessage;
PROCESS_INFORMATION ProcessInformation;
NT::CLIENT_ID Debugger;
ULONG CreationFlags;
ULONG VdmInfo[2];
} csrmsg = {{0}, {0}, {hProcess, hThread, pid, tid}, {0}, 0/*STARTF_USESTDHANDLES | STARTF_USESHOWWINDOW*/, {0}};
CsrClientCallServer(&csrmsg, 0, 0x10000, 0x24);
}
Ici le message est transmit par LPC avec l’API CsrClientCallServer, si on disass CsrClientConnectToServer on voit quelle fait la même opération que le code ci-dessus, tout est donc normal
Lorsque csrss.exe recoit ce message il duplique les handles. Ainsi si on regarde le process csrss avec le tool ProcessExplorer on s’apercoit que celui possède des handles sur tout les processes/threads tournant. En fait csrss à en partie pour rôle de deleter les ressources allouer par les process/threads qui sont terminés.
Maintenant on se place dans l’hypothèse ou le HIDS ne contrôle par l’ouverture du handle sur le process csrss.exe (ou même smss son créateur). Nous on veut obtenir un handle sur notre process ou bien sur un de ses threads, il nous suffit donc d’énumerer tout les handles ouvert par csrss puis de retrouver ceux qui font référence notre process. Dès qu’on les a, on les duplique puis on réalise l’injection. C’est bien evidemment cettre dernière feature de Windows qui est si pratique, la duplication nous permet à partir d’un handle sur un process et de celui qu’on vise de crée notre propre handle dans l’ObjectTable de notre process avec les même droits le même objet. De plus les handles passé au process csrss sont avec les droits PROCESS_ALL_ACCESS/THREAD_ALL_ACCESS, on a donc les pleins pouvoirs par la suite :]
Dans mon cas j’ai utilisé une injection de type GhostWriting qui ne nécessite qu’un handle sur un des threads du process visé pour copier et exécuter le code.
Allay la partie technique. Pour commencer il nous faut ouvrir un handle sur le process csrss avec un OpenProcess, mais comme celui-ci tourne sous un autre account (SYSTEM) il nous faut le SeDebugPrivilege. Ensuite on énumère tout les handles ouvert du système en appelant NtQuerySystemInformation avec l’InformationClass sur SystemHandleInformation. De cette liste on extrait les handles ouvert par csrss puis on les duplique dans notre process (NtDuplicateObject) pour pouvoir y retrouver leur type avec NtQueryObject. Si le le type de l’objet référencé par le handle est un thread alors on appel NtQueryInformationThread pour obtenir son TID, si celui-ci correspond au TID du thread primaire du process à injecter alors on effectue un GhostWriting dans sa stack.
Evidemment, ce qui est cool c’est qu’on utilise aucun Open* sur le process à injecter, mais il nous faut être admin sur la b0x pour pouvoir obtenir le SeDebugPrivilege. De plus le HIDS peu très bien checker l’ouverture de handle sur csrss (même si il aussi possible dans ce cas de dupliquer le handle de smss.exe qui à crée csrss.exe)
C:\\ProgHack\\c\\OpenProcesz>t4pZ.exe calc.exe
T4Pz Gh9sTWr1Y1ng
By Ivanlef0u
B3 M4D!
,-~-, ,-~~~~-, /\\ /\\
(\\ / ,-, \\ ,' ', / ~~ \\
\\'-' / \\ \\ / _ # <0 0> \\
'--' \\ \\/ .' '. # = Y =/
\\ / \\ \\ `#-..O.-'
Sh1TtInG >> O \\ \\ \\ `\\ \\\\
0n >> 0 ) /> / \\ \\\\
y0u >> 0o / /`/ /`__ \\ \\\\__
T4pZ >> o00o (____)))_))) \\__)))
Csrss.exe PID : 916
calc.exe PID : 1896
calc.exe primary thread TID : 1792
Total Handles : 5683
Thread Handle in Csrss : 948
{*|*} 1nj3ctInG c0de 1n d4 ThR34d {*|*}
[*] pUnctUr3 d0n3 [*]
S33 Y0u M0thAfuK4
Il est bien sur possible que le HIDS contrôle les duplications de handles, mais il faudra que celui fonctionne avec des API comme ObReferenceObjectByHandle et devra manipuler des strutures non documentées (pas officiellement bien sur :}) comme les EPROCES, ETHREADS.
Vous trouvez le code/binaire ici :
http://ivanlef0u.fr/repo/t4pZ.rar
w3 4re 4ll T4pZ h3re, 1′M t4pZ, y0u 4r3 t4pz!
Références :
Windows Internals : Key System Components & Flow of CreateProcess
http://www.phrack.org/archives/62/p62-0x06_Kernel_Mode_Backdoors_for_Windows_NT.txt
http://www.defendion.com/EliCZ/bugs/WinBugs.htm
http://www.c0ding.fr/blog/?p=4
juin 27th, 2007
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
juin 22nd, 2007
Je surfais tranquillement sur le net, regardant les news de la journée sans grand intérêt, jusqu’a je tombe sur un post, faisant référence à du hot pachting, Gruuuut kesako ??? Je préfère vous décevoir, cela ne consiste pas à patcher votre OS avec une strip-teaseuse
Anyway, si parfois vous trainez sous olly, vous avez sans doute remarqué que certaines fonctions de Windows commençaient avec un mov edi,edi, évidemment l’instruction ne fait pas grand chose d’utile et comme moi vous vous êtes surement dit que ca devait être une facétie de MS. Par exemple, si on prend la fonction RtlEqualString exportée par NTDLL, son prologue est :
lkd> u RtlEqualString
nt!RtlEqualString:
8050369a 8bff mov edi,edi
8050369c 55 push ebp
8050369d 8bec mov ebp,esp
8050369f 8b4d08 mov ecx,dword ptr [ebp+8]
805036a2 8b550c mov edx,dword ptr [ebp+0Ch]
805036a5 0fb701 movzx eax,word ptr [ecx]
On peut voir que la première instruction est un mov edi, edi dont on ne voit pas vraiment l’intérêt, du moins pour l’instant :p
Dans le cas ou nous voulons patché cette fonction par un inline hook, c’est à dire en remplaçant les 5 premiers bytes par un long jmp il nous avant de patché, sauvegardé ces bytes pour pourvoir plus tard appeler la fonction de façon normale. Dans le cas présent si on regarde juste avant le prologue de la fonction on peut voir :
lkd> u RtlEqualString-7
nt!CcScheduleReadAhead+0xde:
80503693 ebc4 jmp nt!CcScheduleReadAhead+0x102 (80503659)
80503695 90 nop
80503696 90 nop
80503697 90 nop
80503698 90 nop
80503699 90 nop
nt!RtlEqualString:
8050369a 8bff mov edi,edi
8050369c 55 push ebp
Hoho, 5 nop qui s’ennuient dans leurs coins et qui ne seront jamais exécuté. L’astuce consiste donc à savoir qu’un short jmp prend 2 bytes et qu’en patchant le mov edi, edi avec un short jmp on peut sauter à +- 127 bytes de la position courante. Je vous laissez deviner la suite, si on place un long jmp (5 bytes) vers notre fonction de hook au niveau des nop et qu’on fasse sauter notre short jmp sur ce long jmp, SPROUIK on un inline hook super propre qui ne nécessite plus de sauvegardé les instructions du prologue, un hot pachting.
Alors maintenant, étant curieux, je me suis codé un petit tool rapidement qui énumère les fonctions pouvant supporter le hot pachting. J’ai testé mon code sur les fonctions exportées de ntdll, et environ la moitié d’entre elles supportent le hot pachting, voici la liste :
http://ivanlef0u.fr/repo/hot_patching.txt
J’en profite en même pour vous donner le code du petit scanner :
#include <windows.h>
#include <stdio.h>
#pragma comment(linker, "/nodefaultlib:libc.lib")
#pragma comment(linker, "/entry:\"main\"")
#pragma comment (lib, "ntdll.lib")
#pragma comment (lib, "kernel32.lib")
#pragma comment (lib, "msvcrt.lib")
int main()
{
ULONG i,j;
HANDLE hLib;
PCHAR FctName, FctProlog;
PULONG RvaNameTab;
PIMAGE_DOS_HEADER pDOS;
PIMAGE_NT_HEADERS32 pNT;
PIMAGE_EXPORT_DIRECTORY pExp;
hLib=GetModuleHandle("ntdll.dll");
pDOS=(PIMAGE_DOS_HEADER)hLib;
pNT=(PIMAGE_NT_HEADERS32)((PUCHAR)pDOS->e_lfanew+(ULONG)hLib);
pExp=(PIMAGE_EXPORT_DIRECTORY)((PUCHAR)pNT->OptionalHeader.DataDirectory[IMAGE_DIRECTORY_ENTRY_EXPORT].VirtualAddress+(ULONG)hLib);
RvaNameTab=(PULONG)((PUCHAR)pExp->AddressOfNames+(ULONG)hLib);
for(i=0; i<pExp->NumberOfNames; i++)
{
FctName=(PCHAR)(*(RvaNameTab+i)+(ULONG)hLib);
FctProlog=(PCHAR)GetProcAddress((HMODULE)hLib, FctName);
//scan les 5 premiers bytes avant le prologe de la fct
for(j=0; j<5; j++)
{
if(*(PUCHAR)(FctProlog-5+j)!=(UCHAR)0x90)
break;
}
//si on a pas 5 NOP, on passe à la fct suivante
if(j!=5)
continue;
//si pas de mov edi, edi
if(*(PUSHORT)FctProlog!=0xFF8B) //mov edi, edi
continue;
printf("%s : 0x%x : OKAY for hot patching\n", FctName, FctProlog);
}
return 0;
}
Et voici quelques liens parlant de cette technique :
http://kernelmustard.com/2007/06/13/whence-came-function-hooking/
http://msmvps.com/blogs/kernelmustard/archive/2005/04/25/44413.aspx
http://www.openrce.org/articles/full_view/22
http://www.apihooks.com/EliCZ/export.htm
juin 14th, 2007
Cette fois ci, le contenu du post est sous licence no-brain, tout ceux ayant un QI de plus de 2 sont invités à aller se brancher devant la TV pendant 24h pour le faire diminuer. Pourquoi une telle licence ? Tout simplement parce que ce qui va suivre risque de transformé votre cerveau en éponge. Je prévient d’emblé, ce qui va suivre est un voyage dans le hal et le kernel à travers le fonctionnement du context switching, alors préparez votre aspirine.
Comme vous le savez un processeur monocore ne peut réaliser qu’une seule chose à la fois, l’impression du multitâche sur l’OS est donc réalisée en effectuant plein de petites actions rapidement. Chaque tâche est appelé un thread, un thread représente un ensemble d’instructions à être exécuté, ainsi l’OS pour être multitâche va sauter de thread en thread et le processeur va effectuer quelques instructions de ceux-ci à chaque fois avant de changer de thread. Ca va là, tout le monde suit encore :], je vous conseil quand même d’avoir le chapitre du Windows Internals sur le thread scheduling ouvert pour être plus à l’aise avec la suite.
Un thread peut ainsi avoir 7 états :
- Initialized : thread en cour d’initialisation.
- Ready : le thread est prêt à être éxécuté.
- Running : le thread est en court d’exécution.
- Waiting : le thread est en attente.
- Terminated : le thread à finit son exécution.
- Transition : le thread est ready mais sa kernel stack doit être inswappée pour être rechargée en mémoire.
- Standby : attend d’être exécuté sur un processeur précis.
Chaque thread dispose aussi d’une priorité (en faite il y en à 2, une statique et une dynamique mais osef), celle-ci permet au thread scheduler de déterminer quel sera le thread suivant à être lancé. De plus avant d’être exécuté chaque thread reçois un quantum, cette valeur comparable à une unité de temps, permet à l’OS de définir combien de temps va tourner un thread. En fait, le BIOS va régulièrement générer une interruption qui va décrémenté le quantum du thread courant. Lorsque le quantum est nul, le noyau change de thread. Chez moi cette interruption intervient toutes les 15.625 ms. Cette valeur peut être retrouvée en utilisant l’API GetSystemTimeAdjustment.
En fait il y plusieurs raisons pour que l’OS décide de changer de thread :
- Quand un thread passe en mode Waiting après par un appel à WaitForSingleObject par exemple.
- Un thread de priorité supérieure devient Ready, on appel cela la preemption.
- Le thread à finit son exécution.
- Le quantum du thread courant est expiré.
L’interruption qui intervient toutes les 15.625 ms est généré par le BIOS et est handler par la routine HalpClockInterrupt qu’on retrouve dans l’IDT à l’indice 0×30 :
!idt -a
[...]
30: 806f3d48 hal!HalpClockInterrupt
Comme nous nous trouvons dans le cadre d’une interruption le processeur va automatiquement pusher sur la kernel stack du thread courant les valeurs suivantes :
error code <- esp after interrupt
registre EIP
registre CS
registre EFLAGS
registre ESP
segment SS
Toutes ces valeurs sont évidemment celles qui existaient avant l’apparition de l’interruption. Si on désassemble le début de la routine HalpClockInterrupt on peut voir :
kd> u hal!HalpClockInterrupt l 20
hal!HalpClockInterrupt:
806f3d48 push esp
806f3d49 push ebp
806f3d4a push ebx
806f3d4b push esi
806f3d4c push edi
806f3d4d sub esp,54h
806f3d50 mov ebp,esp
806f3d52 mov dword ptr [esp+44h],eax
806f3d56 mov dword ptr [esp+40h],ecx
806f3d5a mov dword ptr [esp+3Ch],edx
806f3d5e test dword ptr [esp+70h],20000h
806f3d66 jne hal!V86_Hci_a (806f3d20)
806f3d68 cmp word ptr [esp+6Ch],8
806f3d6e je hal!HalpClockInterrupt+0x4b (806f3d93)
806f3d70 mov word ptr [esp+50h],fs
806f3d74 mov word ptr [esp+38h],ds
806f3d78 mov word ptr [esp+34h],es
806f3d7c mov word ptr [esp+30h],gs
806f3d80 mov ebx,30h
806f3d85 mov eax,23h
806f3d8a mov fs,bx
806f3d8d mov ds,ax
806f3d90 mov es,ax
806f3d93 mov ebx,dword ptr fs:[0]
806f3d9a mov dword ptr fs:[0],0FFFFFFFFh
806f3da5 mov dword ptr [esp+4Ch],ebx
[...]
Ce prologue sert à sauvegarde sur la stack les valeurs des autres registres de notre thread interrompu. Plus loin, cette routine élève l’IRQL à CLOCK2_LEVEL (28=0x1C) en appelant HalBeginSystemInterrupt, c’est un des IRQL les plus élevé sachant que le maximum HIGH_LEVEL vaut 31. A la fin de HalpClockInterrupt on voit un jmp sur la fonction du kernel KeUpdateSystemTime qui à pour rôle de mettre à jour l’horloge système.
Plus loin KeUpdateSystemTime appel KeUpdateRunTime qui se charge d’actualiser le compteur d’interruptions, le user ou kernel time du thread et surtout de décrémenter le quantum !
Si on regarde la stack lorsque qu’on est dans KeUpdateRunTime on a :
kd> kv
ChildEBP RetAddr Args to Child
f98f8d50 804e32f6 00000000 00000000 00000030 nt!KeUpdateRunTime+0x122 (FPO: [1,1,0])
f98f8d50 77bfc7c0 00000000 00000000 00000030 nt!KeUpdateSystemTime+0x137 (FPO: [0,2] TrapFrame @ f98f8d64)
[INTERRUPT OCCURS !!!]
00e3e07c 77c10017 00e3e094 7ca839e4 00e3e104 msvcrt!_aulldvrm
00e3e0b4 7c9ffd32 00e3e46c 0000005b 7ca839e4 msvcrt!_vsnwprintf+0x30 (FPO: [Non-Fpo])
00e3e0d8 7c9ffd02 00e3e46c 0000005c 7ca839e4 SHELL32!StringVPrintfWorkerW+0x2c (FPO: [Non-Fpo])
[...]
Le trapframe correspond aux valeurs des registres sauvegardés sur la kernel stack du thread courant avant que l’interruption ne débute. On peut dumper le contenu du trapframe avec la commande .trap du KD.
kd> .trap f98f8d64
ErrCode = 00000000
eax=00000010 ebx=00000401 ecx=00000007 edx=00000000 esi=00e3de77 edi=00000000
eip=77bfc7c0 esp=00e3dbf8 ebp=00e3e07c iopl=0 nv up ei pl nz na po nc
cs=001b ss=0023 ds=0023 es=0023 fs=003b gs=0000 efl=00000202
msvcrt!_aulldvrm:
001b:77bfc7c0 push esi
Ici l’interruption à débarqué avant l’exécution du « push esi » de la fonction msvcrt!_aulldvrm.
Dans la routine KeUpdateSystemTime on peut voir le bout ce bout de code :
[...]
nt!KeUpdateSystemTime+0x124:
804e32e3 mov eax,dword ptr [nt!KeMaximumIncrement (8055168c)]
804e32e8 add dword ptr [nt!KiTickOffset (80551694)],eax
804e32ee push dword ptr [esp]
804e32f1 call nt!KeUpdateRunTime (804e332a)
804e32f6 cli
804e32f7 call dword ptr [nt!_imp__HalEndSystemInterrupt (804d7608)]
804e32fd jmp nt!Kei386EoiHelper (804df05c)
[...]
HalEndSystemInterrupt fait redescendre l’IRQL au niveau ou il se trouvait avant l’interruption et Kei386EoiHelper est la fonction qui va nous faire quitter l’interruption. En effet si on la disass on peut voir :
[...]
nt!Kei386EoiHelper+0x12c:
804df188 add esp,3Ch
804df18b pop edx
804df18c pop ecx
804df18d pop eax
804df18e lea esp,[ebp+54h]
804df191 pop edi
804df192 pop esi
804df193 pop ebx
804df194 pop ebp
804df195 cmp word ptr [esp+8],80h
804df19c ja nt!Kei386EoiHelper+0x148 (804df1a4)
nt!Kei386EoiHelper+0x142:
804df19e add esp,4
804df1a1 iretd
nt!Kei386EoiHelper+0x148:
804df1a4 cmp word ptr [esp+2],0
804df1aa je nt!Kei386EoiHelper+0x142 (804df19e)
nt!Kei386EoiHelper+0x150:
804df1ac cmp word ptr [esp],0
804df1b1 jne nt!Kei386EoiHelper+0x142 (804df19e)
nt!Kei386EoiHelper+0x157:
804df1b3 shr dword ptr [esp],10h
804df1b7 mov word ptr [esp+2],0F8h
804df1be lss sp,dword ptr [esp]
804df1c3 movzx esp,sp
804df1c6 iretd
Les pop avant sont la pour remettre les registres avant l’arrivé de l’interruption et iretd effectue le retour. Tout est nickel le thread repart.
Je résume donc un peu, le système balance périodiquement une interruption, la routine qui doit gérer cette interruption fait péter l’IRQL à CLOCK2_LEVEL, se charge de mettre à jour l’horloge système et décrémente le quantum du thread courant (en gros). Le contexte de ce dernier est sauvegardé dans sa kernel stack (même si celui était tournait en userland avant que l’interruption arrive). A la fin de l’interrpution la fonction Kei386EoiHelper va reloader le contexte, PAF ! le thread repart et ca fait des chocapics :]
Maintenant on s’intéresse au moment ou la fonction KeUpdateRunTime décrémente le quantum et que celui-ci devient nul. Cela signifie que le temps d’exécution alloué au thread est écoulé et qu’il faut soit recharger son quantum, soit swapper sur le prochain thread. Désassemblons donc un peu KeUpdateRunTime :
kd> uf nt!KeUpdateRunTime
[...]
nt!KeUpdateRunTime+0x114:
804e343e sub byte ptr [ebx+6Fh],3 ; on soustrait 3 au quantum
804e3442 jg nt!KeUpdateRunTime+0x133 (804e345d) ; si plus grand que 0 on leave normalement la fct
nt!KeUpdateRunTime+0x11a:
804e3444 cmp ebx,dword ptr [eax+12Ch] ; eax=ffdff000=KPCR, compare le thread courant au idle thread
804e344a je nt!KeUpdateRunTime+0x133 (804e345d) ; si egal on leave
nt!KeUpdateRunTime+0x122:
804e344c mov dword ptr [eax+9ACh],esp ; met champ QuantumEnd du KPRCB à une valeur !=0 :p
804e3452 mov ecx,2
804e3457 call dword ptr [nt!_imp_HalRequestSoftwareInterrupt (804d7680)]
nt!KeUpdateRunTime+0x133:
804e345d pop ebx
804e345e ret 4
Lorsque le quantum est nul, KeUpdateRunTime va mettre le ULONG QuantumEnd du KPRCB à true puis fait un appel à HalRequestSoftwareInterrupt. Cette fonction devant être appeler en convention fastcall, ses arguments sont passés par les registres. Ecx contient la valeur du seul argument de HalRequestSoftwareInterrupt qui est 2 (IRQL valant DISPATCH_LEVEL) dans le cas présent.
Regardons le code de HalRequestSoftwareInterrupt :
kd> uf hal!HalRequestSoftwareInterrupt
hal!HalRequestSoftwareInterrupt:
806f3884 mov eax,1
806f3889 shl eax,cl ; si cl=2 alors eax vaut 4
806f388b pushfd
806f388c cli
806f388d or dword ptr ds:[0FFDFF028h],eax
806f3893 mov cl,byte ptr ds:[0FFDFF024h] ; cl= current IRQL
806f3899 mov eax,dword ptr ds:[FFDFF028h] ; IRR=4
d> !pcr
KPCR for Processor 0 at ffdff000:
Major 1 Minor 1
NtTib.ExceptionList: fcd395d4
NtTib.StackBase: fcd39df0
NtTib.StackLimit: fcd37000
NtTib.SubSystemTib: 00000000
NtTib.Version: 00000000
NtTib.UserPointer: 00000000
NtTib.SelfTib: 7ffde000
SelfPcr: ffdff000
Prcb: ffdff120
Irql: 0000001c
IRR: 00000004 <= !
IDR: ffff20f0
InterruptMode: 00000000
IDT: 8003f400
GDT: 8003f000
TSS: 80042000
CurrentThread: ffb47618
NextThread: 00000000
IdleThread: 80559020
806f389e and eax,3 ; si eax=2 alors apres le ‘and’ eax=0
806f38a1 xor edx,edx
806f38a3 mov dl,byte ptr hal!SWInterruptLookUpTable (806f4784)[eax]
kd> db SWInterruptLookUpTable l 10
806f4784 00 00 01 01 02 02 02 02
eax=0 dionc après le mov, dl=0
806f38a9 cmp dl,cl ; si IRQL > 0 on leave
806f38ab jbe hal!HalRequestSoftwareInterrupt+0x30 (806f38b4)
hal!HalRequestSoftwareInterrupt+0x29:
806f38ad call dword ptr hal!SWInterruptHandlerTable (806f476c)[edx*4]
hal!HalRequestSoftwareInterrupt+0x30:
806f38b4 popfd
806f38b5 ret
En détaillant un peu, on voit que HalRequestSoftwareInterrupt va mettre le champ IRR du KPCR à la valeur 4. Je ne connais pas la signification exacte de se champ mais je pense qu’il sert au mettre en place les DPC (Dispatch/Deferred Procedure Call). En effet lors de l’appel à cette fonction l’IRQL courant vaut CLOCK2_LEVEL, nous on veut obtenir une interruption software, or celles ci ne sont pas accessibles à un IRQL si élevé, elles devront donc être appelées lorsque celui ci redescendra. Ainsi le champ devrait donc permettre de dire qu’une demande d’interruption software est en attente.
Alors lorsque l’IRQL redescend, notre interruption software doit être activée. Hop straff-bunny-rail ! On disass halEndSystemInterrupt, la fonction appelé du hal juste avant qu’on quitte l’interruption int 30 avec Kei386EoiHelper.
kd> uf hal!HalEndSystemInterrupt
[...]
hal!HalEndSystemInterrupt+0x22:
806ee23e mov byte ptr ds:[0FFDFF024h],cl ; met l'IRQL à la valeur qu'il avait avant l'interruption
806ee244 mov eax,dword ptr ds:[FFDFF028h]
806ee249 mov al,byte ptr hal!SWInterruptLookUpTable (806f4784)[eax]
kd> db SWInterruptLookUpTable l 10
806f4784 00 00 01 01 02 02 02 02
si IRR=4 alors al=2
hal!HalEndSystemInterrupt+0x37:
806ee253 ret 8
hal!HalEndSystemInterrupt+0x3a:
806ee256 add esp,0Ch
806ee259 jmp dword ptr hal!SWInterruptHandlerTable2 (806f4778)[eax*4]
kd> dps sWInterruptHandlerTable2
806f4778 806f3494 hal!KiUnexpectedInterrupt
806f477c 806f3bd1 hal!HalpApcInterrupt2ndEntry
806f4780 806f3a39 hal!HalpDispatchInterrupt2ndEntry
si eax=2 on call hal!HalpDispatchInterrupt2ndEntry
HalEndSystemInterrupt va lire la valeur IRR du KPCR et jump sur un interrupt handler si nécessaire. Dans notre cas c’est la fonction HalpDispatchInterrupt2ndEntry qui est appelé pour réaliser le DPC.
Now on regarde gentiment HalpDispatchInterrupt2ndEntry
hal!HalpDispatchInterrupt2ndEntry:
806f3a39 push dword ptr ds:[0FFDFF024h]
806f3a3f mov byte ptr ds:[0FFDFF024h],2 ; on définit l'IRQL à DISPATCH_LEVEL
806f3a46 and dword ptr ds:[0FFDFF028h],0FFFFFFFBh
806f3a4d sti
806f3a4e call dword ptr [hal!_imp__KiDispatchInterrupt (806ed428)]
806f3a54 cli
806f3a55 call hal!HalpEndSoftwareInterrupt (806ee260)
806f3a5a jmp dword ptr [hal!_imp_Kei386EoiHelper (806ed4f8)] ; quitte l'exception
Si on disass KiDispatchInterrupt :
kd> uf nt!KiDispatchInterrupt
nt!KiDispatchInterrupt:
804db874 mov ebx,dword ptr ds:[0FFDFF01Ch]
804db87a lea eax,[ebx+980h]
804db880 cli
804db881 cmp eax,dword ptr [eax]
804db883 je nt!KiDispatchInterrupt+0x2e (804db8a2)
nt!KiDispatchInterrupt+0x11:
804db885 push ebp
804db886 push dword ptr [ebx]
804db888 mov dword ptr [ebx],0FFFFFFFFh
804db88e mov edx,esp
804db890 mov esp,dword ptr [ebx+988h]
804db896 push edx
804db897 mov ebp,eax
804db899 call nt!KiRetireDpcList (804dbb8e)
804db89e pop esp
804db89f pop dword ptr [ebx]
804db8a1 pop ebp
nt!KiDispatchInterrupt+0x2e:
804db8a2 sti
804db8a3 cmp dword ptr [ebx+9ACh],0 ; si KPRC->QuantumEnd != on saute !
804db8aa jne nt!KiDispatchInterrupt+0x8e (804db902)
nt!KiDispatchInterrupt+0x38:
804db8ac cmp dword ptr [ebx+128h],0
804db8b3 je nt!KiDispatchInterrupt+0x8d (804db901)
nt!KiDispatchInterrupt+0x41:
804db8b5 mov eax,dword ptr [ebx+128h]
nt!KiDispatchInterrupt+0x47:
804db8bb sub esp,0Ch
804db8be mov dword ptr [esp+8],esi
804db8c2 mov dword ptr [esp+4],edi
804db8c6 mov dword ptr [esp],ebp
804db8c9 mov esi,eax
804db8cb mov edi,dword ptr [ebx+124h]
804db8d1 mov dword ptr [ebx+128h],0
804db8db mov dword ptr [ebx+124h],esi
804db8e1 mov ecx,edi
804db8e3 mov byte ptr [edi+50h],1
804db8e7 call nt!KiReadyThread (804db6da)
804db8ec mov cl,1
804db8ee call nt!SwapContext (804db924)
804db8f3 mov ebp,dword ptr [esp]
804db8f6 mov edi,dword ptr [esp+4]
804db8fa mov esi,dword ptr [esp+8]
804db8fe add esp,0Ch
nt!KiDispatchInterrupt+0x8d:
804db901 ret
nt!KiDispatchInterrupt+0x8e:
804db902 mov dword ptr [ebx+9ACh],0 ; le champ QuantumEnd est mit à 0
804db90c call nt!KiQuantumEnd (804e7446)
804db911 or eax,eax
804db913 jne nt!KiDispatchInterrupt+0x47 (804db8bb)
nt!KiDispatchInterrupt+0xa1:
804db915 ret
KiDispatchInterrupt se charge d’exécuté les différents DPC existant à un IRQL de DISPATCH_LEVEL, puis elle vérifie la valeur du champ QuantumEnd du KPRCB. Si celui ci n’est pas nul alors il se produit un call à KiQuantumEnd.
Pour résumer un peu voici la call stack lors de l’appel à KiQuantumEnd.
kd> kv
ChildEBP RetAddr Args to Child
f9a48908 804db911 806f3a54 00000000 f9a489bc nt!KiQuantumEnd (FPO: [Non-Fpo])
f9a4890c 806f3a54 00000000 f9a489bc 80567cea nt!KiDispatchInterrupt+0x9d (FPO: [Uses EBP] [0,0,3])
f9a4890c 00000000 00000000 f9a489bc 80567cea hal!HalpDispatchInterrupt2ndEntry+0x1b (FPO: [0,1] TrapFrame @ 00000000)
KiQuantumEnd à pour rôle de vérifier si la priorité du thread doit être augmenté et de renvoyé le next thread à lancer.
Plus loin KiDispatchInterrupt va call KiFindReadyThread et mettre dans la liste des thread ready le thread courant avec KiReadyThread. Puis intervient un changement de context pour charger non pas les registres du nouveau thread (pas de façon directe en tout cas) mais bien des éléments comme la nouvelle stack et recharger les nouveaux paramètres dans le KPCR.
Ainsi si on regarde un peu la liste des threads en state ready avec la commande !ready on remarque que la dernière fonction qu’ils ont appelé est celle qui à call SwapContext. Ce qui fait que, lors du changement de context le retn de la fin de la fonction SwapContext se situant dans la nouvelle stack relance l’exécution tu thread. Ainsi si le changement du thread à eu lieu après un DPC provenant de l’interruption int 30, la fonction SwapContext va gentiment retourner de KiDispatchInterrupt puis enfin de la fin de HalpDispatchInterrupt2ndEntry qui contient :
hal!HalpDispatchInterrupt2ndEntry:
[...]
806f3a4e call dword ptr [hal!_imp__KiDispatchInterrupt (806ed428)]
806f3a54 cli
806f3a55 call hal!HalpEndSoftwareInterrupt (806ee260)
806f3a5a jmp dword ptr [hal!_imp_Kei386EoiHelper (806ed4f8)] ; recharge le contexte et quitte l'exception
Le magnifique jmp sur Kei386EoiHelper qui va recharger les registres de notre thread :] et le relancer.
C’est donc grâce à ce model que peut se mettre en place la préemption de threads ou le changement de contexte alors que le thread n’a pas finit son exécution. Remarquez que cela est loin d’être trivial à mettre en place mais se révèle extrêmes puissant par la suite. J’ai bien fait fumé mon IDA et mon KD pour retrouvez cela.
J’espère avoir été le plus clair possible, en cas de question n’hésitez pas à laissez un post ou à me contacter.
juin 10th, 2007
Le SSTIC c’est un peu la gay pride des geeks, tout le monde se ramène avec son plus beau laptop et montre à ses potes n3rdz comment il peut rooter le monde avec la nouvelle GUI de metasploit (sisi ca existe : http://laramies.blogspot.com/2007/05/metasploit-gui-assistant.html ). Le niveau des confs cette année ma fait de la peur, je me suis souvent retrouvé largué par un slide comportant des schémas d’injection de code dans la GDT, surement parce que j’essayais en même temps de montrer par A+B à mon voisin que Windows c’est mieux que linux :] Il faut dire aussi que certaines ne m’intéressaient pas du tout et que les présentations intéressantes étaient parfois difficiles à suivre, soit parce que le speaker va super vite parce qu’il maitrise à fond son sujet (et qu’il lui reste 3 mins pour finir), soit il aime compliquer les choses en sortant des formules de maths alors qu’un bon schéma suffit, ma conf préféré a été celle qui montrait les profondeurs de l’authentification Windows ou les informations étaient super intéressantes et bien présentées à l’aide de démonstrations impressionnantes. Normalement les confs seront sur le site prochainement.
Mais ce qui est vraiment génial au SSTIC ce n’est pas les confs, ni les rumps, c’est surtout le Social Event. Le principe est simple, vous prenez une bande de geeks ub3r affamés et vous les laissez ensemble dans un restaurant avec bières à gogo, effet garantit. C’est durant cette soirée que j’ai rencontré Frédéric Raynal (wiwi celui qui joue à Enemy Territory) et dont la première phrase en me voyant a été : « Ivanlef0u ?!, je suis fan ! ». A ce moment là que je me suis demandé combien de bières il avait bu, mais le pire c’est qu’il ma proposé d’écrire pour son mag ! J’ai comprit qu’il avait vraiment beaucoup bu :} Anyway il reste des choses à confirmer le sujet mais j’espère faire l’article avec le plus grand nombres de fautes d’orthographes jamais paru.
Aussi ce qui est sympa c’est qu’en discutant avec certaines personnes devant une bière et en les faisant bien boire, on peut apprendre des infos croustillantes. C’est là qu’on remarque qu’il existe vraiment un fossé entre les gens qui assistent aux confs régulièrement, c’est à dire surtout les professionnels et la scène du net. D’un coté ceux qui gardent tout pour eux et d’un autres ceux qui release un peu pour que tout le monde en profite (devinez de quel coté je suis :p). Bref dans tous cas chacun a ses petits secrets :]
Enfin le vendredi soir j’ai participé aux 2600 rennes, soirée sympathique avec des vrai morceaux de trolls autour d’un verre et toujours autant de secrets qui circulent :}
Maintenant je vais pouvoir reprendre une activité normale !
Be T.4.P.5
juin 2nd, 2007