Oggetto: Rompere un captatore informatico #2
Mittente: underto
Data: 30/03/2017 00:00

Rompere un captatore informatico #2

ma quanto sono sicuri questi captatori informatici? nel proseguire il nostro studio sull'argomento abbiamo cercato di capire come funzionano e se sono veramente dei generatori di prove affidabili e veritiere.

nella prima puntata su come rompere i captatori informatici, abbiamo visto come sia possibile falsificare le prove prima che vengano raccolte, cerchiamo ora di capire come funziona l'invio delle prove in RCS e vediamo se è possibile inviare delle prove create ad-hoc al suo posto.

Reverse engeenering

la descrizione del reverse engeenering che segue è molto piu' lineare di come si è svolta nella realtà, abbiamo saltato le parti noiose e ripetitive (openssl l'abbiamo ricompilato almeno 6 volte) e cercato inoltre di rendere i ragionamenti sequenziali, quando nella realtà tante scelte sono state fatte intuitivamente. siamo inoltre convinti che si poteva ottenere lo stesso risultato in altri N modi e che quello da noi usato è sicuramente molto grezzo.

innanzitutto, ipotizziamo di avere solo il binario, il malware. dobbiamo capire cosa fa e come si comporta. creiamo quindi un ambiente di studio, un container che lo isoli in un posto sicuro dove non puo' fare danni e in cui è possibile studiarne il comportamento.

noi abbiamo usato runc usando come rootfs una debian dentro una directory montata con loggedfs per controllare tutti i suoi movimenti sul file system (file creati, letti, etc...).
vediamo subito che si installa dentro /var/crash/.report*/whoopsie-report e cerchiamo di capire di che file si tratta con un:

$ file whoopsie-report
whoopsie-report: ELF 64-bit LSB executable, x86-64, version 1 (GNU/Linux), statically linked, stripped`

è un piccolo file statico di 77kb, un po' troppo piccolo per essere veramente statico, lo avviamo con strace ./whoopsie-report e notiamo infatti che cerca di aprire parecchie librerie di sistema, tra cui:

e qui gia' ci sarebbe spazio per divertirsi: libX11 viene sicuramente utilizzata per fare gli screenshot dello schermo, sarebbe possibile ricompilarla per fare in modo che per alcuni programmi dei metodi tornino cose inaspettate.

ad un certo punto notiamo che vengono creati dei files dentro /var/crash/.report*/.tmp*, sono presumibilmente le prove raccolte, anche perchè ciclicamente si moltiplicano.

all'interno solo dati binari, controlliamo se sono solamente compressi, ma non ci sembra, saranno cifrati? ma come? per una cosa del genere a noi verrebbe da utilizzare le librerie standard, quindi openssl, ma come possiamo esserne sicuri? in effetti il malware accede a libssl.
perdiamo un po' di tempo provando con gdb ma stufi di seguire tutti i thread, ci viene in mente di ricompilare libssl inserendo del debug che ci mostri come vengono cifrati i dati.

per ricompilare un pacchetto debian seguiamo questa guida, ci armiamo di pazienza e scarichiamo il tutto.
cercando come si cifra con openssl vediamo che ci sono parecchie chiamate differenti, quindi proviamo con ltrace (con strace si vedono le syscall, con ltrace le libcall) a vedere se possiamo capire quale chiamata viene usata:

 $ ltrace ./whoopsie-report
 Couldn't find .dynsym or .dynstr in "/proc/30234/exe"

questo e' parecchio strano ma smanettando un po' troviamo con strings la seguente stringa dentro il malware:

$ strings ./whoopsie-report
$Info: This file is packed with the UPX executable packer http://upx.sf.net $

leggiamo che upx è un packer che comprime binari, installiamo quindi upx-ucl e proviamo un upx-ucl -d whoopsie-report per fare il processo inverso e decomprimere il binario:

         File size         Ratio      Format      Name
    --------------------   ------   -----------   -----------
     261044 <-     77972   29.87%  linux/ElfAMD   whoopsie-report

ora abbiamo il binario "in chiaro" e riprovando con ltrace (qui siamo passati dentro una macchina virtuale con VirtualBox perchè dentro runc si bloccava per qualche motivo) otteniamo le chiamate usate dal malware, in particolare nel nostro interesse ricadono le seguenti:

a questo punto cerchiamo quei metodi nei sorgenti di libssl e troviamo la prima chiamata in crypto/evp/names.c linea 112 a cui aggiungiamo questo debug che scrive il cifrario usato dentro un file

const EVP_CIPHER *EVP_get_cipherbyname(const char *name)
{
  const EVP_CIPHER *cp;

  // DEBUG: scrivo il cifrario utilizzato dentro un file
  FILE *f;
  f = fopen("/tmp/debug_ciphername", "w"); // <- qui trovo il cifrario
  fprintf(f, "%s", name);
  fclose(f);

  cp = (const EVP_CIPHER *)OBJ_NAME_get(name, OBJ_NAME_TYPE_CIPHER_METH);
  return (cp);
}

e la seconda chiamata in crypto/evp/bio_enc.c e anche qui aggiungiamo del debug:

void BIO_set_cipher(BIO *b, const EVP_CIPHER *c, const unsigned char *k,
               const unsigned char *i, int e)
{
  BIO_ENC_CTX *ctx;

  // DEBUG: scrivo i parametri del cifrario su file
  FILE *f;
  f = fopen("/tmp/debug_cipher", "a+");
  fprintf(f, "\n----\n");
  fprintf(f, "block_size: %d, key_len: %d, iv_len: %d\nkey: ", c->block_size, c->key_len, c->iv_len);
  for(int co=0; co<c->key_len; co++)
  {
    fprintf(f, "%02X",k[co]);
  }
  fprintf(f,"\n\niv: ");
  for(int co=0; co<c->iv_len; co++) {
    fprintf(f, "%02X", i[co]);
  }
  fprintf(f,"\n\n");
  fclose(f);
  ....

ricompiliamo le librerie, installiamo e rilanciamo il malware. dentro /tmp troviamo ora il cifrario usato dal malware e la chiave:

$ cat /tmp/debug_ciphername 
aes-128-cbc
$ cat /tmp/debug_cipher
----
block_size: 16, key_len: 16, iv_len: 16
key: 692CC9D0DC834E4663EF389470E445B4
iv: 00000000000000000000000000000000

(= a questo punto proviamo a decifrare i file salvati su disco dal malware, lo scopo è ovviamente tentare di scriverne noi uno ad-hoc, con la chiave a disposizione dovrebbe essere facile, invece perdiamo un sacco di tempo a capire come mai non riusciamo a decifrare la prima parte del file. pensavamo fosse legato al vettore di inizializzazione e invece nel file c'e' un header di qualche tipo, ovvero il blocco cifrato non comincia all'inizio (questo però ovviamente openssl non lo dice). il punto è che la lunghezza del testo cifrato ogni tanto non è un multiplo del BLOCK_SIZE del cifrario a blocchi usato, il che significa che c'è qualcosa che non dovrebbe esserci. abbiamo provato prima manualmente ad eliminare un po' di bytes prima di tentare il decrypt ed effettivamente ha funzionato:

#!/usr/bin/python
from Crypto.Cipher import AES
import sys

key = "692cc9d0dc834e4663ef389470e445b4"
IV =  "00000000000000000000000000000000"
encrypted = open(sys.argv[1]).read()
x = 16 - len(encrypted)%16 if len(encrypted)%16 else 0
x += 16*int(sys.argv[2])
encrypted = encrypted[x:]
aes = AES.new(key.decode('hex'), AES.MODE_CBC, IV.decode('hex'))
print aes.decrypt(encrypted)


$ chmod +x test.py
$ ./test.py .tmp-1491179387-220638-SJBo7P 10 > image.jpg

il file in questione (il .tmp-149..) è stato selezionato perchè dalle dimensioni poteva sembrare un'immagine, inoltre decifrandolo inizialmente c'erano stringhe che riconducevano al formato JPG (ma non trovavamo il magic byte DD F8!). quel 10 invece indica che prima dell'inizio dell'immagine all'interno del file salvato su disco, ci sono ben 160 bytes di header. il prossimo passo è cercare di scrivere noi l'immagine dentro uno dei file, quindi apriamo con gimp l'immagine estratta in precedenza (quindi con il vero screenshot) in modo da mantenerne le proprietà (colori, dimensioni, ecc.) e sostituirla dentro il file di cui sopra.

#!/usr/bin/python
from Crypto.Cipher import AES
import sys

key = "692cc9d0dc834e4663ef389470e445b4"
IV =  "00000000000000000000000000000000"
original_header = open(sys.argv[1]).read()[:168]
new_image = open('.tmp-1491179387-220638-SJBo7P','w')

# mantengo l'header come era prima senza indagare oltre
new_image.write(original_header)
fake_image = open(sys.argv[2]).read()

# padding
x = 16 - len(fake_image)%16 if len(fake_image)%16 else 0
fake_image += x*"\0"

# cifro l'immagine fake e la scrivo sul nuovo file
aes = AES.new(key.decode('hex'), AES.MODE_CBC, IV.decode('hex'))
new_image.write(aes.encrypt(fake_image))
new_image.close()

ora tocca solo aspettare e....

fake_screenshot1 fake_screenshot2

_TO* hacklab