RogueOneX

Cyberspace. A graphic representation of data abstracted from the banks of every computer in the human system.

Windows Internals – Come scrivere un servizio – Parte 1

Written by

in

Introduzione

Un servizio è un programma (.exe o .sys) che viene eseguito in background e non richiede interazione da parte dell’utente.
In Windows le informazioni relative ai servizi sono salvate al percorso del Registro di Sistema HKLM\SYSTEM\CurrentControlSet\Services, il quale contiene una chiave per ogni servizio.

Nella prima parte di questa serie sui servizi vedremo come crearne uno il cui file binario sarà un eseguibile (.exe), partendo da una veloce infarinatura fino all’installazione.

Struttura di un servizio

L’applicazione che andremo a scrivere sarà di tipo console, ovvero priva di GUI, così come una qualsiasi altra applicazione eseguibile da riga di comando ma che, per essere considerata un servizio valido, deve rispettare i requisiti richiesti da Windows.

Dall’immagine che segue si può da subito notare che il Processo del servizio utilizza due thread per funzionar correttamente: il main thread e il ervice thread.

Struttura di un servizio

Il main thread esegue il codice della funzione main (Service Entry Point) che si occupa di inizializzare una struttura SERVICE_TABLE_ENTRY:

typedef struct _SERVICE_TABLE_ENTRYW {
  LPWSTR                   lpServiceName;
  LPSERVICE_MAIN_FUNCTIONW lpServiceProc;
} SERVICE_TABLE_ENTRYW, *LPSERVICE_TABLE_ENTRYW;

Questa struttura contiene il nome del servizio e la sua funzione principale (ServiceMain).
La variabile di tipo SERVICE_TABLE deve essere un array che può contenere più di una coppia di valori, se lo stesso processo serve più servizi, ma la cui ultima voce deve corrispondere a un valore NULL, che sta ad indicare la fine dell’array.

SERVICE_TABLE_ENTRY entries[]: {
    {lpServiceName1, lpServiceProc1},
    {nullptr}
};

Successivamente invoca la funzione StartServiceControlDispatcher che connette il main thread al Service Control Manager (SCM), rendendolo di fatto il service control dispatcher thread del processo.
Il suo compito sarà, da quel momento in poi, la gestione delle richieste di modifica dello stato del servizio (avvio, arresto, sospensione…) provenienti dal Service Control Manager.

