ProcessIoPortHandlers

septembre 26th, 2009 at 03:16 admin

Ce post décrit une feature du noyau NT très peu documentée, la gestion des I/Os d’un process tournant sous V8086. Je vous conseille d’abord de (re)lire ce post qui discute de la gestion des exceptions et interruptions d’un processus 16 bits, faites attention notamment aux différents endroits ou le noyau prend la main pour gérer ces évènements, notamment pour une émulation par l’API win32 ou bien pour une reinjection en virtual mode.

Peut-être que certains l’on remarqué en lisant les .h du WDK, notamment le fichier %DDK_INC_PATH%\ntddk.h, dans l’enum PROCESSINFOCLASS on peut voir l’identifiant ProcessIoPortHandlers avec un commentaire à coté disant qu’il est accessible uniquement en KernelMode. Ce qui est marrant c’est que même googlant dessus on ne trouve pas grand chose d’intéressant (j’ai p-e mal cherché btw !). Ce post à pour but de montrer quel est son rôle et comment l’utiliser, nous arrivons ainsi à mettre en place un moyen de communication assez original entre notre process et le noyau qui change des IOCTLs et autres appels systèmes tellement classiques :]

Il faut d’abord savoir que ce ProcessInformationClass est uniquement accessible par l’API native ZwSetInformationProcess, on ne peut donc pas demande à un process existant des informations sur cette feature avec ZwQueryInformationProcess. De plus un contrôle est effectué dans la fonction non-exportée du noyau PspSetProcessIoHandler pour vérifier que l’appelant est bien en KernelMode, donc le seul moyen d’utiliser cette feature est d’avoir un driver à notre disposition.

D’ailleurs le fait d’utiliser un driver n’est pas pratique puisque ZwSetInformationProcess demande un handle sur le process cible et que les handles en kernel-mode comme tout le mode le sait, c’est mal vu qu’il sont spécifique à un processus alors que noyau lui ne l’est pas. Bref rien de méchant mais au lieu d’un handle une référence sur la structure EPROCESS aurait suffit. D’un coté le handle permet d’avoir une vérification au niveau des droits de l’objet mais on s’en fou un peu vu qu’on est déjà dans le noyau et qu’on justement le droit de tout faire sur tous les objets ! Oui cela était mon whine inutile du jour..

D’abord un point sur les I/Os supporté par l’architecture x86 :

  • Les I/Os provenant des instructions IN et OUT qui peuvent lire ou écrire des bytes, word et doubleword. AL/AX/EAX est le registre source ou destination de l’I/O.
  • Les I/Os effectués avec les instructions INS/INSB/INSW/INSD et OUTS/OUTSB/OUTSW/OUTSD qui sont des I/Os utilisant les zone mémoires ES:(E)DI ou ES:(E)SI comme destination/source, DX contient le numéro de port. La particularité de ces instructions est quelles peuvent être précédées du prefixe REP, ECX indiquant dans ce cas le nombre de répétitions. Attention au Direction Flag (DF) qui indique le sens de la copie/lecture :p

