mardi 20 octobre 2009

Tutoriel Rootkits Windows - 2ème partie Communication userLand et kernelLand

Windows est divisé en deux modes: noyau (kernelLand, ring0) et utilisateur (userLand, ring3). Les objets du noyau sont difficilement accessibles depuis le mode utilisateur, et vice-versa. Un rootkit comportera généralement deux parties: l'une sera un programme en mode noyau: un pilote (driver). Il permettra l'accès au matériel, l'élévation de privilèges de processus et la furtivité (en modifiant les tables et le registre), l'autre sera un programme en mode utilisateur, qui assurera la plupart des fonctionnalités. Les programmes en mode utilisateur et noyau peuvent communiquer ensemble via les commandes de contrôle d'entrée/sortie (I/O Control, IOCTL), en utilisant les paquets de requêtes d'E/S (I/O Request Packet, IRP). Le but de ce tutoriel est de présenter cette méthode.



  • plan
Introduction
Plan
Outils
1. Création du programme
    1.1. Création du programme en userLand
    1.2. Création du pilote

2. Chronologie des échange dans notre rootkit
    2.1. création du périphérique
    2.2. Création d'un lien symbolique
    2.3. Définition des MajorFunctions
    2.4. Accès au périphérique
    2.5. le I/O Manager fait une requete IRP_MJ_CREATE
    2.6. Le programme user envoie une IOCTL au périphérique
    2.7. le I/O Manager fait une requete IRP_DEVICE_CONTROL
3. Résumé
Conclusion
Références


  • outils

- gcc
Ce programme Linux a été porté sous windows grâce à MinGW. Téléchargez l'installeur Windows sur le site officiel http://www.mingw.org/wiki/Getting_Started .

- Windows Driver Kit (WDK)
http://www.microsoft.com/whdc/devtools/wdk/wdkpkg.mspx

- DebugView
http://technet.microsoft.com/en-us/sysinternals/bb896647.aspx
Ce logiciel permet de lire les informations de débuggage en mode utilisateur comme en mode noyau.

- LoadDrv
http://www.koders.com/c/fid3AA0382374728CEFBC5D0A03128C9DF9404DAC92.aspx. Code source fourni dans le tutoriel rootkits part1 (référence n°1). Ce programme a été créé par Paula Tomlinson. Nous utiliserons la version modifiée de Chris Lietchi. Il permet de loader et unloader un pilote sans relancer l'ordinateur. Il s'agit d'un Service Control Program (SCP), c'est à dire un programme capable d'envoyer des requêtes au Service Control Manager (SCM). Voir référence n°1 pour davantage de précisions.

- WinObj
http://technet.microsoft.com/en-us/sysinternals/bb896657.aspx. Ce programme permet d'afficher des informations sur les pilotes chargés.



  • 1. Création du programme

Nous reprenons le code source du pilote de la partie 1 (référence n°1) . En nous basant sur l'excellent tutoriel de 0verl0ck (référence n°5), nous le modifions comme suit:


11. Création du programme en userLand

- Créez un répertoire C:>exemple2.
- copiez-y loaddrv.exe,
- Créez y un fichier exemple2_r3.c et dans lequel vous copiez:

#include < stdio.h>
#include < windows.h>
#include < winioctl.h>
#include < stdlib.h>
#include < string.h>

#define SIOCTL_TYPE 40000

#define IOCTL_HELLO\
    CTL_CODE( SIOCTL_TYPE, 0x800, METHOD_BUFFERED, FILE_READ_DATA|FILE_WRITE_DATA)

int __cdecl main(int argc, char* argv[])
{
    HANDLE hDevice;
    DWORD NombreByte;
    char *welcome = "Hello World! du userland." , out[50];

    ZeroMemory(out,sizeof(out));

    printf("Tutoriel communication userLand et kernelLand\n");

    hDevice = CreateFile("\\\\.\\exemple2",GENERIC_WRITE|GENERIC_READ,0,NULL,OPEN_EXISTING,FILE_ATTRIBUTE_NORMAL,NULL);
    printf("Pointeur du peripherique: %p\n",hDevice);
    DeviceIoControl(hDevice,IOCTL_HELLO,welcome,strlen(welcome),out,sizeof(out),&NombreByte,NULL);
    printf("Ecriture.\n");
    printf("Message reçu du kernelLand : %s\n",out);
    CloseHandle(hDevice);
    return 0;
}