if(!StartServiceCtrlDispatcher(entries){
    return 1;
}

Giunti a questo punto il main thread avvia il service thread che esegue il codice della funzione principale del servizio, la ServiceMain.
La prima operazione che il service thread deve eseguire è la registrazione, tramite la funzione RegisterServiceCtrlHandler, di un handler che contiene il codice per gestire le richieste del SCM.
Successivamente notifica al SCM che il servizio è in stato di avvio (SERVICE_START_PENDING) e infine che è attivo (SERVICE_RUNNING) tramite la funzione SetServiceStatus e la struttura SERVICE_STATUS.
Il Service Control Manager è “impaziente” e deve essere notificato entro pochi secondi se il servizio è in avvio.
Qualora l’inizializzazione delle risorse necessarie richieda più tempo, è necessario indicare il tempo rimanente o invocare con cadenza regolare che il servizio è in fase di avvio (SERVICE_START_PENDING).

typedef struct _SERVICE_STATUS {
  DWORD dwServiceType;
  DWORD dwCurrentState;
  DWORD dwControlsAccepted;
  DWORD dwWin32ExitCode;
  DWORD dwServiceSpecificExitCode;
  DWORD dwCheckPoint;
  DWORD dwWaitHint;
} SERVICE_STATUS, *LPSERVICE_STATUS;

Scrittura del servizio

Quanto esposto sopra è un breve accenno ai passaggi fondamentali che l’applicazione deve seguire affinché Windows lo possa registrare come servizio ed eseguirlo senza errori.

Per avere una idea, il codice sarà così:

// Headers necessari
#include <Windows.h>

// Variabili globali
WCHAR g_ServiceName[] = L"NomeDelServizio";
SERVICE_STATUS_HANDLE g_ServiceHandle;
SERVICE_STATUS g_ServiceStatus;

// Funzione wrapper attorno a SetServiceStatus
void SetStatus(DWORD state) {
    g_ServiceStatus.dwCurrentState = state;
    SetServiceStatus(g_ServiceHandle, &g_ServiceStatus);
}

// Funzione handler che gestisce le richieste del SCM
void WINAPI ServiceHandler(DWORD dwControl){
    switch (dwControl) {
    case SERVICE_CONTROL_STOP:
        SetStatus(SERVICE_STOPPED);
        break;
    }
}

// Funzione principale del service thread
void WINAPI ServiceMain(DWORD dwNumServicesArgs, LPWSTR* lpServiceArgVectors) {
    
    // Registrazione della funzione handler
    g_ServiceHandle = RegisterServiceCtrlHandler(g_ServiceName, ServiceHandler);

    // Servizio in avvio
    SetStatus(SERVICE_START_PENDING);

    // Qualora fosse necessario inizializzare delle risorse il codice va inserito qui

    // Il servizio è avviato
    SetStatus(SERVICE_RUNNING);

}

// Funzione principale del processo eseguita nel main thread
int main(){

    // Inizializzazione della variabile di tipo SERVICE_TABLE_ENTRY
    SERVICE_TABLE_ENTRY entries[] = {
        {g_ServiceName, ServiceMain},
        {nullptr}
    };

    // Il servizio viene eseguito nel suo processo e accetta le richieste di arresto
    g_ServiceStatus.dwServiceType = SERVICE_WIN32_OWN_PROCESS;
    g_ServiceStatus.dwControlsAccepted = SERVICE_ACCEPT_STOP;

    // Avvia il service thread, in caso di errore chiude il processo
    if(!StartServiceCtrlDispatcher(entries))
        return 1;

    return 0;
}

Per compilare il codice è necessario avere a disposizione Visual Studio (anche edizione Community) con il SDK di Windows e le librerie per la compilazione (link alla guida dal sito Microsoft).
La compilazione in modalità Debug non consente la registrazione del servizio ed è quindi necessario selezionare la modalità Release, come mostrato nel video che segue.

A questo punto al percorso predefinito C:\Users\<nome utente>\sources\repos\<nome soluzione>\x64\Release si trovano il file eseguibile compilato e il file .pdb (utile per effettuare il debug del file).

Distribuzione, installazione e verifica

Il metodo più semplice per installare manualmente il servizio prevede di copiare il file eseguibile nella macchina test e utilizzare il comando sc da prompt dei comandi (cmd.exe) eseguito come amministratore.

Il comando per l’installazione è sc create <nome del servizio> type= own binPath= <percorso del file eseguibile>.
Da notare che gli spazi dopo type= e binPath= sono necessari.

Se il comando ha successo significa che il servizio è stato creato ma, per impostazione predefinita, non è ancora in esecuzione.
Per avviarlo è richiesto un input come sc start <nome del servizio>.

Creazione, avvio e verifica del servizio

Come si vede dallo screenshot, il servizio è in esecuzione.
Essendo stato creato correttamente lo ritroviamo sia tra l’elenco dei servizi disponibili in Gestione dei servizi (services.msc) sia nel Registro di sistema.

Servizio test compare nell’elenco di Gestione dei servizi
Proprietà del servizio test viste dal Registro di sistema

Non avendo specificato diversamente, Windows crea il servizio in avvio manuale (Start 3) e in esecuzione sotto l’utente LocalSystem.
Per un maggiore controllo sulla creazione del servizio occorre scrivere una applicazione client che utilizzi l’API CreateService:

SC_HANDLE CreateServiceW(
  [in]            SC_HANDLE hSCManager,
  [in]            LPCWSTR   lpServiceName,
  [in, optional]  LPCWSTR   lpDisplayName,
  [in]            DWORD     dwDesiredAccess,
  [in]            DWORD     dwServiceType,
  [in]            DWORD     dwStartType,
  [in]            DWORD     dwErrorControl,
  [in, optional]  LPCWSTR   lpBinaryPathName,
  [in, optional]  LPCWSTR   lpLoadOrderGroup,
  [out, optional] LPDWORD   lpdwTagId,
  [in, optional]  LPCWSTR   lpDependencies,
  [in, optional]  LPCWSTR   lpServiceStartName,
  [in, optional]  LPCWSTR   lpPassword
);

Come si vede dal prototipo è possibile specificare il tipo di avvio (dwStartType), sotto quale utente deve essere eseguito (lpServiceStartName), il tipo di servizio (dwServiceType) e l’ordine di avvio nel caso di dipendenze (lpLoadOrderGroup).

Per fermare il servizio è sufficiente utilizzare il comando sc stop <nome del servizio> o il pulsante Arresta da Gestione dei servizi.

Conclusione e anticipazioni

In questa prima parte sono stati fissati i principali concetti riguardo ai servizi in Windows.
Nelle parti che seguiranno il servizio sarà ampliato in un progetto più ampio che includerà la creazione di file, la creazione di mailsllot, la creazione di un client e l’utilizzo di ACL (Access Control List).

Riferimenti