Packer VB6 usato per veicolare il malware Pony

20/03/2019

malware p-code Packer Pony stealer vb6

Pony è un malware ben conosciuto[1] [2] [3] che ruba le credenziali di una lunga lista da programmi potenzialmente installati nella macchina infetta. Pony di per sè presenta solo tecniche di offuscazione statiche, in particolare tecniche volte a confondere i disassembler:

Nell’immagine sopra si vede chiaramente che il flusso di esecuzione viene redirottato, tramite una coppia di push/ret, verso un indirizzo (0x41051c) che si trova un byte oltre il target (0x4151b) di un salto successivamente analizzato dal disassembler (all’indirizzo 0x410518). Questa situazione genera un conflitto poichè la stessa area di memoria (quella successiva a retn) dovrebbe essere analizzata due volte (una volta partendo da 0x41051c ed una volta da 0x41051b), costringendo il disassembler a scegliere quale flusso sia quello reale. A causa dell’istruzione clc presente prima di jb, il secondo salto non si verificherà mai ma molti disassembler preferiscono dare priorità ad un salto condizionale diretto piuttosto che ad uno indiretto (il push/ret) per cui il flusso non viene correttamente analizzato.

Tuttavia, sebbene il codice di Pony faccia ampio uso di questo trucchetto, quanto esposto è solo un inconveniente minore nell’analisi di un malware che altrimenti si presenta in chiaro, perfino nelle sue stringhe:

Per evadere gli antivirus e complicarne il reverse-engineering Pony è quasi sempre distribuito con un packer. Recentemente il CERT-PA è venuto in possesso di un campione veicolato tramite un packer scritto in VB6. La caratteristica di questo packer è quella di essere compilato in p-code, il che farebbe pensare ad un packer interamente scritto in VB6.

Questa è il codice dell’evento Form_Load, punto di ingresso del packer. Per come è presentato l’output dal disassembler p-code, non sono evidenti azioni malevoli ma è evidente la natura sospetta del codice: le prime righe corrispondono al codice VB6 Trim(vbNullString) che non ha nessuna utilità e inoltre i tre metodi UNSCHEDULED8x sono richiamati svariate volte.

Nessuno dei metodi richiamati contiene codice che possa in qualche modo portare all’esecuzione di codice nativo, sono tutti composti da sequenze di op-code validi ma senza fine.Il disassembler p-code fa fatica a ricreare il codice in alcuni punti ma non sono quest’ultimi ad passare il controllo ad una funzione nativa.

Quello che succede è che il disassembler chiama Call l’opcode 0x10, facendo pensare ad una chiamata verso un metodo p-code, tuttavia le chiamate verso metodi p-code hanno op-code da 0x9 a 0xc. In particolare 0x10 è ThisVCallHResult, che invoca un metodo nativo. E’ facile intercettare quando un op-code viene interpretato poichè l’interprete VB6 utilizza un dispatcher-loop in cui gli op-code sono letti un byte alla volta (una sorta di enorme switch, o Select Case per gli amanti del Basic), per cui basta mettere un breakpoint hardware all’indirizzo dell’op-code interessato:

In questo frammento esi è il puntatore al p-code, l’op-code eseguito fa una chiamata nativa ad un metodo dell’oggetto corrente tramite la sua vtable. All’offset 0x6f8 è presente un puntatore all’entry-point nativo del packer (questo puntatore fa parte dell’oggetto Form UNSCHEDULED e viene copiato nell’instanza in memoria dal runtime VB6).

Il codice nativo del packer è facilmente riconoscibile non solo dalle prime istruzioni mostrate sopra ma anche da quelle successive:

Il codice è pieno di istruzioni ridondanti (ad esempio test seguito da cmp) e da operazioni su registri non GP tramite istruzioni SSE e soprattuto MMX, oggi di fatto non più usate (se non da qualche pessimo compilatore). Lo scopo di queste istruzioni è rendere difficile riconoscere il codice utile da quello inutile.

Il packer ha svariate tecniche antidebug e utilizza metodi offuscati per compiere i check antidebug. Ad esempio per accedere al PEB anzichè accedervi direttamente dal TEB, accede al campo Self del TIB, oppure per il controllo del campo BeingDebugged del PEB anzichè utilizzare un test-and-branch utilizza il suo valore per calcolare l’indirizzo di un salto indiretto:

Durante la sua esecuzione il packer alloca un buffer RWX da cui continua l’esecuzione, il codice scrittovi proviene dalla sezione dati dell’eseguibile. Sono presenti altre tecniche antidebug ed antivm, che esporremo brevemente:

  • Tramite EnumWindows vengono contate le finestra, se il totale è meno di 12, il programma termina. Alcune sandbox segregano il processo in esame, impedendogli di enumerare le finestre di altri processi.
  • Vengono usati GetTicksCount e Sleep per verificare se una pausa di un X di secondi corrisponde effettivamente ad X secondi di tempo reale (con un margine). Molte sandbox disattivano le pause ma non GetTicksCount.
  • Utilizza SetErrorMode con valori non documentati per verificare se l’API è emulata o reale (Imposta un valore non documentato e poi lo rilegge, se è uguale l’API è reale).
  • Utilizza SetLastError per verificare la presenza di alcuni debugger che modificano l’errore impostato.
  • Utilizza GetCursorPos per determinare il vi è un utilizzo del mouse.

Il malware utilizza ancora un’altra tecnica antidebug che vale la pena approfondire:

Il flusso inizia dove indicato, viene controllato il campo WOW32Reserved del TEB per verificare se il processo gira su un OS a 64-bit (WOW32Reserved è usato per dispatchare le syscall verso il kernel a 64-bit). Se questo è il caso, una volta eseguiti i vari salti, il malware esegue una chiamata far. Dato che gli OS a 64-bit utilizzano una modalità del processore in cui la segmentazione è di fatto (ma non completamente) disabilitata e che comunque il modello di segmentazione usato da Windows NT in poi è sempre stato flat (4GiB di segmenti, pari al massimo indirizzabile allora senza PAE), l’utilizzo di una chiamata far è estremamente sospetto (specie in un programma in userspace).

Quello che succede è che 0x33 è il selettore per il segmento di codice a 64-bit (bit L settato, si vedano i capitoli 3 e 4 del Manuale Intel Vol 3), di fatto la chiamata far permette di passare da codice a 32-bit a 64-bit. I debugger non sono in grado di gestire questo passaggio per cui si ricorre all’analisi statica (ndisasm, del pacchetto NASM, permette di disassemblare codice x86 di qualsiasi bitness; tuttavia non è un full-descending. IDA permette di disassemblare file raw). E’ anche possibile eseguire il codice in un eseguibile a 64-bit utilizzando un assemblatore (tipo NASM) ed un linker (tipo golink) ma si deve prestare attenzione che i moduli caricati saranno a 64-bit (il TEB ed il PEB invece sono automaticamente corretti per come funziona la segmentazione per fs e gs; si vedano i manuali Intel).

Questo è il codice a 64-bit:

La prima parte del codice utilizza il TEB per controllare la versione dell’OS, tramite questa viene scelto il numero della syscall corrispondente a NtProtectVirtualMemory, quest’API viene usata per rendere scrivibile la sezione del codice di ntdll.dll. Si può vedere nel blocco a sinistra nella seconda riga come venga usato il PEB e parsato, in parte, l’header PE per ottenere i valori necessari; nel blocco accanto viene richiamata l’API (syscall_entry è dove è presente l’istruzione syscall). Il blocco successivo cerca un frammento di codice all’interno di ntdll.dll (i due rami corrispondono alle versione 6 e non di Windows) e una volta trovato sovrascrive un blocco di codice.

Questa scrittura in realtà non fa altro che ripristinare il codice originario della DLL nel caso fosse stato modificato da un monitor o con dei breakpoint software, non viene inserito codice malevolo ed una volta finito il codice ritorna ad utilizzare il descrittore per un segmento a 32-bit. Questa procedura di ripristino del codice viene effettuata anche dal codice a 32-bit su un differente blocco.

Da qui il packer inizia ad effettuare le azioni malevoli:

  • Crea una copia di sè stesso in %TEMP%\subfolder\filename.scr
  • Crea un file VBS in %TEMP%\subfolder\filename.vbs
  • Viene eseguito il file VBS, il quale crea la chiave di registro di autoavvio (chiamata HKCU\Software\Microsoft\CurrentVersion\Run\Registry Key Name) che invoca lo script VBS con un parametro “-ax”
  • Esegue il VBS con il parametro -ax, il quale invoca la copia del malware in %TEMP%.
  • Il malware entra in un ciclo che rilancia il VBS qualora termini.

Il contenuto del VBS è:

Il malware se eseguito da %TEMP%, dopo i controlli antidebug, invece che eseguire le azioni sopra, decodifca in memoria il payload. Questi è preso dalla sezioni dati, cercando delle firme ed utilizzando un’operazione di XOR per la decodifica.

Il payload è poi eseguito con CreateProcessInternalW, come anticipato si tratta di Pony, un malware volto al furto di dati e credenziali.

Indicatori di compromissione

IoC (.txt) – File globale : Domini, hash files (SHA1, MD5 e SHA256), URL

IoC (HASHr.txt) – Lista dei soli hash file da utilizzare in combinazione con il tool HASHr