- compilez exemple2_r3.c:
C:\exemple2>gcc -Wall -o exemple2_r3 exemple2_r3.c


12. Création du pilote

- Créez un fichier exemple2_r0.c et copiez y:

#include < wdm.h>
#include < string.h>

#define SIOCTL_TYPE 40000
typedef unsigned long DWORD;
typedef DWORD *PDWORD;

#define IOCTL_HELLO\
    CTL_CODE( SIOCTL_TYPE, 0x800, METHOD_BUFFERED, FILE_READ_DATA|FILE_WRITE_DATA)

VOID unload(PDRIVER_OBJECT pDriverObject);
NTSTATUS Fonction_IRP_MJ_CREATE(PDEVICE_OBJECT DeviceObject,PIRP Irp);
NTSTATUS Fonction_IRP_MJ_CLOSE(PDEVICE_OBJECT DeviceObject,PIRP Irp);
NTSTATUS Fonction_IRP_DEVICE_CONTROL(PDEVICE_OBJECT DeviceObject,PIRP Irp);


NTSTATUS DriverEntry(PDRIVER_OBJECT pDriverObject,PUNICODE_STRING pRegistryPath)
{
    NTSTATUS retour = 0;
    UNICODE_STRING NomInterface,NomLien;
    PDEVICE_OBJECT ptrInterface = NULL;

    RtlInitUnicodeString(&NomInterface,L"\\Device\\exemple2");

    retour = IoCreateDevice(pDriverObject,0,&NomInterface,FILE_DEVICE_UNKNOWN,FILE_DEVICE_UNKNOWN,FALSE,&ptrInterface);
    pDriverObject->DriverUnload = unload;
    pDriverObject->MajorFunction[IRP_MJ_CREATE] = Fonction_IRP_MJ_CREATE;
    pDriverObject->MajorFunction[IRP_MJ_CLOSE] = Fonction_IRP_MJ_CLOSE;
    pDriverObject->MajorFunction[IRP_MJ_DEVICE_CONTROL] = Fonction_IRP_DEVICE_CONTROL;

    RtlInitUnicodeString(&NomLien,L"\\DosDevices\\exemple2");
    retour = IoCreateSymbolicLink(&NomLien,&NomInterface);

    return STATUS_SUCCESS;
}

VOID unload(PDRIVER_OBJECT pDriverObject)
{
    UNICODE_STRING NomLien;
    RtlInitUnicodeString(&NomLien,L"\\DosDevices\\exemple2");

    IoDeleteSymbolicLink(&NomLien);
    IoDeleteDevice(pDriverObject->DeviceObject);
}

NTSTATUS Fonction_IRP_MJ_CREATE(PDEVICE_OBJECT DeviceObject,PIRP Irp)
{
    DbgPrint("IRP_MJ_CREATE reçu.");
    return STATUS_SUCCESS;
}

NTSTATUS Fonction_IRP_MJ_CLOSE(PDEVICE_OBJECT DeviceObject,PIRP Irp)
{
    DbgPrint("IRP_MJ_CLOSE reçu.");
    return STATUS_SUCCESS;
}

NTSTATUS Fonction_IRP_DEVICE_CONTROL(PDEVICE_OBJECT DeviceObject,PIRP Irp)
{
    PIO_STACK_LOCATION pIoStackLocation;
    PCHAR welcome = "Hello World! du kernelLand.";
    PVOID pBuf = Irp->AssociatedIrp.SystemBuffer;

    pIoStackLocation = IoGetCurrentIrpStackLocation(Irp);

    DbgPrint("IOCTL Hello World.");
    DbgPrint("Message reçu : %s",pBuf);

    RtlZeroMemory(pBuf,pIoStackLocation->Parameters.DeviceIoControl.InputBufferLength);
    RtlCopyMemory( pBuf , welcome , strlen(welcome) );

    Irp->IoStatus.Status = STATUS_SUCCESS;
    Irp->IoStatus.Information = strlen(welcome);

    IoCompleteRequest(Irp,IO_NO_INCREMENT);

    return STATUS_SUCCESS;
}

 - créez un fichier SOURCES et copiez y:

TARGETNAME=EXEMPLE2
TARGETPATH=OBJ
TARGETTYPE=DRIVER
SOURCES=exemple2_r0.c

- créer un fichier MAKEFILE et copiez-y:

!INCLUDE $(NTMAKEENV)\makefile.def

- lancez l'environnement checked-build du WDK, et placez vous dans le répertoire C:\exemple2. Lancez la commande build.

C:\exemple2>build
BUILD: Compile and Link for x86
BUILD: Loading c:\winddk\7600.16385.0\build.dat...
BUILD: Computing Include file dependencies:
BUILD: Start time: Sat Oct 17 19:23:54 2009
BUILD: Examining c:\exemple2 directory for files to compile.
    c:\exemple2 Invalidating OACR warning log for 'root:x86chk'
BUILD: Saving c:\winddk\7600.16385.0\build.dat...
BUILD: Compiling and Linking c:\exemple2 directory
Configuring OACR for 'root:x86chk' -
_NT_TARGET_VERSION SET TO WINXP
Compiling - exemple2_r0.c
Linking Executable - objchk_wxp_x86\i386\exemple2.sys
BUILD: Finish time: Sat Oct 17 19:23:57 2009
BUILD: Done
    3 files compiled
    1 executable built
C:\exemple2>

- installez et lancez le pilote exemple2:
C:\exemple2\objchk_wxp_x86\i386>loaddrv install exemple2
installing exemple2 from C:\exemple2\objchk_wxp_x86\i386\exemple2.sys... ok.

C:\exemple2\objchk_wxp_x86\i386>loaddrv start exemple2
starting exemple2... ok.

- vérifiez que le pilote est bien chargé, avec WinObj:


fig 1.

- lancez exemple2_r3.c.exe:
C:\exemple2>exemple2_r3.exe
Tutoriel communication userLand et kernelLand
Pointeur du peripherique: 000007E8
Ecriture.
Message recu du kernelLand : Hello World! du kerneland.

- jetez un oeil à DbgView:


fig 2.

- Unloadez et désinstallez le pilote exemple2:
C:\exemple2\objchk_wxp_x86\i386>loaddrv stop exemple2
C:\exemple2\objchk_wxp_x86\i386>loaddrv remove exemple2


A ce stade, nous avons pu constater en lisant:
- la sortie débuggage: que le userLand a pu envoyer des données au kernel,
- la sortie standard (le terminal): que le pilote a pu envoyer des données au thread.



  • 2. Chronologie des échange dans notre rootkit

2.1. Création du périphérique

Intéressons nous pour l'instant au pilote:

Dans le tutoriel précédent (cf tutoriel part 1, référence n°1), nous avons vu qu'au chargement de notre pilote, est créée une structure de format PDRIVER_OBJECT (obtenue ici avec WinDbg):

lkd> dt nt!_DRIVER_OBJECT
   +0x000 Type             : Int2B
   +0x002 Size             : Int2B
   +0x004 DeviceObject     : Ptr32 _DEVICE_OBJECT
   +0x008 Flags            : Uint4B
    ...

Le pilote sert à gérer un périphérique (ou device) qui lui est représenté par la structure DEVICE_OBJECT. En fait, un périphérique peut être physique, logique ou virtuel (cf MSDN, référence n°6). Dans notre cas, il s'agira d'un périphérique virtuel. Pour l'instant nous n'avons créé que notre pilote. Le pointeur DeviceObject ne pointe encore nulle part.

La communication ne se fera pas entre le userLand et le pilote, mais bien entre le userLand et le périphérique, donc il nous faut le créer :)

La création du périphérique se fait avec la fonction IoCreateDevice (cf MSDN, référence n°7):

