Archive for septembre, 2008

Virtual Mode on Windows

Aujourd’hui, on va jouer avec le virtual mode, plus précisément sur son implémentation sous Windows. Alors accrochez votre ceinture et préparez-vous pour un plongeon au coeur des CPU et de Windows.

Pour comprendre ce qu’est ce mode il faut se rappeler que les vieux CPUs x86 étaient en 16 bits et fonctionnaient uniquement en real mode comme le 8086, un mode dans lequel on pouvait adresser un peu plus de 1MB de RAM grâce à la segmentation, la gestion des interruptions et exceptions passait par l’IVT (Interrupt Vector Table), une table de couples segment:offset pour chaque handler. Bref c’était roots, pas de pagination, pas de protected mode, mais pour l’époque ca envoyait du lourd. En parlant d’envoyer du lourd je vous conseil d’écouter ce track de ‘I am God Songs’ de Black Sheep Wall :

Modest Machine

Plus tard est arrivé le 80386, qui pour des raisons de compatibilité supporte le real mode mais pose les bases du protected mode, en même temps il introduit le virtual mode, l’exécution d’une tâche real mode dans un environnement protégé. Permettant ainsi de faire tourner des binaires DOS (16 bits) depuis un Windows 9x jusqu’a un Vista. En fait c’est depuis que j’ai regardé les specs du SMM que j’ai commencé à m’intéresser au mode V8086 juste pour le fun, il est clair que plus personne ne code de binaire 16 bits mais retrouver l’implémentation de ce mode est quelque chose d’assez passionnant surtout que cela fait appel à un bon nombre de composants du système.

Avant tout, j’insiste sur le fait qu’il faut bien comprendre que le virtual mode est un fonctionnement prévu par le CPU, ce n’est une émulation purement software d’une tâche 16 bits fait par un programme user-land. D’ailleurs Intel fournit au chapitre 15 ’8086 emulation’ de son manual ‘Intel 64 and IA-32 Architectures Software Developer’s Manual Volume 3A: System Programming Guide‘ toutes les informations nécessaire au programmeur pour mettre en place ce mécanisme.

Cette fois, je vais changer un peu de méthode, au lieu de creuser en partant de l’implémentation sous Windows vers le CPU, je vais partir des docs Intel puis tenter de retrouver les composants Windows correspondant. Pourquoi ? Parce qu’il est plus simple de comprendre le rôle et la position d’un composant si on sait ce qu’il représente pour le CPU, spécialement dans le cas présent ! Qui plus est, l’utilisation de cette feature fait appel à un trop grand nombre de fonctionnalités pour être comprise en partant « du haut », on retrouve pelle mêle des programmes 32 bits, une api native, des interruptions, des librairies 16 bits tournant en ring3, des gestionnaires d’interruptions à la fois en virtual mode et protected mode, ainsi de suite.

C’est partit, on bachote la doc, section 15.2 ‘Virtual-8086 mode’. Le virtual mode est activé lorsque le VM flag (bit 17) de l’EFlags est à 1, attention on ne modifie pas ce bit avec ‘pushfd ; pop eax ; or eax, 0×20000; push eax; popfd’. Notez bien que l’EFlags est spécifique à un thread, chaque thread ayant un contexte (ensemble de registres) qui lui est propre.

Justement, le passage du VM flag à 1 peut se faire de plusieurs manières. En utilisant un TSS 32 bits dans lequel le VM flag du champ EFlag est à 1. Le TSS (Task State Segment) est juste une structure contenant le contexte d’une tâche comme le montre le schéma suivant :

TSS

Le task register (tr) manipulé avec les instructions LTR (load task register) et STR (store task register) et est en fait un sélecteur de segment référençant un TSS descriptor dans la GDT du core. La prise en compte d’un TSS se fait en accédant avec un FAR JMP ou FAR CALL sur une task gate (équivalent d’une callgate pour une tâche) pouvant se situé aussi bien dans la GDT que la LDT.

Sauf que sous Windows, ce n’est pas réalisé du tout de cette manière, j’ai été un peu vite :] mais ca fait toujours de revoir la doc sur les TSS et puis ça sera utile pour la suite.

Windows implémente le passage en V8086 sans utiliser de TSS, trop contraignant. L’API native NtVdmControl fournit le Service VdmStartExecution :

NTSTATUS
NtVdmControl(
    IN VDMSERVICECLASS Service,
    IN OUT PVOID ServiceData
    )
/*++

Routine Description:

    This routine is the entry point for controlling Vdms.

Arguments:

    Service -- Specifies what service is to be performed
    ServiceData -- Supplies a pointer to service specific data

Return Value:

--*/

typedef enum _VdmServiceClass {
    VdmStartExecution,
    VdmQueueInterrupt,
    VdmDelayInterrupt,
    VdmInitialize,
    VdmFeatures,
    VdmSetInt21Handler,
    VdmQueryDir,
    VdmPrinterDirectIoOpen,
    VdmPrinterDirectIoClose,
    VdmPrinterInitialize,
    VdmSetLdtEntries,
    VdmSetProcessLdtInfo,
    VdmAdlibEmulation,
    VdmPMCliControl,
    VdmQueryVdmProcess
} VDMSERVICECLASS, *PVDMSERVICECLASS;

Ce service fait appel à la fonction du noyau VdmpStartExecution, comme son nom l’indique cette fonction va faire passer le thread en virtual mode, pour cela VdmSwapContext va mettre à jour la KTRAP_FRAME crée par KiFastCallEntry (elle même appelée par l’instruction SYSENTER de la fonction KiFastSystemCall fournit par ntdll.dll). En fait la KTRAP_FRAME est juste une structure contenant l’état de tous les registres lors du passage en kernel-land afin, permettant ainsi de les restaurer en revenant du syscall :

