ASTesla, Telegram e la crittoanalisi dei fogli Excel protetti

20/10/2020

ASTesla

Di ASTesla, parente stretto di AgentTesla, abbiamo già parlato, evidenziando come tra i suoi metodi di esfiltrazione figurassero l’invio di e-mail, l’upload su FTP, una richiesta POST (eventualmente tramite TOR) o l’utilizzo di un bot Telegram.
Ma una cosa è la teoria ed una è la pratica: ci è capitato, nella giornata di ieri, un campione di ASTesla configurato per usare Telegram, una scelta fin ora rara a dispetto del più usato invio di e-mail (seguito dall’upload su FTP).

Altri aspetti della campagna, non italiana, si sono rilevati particolari, come la scelta di usare un XLSX con un foglio protetto.

L’e-mail

Come anticipato la campagna non era rivolta all’Italia, l’e-mail è in inglese con l’invitante oggetto “Agro Mega Trading

Si fa riferimento ad un listino prezzi per una fantomatica Request For Quotation.

Il file XLSX allegato

Il file allegato è un file XLSX, un fatto insolito dato che questo tipo di file non può contenere macro.
Un metodo solitamente usato per aggirare questa restrizione è quello di usare dei template in un formato che invece può contenerle, tipo XLS o DOC.

Per verificare questa ipotesi è sufficiente aprire il file XLSX come se fosse un archivio ZIP (che di fatto è) e verificare i file XML contenuti se presentano riferimenti a documenti esterni (hostati in qualche dominio).

Ma il file in questione non è un archivio ZIP:

$ file Agro\ Mega\ Trading\ RFQ.xlsx
Agro Mega Trading RFQ.xlsx: CDFV2 Encrypted

Si tratta quindi di un file in formato CFB, ma non era un XLSX?

Per fugare ogni dubbio, apriamo il file con LibreOffice Calc che non può eseguire il contenuto malevolo pensato per Excel:

Calc non ha problemi ad aprire il file, il quale presenta alcune caratteristiche rilevanti:

  • Si invita ad abilitare il contenuto dalla tipica barra gialla di Excel. Indice che il file contiene qualcosa di malevolo.
  • Il foglio 2 è protetto da una password (si nota il lucchetto).
  • C’è anche un foglio 1 nascosto, contenente dei grafici di poco significato.

Ma dove si trova il contenuto malevolo? Forse è necessario sbloccare il foglio 2 con la relativa password?

In realtà il bandolo della questione da trovare è più facile del previsto.
Analizziamo il contenitore CFB del file XLSX:

$ mscfb Agro\ Mega\ Trading\ RFQ.xlsx mscfb_out
$ tree mscfb_out
mscfb_out/
└── Root Entry
├── %06%00DataSpaces
│   ├── DataSpaceInfo
│   │   └── StrongEncryptionDataSpace
│   ├── DataSpaceMap
│   ├── TransformInfo
│   │   └── StrongEncryptionTransform
│   │   └── %06%00Primary
│   └── Version
├── EncryptedPackage
└── EncryptionInfo
5 directories, 6 files

Nessuno di questi file contiene le informazioni del foglio di calcolo (come l’immagine mostrata prima che invita ad abilitare il contenuto) ma LibreOffice è comunque in grado di leggere il documento per cui la cifratura deve utilizzare una password o un metodo standard.

Ma, domanda retorica, qual è la password per eccellenza dei documenti Excel?

Proviamo quindi a decifrare il documento:

$msoffcrypto-tool -p VelvetSweatshop Agro\ Mega\ Trading\ RFQ.xlsx unenc.xlsx
$file unenc.xlsx
unenc.xlsx: Microsoft Excel 2007+

Ora siamo in presenza di un file XLSX vero e proprio, possiamo scompattarlo con un qualsiasi gestore di archivi e vedere quale sia il contenuto malevolo.

Non sono presenti template (una semplice grep rileva che non sono presenti URL sospetti negli attributi) ma sono presenti invece due oggetti OLE:

zippedout/
...
└── xl
├── drawings
...
├── embeddings
│   ├── oleObject1.bin
│   └── oleObject2.bin

Analizzandoli entrambi con mscfb, vediamo che il primo è un oggetto proveniente dall’Equation editor, siamo quindi in presenza di un contenuto malevolo che sfrutta la ben nota CVE-2017-11882.