NTSTATUS DriverEntry(PDRIVER_OBJECT pDriverObject,PUNICODE_STRING pRegistryPath)
{
    NTSTATUS retour = 0;
    UNICODE_STRING NomInterface,NomLien;
    PDEVICE_OBJECT ptrInterface = NULL;
    RtlInitUnicodeString(&NomInterface,L"\\Device\\exemple2");

    retour = IoCreateDevice(pDriverObject,0,&NomInterface,FILE_DEVICE_UNKNOWN,FILE_DEVICE_UNKNOWN,FALSE,&ptrInterface);
    ...

Nous avons commencé par créer le pointeur ptrInterface vers un objet de structure DEVICE_OBJECT. Ce pointeur contient l'adresse de notre périphérique.
    PDEVICE_OBJECT ptrInterface = NULL;

Puis nous créons la structure du périphérique:
    retour = IoCreateDevice(pDriverObject,0,&NomInterface,FILE_DEVICE_UNKNOWN,FILE_DEVICE_UNKNOWN,FALSE,&ptrInterface);



2.2. Création d'un lien symbolique

Pour faciliter la rechercher de l'adresse de notre périphérique en userLand, nous créons un lien symbolique. Cette étape n'est pas indispensable, mais rend le programme en userLand plus facile à coder. Pour davantage d'informations voir MSDN (référence n°8):
    RtlInitUnicodeString(&NomLien,L"\\DosDevices\\exemple2");
    retour = IoCreateSymbolicLink(&NomLien,&NomInterface);


2.3. Définition des MajorFunctions

Rappel: Notre objet DRIVER_OBJECT a la structure suivante:

lkd> dt nt!_DRIVER_OBJECT
   +0x000 Type             : Int2B
   +0x002 Size             : Int2B
   +0x004 DeviceObject     : Ptr32 _DEVICE_OBJECT
   +0x008 Flags            : Uint4B
   +0x00c DriverStart      : Ptr32 Void
   +0x010 DriverSize       : Uint4B
   +0x014 DriverSection    : Ptr32 Void
   +0x018 DriverExtension  : Ptr32 _DRIVER_EXTENSION
   +0x01c DriverName       : _UNICODE_STRING
   +0x024 HardwareDatabase : Ptr32 _UNICODE_STRING
   +0x028 FastIoDispatch   : Ptr32 _FAST_IO_DISPATCH
   +0x02c DriverInit       : Ptr32     long
   +0x030 DriverStartIo    : Ptr32     void
   +0x034 DriverUnload     : Ptr32     void
   +0x038 MajorFunction    : [28] Ptr32     long

Le pointeur MajorFunction pointe vers un tableau contenant les MajorFunctions suivantes (cf MSDN, référence n°9):



Dans notre pilote, nous plaçons l'adresse de nos trois fonctions dans le tableau pointé dans notre périphérique:

    pDriverObject->MajorFunction[IRP_MJ_CREATE] = Fonction_IRP_MJ_CREATE;
    pDriverObject->MajorFunction[IRP_MJ_CLOSE] = Fonction_IRP_MJ_CLOSE;
    pDriverObject->MajorFunction[IRP_MJ_DEVICE_CONTROL] = Fonction_IRP_DEVICE_CONTROL;



2.4. Accès au périphérique

Nous retournons à présent dans notre programme userLand.

int __cdecl main(int argc, char* argv[])
{
    HANDLE hDevice;
    DWORD NombreByte;
    char *welcome = "Hello World! du userland." , out[50];

    ZeroMemory(out,sizeof(out));

    printf("Tutoriel communication userLand et kernelLand par t0ka7a\n");

    hDevice = CreateFile("\\\\.\\exemple2",GENERIC_WRITE|GENERIC_READ,0,NULL,OPEN_EXISTING,FILE_ATTRIBUTE_NORMAL,NULL);

En appelant la fonction CreateFile, (cf MSDN, référence 13), nous faisons appel à l'I/O Manager qui va:
- envoyer une requête IRP_MJ_CREATE à notre périphérique et renvoyer un pointeur vers notre objet DEVICE_OBJECT,
- créer une structure I/O Request Packet (IRP).

Qu'est ce que l'I/O Manager?

L'OS est optimisé pour réduire la consommation de ses ressources lors des échanges entre le kernelLand et le userLand. Le contrôle de ces échanges est dévolu au contrôleur d'E/S (I/O Manager).



2.5. Le I/O Manager fait une requete IRP_MJ_CREATE

A l'appel, en userLand, de la fonction CreateFile, l'I/O Manager trouve le pointeur de notre périphérique grâce au nom que nous lui avons donné ("\\\\.\\exemple2").

Il crée un I/O request packet (IRP) et envoie une requête IRP_MJ_CREATE au pilote. Cela signifie qu'il lance la MajorFunction associée. Il trouve l'adresse de cette fonction en parcourant les structures:
DEVICE_OBJECT -> DRIVER_OBJECT -> MajorFunction[] -> Fonction_IRP_MJ_CREATE().

NTSTATUS Fonction_IRP_MJ_CREATE(PDEVICE_OBJECT DeviceObject,PIRP Irp)
{
    DbgPrint("IRP_MJ_CREATE reçu.");
    return STATUS_SUCCESS;
}

Nous voyons alors apparaitre "IRP_MJ_CREATE reçu" sur la sortie débuggage (cf supra fig2).

Remarquez que la fonction reçoit bien en paramètre un objet I/O request packet (IRP). Nous verrons ultérieurement ce dont il s'agit.



2.6. Le programme user envoie une IOCTL au périphérique

La fonction DeviceIoControl (MSDN, référence n°14) de notre programme userLand envoie ensuite une requête à l'I/O Manager:

DeviceIoControl(hDevice,IOCTL_HELLO,welcome,strlen(welcome),out,sizeof(out),&NombreByte,NULL);
   
Cette requête est appelée controle E/S (I/O control, ou IOCTL). Nous lui fournissons les adresses vers deux buffers:
- char *welcome = "Hello World! du userland.";,
- char out[50];

Nous initialisons out:
ZeroMemory(out,sizeof(out));.

Le premier contient les données à envoyer au pilote, le second permet de recevoir les données provenant du pilote.

L'IOCTL est définie plus haut dans le programme:
#define SIOCTL_TYPE 40000
#define IOCTL_HELLO\
    CTL_CODE( SIOCTL_TYPE, 0x800, METHOD_BUFFERED, FILE_READ_DATA|FILE_WRITE_DATA)

Le code d'une IOCTL mesure 32 bits.



image tirée du blog de angellook (référence n°15)


Ce qui est important dans notre pilote, c'est le choix de la méthode METHOD_BUFFERED. Pour davantage d'informations sur les codes IOCTL, consulter le blog de angellook (référence n°15).

Il existe trois méthodes pour échanger des données entre un périphérique et un thread (voir MSDN, référence n°16):
- buffered I/O, (MSDN, référence n°17)
- direct I/O, (MSDN, référence n°18)
- neither buffered nor direct I/O. (MSDN, référence n°19)

La méthode direct I/O est pratique pour les échanges importants de données.
La méthode neither buffered nor direct I/O donne au pilote l'accès direct à la mémoire du thread mais elle est contraignante car le thread n'a plus le contrôle de certaines de ses données le temps de l'interruption.



2.7. le I/O Manager fait une requete IRP_DEVICE_CONTROL

Comme pour la requête IRP_MJ_CREATE, le I/O Manager envoie une requête IRP_DEVICE_CONTROL au pilote:
- il crée un IRP dont il donne l'adresse en paramètre à la MajorFunction associée,
- il lance la MajorFunction.

NTSTATUS Fonction_IRP_DEVICE_CONTROL(PDEVICE_OBJECT DeviceObject,PIRP Irp)

Notre IRP ressemble à ceci: (une partie de la structure est non documentée):

lkd> dt nt!_irp
   +0x000 Type             : Int2B
   +0x002 Size             : Uint2B
   +0x004 MdlAddress       : Ptr32 _MDL
   +0x008 Flags            : Uint4B
   +0x00c AssociatedIrp    : __unnamed
   +0x010 ThreadListEntry  : _LIST_ENTRY
   +0x018 IoStatus         : _IO_STATUS_BLOCK
   +0x020 RequestorMode    : Char
   +0x021 PendingReturned  : UChar
   +0x022 StackCount       : Char
   +0x023 CurrentLocation  : Char
   +0x024 Cancel           : UChar
   +0x025 CancelIrql       : UChar
   +0x026 ApcEnvironment   : Char
   +0x027 AllocationFlags  : UChar
   +0x028 UserIosb         : Ptr32 _IO_STATUS_BLOCK
   +0x02c UserEvent        : Ptr32 _KEVENT
   +0x030 Overlay          : __unnamed
   +0x038 CancelRoutine    : Ptr32     void
   +0x03c UserBuffer       : Ptr32 Void
   +0x040 Tail             : __unnamed

Nous récupérons l'adresse de la Stack Location qui a été attribuée par le I/O Manager pour notre IRP  et notre pilote.

    PIO_STACK_LOCATION pIoStackLocation;
    pIoStackLocation = IoGetCurrentIrpStackLocation(Irp);

La Stack Location est un objet de structure IO_STACK_LOCATION:

lkd> dt nt!_IO_STACK_LOCATION
   +0x000 MajorFunction    : UChar
   +0x001 MinorFunction    : UChar
   +0x002 Flags            : UChar
   +0x003 Control          : UChar
   +0x004 Parameters       : __unnamed
   +0x014 DeviceObject     : Ptr32 _DEVICE_OBJECT
   +0x018 FileObject       : Ptr32 _FILE_OBJECT
   +0x01c CompletionRoutine : Ptr32     long
   +0x020 Context          : Ptr32 Void

et plus détaillé: (cf MSDN, référence n°20)

typedef struct _IO_STACK_LOCATION {
  UCHAR  MajorFunction;
  UCHAR  MinorFunction;
  UCHAR  Flags;
  UCHAR  Control;
  union {
        //
        // Parameters for IRP_MJ_CREATE
        //
        struct {
            PIO_SECURITY_CONTEXT  SecurityContext;
            ULONG  Options;
            USHORT POINTER_ALIGNMENT  FileAttributes;
            USHORT  ShareAccess;
            ULONG POINTER_ALIGNMENT  EaLength;
        } Create;
        //
        // Parameters for IRP_MJ_READ
        //
        struct {
            ULONG  Length;
            ULONG POINTER_ALIGNMENT  Key;
            LARGE_INTEGER  ByteOffset;
        } Read;
        //
        // Parameters for IRP_MJ_WRITE
        //
        struct {
            ULONG  Length;
            ULONG POINTER_ALIGNMENT  Key;
            LARGE_INTEGER  ByteOffset;
        } Write;
        //
        // Parameters for IRP_MJ_QUERY_INFORMATION
        //
        struct {
            ULONG  Length;
            FILE_INFORMATION_CLASS POINTER_ALIGNMENT  FileInformationClass;
        } QueryFile;
        //
        // Parameters for IRP_MJ_SET_INFORMATION
        //
        struct {
            ULONG  Length;
            FILE_INFORMATION_CLASS POINTER_ALIGNMENT  FileInformationClass;
            PFILE_OBJECT  FileObject;
            union {
                struct {
                    BOOLEAN  ReplaceIfExists;
                    BOOLEAN  AdvanceOnly;
                };
                ULONG  ClusterCount;
                HANDLE  DeleteHandle;
            };
        } SetFile;
        //
        // Parameters for IRP_MJ_QUERY_VOLUME_INFORMATION
        //
        struct {
            ULONG  Length;
            FS_INFORMATION_CLASS POINTER_ALIGNMENT  FsInformationClass;
        } QueryVolume;
        //
        // Parameters for IRP_MJ_DEVICE_CONTROL and IRP_MJ_INTERNAL_DEVICE_CONTROL
        //
        struct {
            ULONG  OutputBufferLength;
            ULONG POINTER_ALIGNMENT  InputBufferLength;
            ULONG POINTER_ALIGNMENT  IoControlCode;
            PVOID  Type3InputBuffer;
        } DeviceIoControl;
 
       //
        // Nonsystem service parameters.
        //
        // Parameters for IRP_MN_MOUNT_VOLUME
        //
        struct {
            PVOID  DoNotUse1;
            PDEVICE_OBJECT  DeviceObject;
        } MountVolume;
        //
        // Parameters for IRP_MN_VERIFY_VOLUME
        //
        struct {
            PVOID  DoNotUse1;
            PDEVICE_OBJECT  DeviceObject;
        } VerifyVolume;
        //
        // Parameters for Scsi using IRP_MJ_INTERNAL_DEVICE_CONTROL
        //
        struct {
            struct _SCSI_REQUEST_BLOCK  *Srb;
        } Scsi;
        //
        // Parameters for IRP_MN_QUERY_DEVICE_RELATIONS
        //
        struct {
            DEVICE_RELATION_TYPE  Type;
        } QueryDeviceRelations;
        //
        // Parameters for IRP_MN_QUERY_INTERFACE
        //
        struct {
            CONST GUID  *InterfaceType;
            USHORT  Size;
            USHORT  Version;
            PINTERFACE  Interface;
            PVOID  InterfaceSpecificData;
        } QueryInterface;
        //
        // Parameters for IRP_MN_QUERY_CAPABILITIES
        //
        struct {
            PDEVICE_CAPABILITIES  Capabilities;
        } DeviceCapabilities;
        //
        // Parameters for IRP_MN_FILTER_RESOURCE_REQUIREMENTS
        //
        struct {
            PIO_RESOURCE_REQUIREMENTS_LIST  IoResourceRequirementList;
        } FilterResourceRequirements;
        //
        // Parameters for IRP_MN_READ_CONFIG and IRP_MN_WRITE_CONFIG
        //
        struct {
            ULONG  WhichSpace;
            PVOID  Buffer;
            ULONG  Offset;
            ULONG  POINTER_ALIGNMENT Length;
        } ReadWriteConfig;
        //
        // Parameters for IRP_MN_SET_LOCK
        //
        struct {
            BOOLEAN  Lock;
        } SetLock;
        //
        // Parameters for IRP_MN_QUERY_ID
        //
        struct {
            BUS_QUERY_ID_TYPE  IdType;
        } QueryId;
        //
        // Parameters for IRP_MN_QUERY_DEVICE_TEXT
        //
        struct {
            DEVICE_TEXT_TYPE  DeviceTextType;
            LCID POINTER_ALIGNMENT  LocaleId;
        } QueryDeviceText;
        //
        // Parameters for IRP_MN_DEVICE_USAGE_NOTIFICATION
        //
        struct {
            BOOLEAN  InPath;
            BOOLEAN  Reserved[3];
            DEVICE_USAGE_NOTIFICATION_TYPE POINTER_ALIGNMENT Type;
        } UsageNotification;
        //
        // Parameters for IRP_MN_WAIT_WAKE
        //
        struct {
            SYSTEM_POWER_STATE  PowerState;
        } WaitWake;
        //
        // Parameter for IRP_MN_POWER_SEQUENCE
        //
        struct {
            PPOWER_SEQUENCE  PowerSequence;
        } PowerSequence;
        //
        // Parameters for IRP_MN_SET_POWER and IRP_MN_QUERY_POWER
        //
        struct {
            ULONG  SystemContext;
            POWER_STATE_TYPE POINTER_ALIGNMENT  Type;
            POWER_STATE POINTER_ALIGNMENT  State;
            POWER_ACTION POINTER_ALIGNMENT  ShutdownType;
        } Power;
        //
        // Parameters for IRP_MN_START_DEVICE
        //
        struct {
            PCM_RESOURCE_LIST  AllocatedResources;
            PCM_RESOURCE_LIST  AllocatedResourcesTranslated;
        } StartDevice;
        //
        // Parameters for WMI Minor IRPs
        //
        struct {
            ULONG_PTR  ProviderId;
            PVOID  DataPath;
            ULONG  BufferSize;
            PVOID  Buffer;
        } WMI;
        //
        // Others - driver-specific
        //
        struct {
            PVOID  Argument1;
            PVOID  Argument2;
            PVOID  Argument3;
            PVOID  Argument4;
        } Others;
    } Parameters;
  PDEVICE_OBJECT  DeviceObject;
  PFILE_OBJECT  FileObject;
  .
  .
  .
} IO_STACK_LOCATION, *PIO_STACK_LOCATION;


Grace à cette Stack Location, nous trouvons la taille notre buffer et l'initialisons:

    RtlZeroMemory(pBuf,pIoStackLocation->Parameters.DeviceIoControl.InputBufferLength);

Puis nous copions notre chaîne de caractères dans le buffer de l'IRP:

    PCHAR welcome = "Hello World! du kernelLand.";
    PVOID pBuf = Irp->AssociatedIrp.SystemBuffer;
    RtlCopyMemory( pBuf , welcome , strlen(welcome) );
    Irp->IoStatus.Information = strlen(welcome);

Et nous clôturons notre IRP:

    Irp->IoStatus.Status = STATUS_SUCCESS;
    IoCompleteRequest(Irp,IO_NO_INCREMENT);




  • 3. Résumé

Voici un schéma (tiré de MSDN, référence n°17 ) qui résume ce que nous avons vu jusqu'à présent:



image tirée de MSDN, référence 17



  • Conclusion

A ce stade, vous avez pu coder un pilote capable de faire des échanges de données avec le userLand.

Un pilote peut gérer plus d'une IOCTL. Vous pouvez consulter l'exemple fourni par 0verl0ck dans son tutoriel (référence n°5), dont nous nous sommes ici largement inspirés. Il y gère deux IOCTL.




  • références:

1) tutoriel de t0ka7a - coder son premier pilote Windows. Infos sur les services - http://infond.blogspot.com/2009/10/tutoriel-rootkits-coder-son-premier.html
2) livre de Greg Hoglund et James Butler: Rootkits, Infiltrations du noyau Windows. Edition CampusPress
3) Technet Microsoft - How device drivers work - http://technet.microsoft.com/en-us/library/cc776371%28WS.10%29.aspx
4) Microsoft - Handling IRPs, what every driver writer needs to know - http://www.microsoft.com/whdc/driver/kernel/irps.mspx
5) 0vercl0ck - First steps in ring0 - http://0vercl0k.blogspot.com/2008/02/first-steps-into-ring0.html
6) MSDN - DEVICE_OBJECT - http://msdn.microsoft.com/en-us/library/dd852027.aspx
7) MSDN - IoCreateDevice - http://msdn.microsoft.com/en-us/library/aa490468.aspx
8) MSDN - Introduction to MS-DOS device names - http://msdn.microsoft.com/en-us/library/ms794720.aspx
9) MSDN - IRP Major function codes - http://msdn.microsoft.com/en-us/library/ms806157.aspx
10) Microsoft Whdc - What is really in that MDL? - http://www.microsoft.com/whdc/driver/tips/mdl.mspx
11) MSDN - Using Direct I/O with DMA - http://msdn.microsoft.com/en-us/library/ms795356.aspx
12) blog de Peter Wieland- What is DMA? - http://blogs.msdn.com/peterwie/archive/tags/Device+Drivers+General/default.aspx?p=2
13) MSDN - CreateFile Function - http://msdn.microsoft.com/en-us/library/aa363858%28VS.85%29.aspx
14) MSDN - DeviceIoControl Function - http://msdn.microsoft.com/en-us/library/aa363216%28VS.85%29.aspx
15) blog de Angellook - Introduction to I/O Control Codes - http://hi.baidu.com/angellook/blog/item/06b8152338352fad4723e85c.html
16) MSDN - Methods for accessing data buffers - http://msdn.microsoft.com/en-us/library/cc264611.aspx
17) MSDN - Using buffered I/O - http://msdn.microsoft.com/en-us/library/cc264612.aspx
18) MSDN - Using direct I/O with DMA - http://msdn.microsoft.com/en-us/library/ms795356.aspx

19) MSDN - Using neither buffered nor direct I/O - http://msdn.microsoft.com/en-us/library/cc264614.aspx
20) MSDN - IO_STACK_LOCATION - http://msdn.microsoft.com/en-us/library/dd852068.aspx

Aucun commentaire:

Enregistrer un commentaire