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 2

Written by

in

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