kd> dt nt!_KTRAP_FRAME
   +0x000 DbgEbp           : Uint4B
   +0x004 DbgEip           : Uint4B
   +0x008 DbgArgMark       : Uint4B
   +0x00c DbgArgPointer    : Uint4B
   +0x010 TempSegCs        : Uint4B
   +0x014 TempEsp          : Uint4B
   +0x018 Dr0              : Uint4B
   +0x01c Dr1              : Uint4B
   +0x020 Dr2              : Uint4B
   +0x024 Dr3              : Uint4B
   +0x028 Dr6              : Uint4B
   +0x02c Dr7              : Uint4B
   +0x030 SegGs            : Uint4B
   +0x034 SegEs            : Uint4B
   +0x038 SegDs            : Uint4B
   +0x03c Edx              : Uint4B
   +0x040 Ecx              : Uint4B
   +0x044 Eax              : Uint4B
   +0x048 PreviousPreviousMode : Uint4B
   +0x04c ExceptionList    : Ptr32 _EXCEPTION_REGISTRATION_RECORD
   +0x050 SegFs            : Uint4B
   +0x054 Edi              : Uint4B
   +0x058 Esi              : Uint4B
   +0x05c Ebx              : Uint4B
   +0x060 Ebp              : Uint4B
   +0x064 ErrCode          : Uint4B
   +0x068 Eip              : Uint4B
   +0x06c SegCs            : Uint4B
   +0x070 EFlags           : Uint4B
   +0x074 HardwareEsp      : Uint4B
   +0x078 HardwareSegSs    : Uint4B
   +0x07c V86Es            : Uint4B
   +0x080 V86Ds            : Uint4B
   +0x084 V86Fs            : Uint4B
   +0x088 V86Gs            : Uint4B

Une des fonctions de VdmpStartExecution est d’activer le VM Flag dans l’EFlags de la KTRAP_FRAME. Le problème c’est que la fonction qui est chargée de revenir en user-land, KiServiceExit, qui utilise donc l’instruction SYSEXIT va restaurer l’EFlags avec un simple popfd mais comme je l’ai dit auparavant, on ne peut mettre ce bit à 1 avec cette méthode. L’astuce est que KiServiceExit va tester le VM Flag dans la KTRAP_FRAME, si jamais il est à 1 alors elle effectue un passage du protected mode ring0 au virtual mode et en branchant sur une instruction IRET au lieu de sysexit ! Comme l’instruction IRET récupère sur la stack EIP, CS, EFlags, ESP, SS et que le CPU autorise le VM Flag à 1 dans un IRET, le passage en virtual mode est effectué.

A partir de là, on sait comment notre noyau gère le passage du protected mode au virtual mode, pour l’autre sens on verra plus tard.
Reste que savoir passer en virtual mode ne fait pas tout, en effet comme l’explique la doc, la gestion du virtual mode fait appel à 2 parties distinctes du système. D’abord en kernel-land il est nécessaire d’avoir un ‘virtual-8086 monitor’, un module noyau chargé de gérer l’initialisation du virtual mode comme je l’ai dit plus haut, puis la gestion de l’émulation des I/Os, interruptions et exceptions. Ce monitor se retrouve donc très proche des features de base de l’OS, parfois directement codé dans les handlers de l’IDT. Ensuite un ’8086 operating system service’, un programme permettant de représentant la tâche en virtual-mode sous forme de programme ‘normal’ par rapport au système, par exemple en real mode seul le premier méga de mémoire est accessible donc en utilisant la pagination on peut avoir plusieurs process émulant le virtual-mode en leur sein avant dans leur mémoire basse notre programme DOS. Sous Windows c’est le programme processes ntvdm.exe (NT Virtual DOS Machine) qui joue ce rôle. Enfin, lorsqu’on se trouve en virtual mode, notre code s’éxecute à un CPL ring3, l’IOPL vaut normalement 0, même si il est possible de modifier sa valeur avec la clé ‘VdmIOPL’ situé dans \HKLM\SYSTEM\CurrentControlSet\Control\Wow. En fonction de l’IOPL les instructions suivantes sont dites ‘sensibles’ c’est à dire quelles doivent être émulées par le monitor : CLI, STI, PUSHF, POPF, INT x et IRET.

Continuons de parcourir le man Intel, intéressons nous à la gestion des interruptions et exceptions, le gros morceau en fait, car si exécuter du code en virtual mode n’est pas trop difficile par contre définir le comportement du CPU dans le cas d’une interruption ou exception est autre chose.

