Windows fonctionne alternativement dans deux modes: utilisateur (ou Ring3, ou userLand) et noyau (ou Ring0, ou kernelLand). Les rootkits sont des programmes malicieux fonctionnant en mode noyau. Ce sont donc des pilotes (ou drivers). Leur code source est placé dans un fichier .SYS, structuré au format Portable Exécutable (PE), comme tout fichier .EXE de windows. Cependant, le chargement d'un pilote est différent de celui d'un programme en mode utilisateur. Il utilise un service pour être lancé. Un service est un programme spécial qui s'exécute en tâche de fond [un daemon, dans le langage Unix]. Pour coder un rootkit, il est donc nécessaire de mieux comprendre le fonctionnement des services et des pilotes sous Windows. C'est l'objectif de ce tutoriel.
Plan
Outils
1 Création du pilote
2 Installation et load du pilote
3 Analyse du Service Control Program (Loaddrv)
4 Analyse du pilote
Conclusion
Références
Annexe 1:code source de Loaddrv
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
cf annexe 1 pour le code source. 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). Nous verrons ultérieurement de quoi il s'agit. Pour l'utiliser:
Créez les fichiers loaddrv.c et loaddrv.h, copiez-y le code source fourni,
compilez: C:> gcc -o loaddrv loaddrv.c
- Windbg et les Windows symbol packages:
Windbg est l'outil de débugging fourni par Microsoft. Il permet une analyse du kernel. Le logiciel et ses compléments sont téléchargeables sur le site de Microsoft: http://www.microsoft.com/whdc/devtools/debugging/default.mspx (référence n°7).
Windows symbol packages: Ces symboles sont nécessaires pour fournir les structures de données de Windows à Windbg. Il existe un fichier (environ 200 Mo, allez prendre un café pendant le téléchargement...) pour chaque version de Windows. Dans cet article, nous utilisons la version Windows XP SP3 32 bits. Ils se téléchargent sur le même site que Windbg. Le répertoire d'installation par défaut est C:\Windows\Symbols. Dans Windbg, tapez CTRL-K pour ouvrir le mode "Kernel Debug", choisissez l'onglet "Local", puis OK. Tapez CTRL-S qui ouvre la fenêtre "Symbol File Path", entrez le chemin vers le "downstream store"
SRV*C:\Windows\Symbols*http://msdl.microsoft.com/download/symbols , cochez la case "Reload" puis OK.
- regedit
Logiciel fourni avec Windows. Il offre une interface graphique pour consulter et modifier la base de registres. Pour y accéder, tapez regedit dans un terminal.
Dans l'exemple suivant, vous allez installer sur votre machine un pilote.
- copiez-y loaddrv.exe,
- Créez un fichier exemple1.c et copiez y:
//exemple1.c
#include "ntddk.h"
VOID Unload ( IN PDRIVER_OBJECT DriverObject)
{
DbgPrint("fonction Unload appelée");
}
NTSTATUS DriverEntry( IN PDRIVER_OBJECT DriverObject, IN PUNICODE_STRING RegistryPath)
{
DbgPrint("HelloWorld!");
DriverObject ->DriverUnload = Unload;
return STATUS_SUCCESS;
}
- créez un fichier SOURCES et copiez y:
TARGETNAME=MONPILOTE
TARGETPATH=OBJ
TARGETTYPE=DRIVER
SOURCES=exemple1.c
- créer un fichier MAKEFILE et copiez-y:
- lancez l'environnement checked-build du WDK, et placez vous dans le répertoire C:\MonPilote. Lancez la commande build.
C:\MonPilote>build
BUILD: Compile and Link for x86
BUILD: Loading c:\winddk\7600.16385.0\build.dat...
BUILD: Computing Include file dependencies:
BUILD: Start time: Tue Sep 29 11:01:35 2009
BUILD: Examining c:\monpilote directory for files to compile.
BUILD: Saving c:\winddk\7600.16385.0\build.dat...
BUILD: Compiling and Linking c:\monpilote directory
Configuring OACR for 'root:x86chk' -
_NT_TARGET_VERSION SET TO WINXP
Compiling - exemple1.c
Linking Executable - objchk_wxp_x86\i386\monpilote.sys
BUILD: Finish time: Tue Sep 29 11:01:39 2009
BUILD: Done
3 files compiled - 6 LPS
1 executable built
C:\MonPilote>
- lancez DebugView. Activez le KernelCapture (ctrl-K).
- placez vous dans C:\MonPilote\objchk_wxp_x86\i386 puis loadez le pilote créé:
C:\MonPilote\objchk_wxp_x86\i386> loaddrv install MONPILOTE
C:\MonPilote\objchk_wxp_x86\i386> loaddrv start MONPILOTE
- retournez dans DebugView. Vous pouvez constater que le pilote a bien été lancé:
- Une autre méthode pour contrôler que votre pilote a bien été lancé: lancez le Microsoft Auto Code Review (OACR) depuis la barre de tâches Windows:
- pour l'unloader:
C:\MonPilote\objchk_wxp_x86\i386> loaddrv.exe stop MONPILOTE
C:\MonPilote\objchk_wxp_x86\i386> loaddrv.exe remove MONPILOTE
- dans DebugView: vous remarquez que la fonction Unload a bien été appelée.
Quelques explications s'imposent...
Que se passe-t-il quand le pilote est installé et loadé? Un pilote fonctionne au sein d'un service.C'est ce que nous allons étudier dans cette partie, grâce au code source de l'outil fourni par Paula Tomlinson (référence 3, code source dans l'annexe 1).
31 Lorsque vous lancez C:>loaddrv install MonPilote, le programme LoadDrv fait successivement:
* OpenSCManager Cette fonction renvoie un pointeur vers le Service control manager de votre OS (cf ci-après. cf également MSDN référence 5).
* CreateService (cf MSDN, référence 7) Cette fonction crée un nouvel objet service. Elle ajoute les données le concernant à la SCM database (cf ci-après) et renvoie un pointeur vers ce service. Elle ajoute une clé avec le nom du service sous la clé de registre HKEY_LOCAL_MACHINE\System\CurrentControlSet\Services.
Ici, le service créé est du type SERVICE_KERNEL_DRIVER, et sera lancé à la demande (SERVICE_DEMAND_START).
* CloseServiceHandle (cf MSDN, référence 9) Cette fonction clôture le pointeur vers le service. Elle ne supprime pas le service de la base des services.
Qu'est ce que le Service control manager (SCM)? (cf MSDN, référence 6)
Le SCM est lancé durant le boot de Windows. Il:
- maintient la base des services installés,
- démarre les services et les driver services au lancement de Windows ou à la demande,
- énumère les services et les driver services installés,
- maintient les informations sur l'état des services et des driver services actifs,
- transmet les requêtes de contrôle au services actifs,
- verrouille et déverrouille la base des services.
Qu'est ce que la SCM database? (cf MSDN, référence 8)
La SCM database, ou ServicesActive database, est une base de données utilisée par le SCM et les programmes qui ajoutent, modifient ou configurent des services. La clé de registre de cette base est: HKEY_LOCAL_MACHINE\SYSTEM\CurrentControlSet\Services. Cette dernière possède une sous-clé pour chaque service, et driver service installé. La base contient le :
- service type: s'il possède son propre processus, ou s'il partage un processus avec d'autres services, s'il s'agit d'un kernel driver ou d'un file système driver,
- start type: automatiquement au démarrage, ou à la demande,
- error control level,
- le chemin complet du fichier .EXE ou .SYS,
- optional dependecy informations,
- optional account et password,
- optional driver object name utilisé par le système d'entrées sorties (I/O) pour loader le driver
Qu'est ce qu'un Service Control Program (SCP)?
Un Service Control Program est un programme capable d'interragir avec le SCM. Il peut lui envoyer des requêtes, lui demander la liste des services activés ou installés, obtenir des précisions sur un service. Loaddrv est un exemple de SCP.
32 Lorsque vous lancez C:>loaddrv start MonPilote, le programme LoadDrv fait successivement:
* OpenService Cette fonction permet d'obtenir du SCM un pointeur vers le service. (cf MSDN, référence 10)
* StartService Cette fonction envoie une demande de démarrage du service au SCM.
* CloseServiceHandle
La documentation fournie par MSDN sur la fonction StartService (référence n°12 et référence n°13) concerne les services en mode user, et non les services de Kernel (SERVICE_KERNEL_DRIVER). Pour davantage d'informations sur les services userland, voir remarque 2 ci-après.
Dans notre cas, à l'appel de StartService, le SCM:
- lance la fonction DriverEntry,
- attend le retour STATUS_SUCCESS de cette fonction,
- met à jour la SCM database en indiquant que le service est actif,
- rend la main à la fonction StartService.
remarque 1: Le retour STATUS_SUCCESS est bien implémenté dans notre pilote:
NTSTATUS DriverEntry( IN PDRIVER_OBJECT DriverObject, IN PUNICODE_STRING RegistryPath)
{
DbgPrint("HelloWorld!");
DriverObject ->DriverUnload = Unload;
return STATUS_SUCCESS;
},
Remarque 2: Le fonctionnement de StartService est différent dans le cas d'un service en mode user. Les explications qui suivent ne concernent donc pas notre pilote :
Les services ne se limitent pas à lancer des programmes en mode Kernel. Ils permettent également de lancer des programmes en mode User. La documentation MSDN est détaillée afin de permettre aux développeurs de coder ce type de service. Dans ce cas, à l'appel de StartService, le SCM:
- lock la SCM database,
- récupère dans le registre le chemin du fichier .SYS (ou .EXE),
- démarre un nouveau processus, et attend la réponse SERVICE_RUNNING de l'application lancée.
Pour générer cette réponse, l'exécutable doit contenir dans son code:
- StartServiceCtrlDispatcher (cf MSDN, référence 12). Cette fonction connecte le thread principal de l'application au SCM. Ce thread devient alors le control dispacher thread pour le processus appelant. Elle admet en paramètre un pointeur vers un tableau de structures SERVICE_TABLE_ENTRY. Ainsi, un même processus peut contenir plusieurs thread, chacun lançant un service différent. Dans ce tableau est placé le pointeur vers la fonction ServiceMain de chaque service concerné.
Lorsque le SCM envoie une demande d'activation de service, le control dispacher thread crée un nouveau thread et lance la fonction ServiceMain du service concerné.
- ServiceMain. Cette fonction est le point d'entrée du service. Elle lance les fonctions suivantes.
- RegisterServiceCtrlHandlerEx. Cette fonction permet de définir une fonction HandlerEx (MSDN, référence 13)http://msdn.microsoft.com/en-us/library/ms683241%28VS.85%29.aspx, utilisée par le SCM et le service pour communiquer, via des controlrequests.
- SetServiceStatus. Cette fonction transmet la réponse SERVICE_RUNNING au SCM.
En l'absence de cette réponse, au bout de 80 secondes, le SCM log un événement et stoppe le service.
33 Lorsque vous lancez C:>loaddrv stop MonPilote, le programme LoadDrv fait successivement:
* OpenService,
* ControlService Cette fonction précise au SCM qu'il doit stopper le service.
* CloseServiceHandle.
Remarque 1:
Vous pouvez supprimer un service qui n'a pas été stoppé. Dans ce cas, il est marqué comme non installé et démarré (!) dans la SCM database. Vous ne pourrez alors le réinstaller que lorsque vous l'aurez stoppé.
Remarque2: (cf MSDN, référence 11)
Dans le cas des services de programmes, la fonction ControlService envoie une controlrequest, ici SERVICE_CONTROL_STOP, à la fonction HandlerEx du service.
34 Lorsque vous lancez C:>loaddrv remove MonPilote, le programme LoadDrv fait successivement:
* OpenService,
* DeleteService (cf MSDN, référence 15) Cette fonction supprime le service de la SCM database, ainsi que les clés et sous clés du service dans le registre,
* CloseServiceHandle. Finalise la suppression du service.
35 Lorsque vous lancez C:>loaddrv status MonPilote, le programme LoadDrv fait successivement:
* OpenService,
* (LPQUERY_SERVICE_CONFIG) LocalAlloc. Cette fonction alloue un espace pour recevoir la structure QUERY_SERVICE_CONFIG contenant les informations sur le service,
* QueryServiceConfig (MSDN, référence 16). Cette fonction récupère les informations de configuration du service et les copie dans l'espace précédemment alloué,
* printf. Affiche les données,
* LocalFree. Cette fonction libère l'espace mémoire alloué à la structure,
* CloseServiceHandle.
Voici la structure concernée:
typedef struct _QUERY_SERVICE_CONFIG {
DWORD dwServiceType;
DWORD dwStartType;
DWORD dwErrorControl;
LPTSTR lpBinaryPathName;
LPTSTR lpLoadOrderGroup;
DWORD dwTagId;
LPTSTR lpDependencies;
LPTSTR lpServiceStartName;
LPTSTR lpDisplayName;
}QUERY_SERVICE_CONFIG, *LPQUERY_SERVICE_CONFIG;
A présent, intéressons nous à nouveau au code source de notre pilote.
Rappel: voici le code de notre pilote:
#include "ntddk.h"
VOID Unload ( IN PDRIVER_OBJECT DriverObject)
{ DbgPrint("fonction Unload appelée");}
NTSTATUS DriverEntry( IN PDRIVER_OBJECT DriverObject, IN PUNICODE_STRING RegistryPath)
{ DbgPrint("HelloWorld!");
DriverObject ->DriverUnload = Unload;
return STATUS_SUCCESS;}
41 Unload
VOID Unload ( IN PDRIVER_OBJECT DriverObject)
Il est nécessaire d'intégrer une fonction de déchargement dans le pilote, sans quoi, notre service restera actif et nous ne pourrons pas le recharger. C'est le rôle de la fonction Unload. (cf MSDN référence 4), que nous intégrerons par la suite à notre pilote grâce à la structure déclarée PDRIVER_OBJECT.
42 DbgPrint
DbgPrint est une fonction située dans le module ntdll, accessible grace au fichier header ntddk.h . Elle permet de renvoyer des informations sur la sortie débuggage du Kernel. Cette sortie peut être lue grâce à DebugView.
43 PDRIVER_OBJECT
Voici la structure PDRIVER_OBJECT obtenue avec Windbg:
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
Cette structure permet au SCM d'accéder aux différentes fonctions qui permettent une interaction du pilote avec le système. Par exemple, nous retrouvons la fonction DriverUnload, qui interragit avec le SCM pour unloader le pilote. Autre exemple, les MajorFunction permettent d'utiliser les I/O Request Packets (IRP), nécessaires au pilote pour interragir avec le mode utilisateur.
44 PUNICODE_STRING RegistryPath
PUNICODE_STRING donne accès à un buffer de données. Il contient l'adresse de la chaîne de caractères indiquant un chemin dans le registre Windows. En effet, voici sa structure, obtenue avec WinDbg:
lkd> dt nt!_UNICODE_STRING
+0x000 Length : Uint2B
+0x002 MaximumLength : Uint2B
+0x004 Buffer : Ptr32 Uint2B
Notre pilote apparait donc dans le registre Windows. En effet, lancez une recherche MONPILOTE dans regedit
45 NTSTATUS DriverEntry
Il s'agit du point d'entrée de notre pilote. Au chargement, le SCM appelle cette fonction. C'est comme le main() des programmes en mode utilisateur.
46 DriverObject ->DriverUnload = Unload
Cette ligne de programme fait pointer le Ptr32 du DriverUnload du PDRIVER_OBJECT de notre pilote vers la fonction Unload que nous avons précédemment déclarée. Après cela:
adresse pointée par DriverUnload = adresse de la fonction Unload.
47 return STATUS_SUCCESS
Nous avons déjà vu précédemment que la fonction StartService attend le retour de la fonction DriverEntry avant de continuer. STATUS_SUCCESS indique donc à StartService que le pilote a bien été chargé.
Dans ce tutoriel, vous avez codé votre premier pilote et comprenez mieux le fonctionnement des services Windows.
Pour un exemple complet de l'utilisation des services, vous pouvez consulter l'article de Yevgeny Menaker (référence 14). Vous pouvez également consulter l'excellent tutoriel sur les services et les pilotes de four-f, traduit en francais (référence 17).
Les pilotes ont l'avantage de bénéficier de droits illimités, puiqu'ils tournent en Ring0. Cependant, ils n'ont pas facilement accès aux bibliothèques de fonctions disponibles en mode utilisateur. Il est donc souvent opportun, dans un rootkit, de coder une partie fonctionnant dans un processus en mode utilisateur, et une autre partie fonctionnant en Ring0. Par la suite, nous pourrons alors étudier comment un programme en mode utilisateur communique avec un pilote grace aux requêtes d'entrée/sortie I/O Request Packet (IRP).
1) livre de Greg Hoglund et James Butler: Rootkits, Infiltrations du noyau Windows. Edition CampusPress
2) MSDN - Using Function typedefs or Function Role Types in C++ Driver Code to Improve PREfast Results - http://msdn.microsoft.com/en-us/library/ee206394.aspx
3) LoadDrv - Paula Tomlinson, modifié par Chris Lietchi - http://www.koders.com/c/fid3AA0382374728CEFBC5D0A03128C9DF9404DAC92.aspx
4) MSDN - Unloading a Callout Driver - http://msdn.microsoft.com/en-us/library/ms796277.aspx
5) MSDN - OpenSCManager Function - http://msdn.microsoft.com/en-us/library/ms684323%28VS.85%29.aspx
6) MSDN - Service Control Manager - http://msdn.microsoft.com/en-us/library/ms685150%28VS.85%29.aspx
7) MSDN - CreateService Function - http://msdn.microsoft.com/en-us/library/ms682450%28VS.85%29.aspx
8) MSDN- Database of Installed Services - http://msdn.microsoft.com/en-us/library/ms682544%28VS.85%29.aspx
9) MSDN - CloseServiceHandle Function - http://msdn.microsoft.com/en-us/library/ms682028%28VS.85%29.aspx
10) MSDN - Service Startup - http://msdn.microsoft.com/en-us/library/ms685990%28VS.85%29.aspx
11) MSDN - Service Control Requests - http://msdn.microsoft.com/en-us/library/ms685153%28VS.85%29.aspx
12) MSDN - ServiceMain Callback Function - http://msdn.microsoft.com/en-us/library/ms685138%28VS.85%29.aspx
13) MSDN - HandlerEx Callback Function - http://msdn.microsoft.com/en-us/library/ms683241%28VS.85%29.aspx
14) article de Yevgeny Menaker - Five Steps to Writing Windows Services in C - http://www.devx.com/cplus/Article/9857/1954
15) MSDN - DeleteService Function - http://msdn.microsoft.com/en-us/library/ms682562%28VS.85%29.aspx
16) MSDN - QueryServiceConfig Function - http://msdn.microsoft.com/en-us/library/ms684932%28VS.85%29.aspx
17) tutoriel - four-f (traduit par Neitsa) - tutoriel sur les services - http://neitsabes.online.fr/ASM/KMD/kmd02.html#k2d3d3
articlé rédigé par t0ka7a - c2009
// based on Paula Tomlinson's LOADDRV program.
// She describes it in her May 1995 article in Windows/DOS Developer's
// Journal (now Windows Developer's Journal).
// Modified by Chris Liechti < cliechti@gmx.net>
// I removed the old/ugly dialog, it now accepts command line options and
// prints error messages with textual description from the OS.
#include < windows.h>
#include < stdio.h>
#include < stdlib.h>
#include < string.h>
#include < unistd.h>
#include "loaddrv.h"
// globals
SC_HANDLE hSCMan = NULL;
//get ext messages for windows error codes:
void DisplayErrorText(DWORD dwLastError) {
LPSTR MessageBuffer;
DWORD dwBufferLength;
DWORD dwFormatFlags = FORMAT_MESSAGE_ALLOCATE_BUFFER |
FORMAT_MESSAGE_IGNORE_INSERTS |
FORMAT_MESSAGE_FROM_SYSTEM;
dwBufferLength = FormatMessageA(
dwFormatFlags,
NULL, // module to get message from (NULL == system)
dwLastError,
MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT), // default language
(LPSTR) &MessageBuffer,
0,
NULL
);
if (dwBufferLength) {
// Output message
puts(MessageBuffer);
// Free the buffer allocated by the system.
LocalFree(MessageBuffer);
}
}
int exists(char *filename) {
FILE * pFile;
pFile = fopen(filename, "r");
return pFile != NULL;
}
void usage(void) {
printf("USGAE: loaddrv command drivername [args...]\n\n"
"NT/2k/XP Driver and Service modification tool.\n"
"(C)2002 Chris Liechti < cliechti@gmx.net>\n\n"
"Suported commands:\n\n"
" install [fullpathforinstall]\n"
" Install new service. Loaded from given path. If path is not present,\n"
" the local directory is searched for a .sys file. If the service\n"
" already exists, it must be removed first.\n"
" start\n"
" Start service. It must be installed in advance.\n"
" stop\n"
" Stop service.\n"
" remove\n"
" Remove service. It must be stopped in advance.\n"
" status\n"
" Show status information about service.\n"
" starttype auto|manual|system|disable\n"
" Change startup type to the given type.\n"
);
}
int main(int argc, char *argv[]) {
DWORD status = 0;
int level = 0;
if (argc < 3) {
usage();
exit(1);
}
LoadDriverInit();
if (strcmp(argv[1], "start") == 0) {
printf("starting %s... ", argv[2]);
status = DriverStart(argv[2]);
if ( status != OKAY) {
printf("start failed (status %ld):\n", status);
level = 1;
} else {
printf("ok.\n");
}
} else if (strcmp(argv[1], "stop") == 0) {
printf("stoping %s... ", argv[2]);
status = DriverStop(argv[2]);
if ( status != OKAY) {
printf("stop failed (status %ld):\n", status);
level = 1;
} else {
printf("ok.\n");
}
} else if (strcmp(argv[1], "install") == 0) {
char path[MAX_PATH*2];
if (argc< 4) {
char cwd[MAX_PATH];
getcwd(cwd, sizeof cwd);
sprintf(path, "%s\\%s.sys", cwd, argv[2]);
} else {
strncpy(path, argv[3], MAX_PATH);
}
if (exists(path)) {
printf("installing %s from %s... ", argv[2], path);
status = DriverInstall(path, argv[2]);
if ( status != OKAY) {
printf("install failed (status %ld):\n", status);
level = 2;
} else {
printf("ok.\n");
}
} else {
printf("install failed, file not found: %s\n", path);
level = 1;
}
} else if (strcmp(argv[1], "remove") == 0) {
printf("removing %s... ", argv[2]);
status = DriverRemove(argv[2]);
if ( status != OKAY) {
printf("remove failed (status %ld):\n", status);
level = 1;
} else {
printf("ok.\n");
}
} else if (strcmp(argv[1], "status") == 0) {
printf("status of %s:\n", argv[2]);
status = DriverStatus(argv[2]);
if ( status != OKAY) {
printf("stat failed (status %ld):\n", status);
level = 1;
} else {
printf("ok.\n");
}
} else if (strcmp(argv[1], "starttype") == 0) {
if (argc < 4) {
printf("Error: need start type (string) as argument.\n");
level = 2;
} else {
DWORD type = 0;
printf("set start type of %s to %s... ", argv[2], argv[3]);
if (strcmp(argv[1], "boot") == 0) {
type = SERVICE_BOOT_START;
} else if (strcmp(argv[3], "system") == 0) {
type = SERVICE_SYSTEM_START;
} else if (strcmp(argv[3], "auto") == 0) {
type = SERVICE_AUTO_START;
} else if (strcmp(argv[3], "manual") == 0) {
type = SERVICE_DEMAND_START;
} else if (strcmp(argv[3], "disabled") == 0) {
type = SERVICE_DISABLED;
} else {
printf("unknown type\n");
level = 1;
}
if (level == 0) {
status = DriverStartType(argv[2], type);
if ( status != OKAY) {
printf("set start type failed (status %ld):\n", status);
level = 1;
} else {
printf("ok.\n");
}
}
}
} else {
usage();
level = 1;
}
if (status) DisplayErrorText(status);
LoadDriverCleanup();
exit(level);
return 0;
}
DWORD LoadDriverInit(void) {
// connect to local service control manager
if ((hSCMan = OpenSCManager(NULL, NULL,
SC_MANAGER_ALL_ACCESS)) == NULL) {
return -1;
}
return OKAY;
}
void LoadDriverCleanup(void) {
if (hSCMan != NULL) CloseServiceHandle(hSCMan);
}
/**-----------------------------------------------------**/
DWORD DriverInstall(LPSTR lpPath, LPSTR lpDriver) {
BOOL dwStatus = OKAY;
SC_HANDLE hService = NULL;
// add to service control manager's database
if ((hService = CreateService(hSCMan, lpDriver,
lpDriver, SERVICE_ALL_ACCESS, SERVICE_KERNEL_DRIVER,
SERVICE_DEMAND_START, SERVICE_ERROR_NORMAL, lpPath,
NULL, NULL, NULL, NULL, NULL)) == NULL)
dwStatus = GetLastError();
else CloseServiceHandle(hService);
return dwStatus;
} // DriverInstall
/**-----------------------------------------------------**/
DWORD DriverStart(LPSTR lpDriver) {
BOOL dwStatus = OKAY;
SC_HANDLE hService = NULL;
// get a handle to the service
if ((hService = OpenService(hSCMan, lpDriver,
SERVICE_ALL_ACCESS)) != NULL)
{
// start the driver
if (!StartService(hService, 0, NULL))
dwStatus = GetLastError();
} else dwStatus = GetLastError();
if (hService != NULL) CloseServiceHandle(hService);
return dwStatus;
} // DriverStart
/**-----------------------------------------------------**/
DWORD DriverStop(LPSTR lpDriver)
{
BOOL dwStatus = OKAY;
SC_HANDLE hService = NULL;
SERVICE_STATUS serviceStatus;
// get a handle to the service
if ((hService = OpenService(hSCMan, lpDriver,
SERVICE_ALL_ACCESS)) != NULL)
{
// stop the driver
if (!ControlService(hService, SERVICE_CONTROL_STOP,
&serviceStatus))
dwStatus = GetLastError();
} else dwStatus = GetLastError();
if (hService != NULL) CloseServiceHandle(hService);
return dwStatus;
} // DriverStop
/**-----------------------------------------------------**/
DWORD DriverRemove(LPSTR lpDriver)
{
BOOL dwStatus = OKAY;
SC_HANDLE hService = NULL;
// get a handle to the service
if ((hService = OpenService(hSCMan, lpDriver,
SERVICE_ALL_ACCESS)) != NULL)
{ // remove the driver
if (!DeleteService(hService))
dwStatus = GetLastError();
} else dwStatus = GetLastError();
if (hService != NULL) CloseServiceHandle(hService);
return dwStatus;
} // DriverRemove
/**-----------------------------------------------------**/
////extensions by Lch
/**-----------------------------------------------------**/
DWORD DriverStatus(LPSTR lpDriver) {
BOOL dwStatus = OKAY;
SC_HANDLE hService = NULL;
DWORD dwBytesNeeded;
// get a handle to the service
if ((hService = OpenService(hSCMan, lpDriver,
SERVICE_ALL_ACCESS)) != NULL)
{
LPQUERY_SERVICE_CONFIG lpqscBuf;
//~ LPSERVICE_DESCRIPTION lpqscBuf2;
// Allocate a buffer for the configuration information.
if ((lpqscBuf = (LPQUERY_SERVICE_CONFIG) LocalAlloc(
LPTR, 4096)) != NULL)
{
//~ if ((lpqscBuf2 = (LPSERVICE_DESCRIPTION) LocalAlloc(
//~ LPTR, 4096)) != NULL)
{
// Get the configuration information.
if (QueryServiceConfig(
hService,
lpqscBuf,
4096,
&dwBytesNeeded) //&&
//~ QueryServiceConfig2(
//~ hService,
//~ SERVICE_CONFIG_DESCRIPTION,
//~ lpqscBuf2,
//~ 4096,
//~ &dwBytesNeeded
)
{
// Print the configuration information.
printf("Type: [0x%02lx] ", lpqscBuf->dwServiceType);
switch (lpqscBuf->dwServiceType) {
case SERVICE_WIN32_OWN_PROCESS:
printf("The service runs in its own process.");
break;
case SERVICE_WIN32_SHARE_PROCESS:
printf("The service shares a process with other services.");
break;
case SERVICE_KERNEL_DRIVER:
printf("Kernel driver.");
break;
case SERVICE_FILE_SYSTEM_DRIVER:
printf("File system driver.");
break;
case SERVICE_INTERACTIVE_PROCESS:
printf("The service can interact with the desktop.");
break;
default:
printf("Unknown type.");
}
printf("\nStart Type: [0x%02lx] ", lpqscBuf->dwStartType);
switch (lpqscBuf->dwStartType) {
case SERVICE_BOOT_START:
printf("Boot");
break;
case SERVICE_SYSTEM_START:
printf("System");
break;
case SERVICE_AUTO_START:
printf("Automatic");
break;
case SERVICE_DEMAND_START:
printf("Manual");
break;
case SERVICE_DISABLED:
printf("Disabled");
break;
default:
printf("Unknown.");
}
printf("\nError Control: [0x%02lx] ", lpqscBuf->dwErrorControl);
switch (lpqscBuf->dwErrorControl) {
case SERVICE_ERROR_IGNORE:
printf("IGNORE: Ignore.");
break;
case SERVICE_ERROR_NORMAL:
printf("NORMAL: Display a message box.");
break;
case SERVICE_ERROR_SEVERE:
printf("SEVERE: Restart with last-known-good config.");
break;
case SERVICE_ERROR_CRITICAL:
printf("CRITICAL: Restart w/ last-known-good config.");
break;
default:
printf("Unknown.");
}
printf("\nBinary path: %s\n", lpqscBuf->lpBinaryPathName);
if (lpqscBuf->lpLoadOrderGroup != NULL)
printf("Load order grp: %s\n", lpqscBuf->lpLoadOrderGroup);
if (lpqscBuf->dwTagId != 0)
printf("Tag ID: %ld\n", lpqscBuf->dwTagId);
if (lpqscBuf->lpDependencies != NULL)
printf("Dependencies: %s\n", lpqscBuf->lpDependencies);
if (lpqscBuf->lpServiceStartName != NULL)
printf("Start Name: %s\n", lpqscBuf->lpServiceStartName);
//~ if (lpqscBuf2->lpDescription != NULL)
//~ printf("Description: %s\n", lpqscBuf2->lpDescription);
}
//~ LocalFree(lpqscBuf2);
}
LocalFree(lpqscBuf);
} else {
dwStatus = GetLastError();
}
} else {
dwStatus = GetLastError();
}
if (hService != NULL) CloseServiceHandle(hService);
return dwStatus;
} // DriverStatus
/**-----------------------------------------------------**/
DWORD DriverStartType(LPSTR lpDriver, DWORD dwStartType) {
BOOL dwStatus = OKAY;
SC_HANDLE hService = NULL;
SC_LOCK sclLock;
LPQUERY_SERVICE_LOCK_STATUS lpqslsBuf;
DWORD dwBytesNeeded;
// Need to acquire database lock before reconfiguring.
sclLock = LockServiceDatabase(hSCMan);
// If the database cannot be locked, report the details.
if (sclLock == NULL) {
// Exit if the database is not locked by another process.
if (GetLastError() == ERROR_SERVICE_DATABASE_LOCKED) {
// Allocate a buffer to get details about the lock.
lpqslsBuf = (LPQUERY_SERVICE_LOCK_STATUS) LocalAlloc(
LPTR, sizeof(QUERY_SERVICE_LOCK_STATUS)+256);
if (lpqslsBuf != NULL) {
// Get and print the lock status information.
if (QueryServiceLockStatus(
hSCMan,
lpqslsBuf,
sizeof(QUERY_SERVICE_LOCK_STATUS)+256,
&dwBytesNeeded) )
{
if (lpqslsBuf->fIsLocked) {
printf("Locked by: %s, duration: %ld seconds\n",
lpqslsBuf->lpLockOwner,
lpqslsBuf->dwLockDuration
);
} else {
printf("No longer locked\n");
}
}
LocalFree(lpqslsBuf);
}
}
dwStatus = GetLastError();
} else {
// The database is locked, so it is safe to make changes.
// Open a handle to the service.
hService = OpenService(
hSCMan, // SCManager database
lpDriver, // name of service
SERVICE_CHANGE_CONFIG
); // need CHANGE access
if (hService != NULL) {
// Make the changes.
if (!ChangeServiceConfig(
hService, // handle of service
SERVICE_NO_CHANGE, // service type: no change
dwStartType, // change service start type
SERVICE_NO_CHANGE, // error control: no change
NULL, // binary path: no change
NULL, // load order group: no change
NULL, // tag ID: no change
NULL, // dependencies: no change
NULL, // account name: no change
NULL, // password: no change
NULL) ) // display name: no change
{
dwStatus = GetLastError();
}
}
// Release the database lock.
UnlockServiceDatabase(sclLock);
}
if (hService != NULL) CloseServiceHandle(hService);
return dwStatus;
} // DriverStartType
- plan
Plan
Outils
1 Création du pilote
2 Installation et load du pilote
3 Analyse du Service Control Program (Loaddrv)
4 Analyse du pilote
Conclusion
Références
Annexe 1:code source de Loaddrv
- Outils
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
cf annexe 1 pour le code source. 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). Nous verrons ultérieurement de quoi il s'agit. Pour l'utiliser:
Créez les fichiers loaddrv.c et loaddrv.h, copiez-y le code source fourni,
compilez: C:> gcc -o loaddrv loaddrv.c
- Windbg et les Windows symbol packages:
Windbg est l'outil de débugging fourni par Microsoft. Il permet une analyse du kernel. Le logiciel et ses compléments sont téléchargeables sur le site de Microsoft: http://www.microsoft.com/whdc/devtools/debugging/default.mspx (référence n°7).
Windows symbol packages: Ces symboles sont nécessaires pour fournir les structures de données de Windows à Windbg. Il existe un fichier (environ 200 Mo, allez prendre un café pendant le téléchargement...) pour chaque version de Windows. Dans cet article, nous utilisons la version Windows XP SP3 32 bits. Ils se téléchargent sur le même site que Windbg. Le répertoire d'installation par défaut est C:\Windows\Symbols. Dans Windbg, tapez CTRL-K pour ouvrir le mode "Kernel Debug", choisissez l'onglet "Local", puis OK. Tapez CTRL-S qui ouvre la fenêtre "Symbol File Path", entrez le chemin vers le "downstream store"
SRV*C:\Windows\Symbols*http://msdl.microsoft.com/download/symbols , cochez la case "Reload" puis OK.
- regedit
Logiciel fourni avec Windows. Il offre une interface graphique pour consulter et modifier la base de registres. Pour y accéder, tapez regedit dans un terminal.
Dans l'exemple suivant, vous allez installer sur votre machine un pilote.
- 1 création du pilote
- copiez-y loaddrv.exe,
- Créez un fichier exemple1.c et copiez y:
//exemple1.c
#include "ntddk.h"
VOID Unload ( IN PDRIVER_OBJECT DriverObject)
{
DbgPrint("fonction Unload appelée");
}
NTSTATUS DriverEntry( IN PDRIVER_OBJECT DriverObject, IN PUNICODE_STRING RegistryPath)
{
DbgPrint("HelloWorld!");
DriverObject ->DriverUnload = Unload;
return STATUS_SUCCESS;
}
- créez un fichier SOURCES et copiez y:
TARGETNAME=MONPILOTE
TARGETPATH=OBJ
TARGETTYPE=DRIVER
SOURCES=exemple1.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:\MonPilote. Lancez la commande build.
C:\MonPilote>build
BUILD: Compile and Link for x86
BUILD: Loading c:\winddk\7600.16385.0\build.dat...
BUILD: Computing Include file dependencies:
BUILD: Start time: Tue Sep 29 11:01:35 2009
BUILD: Examining c:\monpilote directory for files to compile.
BUILD: Saving c:\winddk\7600.16385.0\build.dat...
BUILD: Compiling and Linking c:\monpilote directory
Configuring OACR for 'root:x86chk' -
_NT_TARGET_VERSION SET TO WINXP
Compiling - exemple1.c
Linking Executable - objchk_wxp_x86\i386\monpilote.sys
BUILD: Finish time: Tue Sep 29 11:01:39 2009
BUILD: Done
3 files compiled - 6 LPS
1 executable built
C:\MonPilote>
2 installation et load du pilote
- placez vous dans C:\MonPilote\objchk_wxp_x86\i386 puis loadez le pilote créé:
C:\MonPilote\objchk_wxp_x86\i386> loaddrv install MONPILOTE
C:\MonPilote\objchk_wxp_x86\i386> loaddrv start MONPILOTE
- retournez dans DebugView. Vous pouvez constater que le pilote a bien été lancé:
- pour l'unloader:
C:\MonPilote\objchk_wxp_x86\i386> loaddrv.exe remove MONPILOTE
- dans DebugView: vous remarquez que la fonction Unload a bien été appelée.
3 Analyse du Service Control Program (loaddrv)
31 Lorsque vous lancez C:>loaddrv install MonPilote, le programme LoadDrv fait successivement:
* OpenSCManager Cette fonction renvoie un pointeur vers le Service control manager de votre OS (cf ci-après. cf également MSDN référence 5).
* CreateService (cf MSDN, référence 7) Cette fonction crée un nouvel objet service. Elle ajoute les données le concernant à la SCM database (cf ci-après) et renvoie un pointeur vers ce service. Elle ajoute une clé avec le nom du service sous la clé de registre HKEY_LOCAL_MACHINE\System\CurrentControlSet\Services.
Ici, le service créé est du type SERVICE_KERNEL_DRIVER, et sera lancé à la demande (SERVICE_DEMAND_START).
* CloseServiceHandle (cf MSDN, référence 9) Cette fonction clôture le pointeur vers le service. Elle ne supprime pas le service de la base des services.
Qu'est ce que le Service control manager (SCM)? (cf MSDN, référence 6)
Le SCM est lancé durant le boot de Windows. Il:
- maintient la base des services installés,
- démarre les services et les driver services au lancement de Windows ou à la demande,
- énumère les services et les driver services installés,
- maintient les informations sur l'état des services et des driver services actifs,
- transmet les requêtes de contrôle au services actifs,
- verrouille et déverrouille la base des services.
Qu'est ce que la SCM database? (cf MSDN, référence 8)
La SCM database, ou ServicesActive database, est une base de données utilisée par le SCM et les programmes qui ajoutent, modifient ou configurent des services. La clé de registre de cette base est: HKEY_LOCAL_MACHINE\SYSTEM\CurrentControlSet\Services. Cette dernière possède une sous-clé pour chaque service, et driver service installé. La base contient le :
- service type: s'il possède son propre processus, ou s'il partage un processus avec d'autres services, s'il s'agit d'un kernel driver ou d'un file système driver,
- start type: automatiquement au démarrage, ou à la demande,
- error control level,
- le chemin complet du fichier .EXE ou .SYS,
- optional dependecy informations,
- optional account et password,
- optional driver object name utilisé par le système d'entrées sorties (I/O) pour loader le driver
Qu'est ce qu'un Service Control Program (SCP)?
Un Service Control Program est un programme capable d'interragir avec le SCM. Il peut lui envoyer des requêtes, lui demander la liste des services activés ou installés, obtenir des précisions sur un service. Loaddrv est un exemple de SCP.
32 Lorsque vous lancez C:>loaddrv start MonPilote, le programme LoadDrv fait successivement:
* OpenService Cette fonction permet d'obtenir du SCM un pointeur vers le service. (cf MSDN, référence 10)
* StartService Cette fonction envoie une demande de démarrage du service au SCM.
* CloseServiceHandle
La documentation fournie par MSDN sur la fonction StartService (référence n°12 et référence n°13) concerne les services en mode user, et non les services de Kernel (SERVICE_KERNEL_DRIVER). Pour davantage d'informations sur les services userland, voir remarque 2 ci-après.
Dans notre cas, à l'appel de StartService, le SCM:
- lance la fonction DriverEntry,
- attend le retour STATUS_SUCCESS de cette fonction,
- met à jour la SCM database en indiquant que le service est actif,
- rend la main à la fonction StartService.
remarque 1: Le retour STATUS_SUCCESS est bien implémenté dans notre pilote:
NTSTATUS DriverEntry( IN PDRIVER_OBJECT DriverObject, IN PUNICODE_STRING RegistryPath)
{
DbgPrint("HelloWorld!");
DriverObject ->DriverUnload = Unload;
return STATUS_SUCCESS;
},
Remarque 2: Le fonctionnement de StartService est différent dans le cas d'un service en mode user. Les explications qui suivent ne concernent donc pas notre pilote :
Les services ne se limitent pas à lancer des programmes en mode Kernel. Ils permettent également de lancer des programmes en mode User. La documentation MSDN est détaillée afin de permettre aux développeurs de coder ce type de service. Dans ce cas, à l'appel de StartService, le SCM:
- lock la SCM database,
- récupère dans le registre le chemin du fichier .SYS (ou .EXE),
- démarre un nouveau processus, et attend la réponse SERVICE_RUNNING de l'application lancée.
Pour générer cette réponse, l'exécutable doit contenir dans son code:
- StartServiceCtrlDispatcher (cf MSDN, référence 12). Cette fonction connecte le thread principal de l'application au SCM. Ce thread devient alors le control dispacher thread pour le processus appelant. Elle admet en paramètre un pointeur vers un tableau de structures SERVICE_TABLE_ENTRY. Ainsi, un même processus peut contenir plusieurs thread, chacun lançant un service différent. Dans ce tableau est placé le pointeur vers la fonction ServiceMain de chaque service concerné.
Lorsque le SCM envoie une demande d'activation de service, le control dispacher thread crée un nouveau thread et lance la fonction ServiceMain du service concerné.
- ServiceMain. Cette fonction est le point d'entrée du service. Elle lance les fonctions suivantes.
- RegisterServiceCtrlHandlerEx. Cette fonction permet de définir une fonction HandlerEx (MSDN, référence 13)http://msdn.microsoft.com/en-us/library/ms683241%28VS.85%29.aspx, utilisée par le SCM et le service pour communiquer, via des controlrequests.
- SetServiceStatus. Cette fonction transmet la réponse SERVICE_RUNNING au SCM.
En l'absence de cette réponse, au bout de 80 secondes, le SCM log un événement et stoppe le service.
33 Lorsque vous lancez C:>loaddrv stop MonPilote, le programme LoadDrv fait successivement:
* OpenService,
* ControlService Cette fonction précise au SCM qu'il doit stopper le service.
* CloseServiceHandle.
Remarque 1:
Vous pouvez supprimer un service qui n'a pas été stoppé. Dans ce cas, il est marqué comme non installé et démarré (!) dans la SCM database. Vous ne pourrez alors le réinstaller que lorsque vous l'aurez stoppé.
Remarque2: (cf MSDN, référence 11)
Dans le cas des services de programmes, la fonction ControlService envoie une controlrequest, ici SERVICE_CONTROL_STOP, à la fonction HandlerEx du service.
34 Lorsque vous lancez C:>loaddrv remove MonPilote, le programme LoadDrv fait successivement:
* OpenService,
* DeleteService (cf MSDN, référence 15) Cette fonction supprime le service de la SCM database, ainsi que les clés et sous clés du service dans le registre,
* CloseServiceHandle. Finalise la suppression du service.
35 Lorsque vous lancez C:>loaddrv status MonPilote, le programme LoadDrv fait successivement:
* OpenService,
* (LPQUERY_SERVICE_CONFIG) LocalAlloc. Cette fonction alloue un espace pour recevoir la structure QUERY_SERVICE_CONFIG contenant les informations sur le service,
* QueryServiceConfig (MSDN, référence 16). Cette fonction récupère les informations de configuration du service et les copie dans l'espace précédemment alloué,
* printf. Affiche les données,
* LocalFree. Cette fonction libère l'espace mémoire alloué à la structure,
* CloseServiceHandle.
Voici la structure concernée:
typedef struct _QUERY_SERVICE_CONFIG {
DWORD dwServiceType;
DWORD dwStartType;
DWORD dwErrorControl;
LPTSTR lpBinaryPathName;
LPTSTR lpLoadOrderGroup;
DWORD dwTagId;
LPTSTR lpDependencies;
LPTSTR lpServiceStartName;
LPTSTR lpDisplayName;
}QUERY_SERVICE_CONFIG, *LPQUERY_SERVICE_CONFIG;
A présent, intéressons nous à nouveau au code source de notre pilote.
4 Analyse du pilote
#include "ntddk.h"
VOID Unload ( IN PDRIVER_OBJECT DriverObject)
{ DbgPrint("fonction Unload appelée");}
NTSTATUS DriverEntry( IN PDRIVER_OBJECT DriverObject, IN PUNICODE_STRING RegistryPath)
{ DbgPrint("HelloWorld!");
DriverObject ->DriverUnload = Unload;
return STATUS_SUCCESS;}
41 Unload
VOID Unload ( IN PDRIVER_OBJECT DriverObject)
Il est nécessaire d'intégrer une fonction de déchargement dans le pilote, sans quoi, notre service restera actif et nous ne pourrons pas le recharger. C'est le rôle de la fonction Unload. (cf MSDN référence 4), que nous intégrerons par la suite à notre pilote grâce à la structure déclarée PDRIVER_OBJECT.
42 DbgPrint
DbgPrint est une fonction située dans le module ntdll, accessible grace au fichier header ntddk.h . Elle permet de renvoyer des informations sur la sortie débuggage du Kernel. Cette sortie peut être lue grâce à DebugView.
43 PDRIVER_OBJECT
Voici la structure PDRIVER_OBJECT obtenue avec Windbg:
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
Cette structure permet au SCM d'accéder aux différentes fonctions qui permettent une interaction du pilote avec le système. Par exemple, nous retrouvons la fonction DriverUnload, qui interragit avec le SCM pour unloader le pilote. Autre exemple, les MajorFunction permettent d'utiliser les I/O Request Packets (IRP), nécessaires au pilote pour interragir avec le mode utilisateur.
44 PUNICODE_STRING RegistryPath
PUNICODE_STRING donne accès à un buffer de données. Il contient l'adresse de la chaîne de caractères indiquant un chemin dans le registre Windows. En effet, voici sa structure, obtenue avec WinDbg:
lkd> dt nt!_UNICODE_STRING
+0x000 Length : Uint2B
+0x002 MaximumLength : Uint2B
+0x004 Buffer : Ptr32 Uint2B
Notre pilote apparait donc dans le registre Windows. En effet, lancez une recherche MONPILOTE dans regedit
Il s'agit du point d'entrée de notre pilote. Au chargement, le SCM appelle cette fonction. C'est comme le main() des programmes en mode utilisateur.
46 DriverObject ->DriverUnload = Unload
Cette ligne de programme fait pointer le Ptr32 du DriverUnload du PDRIVER_OBJECT de notre pilote vers la fonction Unload que nous avons précédemment déclarée. Après cela:
adresse pointée par DriverUnload = adresse de la fonction Unload.
47 return STATUS_SUCCESS
Nous avons déjà vu précédemment que la fonction StartService attend le retour de la fonction DriverEntry avant de continuer. STATUS_SUCCESS indique donc à StartService que le pilote a bien été chargé.
Conclusion
Pour un exemple complet de l'utilisation des services, vous pouvez consulter l'article de Yevgeny Menaker (référence 14). Vous pouvez également consulter l'excellent tutoriel sur les services et les pilotes de four-f, traduit en francais (référence 17).
Les pilotes ont l'avantage de bénéficier de droits illimités, puiqu'ils tournent en Ring0. Cependant, ils n'ont pas facilement accès aux bibliothèques de fonctions disponibles en mode utilisateur. Il est donc souvent opportun, dans un rootkit, de coder une partie fonctionnant dans un processus en mode utilisateur, et une autre partie fonctionnant en Ring0. Par la suite, nous pourrons alors étudier comment un programme en mode utilisateur communique avec un pilote grace aux requêtes d'entrée/sortie I/O Request Packet (IRP).
Références
1) livre de Greg Hoglund et James Butler: Rootkits, Infiltrations du noyau Windows. Edition CampusPress
2) MSDN - Using Function typedefs or Function Role Types in C++ Driver Code to Improve PREfast Results - http://msdn.microsoft.com/en-us/library/ee206394.aspx
3) LoadDrv - Paula Tomlinson, modifié par Chris Lietchi - http://www.koders.com/c/fid3AA0382374728CEFBC5D0A03128C9DF9404DAC92.aspx
4) MSDN - Unloading a Callout Driver - http://msdn.microsoft.com/en-us/library/ms796277.aspx
5) MSDN - OpenSCManager Function - http://msdn.microsoft.com/en-us/library/ms684323%28VS.85%29.aspx
6) MSDN - Service Control Manager - http://msdn.microsoft.com/en-us/library/ms685150%28VS.85%29.aspx
7) MSDN - CreateService Function - http://msdn.microsoft.com/en-us/library/ms682450%28VS.85%29.aspx
8) MSDN- Database of Installed Services - http://msdn.microsoft.com/en-us/library/ms682544%28VS.85%29.aspx
9) MSDN - CloseServiceHandle Function - http://msdn.microsoft.com/en-us/library/ms682028%28VS.85%29.aspx
10) MSDN - Service Startup - http://msdn.microsoft.com/en-us/library/ms685990%28VS.85%29.aspx
11) MSDN - Service Control Requests - http://msdn.microsoft.com/en-us/library/ms685153%28VS.85%29.aspx
12) MSDN - ServiceMain Callback Function - http://msdn.microsoft.com/en-us/library/ms685138%28VS.85%29.aspx
13) MSDN - HandlerEx Callback Function - http://msdn.microsoft.com/en-us/library/ms683241%28VS.85%29.aspx
14) article de Yevgeny Menaker - Five Steps to Writing Windows Services in C - http://www.devx.com/cplus/Article/9857/1954
15) MSDN - DeleteService Function - http://msdn.microsoft.com/en-us/library/ms682562%28VS.85%29.aspx
16) MSDN - QueryServiceConfig Function - http://msdn.microsoft.com/en-us/library/ms684932%28VS.85%29.aspx
17) tutoriel - four-f (traduit par Neitsa) - tutoriel sur les services - http://neitsabes.online.fr/ASM/KMD/kmd02.html#k2d3d3
Annexe 1: code source de LoadDrv (référence 3)
// based on Paula Tomlinson's LOADDRV program.
// She describes it in her May 1995 article in Windows/DOS Developer's
// Journal (now Windows Developer's Journal).
// Modified by Chris Liechti < cliechti@gmx.net>
// I removed the old/ugly dialog, it now accepts command line options and
// prints error messages with textual description from the OS.
#include < windows.h>
#include < stdio.h>
#include < stdlib.h>
#include < string.h>
#include < unistd.h>
#include "loaddrv.h"
// globals
SC_HANDLE hSCMan = NULL;
//get ext messages for windows error codes:
void DisplayErrorText(DWORD dwLastError) {
LPSTR MessageBuffer;
DWORD dwBufferLength;
DWORD dwFormatFlags = FORMAT_MESSAGE_ALLOCATE_BUFFER |
FORMAT_MESSAGE_IGNORE_INSERTS |
FORMAT_MESSAGE_FROM_SYSTEM;
dwBufferLength = FormatMessageA(
dwFormatFlags,
NULL, // module to get message from (NULL == system)
dwLastError,
MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT), // default language
(LPSTR) &MessageBuffer,
0,
NULL
);
if (dwBufferLength) {
// Output message
puts(MessageBuffer);
// Free the buffer allocated by the system.
LocalFree(MessageBuffer);
}
}
int exists(char *filename) {
FILE * pFile;
pFile = fopen(filename, "r");
return pFile != NULL;
}
void usage(void) {
printf("USGAE: loaddrv command drivername [args...]\n\n"
"NT/2k/XP Driver and Service modification tool.\n"
"(C)2002 Chris Liechti < cliechti@gmx.net>\n\n"
"Suported commands:\n\n"
" install [fullpathforinstall]\n"
" Install new service. Loaded from given path. If path is not present,\n"
" the local directory is searched for a .sys file. If the service\n"
" already exists, it must be removed first.\n"
" start\n"
" Start service. It must be installed in advance.\n"
" stop\n"
" Stop service.\n"
" remove\n"
" Remove service. It must be stopped in advance.\n"
" status\n"
" Show status information about service.\n"
" starttype auto|manual|system|disable\n"
" Change startup type to the given type.\n"
);
}
int main(int argc, char *argv[]) {
DWORD status = 0;
int level = 0;
if (argc < 3) {
usage();
exit(1);
}
LoadDriverInit();
if (strcmp(argv[1], "start") == 0) {
printf("starting %s... ", argv[2]);
status = DriverStart(argv[2]);
if ( status != OKAY) {
printf("start failed (status %ld):\n", status);
level = 1;
} else {
printf("ok.\n");
}
} else if (strcmp(argv[1], "stop") == 0) {
printf("stoping %s... ", argv[2]);
status = DriverStop(argv[2]);
if ( status != OKAY) {
printf("stop failed (status %ld):\n", status);
level = 1;
} else {
printf("ok.\n");
}
} else if (strcmp(argv[1], "install") == 0) {
char path[MAX_PATH*2];
if (argc< 4) {
char cwd[MAX_PATH];
getcwd(cwd, sizeof cwd);
sprintf(path, "%s\\%s.sys", cwd, argv[2]);
} else {
strncpy(path, argv[3], MAX_PATH);
}
if (exists(path)) {
printf("installing %s from %s... ", argv[2], path);
status = DriverInstall(path, argv[2]);
if ( status != OKAY) {
printf("install failed (status %ld):\n", status);
level = 2;
} else {
printf("ok.\n");
}
} else {
printf("install failed, file not found: %s\n", path);
level = 1;
}
} else if (strcmp(argv[1], "remove") == 0) {
printf("removing %s... ", argv[2]);
status = DriverRemove(argv[2]);
if ( status != OKAY) {
printf("remove failed (status %ld):\n", status);
level = 1;
} else {
printf("ok.\n");
}
} else if (strcmp(argv[1], "status") == 0) {
printf("status of %s:\n", argv[2]);
status = DriverStatus(argv[2]);
if ( status != OKAY) {
printf("stat failed (status %ld):\n", status);
level = 1;
} else {
printf("ok.\n");
}
} else if (strcmp(argv[1], "starttype") == 0) {
if (argc < 4) {
printf("Error: need start type (string) as argument.\n");
level = 2;
} else {
DWORD type = 0;
printf("set start type of %s to %s... ", argv[2], argv[3]);
if (strcmp(argv[1], "boot") == 0) {
type = SERVICE_BOOT_START;
} else if (strcmp(argv[3], "system") == 0) {
type = SERVICE_SYSTEM_START;
} else if (strcmp(argv[3], "auto") == 0) {
type = SERVICE_AUTO_START;
} else if (strcmp(argv[3], "manual") == 0) {
type = SERVICE_DEMAND_START;
} else if (strcmp(argv[3], "disabled") == 0) {
type = SERVICE_DISABLED;
} else {
printf("unknown type\n");
level = 1;
}
if (level == 0) {
status = DriverStartType(argv[2], type);
if ( status != OKAY) {
printf("set start type failed (status %ld):\n", status);
level = 1;
} else {
printf("ok.\n");
}
}
}
} else {
usage();
level = 1;
}
if (status) DisplayErrorText(status);
LoadDriverCleanup();
exit(level);
return 0;
}
DWORD LoadDriverInit(void) {
// connect to local service control manager
if ((hSCMan = OpenSCManager(NULL, NULL,
SC_MANAGER_ALL_ACCESS)) == NULL) {
return -1;
}
return OKAY;
}
void LoadDriverCleanup(void) {
if (hSCMan != NULL) CloseServiceHandle(hSCMan);
}
/**-----------------------------------------------------**/
DWORD DriverInstall(LPSTR lpPath, LPSTR lpDriver) {
BOOL dwStatus = OKAY;
SC_HANDLE hService = NULL;
// add to service control manager's database
if ((hService = CreateService(hSCMan, lpDriver,
lpDriver, SERVICE_ALL_ACCESS, SERVICE_KERNEL_DRIVER,
SERVICE_DEMAND_START, SERVICE_ERROR_NORMAL, lpPath,
NULL, NULL, NULL, NULL, NULL)) == NULL)
dwStatus = GetLastError();
else CloseServiceHandle(hService);
return dwStatus;
} // DriverInstall
/**-----------------------------------------------------**/
DWORD DriverStart(LPSTR lpDriver) {
BOOL dwStatus = OKAY;
SC_HANDLE hService = NULL;
// get a handle to the service
if ((hService = OpenService(hSCMan, lpDriver,
SERVICE_ALL_ACCESS)) != NULL)
{
// start the driver
if (!StartService(hService, 0, NULL))
dwStatus = GetLastError();
} else dwStatus = GetLastError();
if (hService != NULL) CloseServiceHandle(hService);
return dwStatus;
} // DriverStart
/**-----------------------------------------------------**/
DWORD DriverStop(LPSTR lpDriver)
{
BOOL dwStatus = OKAY;
SC_HANDLE hService = NULL;
SERVICE_STATUS serviceStatus;
// get a handle to the service
if ((hService = OpenService(hSCMan, lpDriver,
SERVICE_ALL_ACCESS)) != NULL)
{
// stop the driver
if (!ControlService(hService, SERVICE_CONTROL_STOP,
&serviceStatus))
dwStatus = GetLastError();
} else dwStatus = GetLastError();
if (hService != NULL) CloseServiceHandle(hService);
return dwStatus;
} // DriverStop
/**-----------------------------------------------------**/
DWORD DriverRemove(LPSTR lpDriver)
{
BOOL dwStatus = OKAY;
SC_HANDLE hService = NULL;
// get a handle to the service
if ((hService = OpenService(hSCMan, lpDriver,
SERVICE_ALL_ACCESS)) != NULL)
{ // remove the driver
if (!DeleteService(hService))
dwStatus = GetLastError();
} else dwStatus = GetLastError();
if (hService != NULL) CloseServiceHandle(hService);
return dwStatus;
} // DriverRemove
/**-----------------------------------------------------**/
////extensions by Lch
/**-----------------------------------------------------**/
DWORD DriverStatus(LPSTR lpDriver) {
BOOL dwStatus = OKAY;
SC_HANDLE hService = NULL;
DWORD dwBytesNeeded;
// get a handle to the service
if ((hService = OpenService(hSCMan, lpDriver,
SERVICE_ALL_ACCESS)) != NULL)
{
LPQUERY_SERVICE_CONFIG lpqscBuf;
//~ LPSERVICE_DESCRIPTION lpqscBuf2;
// Allocate a buffer for the configuration information.
if ((lpqscBuf = (LPQUERY_SERVICE_CONFIG) LocalAlloc(
LPTR, 4096)) != NULL)
{
//~ if ((lpqscBuf2 = (LPSERVICE_DESCRIPTION) LocalAlloc(
//~ LPTR, 4096)) != NULL)
{
// Get the configuration information.
if (QueryServiceConfig(
hService,
lpqscBuf,
4096,
&dwBytesNeeded) //&&
//~ QueryServiceConfig2(
//~ hService,
//~ SERVICE_CONFIG_DESCRIPTION,
//~ lpqscBuf2,
//~ 4096,
//~ &dwBytesNeeded
)
{
// Print the configuration information.
printf("Type: [0x%02lx] ", lpqscBuf->dwServiceType);
switch (lpqscBuf->dwServiceType) {
case SERVICE_WIN32_OWN_PROCESS:
printf("The service runs in its own process.");
break;
case SERVICE_WIN32_SHARE_PROCESS:
printf("The service shares a process with other services.");
break;
case SERVICE_KERNEL_DRIVER:
printf("Kernel driver.");
break;
case SERVICE_FILE_SYSTEM_DRIVER:
printf("File system driver.");
break;
case SERVICE_INTERACTIVE_PROCESS:
printf("The service can interact with the desktop.");
break;
default:
printf("Unknown type.");
}
printf("\nStart Type: [0x%02lx] ", lpqscBuf->dwStartType);
switch (lpqscBuf->dwStartType) {
case SERVICE_BOOT_START:
printf("Boot");
break;
case SERVICE_SYSTEM_START:
printf("System");
break;
case SERVICE_AUTO_START:
printf("Automatic");
break;
case SERVICE_DEMAND_START:
printf("Manual");
break;
case SERVICE_DISABLED:
printf("Disabled");
break;
default:
printf("Unknown.");
}
printf("\nError Control: [0x%02lx] ", lpqscBuf->dwErrorControl);
switch (lpqscBuf->dwErrorControl) {
case SERVICE_ERROR_IGNORE:
printf("IGNORE: Ignore.");
break;
case SERVICE_ERROR_NORMAL:
printf("NORMAL: Display a message box.");
break;
case SERVICE_ERROR_SEVERE:
printf("SEVERE: Restart with last-known-good config.");
break;
case SERVICE_ERROR_CRITICAL:
printf("CRITICAL: Restart w/ last-known-good config.");
break;
default:
printf("Unknown.");
}
printf("\nBinary path: %s\n", lpqscBuf->lpBinaryPathName);
if (lpqscBuf->lpLoadOrderGroup != NULL)
printf("Load order grp: %s\n", lpqscBuf->lpLoadOrderGroup);
if (lpqscBuf->dwTagId != 0)
printf("Tag ID: %ld\n", lpqscBuf->dwTagId);
if (lpqscBuf->lpDependencies != NULL)
printf("Dependencies: %s\n", lpqscBuf->lpDependencies);
if (lpqscBuf->lpServiceStartName != NULL)
printf("Start Name: %s\n", lpqscBuf->lpServiceStartName);
//~ if (lpqscBuf2->lpDescription != NULL)
//~ printf("Description: %s\n", lpqscBuf2->lpDescription);
}
//~ LocalFree(lpqscBuf2);
}
LocalFree(lpqscBuf);
} else {
dwStatus = GetLastError();
}
} else {
dwStatus = GetLastError();
}
if (hService != NULL) CloseServiceHandle(hService);
return dwStatus;
} // DriverStatus
/**-----------------------------------------------------**/
DWORD DriverStartType(LPSTR lpDriver, DWORD dwStartType) {
BOOL dwStatus = OKAY;
SC_HANDLE hService = NULL;
SC_LOCK sclLock;
LPQUERY_SERVICE_LOCK_STATUS lpqslsBuf;
DWORD dwBytesNeeded;
// Need to acquire database lock before reconfiguring.
sclLock = LockServiceDatabase(hSCMan);
// If the database cannot be locked, report the details.
if (sclLock == NULL) {
// Exit if the database is not locked by another process.
if (GetLastError() == ERROR_SERVICE_DATABASE_LOCKED) {
// Allocate a buffer to get details about the lock.
lpqslsBuf = (LPQUERY_SERVICE_LOCK_STATUS) LocalAlloc(
LPTR, sizeof(QUERY_SERVICE_LOCK_STATUS)+256);
if (lpqslsBuf != NULL) {
// Get and print the lock status information.
if (QueryServiceLockStatus(
hSCMan,
lpqslsBuf,
sizeof(QUERY_SERVICE_LOCK_STATUS)+256,
&dwBytesNeeded) )
{
if (lpqslsBuf->fIsLocked) {
printf("Locked by: %s, duration: %ld seconds\n",
lpqslsBuf->lpLockOwner,
lpqslsBuf->dwLockDuration
);
} else {
printf("No longer locked\n");
}
}
LocalFree(lpqslsBuf);
}
}
dwStatus = GetLastError();
} else {
// The database is locked, so it is safe to make changes.
// Open a handle to the service.
hService = OpenService(
hSCMan, // SCManager database
lpDriver, // name of service
SERVICE_CHANGE_CONFIG
); // need CHANGE access
if (hService != NULL) {
// Make the changes.
if (!ChangeServiceConfig(
hService, // handle of service
SERVICE_NO_CHANGE, // service type: no change
dwStartType, // change service start type
SERVICE_NO_CHANGE, // error control: no change
NULL, // binary path: no change
NULL, // load order group: no change
NULL, // tag ID: no change
NULL, // dependencies: no change
NULL, // account name: no change
NULL, // password: no change
NULL) ) // display name: no change
{
dwStatus = GetLastError();
}
}
// Release the database lock.
UnlockServiceDatabase(sclLock);
}
if (hService != NULL) CloseServiceHandle(hService);
return dwStatus;
} // DriverStartType
thinx lot
RépondreSupprimer