2009
11.01

Aujourd’hui je vais vous parlez d’une feature que propose windows depuis pas mal de temps, le  WMI (Windows Management Instrumentation) est une interface servant au contrôle et au monitoring des différentes ressources de Windows comme par exemple les processus et les périphériques. On la retrouve sur win XP/vista et server 2003/2008. La fonction « Propriété Système » par exemple, utilise WMI pour ‘afficher les différentes informations. Mais ce qui est plutôt cool, c’est que WMI n’est pas seulement utilisable avec vbs.

On retrouve un outil, WMIC.exe, qui va permettre de pouvoir faire la demande de différents types d’informations proposé par WMI. On peut par exemple récupérer la liste des processus de la machine en quelques lignes de vbs ; cela évite de passer par les API windows par exemple. Voici quelques scripts d’exemples :

  • Voici un script python qui liste les processus de la machine locale, il affiche le process ident ainsi que le nom du binaire
import wmi
 
c = wmi.WMI()
 
for process in c.Win32_Process():
	print process.ProcessId, process.Name

Ce qui donne :

C:\k3rn3l\WindowsManagementInstrumentation\sample>WmiEnumerationProcess.py
0 System Idle Process
4 System
440 smss.exe
1072 csrss.exe
1592 winlogon.exe
1632 services.exe
1648 lsass.exe
1944 svchost.exe
264 svchost.exe
1016 svchost.exe
1068 svchost.exe
1300 svchost.exe
376 svchost.exe
1512 explorer.exe

Ce script utilise donc des objets instance de la classe Win32_Process ; une instance de cette classe ressemble à ceci :

instance of Win32_Process
{
	Caption = "System";
	CreationClassName = "Win32_Process";
	CSCreationClassName = "Win32_ComputerSystem";
	CSName = "_T4PZ_";
	Description = "System";
	Handle = "4";
	HandleCount = 837;
	KernelModeTime = "1412187500";
	MaximumWorkingSetSize = 1413120;
	MinimumWorkingSetSize = 0;
	Name = "System";
	OSCreationClassName = "Win32_OperatingSystem";
	OSName = "Microsoft Windows XP Home Edition|C:\\WINDOWS|\\Device\\Harddisk0\\Partition2";
	OtherOperationCount = "26827";
	OtherTransferCount = "2463703";
	PageFaults = 22837;
	PageFileUsage = 0;
	ParentProcessId = 0;
	PeakPageFileUsage = 0;
	PeakVirtualSize = "13275136";
	PeakWorkingSetSize = 12611584;
	Priority = 8;
	PrivatePageCount = "28672";
	ProcessId = 4;
	QuotaNonPagedPoolUsage = 0;
	QuotaPagedPoolUsage = 0;
	QuotaPeakNonPagedPoolUsage = 0;
	QuotaPeakPagedPoolUsage = 0;
	ReadOperationCount = "204";
	ReadTransferCount = "3387343";
	SessionId = 0;
	ThreadCount = 100;
	UserModeTime = "0";
	VirtualSize = "1957888";
	WindowsVersion = "5.1.2600";
	WorkingSetSize = "249856";
	WriteOperationCount = "9946";
	WriteTransferCount = "33402036";
};

On retrouve dans cette classe de nombreuses informations : du process ident en passant pas le nombre de page fault ou le parent process ident etc.

  • Le second exemple, en vbs, affichera les noms des processeurs présent sur votre machine :
Set wbemServices = GetObject("winmgmts:\\")
Set wbemObjectSet = wbemServices.InstancesOf("Win32_Processor")
For Each wbemObject In wbemObjectSet
WScript.Echo "Processeur : " & wbemObject.Name
Next

Une instance ressemble à ceci :

