Virtual Mode on Windows

septembre 20th, 2008 at 02:47 admin

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 !

Entry Filed under: RE

4 Comments

  • 1. mxatone  |  septembre 20th, 2008 at 22:22

    Enfin 1 semaine de vacance et tu drop un paper sur intel. Raaah .. je vais devoir lire en details pour mon retour :(. Le debut me paraissait bien croustillant.

    D’ailleurs pour ceux qui s’interessent au real-mode, la création d’un boot loader a l’air bien prise de tete (coucou texane ?). Truecrypt en est un bon exemple open source mais faut surement se poser dessus bien longtemps.

    Merki ivan !


  • 2. newsoft  |  septembre 21st, 2008 at 20:54

    Excellent cet article, ça donne envie de lire la suite.

    Pour info le 80286 pouvait déjà passer en mode protégé, par contre il ne pouvait pas revenir en mode réel sans RESET, d’où les astuces de Triple Fault dont tu as entendu parler ;)

    @mxatone: un bootloader c’est pas si compliqué, du moins si tu veux juste faire un virus de boot pour disquettes :)


  • 3. Laxigue  |  septembre 25th, 2008 at 05:43

    http://arteam.accessroot.com/


  • 4. zorg  |  octobre 10th, 2008 at 23:52

    Une petite anecdote, dans les annees 90 plusieurs jeux utilisaient le mode « unreal », en fait qui consistait a passer en mode protege, installer une GTD et rebasculer en mode reel. A cause d’un bug dans les processeurs intel il etait alors possible d’adresser directement toute la RAM :-)

    @newsoft: d’ailleurs le triple fault etait gere par le BIOS qui cherchait une valeur bien specifique dans la RAM apres l’IVT (0×1234 de memoire), c’est assez astucieux. En plus de cette valeur il y avait un CS:IP pas loin.


Trackback this post


Calendar

mars 2024
L Ma Me J V S D
« fév    
 123
45678910
11121314151617
18192021222324
25262728293031

Most Recent Posts