Archive for juin, 2007

Üb3rGh0stWr1t1nG

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

7 comments juin 27th, 2007

Got the Handle

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 :

lol

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

6 comments juin 22nd, 2007

Hot Patching

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

10 comments juin 14th, 2007

Context Switching

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.

9 comments juin 10th, 2007

Review SSTIC

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

24 comments juin 2nd, 2007


Calendar

juin 2007
L Ma Me J V S D
« mai   juil »
 123
45678910
11121314151617
18192021222324
252627282930  

Posts by Month

Posts by Category