YAU – Parte 11 – Il client, inizializzazione e configurazione

16/02/2021

yau

Il Client è il cuore di Ursnif.
Viene avviato tramite MSHTA e PowerShell ma da questi è in grado di diffondersi in altri processi, primi tra tutti explorer.exe ed i browser Firefox, Edge, Internet Explorer, Opera e Safari.

Il client è in grado di eseguire una serie di comandi, ottenuti dalla sua configurazione (contenuta in un JJ chunk) o come risposta dal C2.
C2 al quale può inviare dati sottoforma di POST verso URL analoghe a quella usata nel secondo stadio.

I comandi del client ricordano più quelle di un RAT e di un infostealer che di un banking trojan.
E’ vero che oltre alla possibilità di scaricare ed eseguire moduli terzi, elencare e inviare al C2 file, riavviare la macchina, fermare processi, catturare schermate (sia foto che video), rubare le password di Thunderbird, fare da keylogger, avviare VNC, rubare i cookie e certificati terzi, raccogliere informazioni sulla macchina, è anche in grado di leggere le informazioni ricevute ed inviate dai browser, ma non vi sono accorgimenti specifici per i siti di banche.

L’architettura del client è piuttosto elaborata ed i sorgenti di Gozi-ISFB, con i quali Ursnif condivide molto del codice, sono fondamentali per navigare la moltitudine di funzioni e strutture dati usate.

Rispetto a Gozi-ISFB, Ursnif aggiunge alcune funzionalità (come la possibilità di usare TOR e 15 nuovi comandi) e ne ignora altre.

YET ANOTHER URSNIF

Questo è l’undicesimo di una serie di articoli, tutti raggruppati qui.

I sorgenti di ISFB e Gozi

Ursnif condivide gran parte del codice con ISFB e Gozi, difatti i sorgenti disponibili in rete (e scaricabili anche da qui) offrono una fonte indispensabile per il reverse engineering.

Se in alcune parti Ursnif è identico ai suoi precedessori, in altre si discosta in modo significativo.
Compito di questa serie di articoli è anche quello di determinare come Ursnif si discosti dai precedessori.

Il DB IDA annotato è scaricabile qui.

Ursnif fa uso di una grande quantità di strutture dati e il suo codice contiene alcuni strati di astrazione che ne allungano il reverse engineering.
Per questo motivo forniremo evidenza solo delle parti più interessanti del malware.

Sia i sorgenti che il DB IDA sono consigliati per la lettura. E’ inoltre assunta la capacità base di muoversi all’interno di un repository di codice per la ricerca delle funzioni interessate.

Inizializzazione

Il client è avviato tramite uno script MSHTA che ne invoca uno PowerShell. Trattandosi di una DLL, il primo codice eseguito si trova in DllMain.
E’ presente anche un’altra funzione esportata (nmstool_1) ma questa non fa altro che richiamare l’entry-point PE.
Per cui, l’infezione procede da DllMain.

Come per gli stadi precedenti, c’è la gestione del numero di instanze attive tramite contatori atomici.
Il codice di Ursnig è identico a quello sorgente di ISFB-Gozi (d’ora in poi, semplicemente “codice sorgente”).