instance of Win32_Processor
{
	AddressWidth = 32;
	Architecture = 0;
	Availability = 3;
	Caption = "x86 Family 6 Model 15 Stepping 11";
	CpuStatus = 1;
	CreationClassName = "Win32_Processor";
	CurrentClockSpeed = 2194;
	CurrentVoltage = 14;
	DataWidth = 32;
	Description = "x86 Family 6 Model 15 Stepping 11";
	DeviceID = "CPU0";
	ExtClock = 200;
	Family = 2;
	L2CacheSize = 4096;
	Level = 6;
	LoadPercentage = 4;
	Manufacturer = "GenuineIntel";
	MaxClockSpeed = 2194;
	Name = "Intel(R) Core(TM)2 Duo CPU     T7500  @ 2.20GHz";
	NumberOfCores = 2;
	NumberOfLogicalProcessors = 2;
	PowerManagementSupported = FALSE;
	ProcessorId = "BFEBFBFF000006FB";
	ProcessorType = 3;
	Revision = 3851;
	Role = "CPU";
	SocketDesignation = "Socket 478";
	Status = "OK";
	StatusInfo = 3;
	Stepping = "11";
	SystemCreationClassName = "Win32_ComputerSystem";
	SystemName = "_T4PZ_";
	UpgradeMethod = 13;
	Version = "Modèle 15, niveau 11";
};

Maintenant, parlons un peu plus en détail de WMI. Tout d’abord voici un schéma récapitulatif de l’architecture globale :
WMIArchitecture
Comme nous l’avons plus haut, les données sont géré en suivant le paradigme objet ; en effet windows a choisis le standard Common Information Model (CIM) qui seront géré par le Common Information Model Object Manager. C’est en fait grâce à un ensemble de standards qu’une « transaction » entres un client et un provider wmi va pouvoir se réaliser. Pour vous documenter un peu plus sur ces différents standard voici plusieurs liens :

  1. http://www.hsc.fr/ressources/breves/WMI.html.fr
  2. http://msdn.microsoft.com/fr-fr/library/ms186137%28VS.80%29.aspx

Le CIM object manager travaille en utilisant la notion d’espace de nom ; en effet chaque classe WMI est placée dans un espace de nom différent, par exemple toutes les classes WMI avec un prefixe Win32 se trouve dans « root\cimv2″. Si vous voulez vous amusez à observer les classes disponibles et bien vous trouverez sur votre machine, nativement, un binaire qui se prénomme « wbemtest.exe ». Une fois lancé celui-ci permet de visualiser les instances de classes, les classes disponibles, les attributs/méthodes des différentes classes etc :
wbemtest
Je pense en avoir finit avec le petit blabla d’introduction. Vous pensez bien que ce qui m’intéresse n’est pas de mettre en place un système de monitoring d’un périphérique ou autre mais l’idée serait de pouvoir entrer en communication avec un driver de façon furtive. Imaginez un driver proposant des informations accessible en utilisant la technologie WMI, celui-ci devient presque indétectable ; en effet je n’ai encore rencontré aucun anti-rk qui énuméraient les providers wmi par exemple. Je suis donc partis avec comme idée de développer un provider wmi factice, un comportement sain en premier lieu pour pouvoir tester les possibilités et de la mise en place de la dite technologie. D’après la msdn nous avons deux façons de parvenir à notre objectif ; soit on utilise les fonctions « classique » ou le « KMDF » (Kernel-Mode Driver Framework). Pour ma part j’ai tenter l’implémentation en utilisant les fonctions classiques : enregistrement du wmi provider avec IoWMIRegistrationControl, puis mise en place de handler pour la gestion des IRPs IRP_MN_REGINFO/IRP_MN_REGINFO_EX et enfin l’utilisation de DispatchSystemControl pour gérer les requêtes WMI faites sur le driver. Je suis donc repartis de zéro pour tenter ma chance avec le KMDF, je vais ici vous expliquez les grandes lignes pour mettre en place votre provider. J’ai d’ailleurs oublier de préciser que le WDK propose un exemple de mise en place ; le driver se nomme WmiSamp, je me suis d’ailleurs largement inspiré de ce driver.

La première chose à faire est de se créer une classe en respectant la syntaxe MOF (Managed Object Format), cette classe modélisera les informations que votre driver et capable de gérer. Comme dans un langage objet classique, nous avons possibilités de déclarer des attributs mais aussi des méthodes. Chaque classe est identifié par un GUID, celui-ci est utilisé pour faire le rapprochement entre le driver provider ainsi que la/les classes qu’il est capable de gérer. Ma classe ressemble à cela :