[mscfb.dir.walk] Directory entry:
[mscfb.dir.walk] 	Name: 
[mscfb.dir.walk] 		52:00:6f:00:6f:00:74:00  20:00:45:00:6e:00:74:00
[mscfb.dir.walk] 		72:00:79:00:00:00:00:00  00:00:00:00:00:00:00:00
[mscfb.dir.walk] 	Name length: 22
[mscfb.dir.walk] 	Safe name: Root Entry
[mscfb.dir.walk] 	Type: 5 (ROOT)
[mscfb.dir.walk] 	Color: 0 (R)
[mscfb.dir.walk] 	Left: -1
[mscfb.dir.walk] 	Right: -1
[mscfb.dir.walk] 	Child: 1
[mscfb.dir.walk] 	CLSID (raw): 02:ce:02:00:00:00:00:00  c0:00:00:00:00:00:00:46
[mscfb.dir.walk] 	CLSID: {0002ce02-0000-0000-c000-000000000046} (MS Equation 3.0)
[mscfb.dir.walk] 	State: 0
[mscfb.dir.walk] 	Creation time: 00:00:00:00:00:00:00:00
[mscfb.dir.walk] 		1601-01-01 00:00:00UTC 
[mscfb.dir.walk] 	Modified time: f0:f5:f2:fb:57:a5:d6:01
[mscfb.dir.walk] 		2020-10-18 14:07:16UTC 
[mscfb.dir.walk] 	Start of stream (S): 3
[mscfb.dir.walk] 	Size of stream: 0x0'00000800 (ignore high DWORD for Major 3)

L’analisi dei payload che sfruttano questa vulnerabilità è rimandata ad altri tempi, si tratta comunque ormai solo di payload offuscati (un esempio, qui).

Il payload scarica il file hxxp://23.95.13.131/kings.exe.

Durante l’analisi dei file XML abbiamo inoltre individuato il tag per la protezione del foglio 2:

<sheetProtection password="8BCF" sheet="1" objects="1" scenarios="1" selectLockedCells="1" selectUnlockedCells="1"/>

Incuriositi, poichè ovviamente 8BCF non è la password reale, abbiamo deciso di rispolverare il vecchio algoritmo usato da Excel per l’hashing delle password.
Ma prima una breve introduzione sul malware.

Il malware

kings.exe è un packer .NET, offuscato ma piuttosto inefficacemente; è infatti relativamente semplice ottenere il payload.

Basta un breakpoint dove indicato per ottenere il payload

Il payload è a sua volta un packer .NET, uno sguardo veloce alle sue risorse ci evita persino la sua analisi:

La stringa ExnDEuDI, in unicode, compare spesso in questa risorsa.

Notare come in questa risorsa la stringa ExnDEuDI compaia spesso, è sintomo questo di un payload offuscato tramite XOR (sezioni di byte nulli rilevano la chiave in questi casi).

Usando CyberChef è facile decifrare la risorsa usando XOR e la stringa unicode come chiave.

Come anticipato il payload è ASTesla, seguendo l’analisi precedente, è facile riconoscerlo ed ottenerne la configurazione.

ASTesla e Telegram

Questa versione di ASTesla è offuscata in modo diverso, è facile identificare il metodo che decifra le stringhe:

<<EMPTY_NAME>> è il metodo che decifra le stringhe, il suo Metadata Token è 0x0600022D

Possiamo quindi chiedere a de4dot di deoffuscarle per noi:

>de4dot astesla.exe --strtyp emulate --strtok 0600022D

Le stringhe deoffuscate

Il codice presenta dell’ofuscamento CFO ma le funzionalità sono identiche a quelle viste nell’articolo apposito ed è quindi facile ricostruire nomi ed azioni.

In particolare la variabile che indica il tipo di esfiltrazione ha valore 3.

Valore corrispondente all’utilizzo di un bot Telegram. Infatti la configurazione contiene l’URL (con ID e Auth Token del bot) da usare.

A questo punto ci chiediamo cosa possiamo fare con l’ID e il token del bot?

Un rapido sguardo alle API di Telegram ci rileva che possiamo fare molto.
Possiamo ad esempio ottenere gli ultimi messaggi inviati con getUpdates:

curl -X POST "https://api.telegram.org/bot1306160776:[redatto]/getUpdates

Un esempio di messaggio ottenuto come risposta:

{
   "update_id":530306906,
   "message":{
      "message_id":35,
      "from":{
         "id":1120598411,
         "is_bot":false,
         "first_name":"BETTY",
         "last_name":"BUTTER",
         "username":"bettybuta",
         "language_code":"en"
      },
      "chat":{
         "id":1120598411,
         "first_name":"BETTY",
         "last_name":"BUTTER",
         "username":"bettybuta",
         "type":"private"
      },
      "date":1603117863,
      "forward_from":{
         "id":1306160776,
         "is_bot":true,
         "first_name":"excel xlx",
         "username":"officexlx_bot"
      },
      "forward_date":1603104871,
      "document":{
         "file_name":"[redatto]",
         "mime_type":"text/html",
         "file_id":"[redatto]",
         "file_unique_id":"[redatto]",
         "file_size":5099
      },
      "caption":"New PW Recovered!\n\nUser Name: [redatto]\nOSFullName: Microsoft Windows 7 Home Premium \nCPU: Intel(R) Core(TM)2 Quad  CPU   Q9300  @ 2.50GHz\nRAM: 3991,25 MB"
   }
}

Mostra come il bot invii i messaggi ad una chat tra l’utente excel xlx (username officexlx_bot) e l’utente BETTY BUTTER (username bettybuta).

E’ possibile ottenere il file allegato ad ogni messaggio, contenente le password rubate, cancellare i messaggi e gestire la chat.

Esempio di un file con le password rubate

E’ inoltre possibile ottenere le informazioni sui membri della chat.

L’immagine di profilo di @bettybuta, a cui sono inviati i messaggi con le password rubate
{
   "ok":true,
   "result":{
      "id":1120598411,
      "first_name":"BETTY",
      "last_name":"BUTTER",
      "username":"bettybuta",
      "type":"private",
      "photo":{
         "small_file_id":"AQADBAADP7MxG7NtSFAACAFZ1CJdAAMCAAOL-cpCAARiym-a7uhHroHUBgABGwQ",
         "small_file_unique_id":"AQADAVnUIl0AA4HUBgAB",
         "big_file_id":"AQADBAADP7MxG7NtSFAACAFZ1CJdAAMDAAOL-cpCAARiym-a7uhHroPUBgABGwQ",
         "big_file_unique_id":"AQADAVnUIl0AA4PUBgAB"
      }
   }
}

Crittoanalisi 101: come non hashare le password

Chiudiamo questo articolo con un semplice esempio di crittoanalisi.

Avevamo trovato nel file Excel la “password” per lo sblocco del foglio protetto.
Ovviamente non si tratta di una password ma dell’hash di questa.

Un hash a 16 bit descritto nel dettaglio in questa pagina e che si può riassumere nella seguente funzione Python:

def hash(pwd:str) -> int:
  acc = 0
  for i, c in enumerate(pwd):
    s = ord(c) << (i+1)
    s = (s & 0x7fff) + (s >> 15)
    acc ^= s
    
  return acc ^ len(pwd) ^ 0xce4b

Dove ogni carattere è shiftato a sinistra di un numero di bit pari alla sua posizione (iniziando da 1) e XORato nel risultato.
Prima di essere xorato però i bit dal 15 in poi (notare che il bit15 è il 16° bit, in quanto il primo bit è il bit0 e, nella nostra convenzione, quest’ultimo è l’LSb) sono riavvolti alla posizione 0, indicata dall’operazione s >> 15 nel codice.

La prima parte dell’algoritmo di hash

C’è un modo per ottenere la password dall’hash?

No, lo XOR con più di due elementi è distruttivo.

C’è il modo di trovare una collisione? Ovvero una password diversa dall’originale ma comunque funzionante?

Sì, l’idea è che ogni carattere della password modifica un valore accumulato via via, ed inizialmente pari a zero. Se ci limitiamo a far settare ad ogni carattere al massimo un solo bit, possiamo generare la password che dà l’hash voluto.

Se ad esempio usassimo solo caratteri di valore 0x80 e 0x00, potremmo usare 0x80 quando il bit (i+8) mod 15 dell’hash è 1 e 0x00 quando è zero (dove i è la posizione del carattere, partendo da zero).
Con 15 caratteri è possibile coprire tutti i bit dell’hash (il bit15 è sempre 1 per via di come è strutturato l’algoritmo).
Usare caratteri che hanno al più un solo bit a 1 e sempre nella solita posizione, rende facile la rottura dell’hash.

