martes, 25 de febrero de 2014

"Cifrado de mensajes" en Telegram

Desde que se confirmó la compra de WhatsApp por parte de Facebook, los usuarios han empezado a buscar alternativas a este medio de comunicación. Una de las aplicaciones que ha llegado pisando fuerte es Telegramque aprovechando la caída que tuvo WhatsApp hace escasos días, ha conseguido cerca de dos millones de nuevos usuarios. Su éxito se ha basado en que sus creadores aseguran que será gratuito para siempre y, sobre todo, que sus conversaciones son y serán seguras. Para demostrar su seguridad, desde Telegram incluso han ofrecido un premio, para aquel que consiga romper el cifrado que utilizan.


La principal diferencia con WhatsApp es que con este servicio de mensajería instantánea, es posible realizar conversaciones de forma cifrada, realizando un cifrado entre pares, pero no es la opción por defecto, por lo que para que éstas se cifren, es necesario que el usuario lo active específicamente, al igual que la caducidad de los mensajes. 

Lo que vamos a analizar hoy es, precisamente, cómo se almacenan estos datos dentro de un dispositivo móvil, concretamente en un iPhone con iOS 7.04, incluso las conversaciones cifradas.

En primer lugar, hemos realizado un backup del dispositivo y hemos accedido a los datos exportados de Telegram. Como se puede observar en la captura, se muestra el archivo "tgdata_index.db", un archivo SQLite, que nos permite observar las distintas tablas y su contenido.



Tanto en la tabla "messageIndex_v29" como en "messageIndex_v29_content" se puede observar los distintos mensajes de las conversaciones, la única información que no se nos facilita es quiénes son los interlocutores. Al analizar la tabla "messageIndex_v29_content" vemos que los identificadores "docid" de los mensajes, se podrían separar en 2 grupos: "Secuenciales" (1, 2, 3...) y "Aleatorios" (-2147483635, 800000013...), correspondiéndose con los dos tipos de comunicación que se puede realizar mediante esta app, "Sin cifrar (por defecto)" o "Cifrada".



De todo ello se deduce que, alguien que consiguiese nuestro backup podría leer nuestros mensajes, aunque sin enterarse demasiado de cómo se han formado las conversaciones. 

¿Que pasaría entonces, si alguien tuviese acceso a nuestro teléfono?

El siguiente paso, para responder a esta pregunta, ha sido acceder al teléfono (con jailbreak) y exportar todo el directorio de Telegram para realizar su posterior análisis.

De esta forma tenemos acceso a un nuevo archivo "tgdata.db". Pero cuando lo abrimos e intentamos acceder a su contenido, siempre obtenemos el mismo error, a pesar de tener la aplicación actualizada a la última versión:
"malformed database schema (unread_by_cid_with_date) - near "WHERE": syntax error"
Para acceder a sus datos, tendremos que volcar toda la información hacia un archivo (dump) y crear una nuevo archivo con la base de datos. De esta forma sí que podemos acceder al contenido y observar los mensajes con sus interlocutores.


Asimismo, en el directorio existe una tabla "encrypted_cids_v29" donde se registran los identificadores de las conversaciones cifradas. Realizando una correlación con la tabla de mensajes, se pueden reproducir de modo íntegro. 

Para poder extraer las conversaciones de la base de datos, ha bastado con crear un script en Python que automatice el acceso a la base de datos, y la exportación de las conversaciones. 

De esta forma, una conversación como la siguiente:


La observaremos en uno de nuestros archivos exportados de la siguiente manera:


Hay que comentar, que los mensajes enviados con orden de autodestrucción se eliminan del dispositivo, por lo que no es posible acceder a ellos, pero sí queda un registro de su existencia. Como se puede observar en el ejemplo, son aquellos denominados "None".

Por lo que hemos visto, la seguridad que anuncian desde Telegram, no se aplica a la forma en que se almacenan las conversaciones, sólo tiene en cuenta el envío de éstas, aunque eso está por mirar con más detenimiento.

A continuación dejamos muestra del código utilizado:

1:  #!/usr/bin/python  
2:  #coded by "Abel Gomez at INCIDE"  
3:  import codecs # -*- coding: utf-8 -*-  
4:  import unicodedata  
5:  import datetime  
6:  import os  
7:  import sqlite3 as lite  
8:  import subprocess  
9:  import sys  
10:  reload(sys)  
11:  sys.setdefaultencoding("utf-8")  
12:    
13:  users = {}  
14:  con = None  
15:  directory = "messages"  
16:  try:  
17:      con = lite.connect('./Documents/tgdata.db')  
18:  except Exception:  
19:      print "\nEs necesario ejecutar el archivo desde el directorio raiz de Telegram\n"  
20:      sys.exit()  
21:  if not os.path.exists(directory):  
22:      #if the directory does not exists we create a directory to store the messages extracted  
23:      os.makedirs(directory)  
24:    
25:  # the file that contains the db is corrupted, so we make a dump of the DB and create a new one.   
26:  if os.path.exists('dump.sql'):  
27:      os.remove('dump.sql')  
28:      os.remove('dump.db')  
29:  f = os.system('sqlite3 Documents/tgdata.db ".dump" |grep -v "CREATE INDEX" >dump.sql; sqlite3 dump.db ".read dump.sql"')  
30:    
31:  con = lite.connect('dump.db')  
32:  cur = con.cursor()  
33:  cur.execute("SELECT uid,first_name,last_name FROM users_v29")  
34:  for i in cur.fetchall():  
35:      #user list  
36:      users[i[0]] = "%s %s" % (i[1],i[2])  
37:    
38:  cur.execute("SELECT cid,date,from_uid,message FROM convesations_v29")  
39:  #at this point we have the conversation list  
40:  convs = cur.fetchall()  
41:  sep = "+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+\n"  
42:  for i in convs:  
43:      #for each conversation, we test if the conversation is encrypted and extract all the conversations  
44:      conv = i[0]  
45:      partner = users[i[2]]  
46:      fd = open(directory+'/conversation_'+str(conv)+'.txt' ,'w');  
47:      fd.write("\nConversation %s with %s :\n\n%s"% (str(conv),partner,sep))  
48:      cur.execute("SELECT 'True' FROM encrypted_cids_v29 WHERE cid='%s'" % (str(conv)))  
49:      cypher = cur.fetchone()  
50:      if cypher is not None:  
51:          fd.write("This conversation is encrypted\n\n%s" % (sep))  
52:      cur.execute("SELECT from_id,to_id,date,message,mid FROM messages_v29 WHERE cid = %s ORDER BY date,mid" % (str(conv)))  
53:      for men in cur.fetchall():  
54:          missage = men[3]  
55:          user_from= users[men[0]]      
56:          if men[1] == conv: user_to = partner  
57:          else: user_to = users[men[1]]  
58:          if men[3] == None: #could be an image or a deleted message  
59:              cur.execute("SELECT 'True' FROM media_v29 WHERE mid = %s" % (str(men[4])))  
60:              if cur.fetchone() is not None: missage = "+-+-+SHARED FILE+-+-+"  
61:          date = datetime.datetime.fromtimestamp(men[2])  
62:            
63:          fd.write("%s ---> %s (%s): %s \n%s\n" % (user_from,user_to,date,missage,sep))  
64:        
65:  print "Ha finalizado el proceso de export, puede consultar los mensajes en './messages'\n"  


Abel Gómez es Pentester Senior en INCIDE

Puedes seguir sus posts en zprian.blogspot.com.es

1 comentario:

  1. Madre mia, vamos que tienen que mejorar esta parte y cifrarlo también en la base de datos SQLlite.

    ResponderEliminar