[WMI,
 Dynamic,
 Provider("WmiProv"),
 guid("{15D851F1-6539-1337-A529-00A0C9062910}")]
class WmiT4pz
{
	[key, read]
		string InstanceName;
 
	[read]
    boolean Active;
 
	[WmiDataId(1),
     read, write]
		sint8 nbMagic;
 
	 [Implemented,
     WmiMethodId(1)]
		void Setter([in] sint8 t);
};

Pour pouvoir tester la syntaxe de votre classe, windows met à votre disposition différent outil ; tout d’abord le premier binaire permet de tester si la syntaxe MOF est respecté il s’agit de mofcomp.exe. Cette utilitaire va tester la syntaxe et est capable de créer un binaire .bmf « compilé » de votre fichier .mof. Je l’utilise de cette façon :

C:\k3rn3l\WindowsManagementInstrumentation\src>mofcomp -B:wmimof.bmf wmimof.mof
Compilateur MOF 32 bits Microsoft (R) - Version 5.1.2600.5512
Copyright (c) Microsoft Corp. 1997-2001. Tous droits réservés.
Analyse du fichier MOF : wmimof.mof
Analyse du fichier MOF effectuée
Enregistrement des données binaires MOF dans wmimof.bmf
Termine !

Si la compilation ce passe correctement et bien l’outil va directement ajouter la classe dans le référentiel wmi (seulement si vous ne spécifiez pas l’option -B), dans l’espace de nom « root\default » ; vous pouvez d’ailleurs utiliser wbemtest.exe afin de vérifier cela :

wbemWmiT4pz

Une fois que vous disposez de votre binaire mof, et bien vous pouvez commencer l’élaboration de votre driver. Celui-ci doit réaliser plusieurs étapes, la msdn nous renseigne de la façon suivante :

A framework-based driver’s DriverEntry routine must:

* Activate WPP software tracing.
DriverEntry should include a WPP_INIT_TRACING macro to activate software tracing.

* Call WdfDriverCreate.
The call to WdfDriverCreate enables the driver to use Windows Driver Framework interfaces. (The driver cannot call other framework routines before calling WdfDriverCreate.)

* Allocate any non-device-specific system resources and global variables that it might need.
Typically, drivers associate system resources with individual devices. Therefore, framework-based drivers allocate most resources in an EvtDriverDeviceAdd callback, which is called when individual devices are detected.

[* Obtain driver-specific parameters from the registry.
Some drivers obtain parameters from the registry. These drivers can call WdfDriverOpenParametersRegistryKey to open the registry key that contains these parameters.]

* Provide a DriverEntry return value.

Pour le moment je n’ai pas grand chose à dire, en effet la msdn explique très bien comment utiliser WdfDriverCreate, il suffit d’initialiser la structure WDF_DRIVER_CONFIG grâce à la macro WDF_DRIVER_CONFIG_INIT qui nous permet de placer une callback : EvtDriverDeviceAdd. Encore une fois la msdn nous detail ce que l’on doit faire dans cette callback pour mettre en place notre provider :

A driver’s EvtDriverDeviceAdd callback function typically performs at least some of the following initialization operations:

  • Create a framework device object to represent the device.
  • Create I/O queues so the driver can receive I/O requests.
  • Create device interfaces that applications use to communicate with the device.
  • Create driver-defined interfaces that other drivers can use.
  • Initialize Windows Management Instrumentation (WMI) support.
  • Create interrupt objects, if the driver handles device interrupts.
  • Enable direct memory access (DMA) transactions, if the driver handles DMA operations.

Dans cette fonction la première chose qu’il faut faire c’est de créer un framework device object qui representera le device ; pour cela on utilisera la fonction WdfDeviceCreate ainsi que la  structure WDF_OBJECT_ATTRIBUTES. Dans cette structure nous allons pouvoir créer un espace de stockage de nos donnée, on parle d’object context, celui-ci sera spécifier avec la macro WDF_OBJECT_ATTRIBUTES_SET_CONTEXT_TYPE. Nous donnons à cette macro le type de la structure qui stockera nos instance de notre classe MOF, voici ma structure très simple :