Ma la seconda parte dell’algoritmo XORa il risultato con la lunghezza della password, come ottenere la lunghezza di una password che non conosciamo ancora?
In realtà, come abbiamo visto sopra, la nostra password sarà sempre di 15 caratteri, per cui la seconda parte dell’algoritmo (quella che fa lo XOR con la lunghezza e la costante) può essere “rotta” facilmente.
Assumeremo quindi che il valore dell’hash faccia rifermento al valore finale della prima parte dell’algoritmo (ovvero senza la seconda parte).

Sebbene questo metodo funzioni, non è ideale. I caratteri 0x80 e 0x00 non sono stampabili!

E’ possibile usare solo caratteri stampabili?

Sì, di fatto, la vera “sfida” è quella di usare solo caratteri stampabili.

Dato che 0x80 non è usabile possiamo passare al candidato successivo: 0x40. Questo valore corrisponde alla chiocciola (@) nel codice ASCII, per cui è perfetto.
Notare che adesso 0x40 va usato se il bit (i+7) mod 15 è 1 nell’hash (prima controllavamo il bit (i+8) mod 15).

Tuttavia 0x00 non è ugualmente aggiustabile con la solita facilità, infatti questo valore ci è utile per non settare alcun bit, per cui non può avere bit a 1. Zero è l’unico valore usabile!

Per aggirare questo problema dobbiamo cambiare strategia, dato che siamo forzati a settare comunque un bit per ogni carattere, tanto vale usare direttamente 0x40 ed eventualmente azzerare il bit al passo successivo.
Anzichè controllare se il bit (i+7) mod 15 è 1 o meno, controlliamo se il bit (i+6) mod 15 nel valore accumulato è diverso dal valore nell’hash.
Nel caso lo sia, usiamo 0x60 come carattere altrimenti 0x40.

0x60 corrisponde al carattere di back tick nel codice ASCII, non un carattere molto usato ma comunque stampabile.
Dato che 0x60 ha il bit5 settato, quando è xorato nel valore accumulato, farà cambiare di segno al bit (i+6) mod 15.
Quello che volevamo ottenere.

Ci sono due ulteriori accorgimenti:

  • All’ultima iterazione, non possiamo usare 0x40 o 0x60 perchè questi cambierebbero il bit settato con il primo carattere. Possiamo quindi limitarci ad usare solo 0x00 o 0x20, in pratica il bit6 deve essere zero.
  • Ma se usiamo 0x00 come ultimo carattere, questo non è comunque stampabile.
    Per risolvere usiamo la proprietà dello XOR per cui a xor 1 === not a.
    Se l’ultimo carattere è 0x00, lo trasformiamo in un 0x20. Questo ha l’effetto di cambiare un bit nell’hash. Contrastiamo questo effetto XORando il valore 0x10 al primo carattere della password trovata.
    Per la circolarità dell’algoritmo il bit5 dell’ultimo carattere della password ed il bit4 del primo sono XORati insieme.
    (In generale la relazione vale per ogni bit di ogni carattere adiacente)

La procedura per sintetizzare una password è mostrata sotto:

def bts(num:int, mask:int, bit:int) -> bool:
  return (num ^ mask) & (1 << bit) > 0
  
def synth_pass(target:int) -> str:
  target2 = target ^ 0xce4b ^ 0xf
  
  pwd = ""
  acc = 0
  for i in range(15):
    cur = (0x40 if i < 14 else 0) + (0x20 if bts(target2, acc, (i+6) % 15) else 0)
    aux = (cur << (i+1))
    acc ^= ((aux & 0x7fff) + (aux >> 15))
    
    pwd += chr(cur)
  
  if cur == 0:
    pwd  = chr(ord(pwd[0]) ^ 0x10 ) + pwd[1:-1] + chr(ord(pwd[-1]) | 0x20)
  
  return pwd

Usandola, otteniamo che per l’hash 8BCF, trovato nel documento Excel, otteniamo la password

"@@@`@```@@@`@` "

(senza virgolette ma con uno spazio finale).

[Grazie a Diego Zanga per averci segnalato un errore]

Prima di sbloccare il foglio 2 con la password trovata
Il foglio 2 sbloccato

Il foglio 2 non presenta contenuto particolare, l’utilizzo di una password per bloccarlo è stata solo una mossa per rendere più credibile la necessità di attivare il vero contenuto malevolo cliccando sulla barra gialla di Excel.

Decifra il tuo hash

Taggato  ASTesla