WINERROR CrmStartup(
	PVOID	pReserved
	)
{
	WINERROR Status = ERROR_UNSUCCESSFULL;
	NT_SID	Sid = {0};

	// The dll can be loaded twice or more, so it's a good idea to reinitialize variables.
	g_MachineRandSeed = 0;

	// Initializing default security attributes
	if (LOBYTE(LOWORD(g_SystemVersion)) > 5)
		LsaInitializeLowSecurityAttributes(&g_DefaultSA);
	else
		LsaInitializeDefaultSecurityAttributes(&g_DefaultSA);

	do	// not a loop
	{
		// Obtaining current user SID 
		if (!(LsaGetProcessUserSID(g_CurrentProcessId, &Sid)))
		{
			DbgPrint("ISFB_%04x: Failed to resolve current user SID.\n", g_CurrentProcessId);
			Status = ERROR_ACCESS_DENIED;
			break;
		}

		// Initializing rand seed with the hash of the machine ID taken from the user SID
		if (Sid.SubcreatedityCount > 2)
		{
			LONG i;
			for (i=0; i<(Sid.SubcreatedityCount-2); i++)
			{
//				DbgPrint("ISFB_%04x: SID.SubAthority[%u] = 0x%x.\n", g_CurrentProcessId, i, Sid.Subcreatedity[i+1]);
				g_MachineRandSeed += Sid.Subcreatedity[i+1];
			}

			// Randomizing installer-specific GUID values
			g_MachineRandSeed ^= uInstallerSeed;
		}
		else
		{
			DbgPrint("ISFB_%04x: Started within system process, exiting.\n", g_CurrentProcessId);
			Status = ERROR_INVALID_FUNCTION;
			break;
		}

		// Createing active state event
		if (!(g_ActiveEvent = CreateEvent(NULL, TRUE, FALSE, NULL)))
			break;

		// Generating random object names
		if (!CrmGenerateNames())
		{
			DbgPrint("ISFB_%04x: Failed to generate random names.\n", g_CurrentProcessId);
			break;
		}

#if _INJECT_AS_IMAGE
		// Setting our VEH to handle exceptions within our DLL image
		g_hExceptionHandler = AddVectoredExceptionHandler(FALSE, &CrmVectoredExceptionHandler);
#endif
		// Statring the dll normaly
		Status = CrmStartDll(pReserved);

	} while(FALSE);
	
	if (Status == ERROR_UNSUCCESSFULL)
		Status = GetLastError();

#if _INJECT_AS_IMAGE
	if (Status != NO_ERROR && g_hExceptionHandler)
	{
		RemoveVectoredExceptionHandler(g_hExceptionHandler);
		g_hExceptionHandler = NULL;
	}
#endif
			
	return(Status);
}
DllMain come trovata nel codice sorgente (client/client.c).
Il codice attuale di Ursnif, identico a quanto trovato nel sorgente.

L'esecuzione prosegue in ClientStartup dove sono effettuati, ancora una volta, i soliti passi degli stadi precedenti: viene ottenuto un handle al processo, decodificata la sezione bss, determinata la presenza di WOW64 e invocato il metodo che prosegue l'infezione (CrmStartup).

CrmStatup trovata nel client di Ursnif.

Contrariamente al codice sorgente, in Ursnif è presente un meccanismo per determinare se alla fine di ClientStartup terminare o meno il thread di esecuzione.
Nel caso l'entry-point del processo (ottenuto tramite NTQueryInformationThread) corrisponda a quello della DLL del client, il thread è terminato (in quanto la DLL non si trova in un processo infettato).

Nel sorgente questo passo è assente.

//
//	Client DLL initialization routine.
//
static WINERROR ClientStartup(
	HMODULE hModule,	// Current DLL base
	PVOID	pReserved	// Reserved DLL parameter	
	)
{
	WINERROR Status;

	do	// not a loop
	{
		if ((g_AppHeap = HeapCreate(0, 0x400000, 0)) == NULL)
		{
			Status = ERROR_NOT_ENOUGH_MEMORY;
			break;
		}

#if (defined(_USER_MODE_INSTALL) && !(_INJECT_AS_IMAGE))
		Status = InitGlobals(hModule, G_SYSTEM_VERSION | G_CURRENT_PROCESS_ID | G_CURRENT_MODULE_PATH | G_APP_SHUTDOWN_EVENT | G_CURRENT_PROCESS_PATH);
#else
		Status = InitGlobals(hModule, G_SYSTEM_VERSION | G_CURRENT_PROCESS_ID | G_APP_SHUTDOWN_EVENT | G_CURRENT_PROCESS_PATH);
#endif
		if (Status != NO_ERROR)
			break;

		if ((Status = CsDecryptSection(hModule, 0)) != NO_ERROR)
			break;

		if (PsSupIsWow64Process(g_CurrentProcessId, 0))
			g_CurrentProcessFlags = GF_WOW64_PROCESS;

		Status = CrmStartup(pReserved);
	} while(FALSE);

	return(Status);
}
ClientStartup come trovato nel sorgente.

CrmStartup è la routine che si occupa di inizializzare le variabili base di Ursnif. Anche questo passo è condiviso in gran parte con gli stadi precedenti, si tratta della generazione del seed (chiamanto g_MachineRandSeed nei sorgenti), del security descriptor e dei nomi (ottenuti dal seed) per gli eventi ed i mutex.