// Warning: Header for class WmiT4pz cannot be created
typedef struct _WmiT4pz
{
    //
    CHAR nbMagic;
    #define WmiT4pz_nbMagic_SIZE sizeof(CHAR)
    #define WmiT4pz_nbMagic_ID 1
 
} WmiT4pz, *PWmiT4pz;
 
typedef struct
{
    PWmiT4pz a;
} OBJECT_CONTEXT, *POBJECT_CONTEXT;

Petite note, la structure qui décris votre classe peut directement être générer grâce à un outil contenu dans les binaires de votre WDK, il s’agit de wmimofck.exe ; en ce qui me concerne je le trouve ici : « C:\WinDDK\7600.16385.0\bin\x86\wmimofck.exe » et je génère mon en-tête C avec l’option -h. Maintenant revenons à notre espace de donnée ; à présent il faut aussi spécifier un accesseur à cette espace. En effet il faut être capable de récupérer un pointeur sur cette espace à tout moment (n’oublions pas que nous allons travailler avec des évènements, c’est pour cela qu’il nous faut cette espace), cela est réaliser par grâce à la macro WDF_DECLARE_CONTEXT_TYPE_WITH_NAME. On pourrait utiliser un système de protection/synchronisation des accès sur les instances des classes, c’est d’ailleurs ce que réalise l’exemple du WDK, le driver va créer plusieurs splinlock avec WdfSpinLockCreate et stocker le handle de l’objet dans leur object-context. Ensuite dans les callbacks ils jouent avec les apis WdfSpinLockAcquire/WdfSpinLockRelease pour accéder/relâcher la ressource critique. Pour ma part j’ai pas pris le temps de bien protéger mes données >:].

Nous pouvons maintenant initialiser les données qui se trouve dans notre object-context :

POBJECT_CONTEXT pContext = NULL;
 
//Specifions notre accesseur pour accéder au context
WDF_DECLARE_CONTEXT_TYPE_WITH_NAME(OBJECT_CONTEXT, GetWmiDeviceData)
 
pContext = GetWmiDeviceData(deviceObject);
pContext->a = (PWmiT4pz)ExAllocatePoolWithTag(NonPagedPool, sizeof(WmiT4pz), 't4pz');
pContext->a->nbMagic = 0;

Et bien évidemment pour rester un minimum propre, nous pouvons définir une fonction qui sera appeler lors de la destruction du device en spécifiant le champs EvtDestroyCallback dans la structure WDF_OBJECT_ATTRIBUTES. Nous pourrons alors libérer l’espace mémoire alloué :].

A présent, il faut donner le nom de la ressource MOF grâce à la fonction WdfDeviceAssignMofResourceName. C’est ici en fait que je suis resté pas mal de temps bloqué ; en effet la petite astuce est qu’il faut embarqué le fichier MOF « compilé » en ressource (le fichier .bmf donc) dans notre driver. Pour cela rien de plus simple, il nous suffit de créer un fichier ressource-script (.rc) :

MofResourceName MOFDATA wmimof.bmf

Il faut ensuite l’ajouter dans votre fichier « sources », et lancer la compilation le binaire sera présent dans le DataDirectory Ressource de celui-ci. C’est donc le nom de la ressource qu’il faut passer à la fonction WdfDeviceAssignMofRessourceName. Et maintenant il nous reste une unique étape, il s’agit d’enregistrer l’instance au prêt du système grâce à la fonction WdfWmiInstanceCreate. C’est ici que l’on va pouvoir définir nos différentes callback, celles qui iront placer des valeurs dans nos variable, ou executer les méthodes que nous avions définit dans la classe MOF. J’ai choisis de gérer seulement les callbacks suivante : EvtWmiInstanceQueryInstance et EvtWmiInstanceExecuteMethod. La première est appelé quand un script demande l’état de l’instance, de ses différentes variables ; et l’autre lorsqu’une méthode est appelé. Une callback pour gérer toutes les méthodes proposées, elles sont chacune d’elles identifiées par un identifiant ce qui permet de savoir quelle méthode à été appelée dans notre callback. C’est aussi dans ces callbacks qu’ils seraient bon de poser notre 3v1lc0d3.

