aujourd’hui on va voir un pilier du système Windows en Win32 : les services.
C’est un sujet très important en développement système, parce que énormément de composants réels du système ou des logiciels de sécurité reposent dessus. Antivirus, agents EDR, services de mise à jour, collecteurs de logs, brokers, services réseau, superviseurs, lanceurs de tâches, composants d’entreprise… tous utilisent d’une manière ou d’une autre le modèle des services Windows.
Le problème, c’est que beaucoup de développeurs connaissent vaguement le mot “service”, savent qu’on peut en créer un avec CreateServiceW, mais ne comprennent pas vraiment le modèle complet. Or un service n’est pas juste un EXE “sans interface”. C’est un programme géré par le Service Control Manager, avec un cycle de vie précis, des états, un compte d’exécution, des contrôles, des obligations vis-à-vis du système, et souvent des contraintes de sécurité importantes.
Pourquoi les services sont un sujet fondamental
Comprendre les services Windows permet de mieux voir :
- comment Windows exécute du code en arrière-plan sur la durée
- comment un programme peut démarrer avant meme qu’un utilisateur ouvre sa session
- comment le SCM supervise le cycle de vie d’un service
- comment démarrer, arrêter, configurer et interroger un service proprement
- comment choisir le bon compte d’exécution
- comment gérer la sécurité, les droits et les dépendances
- comment écrire un vrai service Win32 robuste au lieu d’un simple programme “qui tourne en boucle”
Ce qu’est réellement un service Windows
Un service Windows est un processus, ou plus exactement un programme conçu pour etre géré par Windows via le Service Control Manager, souvent abrégé SCM.
Un service se distingue d’une application classique par plusieurs propriétés :
- il peut démarrer sans utilisateur connecté
- il s’exécute en arrière-plan
- son cycle de vie est géré par le SCM
- il peut etre lancé automatiquement au boot selon sa configuration
- il doit signaler son état au système
- il n’est pas conçu pour interagir directement avec l’utilisateur comme une application GUI classique
Services et applications classiques : la différence de logique
Une application classique est généralement lancée par un utilisateur, depuis l’explorateur, une console, un raccourci, ou un autre programme. Elle vit dans la session utilisateur, possède souvent une interface, et son cycle de vie est lié à la logique de l’utilisateur ou de la session.
Un service, lui, suit une logique différente :
- il est lancé par le système via le SCM
- il peut continuer à tourner sans aucune session utilisateur active
- il appartient au monde du fond de tâche, pas à celui de l’interface
- il doit gérer correctement démarrage, arrêt, pause éventuelle et shutdown système
- il est pensé pour durer et pour etre robuste
Le Service Control Manager : centre de contrôle des services
Le composant central de ce modèle est le Service Control Manager, ou SCM.
Le SCM sert notamment à :
- maintenir la base de configuration des services
- démarrer les services
- les arrêter
- leur envoyer des commandes de contrôle
- interroger leur état
- gérer certaines dépendances
- appliquer des politiques de sécurité et de démarrage
Ouvrir le Service Control Manager
Pour interagir avec la base des services, il faut d’abord ouvrir le SCM :
Code: Select all
SC_HANDLE hSCM = OpenSCManagerW(
NULL,
NULL,
SC_MANAGER_ALL_ACCESS
);
if (!hSCM)
return 1;
- CloseServiceHandle
Exemple :
Code: Select all
CloseServiceHandle(hSCM);
Parmi les droits SCM importants :
- SC_MANAGER_CONNECT
- SC_MANAGER_CREATE_SERVICE
- SC_MANAGER_ENUMERATE_SERVICE
- SC_MANAGER_LOCK
- SC_MANAGER_QUERY_LOCK_STATUS
- SC_MANAGER_MODIFY_BOOT_CONFIG
- SC_MANAGER_ALL_ACCESS
Créer un service avec CreateServiceW
L’API de base pour enregistrer un service est :
- CreateServiceW
Code: Select all
SC_HANDLE hService = CreateServiceW(
hSCM,
L"MyService",
L"My Service",
SERVICE_ALL_ACCESS,
SERVICE_WIN32_OWN_PROCESS,
SERVICE_AUTO_START,
SERVICE_ERROR_NORMAL,
L"C:\\Path\\service.exe",
NULL,
NULL,
NULL,
NULL,
NULL
);
if (!hService)
return 1;
Parmi les paramètres à bien comprendre :
- le nom de service, utilisé en interne par le SCM
- le display name, plus lisible pour l’humain
- les droits d’accès demandés sur le service créé
- le type du service
- le type de démarrage
- le contrôle d’erreur
- le chemin du binaire
- le groupe de chargement éventuel
- les dépendances
- le compte du service
- le mot de passe associé si nécessaire
Le type du service décrit comment il s’exécute. Parmi les valeurs importantes :
- SERVICE_WIN32_OWN_PROCESS
- SERVICE_WIN32_SHARE_PROCESS
- SERVICE_KERNEL_DRIVER
- SERVICE_FILE_SYSTEM_DRIVER
- SERVICE_USER_SERVICE
- SERVICE_INTERACTIVE_PROCESS
- SERVICE_WIN32_OWN_PROCESS : le service tourne dans son propre processus
- SERVICE_WIN32_SHARE_PROCESS : plusieurs services partagent un meme processus hote
Types de démarrage
Le mode de démarrage est lui aussi crucial.
Les grandes valeurs à connaitre :
- SERVICE_BOOT_START
- SERVICE_SYSTEM_START
- SERVICE_AUTO_START
- SERVICE_DEMAND_START
- SERVICE_DISABLED
- SERVICE_AUTO_START
- SERVICE_DEMAND_START
- SERVICE_DISABLED
Il faut aussi connaitre la notion de delayed auto-start, gérée via une configuration supplémentaire et non via une simple valeur de démarrage.
Le contrôle d’erreur
CreateServiceW prend aussi un type de sévérité pour l’échec de démarrage :
- SERVICE_ERROR_IGNORE
- SERVICE_ERROR_NORMAL
- SERVICE_ERROR_SEVERE
- SERVICE_ERROR_CRITICAL
Ouvrir un service existant
Si le service existe déjà, on l’ouvre avec :
- OpenServiceW
Code: Select all
SC_HANDLE hService = OpenServiceW(
hSCM,
L"MyService",
SERVICE_QUERY_STATUS | SERVICE_START | SERVICE_STOP
);
if (!hService)
return 1;
Parmi les droits service importants :
- SERVICE_QUERY_CONFIG
- SERVICE_CHANGE_CONFIG
- SERVICE_QUERY_STATUS
- SERVICE_ENUMERATE_DEPENDENTS
- SERVICE_START
- SERVICE_STOP
- SERVICE_PAUSE_CONTINUE
- SERVICE_INTERROGATE
- SERVICE_USER_DEFINED_CONTROL
- DELETE
- SERVICE_ALL_ACCESS
Pour démarrer un service déjà enregistré :
Code: Select all
if (!StartServiceW(hService, 0, NULL))
return 1;
Il est ensuite fréquent d’interroger son état pour savoir s’il est réellement passé à SERVICE_RUNNING, au lieu de supposer que l’appel suffit.
Arrêter un service
Pour demander l’arrêt d’un service :
Code: Select all
SERVICE_STATUS status = { 0 };
if (!ControlService(
hService,
SERVICE_CONTROL_STOP,
&status))
{
return 1;
}
Il faut donc bien distinguer :
- la demande d’arrêt
- l’arrêt effectif
Un service Win32 n’est pas un simple processus qui tourne librement. Il possède un cycle de vie explicite, avec des états bien connus.
Les états les plus importants sont :
- SERVICE_STOPPED
- SERVICE_START_PENDING
- SERVICE_STOP_PENDING
- SERVICE_RUNNING
- SERVICE_CONTINUE_PENDING
- SERVICE_PAUSE_PENDING
- SERVICE_PAUSED
SERVICE_STATUS et SERVICE_STATUS_PROCESS
Deux structures très importantes reviennent souvent :
- SERVICE_STATUS
- SERVICE_STATUS_PROCESS
Ses champs importants incluent notamment :
- dwServiceType
- dwCurrentState
- dwControlsAccepted
- dwWin32ExitCode
- dwServiceSpecificExitCode
- dwCheckPoint
- dwWaitHint
- dwProcessId
- dwServiceFlags
Le vrai point d’entrée : StartServiceCtrlDispatcherW
Un service ne démarre pas comme une application classique où main ou wmain ferait tout librement du début à la fin. Le programme service doit se connecter au SCM via :
- StartServiceCtrlDispatcherW
Code: Select all
SERVICE_TABLE_ENTRYW serviceTable[] =
{
{ (LPWSTR)L"MyService", ServiceMain },
{ NULL, NULL }
};
if (!StartServiceCtrlDispatcherW(serviceTable))
return 1;
La structure importante ici est :
- SERVICE_TABLE_ENTRYW
Dans un cas simple, il n’y a qu’un service dans le processus. Dans un cas partagé, un meme processus peut héberger plusieurs services.
ServiceMain : le point d’entrée du service
Le SCM appelle ensuite :
Code: Select all
void WINAPI ServiceMain(
DWORD argc,
LPWSTR* argv
);
- s’initialise
- enregistre son handler de contrôle
- signale ses changements d’état
- crée ses threads de travail éventuels
- commence sa logique réelle
Enregistrer le handler de contrôle
Pour recevoir les commandes du SCM, le service doit enregistrer un handler.
La forme moderne à privilégier est :
- RegisterServiceCtrlHandlerExW
- RegisterServiceCtrlHandlerW
Exemple :
Code: Select all
SERVICE_STATUS_HANDLE g_StatusHandle = RegisterServiceCtrlHandlerExW(
L"MyService",
ServiceCtrlHandlerEx,
NULL
);
if (!g_StatusHandle)
return;
- SERVICE_STATUS_HANDLE
Les contrôles que peut recevoir un service
Le handler peut recevoir différents contrôles, parmi lesquels :
- SERVICE_CONTROL_STOP
- SERVICE_CONTROL_PAUSE
- SERVICE_CONTROL_CONTINUE
- SERVICE_CONTROL_INTERROGATE
- SERVICE_CONTROL_SHUTDOWN
- SERVICE_CONTROL_PARAMCHANGE
- SERVICE_CONTROL_NETBINDADD
- SERVICE_CONTROL_NETBINDREMOVE
- SERVICE_CONTROL_DEVICEEVENT
- SERVICE_CONTROL_SESSIONCHANGE
- SERVICE_CONTROL_POWEREVENT
Le service déclare ce qu’il accepte via le champ dwControlsAccepted dans SERVICE_STATUS.
Flags importants :
- SERVICE_ACCEPT_STOP
- SERVICE_ACCEPT_PAUSE_CONTINUE
- SERVICE_ACCEPT_SHUTDOWN
- SERVICE_ACCEPT_PARAMCHANGE
- SERVICE_ACCEPT_SESSIONCHANGE
- SERVICE_ACCEPT_POWEREVENT
L’API fondamentale pour notifier le SCM est :
- SetServiceStatus
Code: Select all
SERVICE_STATUS status = { 0 };
status.dwServiceType = SERVICE_WIN32_OWN_PROCESS;
status.dwCurrentState = SERVICE_START_PENDING;
status.dwControlsAccepted = 0;
status.dwWin32ExitCode = NO_ERROR;
status.dwCheckPoint = 1;
status.dwWaitHint = 3000;
if (!SetServiceStatus(g_StatusHandle, &status))
return;
Code: Select all
status.dwCurrentState = SERVICE_RUNNING;
status.dwControlsAccepted = SERVICE_ACCEPT_STOP | SERVICE_ACCEPT_SHUTDOWN;
status.dwCheckPoint = 0;
status.dwWaitHint = 0;
SetServiceStatus(g_StatusHandle, &status);
Bonne architecture d’un service
Une erreur classique consiste à bloquer ServiceMain avec toute la logique métier du service sans gérer proprement l’arrêt. Une architecture plus propre ressemble souvent à ceci :
- ServiceMain s’enregistre auprès du SCM
- ServiceMain signale SERVICE_START_PENDING
- ServiceMain initialise les ressources essentielles
- ServiceMain crée un thread worker ou une boucle de travail structurée
- ServiceMain signale SERVICE_RUNNING
- le handler reçoit STOP ou SHUTDOWN et déclenche un event d’arrêt
- le worker termine proprement
- le service signale SERVICE_STOPPED
SERVICE_STATUS_HANDLE, worker thread et event d’arrêt
Dans beaucoup de services propres, on retrouve :
- un SERVICE_STATUS_HANDLE global ou de contexte
- un SERVICE_STATUS
- un HANDLE d’event d’arrêt
- un ou plusieurs threads de travail
Code: Select all
HANDLE g_StopEvent = CreateEventW(NULL, TRUE, FALSE, NULL);
if (!g_StopEvent)
return;
Code: Select all
SetEvent(g_StopEvent);
C’est un schéma extrêmement classique dans les services Win32.
Interroger l’état d’un service
Pour connaitre l’état d’un service, plusieurs API existent. La plus simple historiquement :
- QueryServiceStatus
- QueryServiceStatusEx
Code: Select all
SERVICE_STATUS_PROCESS ssp = { 0 };
DWORD bytesNeeded = 0;
if (!QueryServiceStatusEx(
hService,
SC_STATUS_PROCESS_INFO,
(LPBYTE)&ssp,
sizeof(ssp),
&bytesNeeded))
{
return 1;
}
- l’état courant
- les contrôles acceptés
- le code de sortie
- le PID du processus service
Un service ne se limite pas à sa création initiale. Plusieurs API permettent de le reconfigurer.
Les plus importantes :
- ChangeServiceConfigW
- ChangeServiceConfig2W
- QueryServiceConfigW
- QueryServiceConfig2W
- QUERY_SERVICE_CONFIGW
Parmi les structures ou infos importantes autour de ChangeServiceConfig2W / QueryServiceConfig2W :
- SERVICE_DESCRIPTIONW
- SERVICE_FAILURE_ACTIONSW
- SC_ACTION
- SERVICE_DELAYED_AUTO_START_INFO
- SERVICE_SID_INFO
- SERVICE_REQUIRED_PRIVILEGES_INFOW
- SERVICE_PRESHUTDOWN_INFO
- SERVICE_TRIGGER_INFO
- SERVICE_LAUNCH_PROTECTED_INFO
Description, actions en cas d’échec, delayed auto-start
Quelques exemples particulièrement utiles.
Pour définir une description lisible :
- SERVICE_DESCRIPTIONW
- SERVICE_FAILURE_ACTIONSW
- SC_ACTION
- redémarrer le service
- redémarrer la machine
- ne rien faire
- lancer une commande
- SERVICE_DELAYED_AUTO_START_INFO
Supprimer un service
Pour supprimer l’enregistrement d’un service :
- DeleteService
Code: Select all
if (!DeleteService(hService))
return 1;
Enumérer les services
Pour lister les services du système :
- EnumServicesStatusExW
- ENUM_SERVICE_STATUS_PROCESSW
- le nom
- le display name
- l’état
- le PID si applicable
Dépendances entre services
Les services peuvent dépendre d’autres services ou groupes de chargement. Cela fait partie de leur configuration.
API utiles autour de ce sujet :
- EnumDependentServicesW
- QueryServiceConfigW
Le compte d’exécution du service
Un service tourne toujours sous un compte. C’est un point absolument central.
Les comptes courants sont notamment :
- LocalSystem
- LocalService
- NetworkService
- un compte utilisateur spécifique
- un gMSA dans des contextes d’entreprise
- les privilèges locaux
- l’identité de sécurité
- la capacité d’accès à certaines ressources
- le comportement réseau
- l’exposition en cas de compromission
Services et tokens
Comme tout processus user-mode, un service s’exécute sous un token de sécurité. Ce token détermine :
- ce qu’il peut lire
- ce qu’il peut modifier
- les privilèges dont il dispose
- les processus qu’il peut ouvrir
- les clés registre ou fichiers auxquels il peut accéder
API importantes de ce monde :
- OpenProcessToken
- GetTokenInformation
- AdjustTokenPrivileges
- DuplicateTokenEx
- CreateProcessAsUserW
Session 0 isolation
Il faut aussi connaitre une notion très importante des services modernes : la Session 0 isolation.
Les services tournent généralement en Session 0, alors que les utilisateurs interactifs travaillent dans d’autres sessions. Cela implique notamment :
- pas d’interface utilisateur classique directement visible pour l’utilisateur
- pas de modèle “service interactif” normal comme autrefois
- séparation claire entre code système et interface utilisateur
Types de notifications avancées et handler Ex
La version étendue du handler, RegisterServiceCtrlHandlerExW, permet de traiter plus de cas, avec un contexte plus riche.
On voit alors apparaitre des données liées à :
- SESSIONCHANGE
- POWEREVENT
- DEVICEEVENT
Sécurité des services et des handles SCM
Les services eux-memes sont des objets sécurisés. Leur manipulation dépend donc des droits accordés.
API utiles à connaitre de nom autour de la sécurité des services :
- QueryServiceObjectSecurity
- SetServiceObjectSecurity
Quelques structures importantes à connaitre
Pour avoir une bonne base dans le monde des services, il faut reconnaitre rapidement plusieurs structures.
Les plus importantes :
- SERVICE_TABLE_ENTRYW
- SERVICE_STATUS
- SERVICE_STATUS_PROCESS
- SERVICE_STATUS_HANDLE
- STARTUPINFOW
- PROCESS_INFORMATION
- QUERY_SERVICE_CONFIGW
- SERVICE_DESCRIPTIONW
- SERVICE_FAILURE_ACTIONSW
- SC_ACTION
- SERVICE_DELAYED_AUTO_START_INFO
- ENUM_SERVICE_STATUS_PROCESSW
- SERVICE_REQUIRED_PRIVILEGES_INFOW
- SERVICE_SID_INFO
- SERVICE_PRESHUTDOWN_INFO
- SERVICE_TRIGGER_INFO
Les API à connaitre absolument
Pour le SCM et l’ouverture :
- OpenSCManagerW
- CloseServiceHandle
- OpenServiceW
- CreateServiceW
- DeleteService
- ChangeServiceConfigW
- ChangeServiceConfig2W
- QueryServiceConfigW
- QueryServiceConfig2W
- StartServiceW
- ControlService
- ControlServiceEx
- QueryServiceStatus
- QueryServiceStatusEx
- SetServiceStatus
- StartServiceCtrlDispatcherW
- RegisterServiceCtrlHandlerW
- RegisterServiceCtrlHandlerExW
- EnumServicesStatusExW
- EnumDependentServicesW
- QueryServiceObjectSecurity
- SetServiceObjectSecurity
- OpenProcessToken
- GetTokenInformation
- AdjustTokenPrivileges
- CreateProcessAsUserW
Une architecture raisonnable pour un service Win32 ressemble souvent à ceci :
- main ou wmain appelle StartServiceCtrlDispatcherW
- ServiceMain s’enregistre avec RegisterServiceCtrlHandlerExW
- le service signale SERVICE_START_PENDING
- il crée ses ressources de base, notamment un event d’arrêt
- il lance un ou plusieurs workers
- il signale SERVICE_RUNNING
- le handler reçoit STOP ou SHUTDOWN et signale la fin
- les workers quittent proprement
- le service fait son cleanup
- il signale SERVICE_STOPPED
Erreurs classiques
Comme souvent en Win32 système, les erreurs les plus gênantes viennent d’un mauvais modèle mental.
Parmi les fautes fréquentes :
- vouloir afficher une vraie interface graphique depuis le service
- bloquer ServiceMain sans stratégie d’arrêt propre
- oublier de notifier le SCM avec SetServiceStatus
- oublier CloseServiceHandle
- mal choisir le compte du service
- utiliser LocalSystem sans nécessité
- ignorer Session 0 isolation
- supposer qu’un StartServiceW signifie que le service est déjà totalement running
- mal gérer les états pending, dwCheckPoint et dwWaitHint
- oublier les dépendances ou la sécurité du service
Un service Windows est un programme spécial géré par le Service Control Manager. Il n’est pas simplement “un EXE sans fenêtre”, mais un composant structuré avec un cycle de vie précis, un compte d’exécution, un état courant, des contrôles, et des obligations de notification vers le système.
Les idées essentielles à garder sont les suivantes :
- le SCM est le centre de contrôle obligatoire
- CreateServiceW enregistre un service, StartServiceW le démarre
- le vrai point d’entrée logique du service passe par StartServiceCtrlDispatcherW puis ServiceMain
- RegisterServiceCtrlHandlerExW et SetServiceStatus sont au coeur du protocole de fonctionnement
- le service doit signaler correctement ses états
- le compte d’exécution et le token déterminent les droits réels
- Session 0 isolation interdit de raisonner comme pour une application GUI classique
Comprendre les services, c’est comprendre comment Windows exécute du code de manière durable, supervisée et structurée en arrière-plan. C’est aussi comprendre qu’un service n’est pas seulement une question d’API, mais un vrai modèle d’exécution avec gestion du cycle de vie, sécurité, comptes, dépendances et supervision par le SCM.
Si tu maitrises déjà correctement :
- OpenSCManagerW
- CreateServiceW
- OpenServiceW
- StartServiceW
- ControlService
- StartServiceCtrlDispatcherW
- ServiceMain
- RegisterServiceCtrlHandlerExW
- SetServiceStatus
- QueryServiceStatusEx
- SERVICE_STATUS et SERVICE_STATUS_PROCESS
Et surtout, tu commences à voir que les services sont l’un des grands cadres d’exécution permanents de Windows, au meme titre que les processus, les threads, les tokens ou les I/O.
A bientot pour le prochain cours