Un passo che nei sorgenti si trova altrove (Dentro ParserGetHostProcess, richiamata da CrmStartDll, a sua volta chiamata dalla funzione in analisi) è quello seguente:

CRC32(UPPER(PROCESS_NAME)), un passo che fa presupporre la natura infettante della DLL.

Questo codice calcola CRC32(UPPER(PROCESS_NAME)), ovvero il CRC32 (visto negli articoli precedenti e disponibile qui) del nome del file dell'immagine del processo, messo tutto in maiuscolo.

Questo valore sarà usato ampiamente per discriminare quale azioni compiere in base al processo in cui si trova la DLL.

Sia in Ursnif che nel sorgente è presente un Vectored Exception Handler, la cui funzione è quella di richiamare gli handler SEH presenti nella DLL anche quando è iniettata in altri processi.

Il flusso continua con la chiamata a CrmStartDll che contiene la logica principale per l'infezione.

Il sorgente è un po' diverso da quanto trovato in Ursnif ma i passi sono simili.

Ursnif usa una "Handle table" per gestire i buffer in cui accumulare i dati raccolti. Questa tabella è composta da una lista concatenata (la classica struttura LIST_ENTRY di Windows) di elementi, da uno stream COM e da vari oggetti di sincronizzazione.
Per la nostra analisi, la consideriamo come uno "storage" in cui poter inserire, rimuovere ed enumerare elementi (non è chiaro perchè è chiamata Handle table).

L'inizializzazione del meccanismo di notifiche per il caricamento di DLL e la tabella degli handle.

