sábado, 15 de marzo de 2014

Descifrando msgstore.db.crypt5, la nueva base de datos de WhatsApp

Desde la última actualización (2.11.152), WhatsApp ha cambiado el algoritmo y la clave para cifrar las copias de seguridad en las SD. No es que la hayan mejorado sustancialmente, pero ha cambiado y el viejo método ha dejado de funcionar para los nuevos archivos con extensión "crypt5".
Esta vez el mérito de obtener las claves para descifrar este formato se lo ha llevado el autor de una aplicación de estadísticas: Chat Statitics for WhatsApp. y aunque no ha publicado los datos, si ha facilitado la tarea para que otros la obtengan de su propia herramienta.
Una vez comprada la aplicación y diseccionada, si observamos su contenido, rápidamente se ven los métodos para cifrar y descifrar las bases de datos de la copia de seguridad.
dex2jar, jd-gui, etc.
Como se puede observar, carga una librería "libaes.so" dónde realmente está la chicha.

[root@digitalsec aramosf]# readelf -a libaes.so | grep FUNC
     1: 00000000     0 FUNC    GLOBAL DEFAULT  UND __cxa_finalize
     2: 00000000     0 FUNC    GLOBAL DEFAULT  UND __cxa_atexit
     3: 00000000     0 FUNC    GLOBAL DEFAULT  UND __stack_chk_fail
     4: 00000740  1225 FUNC    GLOBAL DEFAULT    7 AES_decrypt
     5: 00001470   574 FUNC    GLOBAL DEFAULT    7 CRYPTO_cbc128_decrypt
     7: 000016b0  1218 FUNC    GLOBAL DEFAULT    7 private_AES_set_encrypt_k
     8: 00001b80   605 FUNC    GLOBAL DEFAULT    7 private_AES_set_decrypt_k
     9: 00001de0  1249 FUNC    GLOBAL DEFAULT    7 AES_encrypt
    10: 000022d0    46 FUNC    GLOBAL DEFAULT    7 MD5_Init
    11: 00002300   258 FUNC    GLOBAL DEFAULT    7 MD5_Update
    12: 00000000     0 FUNC    GLOBAL DEFAULT  UND memcpy
    13: 00002410   429 FUNC    GLOBAL DEFAULT    7 MD5_Final
    14: 00000000     0 FUNC    GLOBAL DEFAULT  UND memset
    15: 000025c0  2570 FUNC    GLOBAL DEFAULT    7 Java_de_tiflo_whatsapp_st
    16: 00000000     0 FUNC    GLOBAL DEFAULT  UND strlen
    17: 00000000     0 FUNC    GLOBAL DEFAULT  UND fopen
    18: 00000000     0 FUNC    GLOBAL DEFAULT  UND malloc
    19: 00000000     0 FUNC    GLOBAL DEFAULT  UND fseek
    20: 00000000     0 FUNC    GLOBAL DEFAULT  UND ftell
    21: 00000000     0 FUNC    GLOBAL DEFAULT  UND fwrite
    22: 00000000     0 FUNC    GLOBAL DEFAULT  UND fread
    23: 00000000     0 FUNC    GLOBAL DEFAULT  UND free
    24: 00000000     0 FUNC    GLOBAL DEFAULT  UND fclose
Analizando más lentamente estas funciones se obtienen las claves necesarias.  que le ha puesto luz a parte del código donde yo me perdía y en la que hace falta jugar con el md5.
Tras un rato hemos descubierto que la función "CRYPTO_cbc128_decrypt" parecía una trampa, ya que el algoritmo usado realmente es: aes-cbc-192. Al final de la mañana hemos terminado el proceso con un script para descifrarlas automáticamente.
No hemos puesto la bandera, ya que el autor de Chat Statitics ya conocía este método, pero por lo menos, si lo hemos liberado para que lo use todo el mundo con sus herramientas y como no, ya está implementado en la recuperación de mensajes eliminados de http://www.recovermessages.com.
El script en cuestión ya está en mi github y requiere que se conozca el nombre de la cuenta asociada al móvil, que se puede sacar de Ajustes->Cuentas y sincronización->Google
Para usarlo es tan sencillo como invocarlo así: 
- python pwncrypt5.py grbnz0@gmail.com msgstore.db.crypt5 > msgstore.sdb
Y finalizo con el codiciado código:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
#!/usr/bin/python              
"""
48bits presents:
8===============================================D~~~
WhatsApp msgstore crypt5 decryptor by grbnz0 and nullsub
8===============================================D~~~
 
"""
                                                                                                                                                                                   
import sys
import hashlib
import StringIO
from M2Crypto import EVP
 
key = bytearray([141, 75, 21, 92, 201, 255, 129, 229, 203, 246, 250, 
   120, 25, 54, 106, 62, 198, 33, 166, 86, 65, 108, 215, 147])
iv = bytearray([0x1E,0x39,0xF3,0x69,0xE9,0xD,0xB3,0x3A,0xA7,0x3B,0x44,
   0x2B,0xBB,0xB6,0xB0,0xB9])
 
def decrypt(db,acc):
  fh = file(db,'rb')
  edb = fh.read()
  fh.close()
  m = hashlib.md5()
  m.update(acc)
  md5 = bytearray(m.digest())
  for i in xrange(24): key[i] ^= md5[i&0xF]
  cipher = EVP.Cipher('aes_192_cbc', key=key, iv=iv, op=0)
  sys.stdout.write(cipher.update(edb))
  sys.stdout.write(cipher.final())
 
if __name__ == '__main__':
  if len(sys.argv) != 3:
    print 'usage %s <db> <accountname> > decrypted.db' % sys.argv[0]
  else:
    decrypt(sys.argv[1],sys.argv[2])
 
fuente:securitybydefault 

0 comentarios:

Publicar un comentario

Buscar este blog

mrgansomer. Con tecnología de Blogger.