Introduzione
Proseguiamo la serie sui servizi di Windows arricchendo il servizio scritto nella parte 1 con istruzioni per la creazione di un file con una ACL personalizzata.
Creazione di un file
In Windows l’API per la creazione di un file, da user-mode, è CreateFile.
La stessa funzione si utilizza anche per l’apertura di oggetti o device (vedremo cosa sono in post dedicati ai driver) e il valore restituito è sempre un handle.
Nel caso di errori il valore restituito è INVALID_HANDLE_VALUE.
Per ottenere il dettaglio dell’errore bisogna confrontare il valore restituito dalla funzione GetLastError con i codici errori di Windows: ad esempio il valore di ritorno pari a 0x5 corrisponde ad ERROR_ACCESS_DENIED.
HANDLE CreateFileW(
[in] LPCWSTR lpFileName,
[in] DWORD dwDesiredAccess,
[in] DWORD dwShareMode,
[in, optional] LPSECURITY_ATTRIBUTES lpSecurityAttributes,
[in] DWORD dwCreationDisposition,
[in] DWORD dwFlagsAndAttributes,
[in, optional] HANDLE hTemplateFile
);
Il primo parametro richiede il nome del file o del device da creare o aprire.
Il secondo parametro, dwDesiredAccess, richiede i permessi necessari per effettuare operazioni sul file che possono essere di sola lettura, solo modifica, lettura e modifica con un certo grado di granularità.
Infatti, è possibile specificare un valore generico come FILE_GENERIC_WRITE o un permesso specifico come FILE_APPEND_DATA, incluso in FILE_GENERIC_WRITE.
Il terzo parametro, dwShareMode, richiede il tipo di condivisione del file/device mentre ci sono degli handle aperti.
Se non si specifica un valore non sarà possibile aprire un altro handle fintanto che il primo rimane attivo.
dwCreationDisposition specifica il comportamento da mantenere durante la creazione/apertura del file nel caso questo non esista o sia già presente.
Con l‘handle ottenuto possiamo poi procedere a leggere il contenuto del file o a scriverne di nuovo.
Esempio di utilizzo:
g_FileHandle = CreateFile(
L"C:\\LoggerSvc.log", // nome del file
FILE_APPEND_DATA, // permesso per aggiungere dati alla fine del file
FILE_SHARE_READ, // altri processi possono aprire il file in sola lettura
&saFile, // ACL
OPEN_ALWAYS, // Apri sempre il file, se non esiste crealo
FILE_ATTRIBUTE_NORMAL,
nullptr);
Applicazione di ACL
Al paragrafo precedente non abbiamo commentato lo scopo del parametro lpSecurityAttributes.
Questo parametro accetta un puntatore a una struttura SECURITY_ATTRIBUTES:
typedef struct _SECURITY_ATTRIBUTES {
DWORD nLength;
LPVOID lpSecurityDescriptor;
BOOL bInheritHandle;
} SECURITY_ATTRIBUTES, *PSECURITY_ATTRIBUTES, *LPSECURITY_ATTRIBUTES;
Questa struttura riceve un parametro nLength che corrisponde alla dimensione della struttura stessa (sizeof(SECURITY_ATTRIBUTES)), lpSecurityDescriptor che riceve un puntatore a una struttura SECURITY_DESCRIPTOR e bInheritHandle che determina se gli attributi saranno ereditati da un processo figlio.
Una struttura SECURITY_DESCRIPTOR contiene i valori che identificano le informazioni di sicurezza applicate a un oggetto, tra cui le ACL:
typedef struct _SECURITY_DESCRIPTOR {
BYTE Revision;
BYTE Sbz1;
SECURITY_DESCRIPTOR_CONTROL Control;
PSID Owner;
PSID Group;
PACL Sacl;
PACL Dacl;
} SECURITY_DESCRIPTOR, *PISECURITY_DESCRIPTOR;
L’inizializzazione del security descriptor è affidata alla funzione InitializeSecurityDescriptor che prepara la struttura a ricevere nuove informazioni come la DACL (Discretionary Access List) di seguito passata con la funzione SetSecurityDescriptorDacl.
L’ACL è formata da una o più ACE (Access Control Entries) che sono inizializzate in strutture EXPLICIT_ACCESS.
I campi di questa struttura definiscono se i permessi di lettura, scrittura o esecuzione sono concessi o revocati.
typedef struct _EXPLICIT_ACCESS_W {
DWORD grfAccessPermissions; // permessi di lettura | scrittura | esecuzione
ACCESS_MODE grfAccessMode; // permessi concessi o negati
DWORD grfInheritance; // se e come l'ACL è ereditata da oggetti figlio
TRUSTEE_W Trustee; // utente, gruppo o servizio a cui si applica l'ACE
} EXPLICIT_ACCESS_W, *PEXPLICIT_ACCESS_W, EXPLICIT_ACCESSW, *PEXPLICIT_ACCESSW;
Una volta pronta la struttura viene aggiunta a una struttura ACL tramite la funzione SetEntriesInAcl.
Nel codice che segue vengono create due ACE, una che concede il controllo completo sul file a NT\SYSTEM e una che concede solo il permesso di lettura al gruppo Everyone.
PSID pEveryoneSid, pLocalSystemSid;
PACL pAclFile;
HANDLE hFile;
// Inizializzazione del SID per il gruppo noto Everyone
SID_IDENTIFIER_AUTHORITY sidAuthWorld = SECURITY_WORLD_SID_AUTHORITY;
if (!AllocateAndInitializeSid(&sidAuthWorld, 1, SECURITY_WORLD_RID, 0, 0, 0, 0, 0, 0, 0, &pEveryoneSid))
SetStatus(SERVICE_STOPPED);
SID_IDENTIFIER_AUTHORITY sidAuthNt = SECURITY_NT_AUTHORITY;
if (!AllocateAndInitializeSid(&sidAuthNt, 1, SECURITY_LOCAL_SYSTEM_RID, 0, 0, 0, 0, 0, 0, 0, &pLocalSystemSid))
SetStatus(SERVICE_STOPPED);
EXPLICIT_ACCESS eaFile[2];
ZeroMemory(&eaFile, 2 * sizeof(EXPLICIT_ACCESS));
// Creazione dell'ACE con permesso di sola lettura per il gruppo Everyone
eaFile[0].grfAccessPermissions = FILE_GENERIC_READ;
eaFile[0].grfAccessMode = SET_ACCESS;
eaFile[0].grfInheritance = NO_INHERITANCE;
eaFile[0].Trustee.pMultipleTrustee = nullptr;
eaFile[0].Trustee.MultipleTrusteeOperation = NO_MULTIPLE_TRUSTEE;
eaFile[0].Trustee.TrusteeForm = TRUSTEE_IS_SID;
eaFile[0].Trustee.TrusteeType = TRUSTEE_IS_WELL_KNOWN_GROUP;
eaFile[0].Trustee.ptstrName = (LPTCH)pEveryoneSid;
// Creazione dell'ACE con permessi di controllo completo per l'account LocalSystem
eaFile[1].grfAccessPermissions = FILE_ALL_ACCESS;
eaFile[1].grfAccessMode = SET_ACCESS;
eaFile[1].grfInheritance = NO_INHERITANCE;
eaFile[1].Trustee.pMultipleTrustee = nullptr;
eaFile[1].Trustee.MultipleTrusteeOperation = NO_MULTIPLE_TRUSTEE;
eaFile[1].Trustee.TrusteeForm = TRUSTEE_IS_SID;
eaFile[1].Trustee.TrusteeType = TRUSTEE_IS_USER;
eaFile[1].Trustee.ptstrName = (LPTCH)pLocalSystemSid;
// Aggiunta delle ACE nell'ACL
DWORD aclStatus = SetEntriesInAcl(2, eaFile, nullptr, &pAclFile);
if (aclStatus != ERROR_SUCCESS) {
FreeSid(pLocalSystemSid);
FreeSid(pEveryoneSid);
SetStatus(SERVICE_STOPPED);
}
// Inizializzazione del Security Descriptor
SECURITY_DESCRIPTOR sdFile;
InitializeSecurityDescriptor(&sdFile, SECURITY_DESCRIPTOR_REVISION);
if (!SetSecurityDescriptorDacl(&sdFile, true, pAclFile, false) {
FreeSid(pEveryoneSid);
FreeSid(pLocalSystemSid);
SetStatus(SERVICE_STOPPED);
}
// Inizializzazione della struttura SECURITY_ATTRIBUTES
SECURITY_ATTRIBUTES saFile;
saFile.nLength = sizeof(SECURITY_ATTRIBUTES);
saFile.lpSecurityDescriptor = &sdFile;
saFile.bInheritHandle = FALSE;
hFile = CreateFile(
L"C:\\test.txt",
FILE_APPEND_DATA,
FILE_SHARE_READ,
&saFile,
OPEN_ALWAYS,
FILE_ATTRIBUTE_NORMAL,
nullptr);
Circa i SID (Security Identifiers) dedicherò in futuro un post, per il momento è sufficiente sapere che sono identificatori univoci con i quali Windows gestisce utenti, gruppi e account nei contesti di sicurezza.
Comparazione permessi con e senza ACL
Giunti a questo punto il codice del servizio sarà così:
#include <Windows.h>
#include <iostream>
#include <aclapi.h>
// Variabili globali
WCHAR g_ServiceName[] = L"LoggerSvc";
SERVICE_STATUS_HANDLE g_ServiceStatusHandle;
SERVICE_STATUS g_ServiceStatus;
HANDLE g_FileHandle;
BOOL g_Status;
PSID g_pEveryoneSid, g_pLocalSystemSid;
// Funzione wrapper attorno a SetServiceStatus per cambiare lo stato del servizio
void SetStatus(DWORD state) {
g_ServiceStatus.dwCurrentState = state;
SetServiceStatus(g_ServiceStatusHandle, &g_ServiceStatus);
}
// Funzione per gestire le richieste di control provenienti dal Service Control Handler
void WINAPI LoggerHandler(DWORD dwControl) {
switch (dwControl) {
case SERVICE_CONTROL_STOP:
CloseHandle(g_FileHandle);
FreeSid(g_pEveryoneSid);
FreeSid(g_pLocalSystemSid);
SetStatus(SERVICE_STOPPED);
}
}
// Funzione ServiceMain
void WINAPI LoggerSvcMain(DWORD dwNumServicesArgs, LPWSTR* lpServiceArgVectors) {
// Registrazione dell'handler al SCM
g_ServiceStatusHandle = RegisterServiceCtrlHandler(g_ServiceName, LoggerHandler);
SetStatus(SERVICE_START_PENDING);
// Inizializzazione dele risorse
PACL pAclFile;
// Inizializzazione del SID per il gruppo Everyone
SID_IDENTIFIER_AUTHORITY sidAuthWorld = SECURITY_WORLD_SID_AUTHORITY;
if (!AllocateAndInitializeSid(&sidAuthWorld, 1, SECURITY_WORLD_RID, 0, 0, 0, 0, 0, 0, 0, &g_pEveryoneSid))
SetStatus(SERVICE_STOPPED);
SID_IDENTIFIER_AUTHORITY sidAuthNt = SECURITY_NT_AUTHORITY;
if (!AllocateAndInitializeSid(&sidAuthNt, 1, SECURITY_LOCAL_SYSTEM_RID, 0, 0, 0, 0, 0, 0, 0, &g_pLocalSystemSid))
SetStatus(SERVICE_STOPPED);
EXPLICIT_ACCESS eaFile[2];
ZeroMemory(&eaFile, 2 * sizeof(EXPLICIT_ACCESS));
// Inizializzazione del primo elemento EXPLICIT_ACCESS per accesso in sola lettura per il gruppo Everyone
eaFile[0].grfAccessPermissions = FILE_GENERIC_READ;
eaFile[0].grfAccessMode = SET_ACCESS;
eaFile[0].grfInheritance = NO_INHERITANCE;
eaFile[0].Trustee.pMultipleTrustee = nullptr;
eaFile[0].Trustee.MultipleTrusteeOperation = NO_MULTIPLE_TRUSTEE;
eaFile[0].Trustee.TrusteeForm = TRUSTEE_IS_SID;
eaFile[0].Trustee.TrusteeType = TRUSTEE_IS_WELL_KNOWN_GROUP;
eaFile[0].Trustee.ptstrName = (LPTCH)g_pEveryoneSid;
// Inizializzazione del primo elemento EXPLICIT_ACCESS per accesso in controllo completo per l'account LocalSystem
eaFile[1].grfAccessPermissions = FILE_ALL_ACCESS;
eaFile[1].grfAccessMode = SET_ACCESS;
eaFile[1].grfInheritance = NO_INHERITANCE;
eaFile[1].Trustee.pMultipleTrustee = nullptr;
eaFile[1].Trustee.MultipleTrusteeOperation = NO_MULTIPLE_TRUSTEE;
eaFile[1].Trustee.TrusteeForm = TRUSTEE_IS_SID;
eaFile[1].Trustee.TrusteeType = TRUSTEE_IS_USER;
eaFile[1].Trustee.ptstrName = (LPTCH)g_pLocalSystemSid;
// Creazione dell'ACL con le due ACE
DWORD aclStatus = SetEntriesInAcl(2, eaFile, nullptr, &pAclFile);
if (aclStatus != ERROR_SUCCESS) {
FreeSid(g_pLocalSystemSid);
FreeSid(g_pEveryoneSid);
SetStatus(SERVICE_STOPPED);
}
// Inizializzazione del Security Descriptor
SECURITY_DESCRIPTOR sdFile;
InitializeSecurityDescriptor(&sdFile, SECURITY_DESCRIPTOR_REVISION);
if (!SetSecurityDescriptorDacl(&sdFile, true, pAclFile, false)){
FreeSid(g_pEveryoneSid);
FreeSid(g_pLocalSystemSid);
SetStatus(SERVICE_STOPPED);
}
// Inizializzazione degli attributi di sicurezza
SECURITY_ATTRIBUTES saFile, saMailslot;
saFile.nLength = sizeof(SECURITY_ATTRIBUTES);
saFile.lpSecurityDescriptor = &sdFile;
saFile.bInheritHandle = FALSE;
// Creazione/apertura del file log
g_FileHandle = CreateFile(
L"C:\\LoggerSvc.log",
FILE_APPEND_DATA,
FILE_SHARE_READ,
&saFile,
OPEN_ALWAYS,
FILE_ATTRIBUTE_NORMAL,
nullptr);
if (g_FileHandle == INVALID_HANDLE_VALUE) {
FreeSid(g_pEveryoneSid);
FreeSid(g_pLocalSystemSid);
SetStatus(SERVICE_STOPPED);
}
// Set service status as running
SetStatus(SERVICE_RUNNING);
}
int main(void) {
// Array of services
SERVICE_TABLE_ENTRY ServiceTableEntries[] = {
{ g_ServiceName, LoggerSvcMain },
{ nullptr }
};
// Determine if the service runs in its own process or the process is shared with other processes
g_ServiceStatus.dwServiceType = SERVICE_WIN32_OWN_PROCESS;
g_ServiceStatus.dwControlsAccepted = SERVICE_ACCEPT_STOP;
// The main thread becomes the service control dispatcher and launch another thread with the service main function
if (!StartServiceCtrlDispatcher(ServiceTableEntries))
return 1;
return 0;
}
Procediamo quindi alla compilazione del file e installazione del servizio nella macchina di test.
Appena avviamo il servizio possiamo verificare che il file è stato creato con successo.
Il file è creato con i criteri di sicurezza specificati, ovvero sola lettura per tutti gli utenti e permessi di controllo completo per l’account LocalSystem.
Se si prova a creare un file nella stessa directory, lasciando che erediti i criteri di sicurezza dalla cartella padre, i permessi sono diversi:

In questo modo è possibile proteggere quanto prodotto dal servizio, che sia un file o un mailslot, da utenti standard.
Da notare che un utente con poteri d’amministratore può effettuare l’override dei criteri di sicurezza degli oggetti e ottenerne l’accesso.
Conclusioni
I servizi sono un elemento importante in Windows e in ambito cybersecurity diventano cruciali per lo sviluppo di soluzioni EDR per il caricamento di driver e sensori.
Come vedremo, infatti, questa serie sui servizi si inserisce in un quadro più ampio di approfondimento sul funzionamento degli EDR e del kernel di Windows, con esempi pratici di sviluppo di driver software e tecniche di evasione.
Riferimenti
- CreateFileW function (fileapi.h) – Win32 apps | Microsoft Learn
- GetLastError function (errhandlingapi.h) – Win32 apps | Microsoft Learn
- Tutorial – Debug system error codes – Win32 apps | Microsoft Learn
- SECURITY_ATTRIBUTES – Win32 apps | Microsoft Learn
- SECURITY_DESCRIPTOR (winnt.h) – Win32 apps | Microsoft Learn
- Creating a Security Descriptor for a New Object in C++ – Win32 apps | Microsoft Learn
- InitiazliSecurityDescriptor | Microsoft Learn
- SetSecurityDescriptorDacl | Microsoft Learn
- SetEntriesinAcl | Microsoft Learn
- EXPLICIT_ACCESS
- ACL