Comme je l’ai dans mon autre post sur le VDM, les I/Os en V8086 provoquent forcément une exception de type General Protection Fault (#GP).

Rentrons dans le vif du sujet, que permet donc cette feature ? Elle fournit justement la possibilité d’ajouter des handlers qui vont trappés les I/Os de votre process 16 bits. Vu comme cela ce n’est très pas utile, surtout que de nos jours plus personne ne dev en 16 bits mais à l’époque (c’est à dire en l’an 2337 avant la guerre des barbus) cela devait servir à quelque chose. Arrivé ici vous devriez comprendre que ce post ne sert pas à grand chose et qu’il ne vous apportera strictement rien. Je dis ça, mais c’est certainement mieux que les posts de newsoft sur l’échec de la sécu ! Merde je vais me faire blacklister de la blogosphère en disant ça :(

Maintenant passons à l’implémentation, d’abord on a besoin de connaitre le forme du paramètre ProcessInformation de ZwSetInformationProcess. Il s’agit d’une structure PROCESS_IO_PORT_HANDLER_INFORMATION définie par :

//
// SetProcessInformation Structure for ProcessSetIoHandlers info class
//

typedef struct _PROCESS_IO_PORT_HANDLER_INFORMATION {
    BOOLEAN Install;            // true if handlers to be installed
    ULONG NumEntries;
    ULONG Context;
    PEMULATOR_ACCESS_ENTRY EmulatorAccessEntries;
} PROCESS_IO_PORT_HANDLER_INFORMATION, *PPROCESS_IO_PORT_HANDLER_INFORMATION;

On voit qu’il s’agit simplement d’un wrapper pour un tableau de structures plus intéressantes, les EMULATOR_ACCESS_ENTRY :

//
// Structures used by the kernel drivers to describe which ports must be
// hooked out directly from the V86 emulator to the driver.
//

typedef enum _EMULATOR_PORT_ACCESS_TYPE {
    Uchar,
    Ushort,
    Ulong
} EMULATOR_PORT_ACCESS_TYPE, *PEMULATOR_PORT_ACCESS_TYPE;

//
// Access Modes
//

#define EMULATOR_READ_ACCESS    0x01
#define EMULATOR_WRITE_ACCESS   0x02

typedef struct _EMULATOR_ACCESS_ENTRY {
    ULONG BasePort;
    ULONG NumConsecutivePorts;
    EMULATOR_PORT_ACCESS_TYPE AccessType;
    UCHAR AccessMode;
    UCHAR StringSupport;
    PVOID Routine;
} EMULATOR_ACCESS_ENTRY, *PEMULATOR_ACCESS_ENTRY;

Quelques détails sur les membres de EMULATOR_ACCESS_ENTRY :

  • BasePort TODO
  • NumConsecutivePorts est le nombre de ports à partir de BasePort qui auront le même handler.
  • AccessType spécifie si notre handler est pour gérer les I/Os sur des bytes, words ou doublewords.
  • AccessMode permet de définir si on veut trapper les IN* ou OUT*.
  • StringSupport est un booléan indiquant si notre handler gère les IN*/OUT* ou INS*/OUTS*.
  • Routine est un pointeur sur notre handler.

Concernant le format de Routine, cela depend de 2 choses :

  • I/O String ou pas.
  • Taille de l’I/O (1, 2 ou 4).

Au final on a donc 6 prototypes de handlers possibles :

//
// These are the various function prototypes of the routines that are
// provided by the kernel driver to hook out access to io ports.
//

typedef
NTSTATUS
(*PDRIVER_IO_PORT_UCHAR ) (
    IN ULONG_PTR Context,
    IN ULONG Port,
    IN UCHAR AccessMode,
    IN OUT PUCHAR Data
    );

typedef
NTSTATUS
(*PDRIVER_IO_PORT_UCHAR_STRING ) (
    IN ULONG_PTR Context,
    IN ULONG Port,
    IN UCHAR AccessMode,
    IN OUT PUCHAR Data,
    IN ULONG DataLength
    );

typedef
NTSTATUS
(*PDRIVER_IO_PORT_USHORT ) (
    IN ULONG_PTR Context,
    IN ULONG Port,
    IN UCHAR AccessMode,
    IN OUT PUSHORT Data
    );

typedef
NTSTATUS
(*PDRIVER_IO_PORT_USHORT_STRING ) (
    IN ULONG_PTR Context,
    IN ULONG Port,
    IN UCHAR AccessMode,
    IN OUT PUSHORT Data,
    IN ULONG DataLength // number of words
    );

typedef
NTSTATUS
(*PDRIVER_IO_PORT_ULONG ) (
    IN ULONG_PTR Context,
    IN ULONG Port,
    IN UCHAR AccessMode,
    IN OUT PULONG Data
    );

typedef
NTSTATUS
(*PDRIVER_IO_PORT_ULONG_STRING ) (
    IN ULONG_PTR Context,
    IN ULONG Port,
    IN UCHAR AccessMode,
    IN OUT PULONG Data,
    IN ULONG DataLength  // number of dwords
    );

Dans le cas d’un IN* exécuté par le process 16 bits il est possible de modifier la valeur lue par l’instruction. Je ne sais pas à quoi ca peut servir mais c’est marrant :)

De son coté le noyau maintient des handlers pour les cas suivants :

  • Ecriture ou lecture d’un port avec une I/O ‘classique’.
  • Ecriture ou lecture d’un port à l’aide d’une String I/O.
  • I/O sur un byte ou un word ou un doubleword.
  • Les I/O sur des doubleword sont découpés en double I/O sur des word s’il n’y a pas de handler existant.
  • Les I/O sur les word sont découpés en double I/O sur les bytes s’il n’y a pas de handler existant.

Ce qui nous amène à ces structures, notez que dans le tableau IoFunctions l’indice 0 correspond aux fonctions handler des IN* alors que l’indice 1 est celui des OUT*:

//
//    Vdm Objects and Io handling structure
//

typedef struct _VDM_IO_HANDLER_FUNCTIONS {
    PDRIVER_IO_PORT_ULONG  UlongIo;
    PDRIVER_IO_PORT_ULONG_STRING UlongStringIo;
    PDRIVER_IO_PORT_USHORT UshortIo[2];
    PDRIVER_IO_PORT_USHORT_STRING UshortStringIo[2];
    PDRIVER_IO_PORT_UCHAR UcharIo[4];
    PDRIVER_IO_PORT_UCHAR_STRING UcharStringIo[4];
} VDM_IO_HANDLER_FUNCTIONS, *PVDM_IO_HANDLER_FUNCTIONS;

typedef struct _VDM_IO_HANDLER {
    struct _VDM_IO_HANDLER *Next;
    ULONG PortNumber;
    VDM_IO_HANDLER_FUNCTIONS IoFunctions[2];
} VDM_IO_HANDLER, *PVDM_IO_HANDLER;

Pour retrouver ces structures le noyau passe par champ VdmObjects de l’EPROCESS (offset 0×158 sous XP). Ce champ pointe sur une structure VDM_PROCESS_OBJECTS de la forme :

typedef struct _VDM_PROCESS_OBJECTS {
    PVDM_IO_LISTHEAD VdmIoListHead;
    KAPC             QueuedIntApc;
    KAPC             QueuedIntUserApc;
    FAST_MUTEX       DelayIntFastMutex;
    KSPIN_LOCK       DelayIntSpinLock;
    LIST_ENTRY       DelayIntListHead;
    PVDMICAUSERDATA  pIcaUserData;
    PETHREAD         MainThread;
    PVDM_TIB         VdmTib;
    PUCHAR           PrinterState;
    PUCHAR           PrinterControl;
    PUCHAR           PrinterStatus;
    PUCHAR           PrinterHostState;
    USHORT           AdlibStatus;
    USHORT           AdlibIndexRegister;
    USHORT           AdlibPhysPortStart;
    USHORT           AdlibPhysPortEnd;
    USHORT           AdlibVirtPortStart;
    USHORT           AdlibVirtPortEnd;
    USHORT           AdlibAction;
    USHORT           VdmControl;
    ULONG            PMCliTimeStamp;
} VDM_PROCESS_OBJECTS, *PVDM_PROCESS_OBJECTS;

Je ne vais pas détaillé tous les champs de cette structure. Celui qui nous intéresse est VdmIoListHead de type PVDM_IO_LISTHEAD dont voici la définitition :

typedef struct _VDM_IO_LISTHEAD {
    PVDM_IO_HANDLER VdmIoHandlerList;
    ERESOURCE       VdmIoResource;
    ULONG           Context;
} VDM_IO_LISTHEAD, *PVDM_IO_LISTHEAD;

On retombe sur nos pattes vu que VmdIoHandlerList pointe sur une liste de VDM_IO_HANDLER.

Toujours dans le noyau mais cette fois au niveau des fonctions qui sont mises en oeuvres, sachant qu’une I/O en V8086 lève une exception de type #GP on passe donc par le handler KiTrap0D. Ce handler vérifie que c’est un process VDM à l’aide du champ VdmObjects de l’EPROCESS, si ce pointeur est différent de NULL alors il appel la routine VdmDispatchOpcodeV86_try. Ensuite on traverse Ki386DispatchOpcode qui utilise les tables OpcodeIndex et OpcodeDispatch pour appeler les handlers d’opcodes correspondant à l’instruction fautive. Seul les handlers suivants appelent Ki386VdmDispatchIo :

 
OpcodeINBimm
OpcodeINWimm
OpcodeOUTBimm
OpcodeOUTWimm
OpcodeINB 
OpcodeINW  
OpcodeOUTB  
OpcodeOUTW
OpcodeINBimmV86
OpcodeINWimmV86
OpcodeOUTBimmV86
OpcodeOUTWimmV86
OpcodeINBV86
OpcodeINWV86
OpcodeOUTBV86
OpcodeOUTWV86

Ki386VdmDispatchIo utilise Ps386GetVdmIoHandler pour vérifier la présence d’un handler I/O, s’il en existe un alors on passe dans VdmDispatchIoToHandler ou VdmDispatchUnalignedIoToHandler. Cette dernière fonction se charge d’appeler notre handler d’I/O avec les bons paramètres. Note à moi même, regarder à quoi correspondent les fonctions Ki386AdlibEmulation et Ki386AdlibDirectIo dans Ki386VdmDispatchIo.

Du coté de ZwSetInformationProcess on fait appel à PspSetProcessIoHandlers, Psp386InstallIoHandler puis Psp386InsertVdmIoHandlerBlock.

Pour finir, j’ai codé un POC. Il est constitué de 3 élèments :

  1. Un driver pouvant recevoir des IOCTLs, il sert principalement à appeler ZwSetInformationProcess sur ProcessIoPortHandlers et définit la routine de gestion des I/Os.
  2. Un process user-land, qui communique avec le driver pour lui fournir le PID du process sur lequel il doit ouvrir le handle passé à ZwSetInformationProcess.
  3. Un programme 16 bits compilé avec RadAsm qui utilise l’instruction ‘repo outsb’ pour écrire une string sur un port réservé 0×74.

Pour l’utilisation vous chargez le driver, vous lancez le binaire 16 bits qui se met en attente puis vous spécifiez le PID au programme user-land (cherchez le process ntvdm.exe) afin que le driver mette en place le handler d’I/O.

Bien sur il s’agit juste d’un POC, l’idée est de faire transiter des infos plus intéressantes vers notre driver avec cette méthode. Cependant la méthode reste lourde, vu qu’il ne suffit pas d’exécuter du code 16 bits. Pour info vous pouvez le faire de 2 manières :

  1. Mettre le prefixe de taille d’opérande (0×66) qui inverse le sens du bit D du segment selector. De fait qu’on soit avec un segment de code user-land/kernel-land avec le bit D (Default) à 1 les opérandes et les adresses sont sur 32 bits.
  2. Mettre en place une LDT avec un segment de code ayant le bit D à 1 dans son segment descriptor.

Mais de toute façon exécuter du code 16 bits ne suffit pas car il faut un process wraper par ntvdm.exe pour que cette feature soit applicable. On pourrait très bien imaginer un programme 16 bits servant de proxy avec un autre programme user-land, mais les IPC sont plutôt rares entre les 2 mondes :(

Donc pour le moment le programme 16 bits doit tout faire de lui même, pas très pratique vous me direz. Surement qu’une vraie solution existe afin d’avoir quelque chose de souple (tant qu’on touche pas le fond à injecter du code 16 bits dans la mémoire basse de ntvdm.exe tout va bien :p) mais pour le moment je ne vois pas. D’un coté je suis pratiquement sûr que personne n’osera jamais utiliser ce code :]

Finalement rien d’impressionnant mais je voulais attirer votre attention sur une feature quasiment inconnue du noyau. J’ai pu remarquer de nombreuses autres fonctionnalités perdue dans le kernel qui datent des premières versions et qui restent là pour des raisons de compatibilités. C’est beau d’arriver en 2010 et de voir que même votre Windows 7 est capable d’exécuter du code 16 bits, surtout que le code de ntvdm.exe et de ses libs est lui aussi très vieux. Cela me rappel un peu le problème de MS09-048 qui ne se verra pas patcher à cause de problèmes architecturaux trop lourd à corriger. On se rend compte de la lourdeur des systèmes actuels qui font tourner des codes développés il y plusieurs années par des personnes qui pour la plupart ont prit leur retraite à l’heure actuelle. Ainsi toute aptitude de maintenance est perdue et je pense que cela va aller en s’empirant dans les années à venir car les systèmes augmentent en complexité et reposent de plus en plus sur la couche inférieure. J’ai peur du futur !

Vous trouverez les codes et binaires de ce POC ici :
http://ivanlef0u.fr/repo/ProcessIoPortHandlers.rar

Entry Filed under: RE

8 Comments

  • 1. Geo  |  septembre 26th, 2009 at 16:10

    Tu nous fais des bêtes de trucs même à 3h du matin. T’es pas croyable comme mec.

    Merci pour cet article très enrichissant. J’attendrai d’avoir plus de temps et de motivation pour m’y attarder. Faut de la patience pour digérer tout ça. ;)

    Allez, bonne continuation !

    Geo


  • 2. Matt  |  septembre 27th, 2009 at 14:58

    A propos de MS09-048

    Jack C. Louis of Outpost24 for reporting the TCP/IP Zero Window Size Vulnerability (CVE-2008-4609)
    Unicornscan Author Jack C. Louis Dies in Tragic fire at his home in Sweden
    Source: http://blogs.hackerscenter.com/2009/03/unicornscan-author-jack-c-louis-dies-in.html

    .. Nevermind.

    Et tout les devs de MSFT ne sont pas tous parti avec leurs stock options ;)


  • 3. anonymous/  |  septembre 28th, 2009 at 17:19

    La Not Knowed Agencie (NKA) à encore frappé.


  • 4. admin  |  septembre 28th, 2009 at 22:03

    @Geo : merci :]
    @Matt : failed …


  • 5. newsoft  |  septembre 29th, 2009 at 07:33

    Mes posts sont bien plus utiles, car l’échec est une valeur d’avenir ! (contrairement au 16-bit ;)

    Sinon en vrac:
    * Pour info, à l’époque des systèmes 16-bit, il fallait avoir la Bible du PC sous le coude et programmer soi-même les I/O. C’est formateur.
    * Ki386AdlibEmulation IMHO c’est une émulation de la carte son Adlib (disparue en … 1992)
    * Tu as oublié un « BasePort TODO » dans le texte. Enfin on comprend de quoi il s’agit.

    Bonne rentrée !


  • 6. www  |  octobre 30th, 2009 at 18:09

    Stop WoW , reviens écrire des articles.


  • 7. xxx  |  octobre 31st, 2009 at 10:53

    Un article sur la detection de la VM VirtualBox :p s’il te plait :D
    C’est du jamais vue.


  • 8. admin  |  octobre 31st, 2009 at 13:27

    @www
    http://joe-is-a-rocknroll-star.blogspot.com/2009/10/int-0x2e-la-trick.html & http://www.woodmann.com/forum/showthread.php?p=83479#post83479
    Ca devrait te plaire :)
    Faut que je me bouge pour de nouveaux articles c’est vrai ! Mais j’ai pas de mal de taff d’abord à finir, je verrais.
    +


Trackback this post


Calendar

décembre 2018
L Ma Me J V S D
« fév    
 12
3456789
10111213141516
17181920212223
24252627282930
31  

Most Recent Posts