Voilà en ce qui concerne la création du driver, il faut maintenant créer un fichier INF afin de pouvoir installer le provider. Pour ma part je me suis pas cassé la tête, j’ai repris celui que proposait le WDK pour leur exemple. Maintenant il faut installer le provider, car celui-ci ne s’installe pas comme un driver « classique » le WDK indique l’utilisation du binaire devcon.exe pour enregistrer le driver :

C:\Documents and Settings\0vercl0k\Bureau>devcon.exe install WmiSamp.inf root\WmiSamp
Device node created. Install is complete when drivers are updated...
Updating drivers for root\WmiSamp from C:\Documents and Settings\0vercl0k\Bureau\WmiSamp.inf.
Drivers updated successfully.

Et maintenant nous pouvons utiliser le binaire wmimofck.exe pour générer des fichiers HTMLs qui vont directement embarqué du code visual basic et nous afficher une espèce de web-interface nous permettant d’interagir avec notre instance WMI (l’option -w de wmimofck.exe) :

pwnd

Et voilà :)))).

Bon vous avez surement du comprendre que la mise en place d’un rootkit WMI de cette façon est plutôt quelque chose à oublier ; en effet nous sommes contraint (apriori) à utiliser le binaire devcon.exe afin de pouvoir installer correctement notre device. Bien entendu on pourrait entreprendre une analyse de devcon.exe lui même pour savoir comment il procède pour l’enregistrement du driver mais j’ai bien peur que tous cela soit trop lourd pour si peu au final. En tous cas si des personnes ont des idées la dessus, ou qu’ils ont déjà eux aussi tenter l’experience ça serait plutôt cool de pouvoir partager tous ça, je vous invite donc à me contacter par le système de commentaire ou bien directement sur ma boite mail : 0vercl0k[hat]tuxfamily[d0t]org. En attendant j’espère vous avoir divertis pendant quelques minutes avec tout mon périple dans le monde WMI. Je vais aussi vous filez quelques liens que j’ai révélé pendant la création de ce projet :

  1. :: WMI – Contrôler Windows en Python
  2. Windows Management Instrumentation (Windows)
  3. Providing Data to WMI (Windows)
  4. Designing Managed Object Format (MOF) Classes (Windows)
  5. WMI Driver Testing for Windows 9x
  6. http://www.phdcc.com/wdmbook/wmi.cpp
  7. WMI, partie 1 – Club des décideurs et professionnels en Informatique

Et enfin les sources/binaires du projet consultable en html :

NB : n’oubliez pas de préciser dans votre fichier « sources » la version du KMDF, sinon vous ne pourrez compiler le driver (KMDF_VERSION_MAJOR=1).

Clin d’oeil spécial à mon jedi, Ivanlef0u, qui m’a lui même donner l’idée d’aller creuser un peu dans cette direction =).

7 commentaires pour le moment

Ajoutez votre commentaire
  1. Yo
    GG mofo :]
    +

  2. C’est intéressant tout ça bien jouer :)

  3. Salut 0vercl0k ! Come va ?

    Ça fait trop longtemps que j’ai pas visité ton blog, tu deviens quoi ? Tu nous rejoins l’année prochaine avec Arnaud ?

    Sinon pour le WMI ils en parlent vite fait dans le dernier MISC, ça à l’air intéressant !

    See ya

  4. merci

  5. salut,
    Ton blog est très bien. J’ai créé un lien vers lui sur le mien. J’apprécierais que tu en fasses autant si tu considérais que cela vaut la peine: http://infond.blogspot.com
    Au plaisir de te rencontrer. ++
    t0ka7a

  6. […] 0vercl0k’s w0rld. This entry was posted in Code and tagged another, communicate, rootkit. Bookmark the permalink. ← probleme handshake […]

  7. Wow. its amazing in my opinion dude . Good luck and keep working .ok

Get Adobe Flash player