Oltre a questo, Ursnif inizializza un meccanismo per installare callback chiamati quando una DLL viene caricata (utile per l'hook just-in-time delle funzioni).
Da Windows Vista in poi utilizza l'API LdrRegisterDllNotification, per le versioni precedenti di Windows, cerca la lista di callback interni in NTDLL.DLL e vi aggiunge i propri callback.

//
//	Scans NTDLL's .data section for LdrpDllNotificationList:
//	searches for an initialized LIST_ENTRY, adds our callbak to the list and attempts to load any DLL, if the callback is being executed - 
//	 this LIST_ENTRY is what we looking for.
//
static PLIST_ENTRY LookupLdrpDllNotificationList(VOID)
{
	HMODULE hModule, NtDllBase = GetModuleHandle(szNtdll);
	PVOID	DataSection = PeSupGetFirstWritableSection((PCHAR)NtDllBase);
	PLIST_ENTRY pList = NULL;

	if (DataSection)
	{
		PLIST_ENTRY	pEntry = (PLIST_ENTRY)((PCHAR)NtDllBase + PeSupGetSectionRva(DataSection));
		PVOID		pEnd = (PVOID)((PCHAR)pEntry + PeSupGetSectionVSize(DataSection) - sizeof(LIST_ENTRY));
		BOOL		Complete = FALSE;
		LDR_DLL_NOTIFICATION_DESCRIPTOR Ldrn = {0};

		Ldrn.NotificationRoutine = &my_LdrDllNotificationCallback;
		Ldrn.NotificationContext = &Complete;
		InitializeListHead(&Ldrn.Entry);

		while (!Complete && ((ULONG_PTR)pEntry <= (ULONG_PTR)pEnd))
		{
			// Checking if there's an empty initialized list
			if ((pEntry->Flink == pEntry) && (pEntry->Flink == pEntry->Blink))
			{
				InsertHeadList(pEntry, &Ldrn.Entry);

				// Loading a DLL that surely hasn't been loaded yet
				if (hModule = LoadLibrary(_T("ntdsapi.dll")))
					FreeLibrary(hModule);

				RemoveEntryList(&Ldrn.Entry);

				if (Complete)
					pList = pEntry;
			}	// if ((pEntry->Flink == pEntry) && (pEntry->Flink == pEntry->Blink))
			pEntry += 1;	
		}	// while (!Complete && ((ULONG_PTR)pEntry <= (ULONG_PTR)pEnd))
	}	// if (DataSection)

	return(pList);
}
La funzione per la ricerca della lista di callback in NTDLL.DLL.

Il primo hook: modifica della command line

Appena finite le inizializzazioni, Ursnif utilizza il CRC32 del nome del processo per determinare se si trova eventualmente nel vero explorer.exe (e non in un processo con lo stesso nome) confrontando il process id ottenuto dalla finestra della shell (con GetShellWindow e GetWindowThreadProcessId).

Successivamente installa i primi hook, si tratta di hook per le API di creazione di processi (tipo CreateProcessA e simili) tramite la funzione AcStartup.

La chiamata (in basso) ad AcStartup e (sopra) della logica di controllo di EXPLORER.EXE

Gli hook sono installati modificando l'export directory della DLL che contiene l'API interessanta e le import directory di tutti i moduli.
La funzione che se ne occupa è presente anche nel sorgente con il nome di SetMultipleHooks.
Infatti gli hooks sono descritti da un array di strutture che contengono il nome della DLL, il nome della funzione e la procedura sostitutiva.

Esempio di array di hook.

Tutti gli hook di Ursnif sono impostanti in questo modo, rendendo semplice la loro identificazione.

L'intento di Ursnif è quello di modificare la command line dei processi creati aggiungendovi "--use-spdy=off --disable-http2" qualora si stia avviando un browser come Chrome od Opera (o simili) in modo da disabilitare il protocollo SPDY che non evidentemente non supporta (per la cattura dei dati).

Parte della funzione ParserModifyCmdLineW che modifica la command line dei processi creati.

Il registro di sistema

Ursnif usa il registro di sistema come uno storage condiviso tra le sue varie instanze iniettate in altre processi.
Le chiavi create si trovano dentro i percorsi visti negli articoli precedenti.
Non abbiamo analizzato l'utilizzo di ogni singola chiave ma il loro nome è piuttosto intuitivo, raccolgono le informazioni collezionate tramite alcuni dei comandi e mantengono lo stato dei comandi in corso.

Alcune chiavi usate da Ursnif.

Non elencheremo tutti i percorsi usati da Ursnif, il DB IDA allegato permette di ottenere i percorsi voluti con poco sforzo.

La configurazione

Subito dopo l'installazione dei primi hooks, Ursnif carica la propria configurazione.

CrmLoadINI è la funzione che carica la configurazione, dal registro di sistema o dal JJ chunk.

La configurazione è caricata dal registro di sistema (Chiave Ini nel percorso Software\AppDataLow\Software\Microsoft\) o dal JJ chunk in assenza di dati nel primo caso.

Nel JJ chunk la configurazione si presenta nel solito formato visto precedentemente (nel registro è codificata con un algoritmo simile a quello usato per la sezione bss, i dettagli nel DB IDA).

La configurazione del client è simile a quella del secondo stadio.

In blu sono evidenziati i C2, questi sono simili ma non identici a quello usato dal secondo stadio.
Infatti quest'ultimi C2 hanno la possibilità di salvare i dati ottenuti tramite post che utilizzano URL nel formato visto precedentemente nell'analisi del secondo stadio.

In giallo vi sono i dati non usati. L'URL della costituzione americana sembra essere usato da altri tool per generare nomi di domini plausibili da registrare.
Dato che quelli di Ursnif contengono nomi italiani, questo campo è un refuso di Gozi-ISFB.

In rosso i servizi che permettono di ottenere l'IP pubblico di una macchina.

In viola la configurazione che contiene i parametri comuni anche al secondo stadio (group id, chiave, pause).

In verde ci sono i comandi inizialmente eseguiti da Ursnif. Vedremo meglio questi comandi in seguito.

Tra le possibili configurazioni (non presenti in questo sample) vi sono anche due che riguardano TOR ed un proxy SOCKS.

Ursnif può scaricare un client TOR dall'URL indicato nella configurazione.

Il Proxy SOCKS è un proxy implementato nella DLL ed eseguito dentro explorer.exe (per evitare firewall), può essere configurato per mettersi in ascolto su una certa interfaccia e porta.

Il client id

Il client id è il group id. E' salvato nel registro, nello stesso percorso della configurazione (con nome Client anzichè reg).
Evidentemente è possibile che questo cambi nel tempo.

Taggato  yau