Intel définit 3 types de classes pour les interruptions et exceptions :

  1. La première classe, regroupe à la fois les interruptions hardware (provenant de l’APIC), les NMI et les exceptions. Elles sont gérées en protected mode ring0 par les handlers de l’OS, ce que signifie que chaque gestionnaire d’interruptions ou d’exceptions doit vérifier le mode du CPU pour savoir s’il était en virtual mode avant de brancher sur le handler. Cette façon de fonctionner est donc assez lourde à mettre en place car chaque handler de l’IDT devra être capable de gérer des interruptions provenant aussi bien du virtual mode ou du protected mode. Dans le cas du noyau de Windows, on retrouve cette implémentation chaque handle fait appel à Ki386VdmReflectException ou Ki386VdmReflectException_A (Même chose que Ki386VdmReflectException sauf que l’IRQL est levé à APC_LEVEL) pour dispatcher l’exception au monitor V8086 en fonction du numéro de trap, pour info sous Windows KiTrap00, 01, 03, 04, 05, 07, 0C utilisent Ki386VdmReflectException_A et KiTrap06 et 0D utilisent Ki386VdmReflectException, notez bien la différentiation des handlers 06 (#UD Invalid Opcode) et (#GP General Protection) c’est important pour la suite. Reste le cas des NMIs, d’après ce que j’ai vu Windows s’en fou et gère les NMIs avec KiTrap02 sans prendre en compte le mode dans lequel se situait le core au moment de l’arrivée de l’interruption non masquable.
  2. La seconde classe est constituée des interruptions hardware masquables, quand je dis masquable c’est relatif à la position de l’IF flag de l’EFLags, qui, si il est à 1, permet d’inhiber l’interruption du core par ces interruptions. A partir de là imaginez que vous être en virtual mode, votre programme tourne tranquillement, il décide d’exécuter l’instruction CLI pour désactiver les interruptions, comme cette instruction est ‘sensible’ vous vous retrouver dans la partie ring0 de votre monitor à devoir émuler cette instruction. Normalement CLI met à 0 le IF flag de l’EFlags, le souci c’est qu’en virtual mode, autoriser une application qui se trouve être en réalité un programme user-land (en ring3 donc) à modifier ce bit peut poser de gros problèmes, simplement en empêchant les interruptions hardware d’arriver au core. Pour remédier à ce problème, Intel fournit une virtual mode extension, représenté par le VME flag (bit 0) du CR4, cette extension offre une gestion ‘virtuelle’ des interruptions hardware à travers 2 nouveaux bits de l’EFlags, le VIF (Virtual Intterrupt Flag) et le VIP (Virtual Interrupt Pending flag). Le VIF est tout simplement une virtualisation de l’IF, lorsqu’en en virtual mode avec l’extension active, les instructions CLI et STI sont exécutées, celles-ci iront modifier le VIF flag. Au moment de l’arrivée d’une interruption hardware sur le core délivrée par la local APIC, le handler ring0 du protected mode vérifie l’état du VIF flag, si il est à 1 alors il peut renvoyer l’interruption pour quelle soit gérée par le monitor V8086, sinon le monitor place le VIP à 1, signifiant qu’une ou des interruptions sont en attente. Dans ce cas, lorsque le programme 8086 exécute l’instruction STI, le core contrôle l’état du VIP, si il est à 0, il met juste le VIF à 1, par contre si le VIP est à 1 alors génère une #GP pour que le monitor ring0 s’occupe d’émuler le comportement de l’interruption en attente. Sous Windows, le noyau se charge d’activer l’extension VME du CR4 avec KeI386VdmInitialize qui envoie un IPI (Interprocessor Interrupt) sur tous les processeurs à l’aide de KiIpiGenericCall pour exécuter Ki386VdmEnablePentiumExtentions. Il est possible de désactiver l’extension VME avec la clé ‘DisableVme’ dans \HKLM\SYSTEM\CurrentControlSet\Control\Wow.
  3. Enfin, le dernière type d’interruptions correspond aux interruptions software, c’est à dire celles générées par l’instruction INT xx. Leur gestion dépend de plusieurs paramètres qui sont : l’état du bit VME, la valeur de l’IOPL et l’état du bit dans Interruption Redirection Bit Map du TSS courant. Le IntDirectionMap est un bitmap de 32 bytes situé dans le TSS, si le bit correspondant à une interruption vaut 0 alors son activation sera redirigé vers le handler pointé par l’IVT du programme 8086, si il est 1, l’interruption est renvoyé vers le handler de l’IDT #GP. Intel propose 6 méthodes pour traiter ces interruptions software :

    Pour le moment, on sait que le VME est à 1, que l’IOPL est inférieur à 3, reste à trouver l’état des bits dans le IntDirectionMap, pour cela on examine le contenu du TSS :

    	kd> r tr
    	Last set context:
    	tr=00000028
    	kd> dg 28
    	                                  P Si Gr Pr Lo
    	Sel    Base     Limit     Type    l ze an es ng Flags
    	---- -------- -------- ---------- - -- -- -- -- --------
    	0028 80042000 000020ab TSS32 Busy 0 Nb By P  Nl 0000008b
    	kd> .tss 28
    	Unable to get program counter
    	eax=eb0bb70f ebx=50535051 ecx=8bc93302 edx=50561445 esi=ff500875 edi=5750f875
    	eip=3774187d esp=fff475ff ebp=ff50f075 iopl=1 vip     ov up ei ng nz na pe cy
    	cs=5000  ss=0010  ds=33b5  es=7068  fs=c483  gs=5fc7             efl=0574db85
    	5000:187d 0401            add     al,1
    	kd> dt nt!_KTSS 80042000 -a IntDirectionMap
    	   +0x208c IntDirectionMap :  "???"
    	    [00] 0x4 '' (int 0 à 7)
    	    [01] 0 ''   (int 8 à 15)
    	    [02] 0 ''   (int 16 à 23)
    	    [03] 0x18 '' (int 24 à 31)
    	    [04] 0x18 '' (int 32 à 39)
    	    [05] 0 ''
    	    [06] 0 ''
    	    [07] 0 ''
    	    [08] 0 ''
    	    [09] 0 ''
    	    [10] 0 ''
    	    [11] 0 ''
    	    [12] 0 ''
    	    [13] 0 ''
    	    [14] 0 ''
    	    [15] 0 ''
    	    [16] 0 ''
    	    [17] 0 ''
    	    [18] 0 ''
    	    [19] 0 ''
    	    [20] 0 ''
    	    [21] 0 ''
    	    [22] 0 ''
    	    [23] 0 ''
    	    [24] 0 ''
    	    [25] 0 ''
    	    [26] 0 ''
    	    [27] 0 ''
    	    [28] 0 ''
    	    [29] 0 ''
    	    [30] 0 ''
    	    [31] 0 ''

    A partir de ce bitmap on peut dire que les interruptions softwares 2, 27 (0x1B), 28 (0x01C), 35(0×23) et 36(0×24) sont gérées en protected mode. Pour savoir à quoi elles correspondent on peut se référer à la fameuse Ralf Brown’s Interrupt List. Sous Windows, on se retrouve donc à utiliser les méthodes 3 et 6 pour les interruptions software. Pour le moment je n’ai pas encore étudié l’IVT du virtual mode pour retrouver les handlers, ceux qui m’intéressent sont les vieux gestionnaires DOS comme par exemple l’INT 21h et plus généralement tous ceux de cette liste, retrouver comment l’OS gère des fonctions DOS comme OPEN ou READ doit être assez intéressant, mais cela je vous dévoilerais, si je le trouve, plus tard.

Voilà, c’est finit pour la gestion des interruptions hardware, software et des exceptions. Il s’agit clairement d’une partie lourde à mettre en place, faisant appel à de nombreuses features complexes du CPU et demandant une implémentation très low-level dans le noyau.

Au passage un mot sur les I/Os, en fonction de l’IOPL les instructions IN, INS, OUT et OUTS sont ‘IOPL sensitives’, c’est à dire que pour faire un I/O il faut que le CPL soit inférieur ou égal à l’IOPL. Dans notre case nous avons un IOPL à 0 avec un CPL de 3, donc pas d’I/Os possibles.

Voilà, je m’arrête là pour le moment, je pense avoir couvert une bonne partie de la gestion du virtual mode sous Windows même si il me reste encore de nombreux composants à reverser. Pour cette partie j’ai juste exposé l’interfaçage entre les specs Intel sur l’émulation 8086 et le noyau Windows. Evidemment, cela ne suffit pas à faire tourner des applications DOS ou WOW, cela passe notamment par le fonctionnement des processes ntvdm.exe est wowexec.exe qui sont en fait des wrappers par rapport aux différentes librairies 16 bits nécessaire au fonctionnement de ces binaires. Mais cela je l’expliquerais dans un prochain post. Il me reste aussi à décrire la manière dont un thread gère une tâche 16 bits, comment l’espace mémoire DOS est agencé et de vous parler plus en détails de l’API native NtVdmControl et de l’instruction magique décrite par Rater dans 29A. Pour l’instant je laisse décanter cela, surtout pour moi en fait et je tente d’expliquer tout cela la prochaine fois.

En attendant, si vous avez des questions, des suggestion ou des remarques n’hésitez pas à les laisser en commentaires !

4 comments septembre 20th, 2008

Windows Subsytem Csrss

De retour après une longue absence durant laquelle je faisais semblant de taff avec mon maître. Pour ceux qui croyaient que j’avais une vie sociale c’est raté, j’étais plutôt enfermé dans une salle sombre, humide et froide ; travaillant sur des sujets douteux avec pour unique lumière un laptop qui éclairait mon visage pâle et amaigrit, nourrit au café, écoutant du black métal : un rêve pour certains en quelque sorte. Cette période étant finie, je peux reprendre une activité normale. Cette fois on va descendre toujours plus loin dans notre OS préféré pour s’intéresser à un composant méconnu mais extrêmement important. Je veux parler du subsystem constitué par le process csrss.exe.

Le processes csrss (Client Server Runtime Process), crée par le système lors du boot, est responsable de la gestion des threads et processes en maintenant une liste interne de ceux-ci comme on peut le lire ici pour pouvoir effectuer diverses opérations sur ses objets en cas de besoin, même si j’ai du mal à comprendre exactement pourquoi. Csrss est aussi responsable de la Console Win32 et c’est sur ce sujet que je vais m’attarder ici. Csrss agit simplement comme un serveur fournissant aux processes console un ensemble de features non accessibles via l’API standard de Windows.

Quand on regarde le binaire csrss.exe sous IDA on s’aperçoit qu’il est très petit, 6ko seulement, par contre il charge une DLL csrss.dll qui export un ensemble de fonctions assez sympas :

->Export Table
   Characteristics:        0x00000000
   TimeDateStamp:          0x48023843  (GMT: Sun Apr 13 16:43:47 2008)
   MajorVersion:           0x0000
   MinorVersion:           0x0000  -> 0.00
   Name:                   0x000078D6  ("CSRSRV.dll")
   Base:                   0x00000001
   NumberOfFunctions:      0x00000023
   NumberOfNames:          0x00000023
   AddressOfFunctions:     0x00007778
   AddressOfNames:         0x00007804
   AddressOfNameOrdinals:  0x00007890

   Ordinal RVA        Symbol Name
   ------- ---------- ----------------------------------
   0x0001  0x000053C5 "CsrAddStaticServerThread"
   0x0002  0x00004160 "CsrCallServerFromServer"
   0x0003  0x00003FCE "CsrConnectToUser"
   0x0004  0x00005C9C "CsrCreateProcess"
   0x0005  0x00006056 "CsrCreateRemoteThread"
   0x0006  0x00005F86 "CsrCreateThread"
   0x0007  0x00006375 "CsrCreateWait"
   0x0008  0x000062D8 "CsrDebugProcess"
   0x0009  0x000062E5 "CsrDebugProcessStop"
   0x000A  0x00004F8E "CsrDereferenceProcess"
   0x000B  0x00005520 "CsrDereferenceThread"
   0x000C  0x00006502 "CsrDereferenceWait"
   0x000D  0x00005ECE "CsrDestroyProcess"
   0x000E  0x00006110 "CsrDestroyThread"
   0x000F  0x00005438 "CsrExecServerThread"
   0x0010  0x00005010 "CsrGetProcessLuid"
   0x0011  0x00004E6C "CsrImpersonateClient"
   0x0012  0x000052D3 "CsrLockProcessByClientId"
   0x0013  0x00005353 "CsrLockThreadByClientId"
   0x0014  0x0000658C "CsrMoveSatisfiedWait"
   0x0015  0x000064A4 "CsrNotifyWait"
   0x0016  0x00002A17 "CsrPopulateDosDevices"
   0x0017  0x00003FC3 "CsrQueryApiPort"
   0x0018  0x00004F20 "CsrReferenceThread"
   0x0019  0x00004EB3 "CsrRevertToSelf"
   0x001A  0x0000305E "CsrServerInitialization"
   0x001B  0x00004CA2 "CsrSetBackgroundPriority"
   0x001C  0x000050F2 "CsrSetCallingSpooler"
   0x001D  0x00004C7B "CsrSetForegroundPriority"
   0x001E  0x000061C5 "CsrShutdownProcesses"
   0x001F  0x00003204 "CsrUnhandledExceptionFilter"
   0x0020  0x00005330 "CsrUnlockProcess"
   0x0021  0x000061A2 "CsrUnlockThread"
   0x0022  0x00004421 "CsrValidateMessageBuffer"
   0x0023  0x0000449D "CsrValidateMessageString"

La fonction importante dans cette liste est CsrServerInitialization, c’est celle-ci qui va vraiment initialiser le subsystem, notamment en chargeant les autres fournisseurs. La ligne de commande de csrss.exe est assez marrante :

C:\WINDOWS\system32\csrss.exe ObjectDirectory=\Windows SharedSection=1024,3072,512 Windows=On SubSystemType=Windows ServerDll=basesrv,1 ServerDll=winsrv:UserServerDllInitialization,3 ServerDll=winsrv:ConServerDllInitialization,2 ProfileControl=Off MaxRequestThreads=16

On retrouve 2 noms de DLL, baserv.dll et winsrv.dll suivis de 2 noms faisant partit de leurs APIs exportées, UserServerDllInitialization et ConServerDllInitialization. Ces DLLS sont en fait des providers que csrss wrappe pour l’interfacer avec le reste du système. La communication avec ses providers est basée sur les LPC à travers 2 ports \Windows\ApiPort et \Windows\SbApiPort crées par cssrv.dll et gérés par les threads CsrApiRequestThread. On peut le voir facilement en regardant les stacks des threads de csrss avec Process Explorer et puis il y existe une ré-implémentation dans ReactOS ici. Pour info le LPC ApiPort gère toutes les fonctions du subsytem alors que le SbApiPort est utilisé par csrsrv.dll dans le contexte des sessions. J’ai repéré aussi d’autres providers possible qui sont consrv.dll et usersrv.dll mais ceux ci ne semblent pas chargés sur mon système.

Maintenant voyons la liste des fonctions qui sont fournies par basesrv.dll et winsrv.dll :

bassrv.dll
kd> dds basesrv!BaseServerApiDispatchTable l 20
75aed080  75ae4dab basesrv!BaseSrvCreateProcess
75aed084  75ae4b61 basesrv!BaseSrvCreateThread
75aed088  75ae4d10 basesrv!BaseSrvGetTempFile
75aed08c  75ae4cad basesrv!BaseSrvExitProcess
75aed090  75ae4d37 basesrv!BaseSrvDebugProcess
75aed094  75ae8410 basesrv!BaseSrvCheckVDM
75aed098  75ae84fe basesrv!BaseSrvUpdateVDMEntry
75aed09c  75ae702e basesrv!BaseSrvGetNextVDMCommand
75aed0a0  75ae857d basesrv!BaseSrvExitVDM
75aed0a4  75ae523b basesrv!BaseSrvIsFirstVDM
75aed0a8  75ae76ee basesrv!BaseSrvGetVDMExitCode
75aed0ac  75ae5a7d basesrv!BaseSrvSetReenterCount
75aed0b0  75ae4d44 basesrv!BaseSrvSetProcessShutdownParam
75aed0b4  75ae4d7d basesrv!BaseSrvGetProcessShutdownParam
75aed0b8  75ae8718 basesrv!BaseSrvNlsSetUserInfo
75aed0bc  75ae87a5 basesrv!BaseSrvNlsSetMultipleUserInfo
75aed0c0  75ae8bf3 basesrv!BaseSrvNlsCreateSection
75aed0c4  75ae64c9 basesrv!BaseSrvSetVDMCurDirs
75aed0c8  75ae65fb basesrv!BaseSrvGetVDMCurDirs
75aed0cc  75ae65aa basesrv!BaseSrvBatNotification
75aed0d0  75ae78b5 basesrv!BaseSrvRegisterWowExec
75aed0d4  75ae9f92 basesrv!BaseSrvSoundSentryNotification
75aed0d8  75ae9a30 basesrv!BaseSrvRefreshIniFileMapping
75aed0dc  75ae40e9 basesrv!BaseSrvDefineDosDevice
75aed0e0  75ae966e basesrv!BaseSrvSetTermsrvAppInstallMode
75aed0e4  75ae8a9a basesrv!BaseSrvNlsUpdateCacheCount
75aed0e8  75ae2db6 basesrv!BaseSrvSetTermsrvClientTimeZone
75aed0ec  75aea764 basesrv!BaseSrvSxsCreateActivationContext
75aed0f0  75ae4d37 basesrv!BaseSrvDebugProcess
75aed0f4  75ae4c01 basesrv!BaseSrvRegisterThread
75aed0f8  75ae86a7 basesrv!BaseSrvNlsGetUserInfo
75aed0fc  75ae283a basesrv!BaseSrvAppHelpQueryModuleData


winsrv.dll
kd> dds winsrv!UserServerApiDispatchTable l B
75b2d560  75b08910 winsrv!SrvExitWindowsEx
75b2d564  75b08f18 winsrv!SrvEndTask
75b2d568  75af7977 winsrv!SrvLogon
75b2d56c  75af7b6c winsrv!SrvRegisterServicesProcess
75b2d570  75b0796f winsrv!SrvActivateDebugger
75b2d574  75af16a9 winsrv!SrvGetThreadConsoleDesktop
75b2d578  75b078cc winsrv!SrvDeviceEvent
75b2d57c  75af7d09 winsrv!SrvRegisterLogonProcess
75b2d580  75b0787c winsrv!SrvWin32HeapFail
75b2d584  75b0787c winsrv!SrvWin32HeapFail
75b2d588  75afbd30 winsrv!SrvCreateSystemThreads

kd> dds winsrv!ConsoleServerApiDispatchTable l 55
75af89f0  75afbae8 winsrv!SrvOpenConsole
75af89f4  75b05a50 winsrv!SrvGetConsoleInput
75af89f8  75b16627 winsrv!SrvWriteConsoleInput
75af89fc  75b0694d winsrv!SrvReadConsoleOutput
75af8a00  75b169ab winsrv!SrvWriteConsoleOutput
75af8a04  75b16c0b winsrv!SrvReadConsoleOutputString
75af8a08  75b16cc1 winsrv!SrvWriteConsoleOutputString
75af8a0c  75b058c6 winsrv!SrvFillConsoleOutput
75af8a10  75af24e5 winsrv!SrvGetConsoleMode
75af8a14  75b10a82 winsrv!SrvGetConsoleNumberOfFonts
75af8a18  75b115b0 winsrv!SrvGetConsoleNumberOfInputEvents
75af8a1c  75af411a winsrv!SrvGetConsoleScreenBufferInfo
75af8a20  75b06a60 winsrv!SrvGetConsoleCursorInfo
75af8a24  75b10b3f winsrv!SrvGetConsoleMouseInfo
75af8a28  75b10b77 winsrv!SrvGetConsoleFontInfo
75af8a2c  75b10c15 winsrv!SrvGetConsoleFontSize
75af8a30  75b10c93 winsrv!SrvGetConsoleCurrentFont
75af8a34  75af2814 winsrv!SrvSetConsoleMode
75af8a38  75b10dc9 winsrv!SrvSetConsoleActiveScreenBuffer
75af8a3c  75b10e2c winsrv!SrvFlushConsoleInputBuffer
75af8a40  75b06495 winsrv!SrvGetLargestConsoleWindowSize
75af8a44  75b061cd winsrv!SrvSetConsoleScreenBufferSize
75af8a48  75b05c72 winsrv!SrvSetConsoleCursorPosition
75af8a4c  75b10e8f winsrv!SrvSetConsoleCursorInfo
75af8a50  75b06293 winsrv!SrvSetConsoleWindowInfo
75af8a54  75b10f0e winsrv!SrvScrollConsoleScreenBuffer
75af8a58  75b05ec4 winsrv!SrvSetConsoleTextAttribute
75af8a5c  75b110d2 winsrv!SrvSetConsoleFont
75af8a60  75b11189 winsrv!SrvSetConsoleIcon
75af8a64  75b05da3 winsrv!SrvReadConsole
75af8a68  75af358a winsrv!SrvWriteConsole
75af8a6c  75af41ff winsrv!SrvDuplicateHandle
75af8a70  75b17428 winsrv!SrvGetHandleInformation
75af8a74  75b17490 winsrv!SrvSetHandleInformation
75af8a78  75af4186 winsrv!SrvCloseHandle
75af8a7c  75af267c winsrv!SrvVerifyConsoleIoHandle
75af8a80  75b0d1a7 winsrv!SrvAllocConsole
75af8a84  75b0d35d winsrv!SrvFreeConsole
75af8a88  75af28c6 winsrv!SrvGetConsoleTitle
75af8a8c  75af8f17 winsrv!SrvSetConsoleTitle
75af8a90  75b16d8d winsrv!SrvCreateConsoleScreenBuffer
75af8a94  75b1611c winsrv!SrvInvalidateBitMapRect
75af8a98  75b15e6e winsrv!SrvVDMConsoleOperation
75af8a9c  75b0ee09 winsrv!SrvSetConsoleCursor
75af8aa0  75b0eeda winsrv!SrvShowConsoleCursor
75af8aa4  75b07402 winsrv!SrvConsoleMenuControl
75af8aa8  75b0d742 winsrv!SrvSetConsolePalette
75af8aac  75b0d9b6 winsrv!SrvSetConsoleDisplayMode
75af8ab0  75b06e32 winsrv!SrvRegisterConsoleVDM
75af8ab4  75b0dcf3 winsrv!SrvGetConsoleHardwareState
75af8ab8  75b0f725 winsrv!SrvSetConsoleHardwareState
75af8abc  75afc0f4 winsrv!SrvGetConsoleDisplayMode
75af8ac0  75b13b46 winsrv!SrvAddConsoleAlias
75af8ac4  75b13d48 winsrv!SrvGetConsoleAlias
75af8ac8  75b13f2c winsrv!SrvGetConsoleAliasesLength
75af8acc  75b11ef6 winsrv!SrvGetConsoleAliasExesLength
75af8ad0  75b1400d winsrv!SrvGetConsoleAliases
75af8ad4  75b11f4e winsrv!SrvGetConsoleAliasExes
75af8ad8  75b14570 winsrv!SrvExpungeConsoleCommandHistory
75af8adc  75b145d9 winsrv!SrvSetConsoleNumberOfCommands
75af8ae0  75b14648 winsrv!SrvGetConsoleCommandHistoryLength
75af8ae4  75b146da winsrv!SrvGetConsoleCommandHistory
75af8ae8  75b12081 winsrv!SrvSetConsoleCommandHistoryMode
75af8aec  75af278f winsrv!SrvGetConsoleCP
75af8af0  75b1129b winsrv!SrvSetConsoleCP
75af8af4  75b0de0e winsrv!SrvSetConsoleKeyShortcuts
75af8af8  75b0dd83 winsrv!SrvSetConsoleMenuClose
75af8afc  75b0dae0 winsrv!SrvConsoleNotifyLastClose
75af8b00  75b10d19 winsrv!SrvGenerateConsoleCtrlEvent
75af8b04  75b06bad winsrv!SrvGetConsoleKeyboardLayoutName
75af8b08  75b114e6 winsrv!SrvGetConsoleWindow
75af8b0c  75b11a6a winsrv!SrvGetConsoleCharType
75af8b10  75b11b3b winsrv!SrvSetConsoleLocalEUDC
75af8b14  75b11619 winsrv!SrvSetConsoleCursorMode
75af8b18  75b1168f winsrv!SrvGetConsoleCursorMode
75af8b1c  75b11704 winsrv!SrvRegisterConsoleOS2
75af8b20  75b11783 winsrv!SrvSetConsoleOS2OemFormat
75af8b24  75b117cd winsrv!SrvGetConsoleNlsMode
75af8b28  75b118c7 winsrv!SrvSetConsoleNlsMode
75af8b2c  75b11984 winsrv!SrvRegisterConsoleIME
75af8b30  75b11a35 winsrv!SrvUnregisterConsoleIME
75af8b34  75af2730 winsrv!SrvGetConsoleLangId
75af8b38  75b0d3cb winsrv!SrvAttachConsole
75af8b3c  75b10acd winsrv!SrvGetConsoleSelectionInfo
75af8b40  75b1151d winsrv!SrvGetConsoleProcessList

La console n’étant pas gérée par les APIS Win32 GDI classiques, il suffit de regarder un peu comment sont implémentées ces fonctions pour comprendre. En effet, toutes les requêtes sont en fait passées au subsystem et plus précisément aux fonctions de winsrv.dll. Maintenant, il reste à savoir comment s’interfacer avec le subsystem pour profiter des ces APIs. Tout ceci est bien évidemment non documenté donc si on veut plus d’infos il faut reverser, googler et lire les sources de ReactOS. En regardant le code des fonctions de kernel32.dll comme GetConsoleTitle, on peut voir quelles utilisent une partie des APIS exportées de ntdll qui sont :

ntdll export table :

CsrAllocateCaptureBuffer
CsrAllocateMessagePointer
CsrCaptureMessageBuffer
CsrCaptureMessageMultiUnicodeStringsInPlace
CsrCaptureMessageString
CsrCaptureTimeout
CsrClientCallServer
CsrClientConnectToServer
CsrFreeCaptureBuffer
CsrGetProcessId
CsrIdentifyAlertableThread
CsrNewThread
CsrProbeForRead
CsrProbeForWrite
CsrSetPriorityClass

Ce sont ces fonctions qui permettent de communiquer avec winsrv.dll, voici les prototypes des plus importantes.

from umfuncs.h@ndk

//
// CSR Functions
//
PVOID
NTAPI
CsrAllocateCaptureBuffer(
    ULONG ArgumentCount,
    ULONG BufferSize
);

ULONG
NTAPI
CsrAllocateMessagePointer(
    struct _CSR_CAPTURE_BUFFER *CaptureBuffer,
    ULONG MessageLength,
    PVOID *CaptureData
);

VOID
NTAPI
CsrCaptureMessageBuffer(
    struct _CSR_CAPTURE_BUFFER *CaptureBuffer,
    PVOID MessageString,
    ULONG StringLength,
    PVOID *CapturedData
);


NTSTATUS
NTAPI
CsrClientCallServer(
    struct _CSR_API_MESSAGE *Request,
    struct _CSR_CAPTURE_BUFFER *CaptureBuffer OPTIONAL,
    ULONG ApiNumber,
    ULONG RequestLength
);


VOID
NTAPI
CsrFreeCaptureBuffer(
    struct _CSR_CAPTURE_BUFFER *CaptureBuffer
);

Après, en cherchant un peu dans sur le net et surtout dans les headers du NDK on retrouve les définitions des structures qui doivent être envoyées au LPC. Le listing suivant donne juste les structures de bases. Il existe en effet une structure spécifique en fonction de l’API qu’on veut appeler, qui se trouve dans l’union de la structure CSR_API_MSG.

/************************************************************************************/
//
// Csrss headers
//

#define WINSS_OBJECT_DIRECTORY_NAME     L"\\Windows"

#define CSRSRV_SERVERDLL_INDEX          0
#define CSRSRV_FIRST_API_NUMBER         0

#define BASESRV_SERVERDLL_INDEX         1
#define BASESRV_FIRST_API_NUMBER        0

#define CONSRV_SERVERDLL_INDEX          2
#define CONSRV_FIRST_API_NUMBER         512

#define USERSRV_SERVERDLL_INDEX         3
#define USERSRV_FIRST_API_NUMBER        1024

#define CSR_MAKE_API_NUMBER( DllIndex, ApiIndex ) \
    (CSR_API_NUMBER)(((DllIndex) << 16) | (ApiIndex))

#define CSR_APINUMBER_TO_SERVERDLLINDEX( ApiNumber ) \
    ((ULONG)((ULONG)(ApiNumber) >> 16))

#define CSR_APINUMBER_TO_APITABLEINDEX( ApiNumber ) \
    ((ULONG)((USHORT)(ApiNumber)))

//
// This structure is filled in by the client prior to connecting to the CSR
// server.  The CSR server will fill in the OUT fields if prior to accepting
// the connection.
//

typedef struct _CSR_API_CONNECTINFO {
    OUT HANDLE ObjectDirectory;
    OUT PVOID SharedSectionBase;
    OUT PVOID SharedStaticServerData;
    OUT PVOID SharedSectionHeap;
    OUT ULONG DebugFlags;
    OUT ULONG SizeOfPebData;
    OUT ULONG SizeOfTebData;
    OUT ULONG NumberOfServerDllNames;
    OUT HANDLE ServerProcessId;
} CSR_API_CONNECTINFO, *PCSR_API_CONNECTINFO;

typedef struct _CSR_CLIENTCONNECT_MSG {
    IN ULONG ServerDllIndex;
    IN OUT PVOID ConnectionInformation;
    IN OUT ULONG ConnectionInformationLength;
} CSR_CLIENTCONNECT_MSG, *PCSR_CLIENTCONNECT_MSG;

#define CSR_NORMAL_PRIORITY_CLASS   0x00000010
#define CSR_IDLE_PRIORITY_CLASS     0x00000020
#define CSR_HIGH_PRIORITY_CLASS     0x00000040
#define CSR_REALTIME_PRIORITY_CLASS 0x00000080

//
// This helps out the Wow64 thunk generater, so we can change
// RelatedCaptureBuffer from struct _CSR_CAPTURE_HEADER* to PCSR_CAPTURE_HEADER.
// Redundant typedefs are legal, so we leave the usual form in as well.
//
struct _CSR_CAPTURE_HEADER;
typedef struct _CSR_CAPTURE_HEADER CSR_CAPTURE_HEADER, *PCSR_CAPTURE_HEADER;

typedef struct _CSR_CAPTURE_HEADER {
    ULONG Length;
    PCSR_CAPTURE_HEADER RelatedCaptureBuffer;
    ULONG CountMessagePointers;
    PCHAR FreeSpace;
    ULONG_PTR MessagePointerOffsets[1]; // Offsets within CSR_API_MSG of pointers
} CSR_CAPTURE_HEADER, *PCSR_CAPTURE_HEADER;

typedef ULONG CSR_API_NUMBER;

typedef struct _CSR_API_MSG {
    PORT_MESSAGE h;
    union {
        CSR_API_CONNECTINFO ConnectionRequest;
        struct {
            PCSR_CAPTURE_HEADER CaptureBuffer;
            CSR_API_NUMBER ApiNumber;
            ULONG ReturnValue;
            ULONG Reserved;
            union {
                //
                // Place data for srv here
                //
            } u;
        };
    };
} CSR_API_MSG, *PCSR_API_MSG;
/************************************************************************************/

Une fois qu’on connaît ces structures on peut s’interfacer avec le subsystem en utilisant la fonction CsrClientCallServer. Le problème c’est qu’il faut connaître les structures spécifiques aux APIs qu’on veut utiliser et là, ce n’est pas gagner. Celles de winsrv.dll qui gèrent la console se reversent assez facilement et sont en partie disponibles ici, et celles de basesrv.dll le sont .

Remarquez dans la ligne de commande de csrss les indices après les noms des fonctions d’init :
ServerDll=basesrv,1
ServerDll=winsrv:ConServerDllInitialization,2
ServerDll=winsrv:UserServerDllInitialization,3

Ces indices permettent de savoir quel provider utiliser puis quelle fonction appeler (les fonctions ont aussi un indice). Attention comme winsrv.dll fournit 2 interfaces il existe 2 indices de départ : BASESRV_FIRST_API_NUMBER qui vaut 0 et CONSRV_FIRST_API_NUMBER qui vaut 512.

En fait, le mécanisme intéressant est la manipulation des buffers entre le process client et le subsystem, car un LPC ne se casse pas la tête, il transmet juste un buffer entre 2 processes. Si le client à besoin de fournir une zone mémoire destinée à recevoir des données du serveur, il doit utiliser un CaptureBuffer avec CsrAllocateCaptureBuffer qui va allouer de l’espace dans un heap spécial du process, le CsrPortHeap. Ce heap vaut de l’or car il est situé dans une section qui est accessible par le subsystem ! C’est ce qu’on appel un Port Memory section … Pour s’en assurer vous pouvez regarder la fonction CsrpConnectToServer de ntdll et voir un appel à ZwCreateSection. Ensuite il faut appeler CsrCaptureMessageBuffer afin que le pointeur sur le buffer de destination des datas pointe sur notre CaptureBuffer. Par exemple :

typedef struct _CSR_CAPTURE_HEADER {
    ULONG Length;
    PCSR_CAPTURE_HEADER RelatedCaptureBuffer;
    ULONG CountMessagePointers;
    PCHAR FreeSpace;
    ULONG_PTR MessagePointerOffsets[1]; // Offsets within CSR_API_MSG of pointers
} CSR_CAPTURE_HEADER, *PCSR_CAPTURE_HEADER;

CSR_CAPTURE_HEADER CaptureBuffer;

CaptureBuffer=CsrAllocateCaptureBuffer(1, b->TitleLen);

/* valeurs lues sous olly
CsrAllocateCaptureBuffer
CsrHeader.Length=0x11C
CsrHeader.RelatedCaptureBuffer=0x260178
CsrHeader.CountMessagePointers=0
CsrHeader.FreeSpace=0x26069C
CsrHeader.MessagePointerOffsets[0]=0;
*/

CsrCaptureMessageBuffer(CaptureBuffer,
                           	NULL,
                           	b->TitleLen,
                          	(PVOID *)&b->Title);
/*	valeurs lues sous olly
CsrAllocateCaptureBuffer
CsrHeader.Length=0x11C
CsrHeader.RelatedCaptureBuffer=0x260178
CsrHeader.CountMessagePointers=1
CsrHeader.FreeSpace=0x2607A0
CsrHeader.MessagePointerOffsets[0]=0x12FF40; //Pointeur sur l'adresse du buffer alloué par CsrAllocateCaptureBuffer, les datas suivent le CaptureBuffer
*/

En gros toutes ces opérations servent à translater les pointeurs des buffers du client vers le serveur. Pour finir, la fonction CsrClientCallServer effectuera le traitement final en remplissant le champ CaptureBuffer de la structure CSR_API_MSG puis appellera ZwRequestWaitReplyPort. Arrivé dans le serveur avec le LPC, un des threads CsrApiRequestThread qui attend avec ZwReplyWaitReceivePort dispatchera la requête. Bien évidemment le buffer est vérifié dans le serveur avec CsrValidateMessageBuffer :P

Le code suivant est simplement une ré-implemenation de GetConsoleTitle (ou plutôt de GetConsoleTitleInternal pour être précis) de kernel32.dll. On retrouve quasiment le même code dans ReactOS.

HANDLE GetConsoleHandle(VOID)
{
	__asm
	{
		mov eax, fs:[18h]
		mov eax, [eax+30h]
		mov eax, [eax+10h]
		mov eax, [eax+10h]
	}
}

int main(int argc, char ** argv)
{	
	NTSTATUS Status;
	CSR_API_MSG m;
	PCONSOLE_TITLE_MSG b=&m.u.ConsoleTitle;
	PCSR_CAPTURE_HEADER CaptureBuffer;
	
	RtlZeroMemory(&m, sizeof(m));
	
	b->ConsoleHandle=GetConsoleHandle();
	b->TitleLen=260;
	b->Unicode=0;
	
	CaptureBuffer=CsrAllocateCaptureBuffer(1, b->TitleLen);
	
	CsrCaptureMessageBuffer(CaptureBuffer,
                           	NULL,
                           	b->TitleLen,
                          	(PVOID *)&b->Title);
	
	Status=CsrClientCallServer((PCSR_API_MSG)&m, 
							CaptureBuffer,
							CSR_MAKE_API_NUMBER(CONSRV_SERVERDLL_INDEX, CONSRV_FIRST_API_NUMBER+38), //38=SrvGetConsoleTitle index
							sizeof(*b));
	if(!NT_SUCCESS(Status))
	{
		printf("Error with CsrClientCallServer : 0x%X\n", Status);
		CsrFreeCaptureBuffer(CaptureBuffer);
		return 0;	
	}
	
	printf("ConsoleTitle is : %s\n", m.u.ConsoleTitle.Title);
	
	CsrFreeCaptureBuffer(CaptureBuffer);
	return 0;	
}

Pour les autres interfaces, je vous laisser creuser :], vous pouvez reverser les APIs de kernel32.dll pour retrouver les structures et faire mumuse avec le subsystem.

Voici le code+binaire de MyGetConsoleTitle :
http://ivanlef0u.fr/repo/MyGetConsoleTitle.rar

En espérant que vous continuerez à fouiner le sujet :]

13 comments septembre 3rd, 2008


Calendar

septembre 2008
L Ma Me J V S D
« juil   oct »
1234567
891011121314
15161718192021
22232425262728
2930  

Posts by Month

Posts by Category