La ingeniería inversa de WhatsApp

Introducción

Este proyecto pretende proporcionar una descripción completa y una nueva implementación de la API web de WhatsApp, que eventualmente conducirá a un cliente personalizado. WhatsApp Web funciona internamente usando WebSockets; este proyecto también lo hace.

Probando

Para poder ejecutar la aplicación, asegúrese de tener instalado el siguiente software:

  • Node.js (al menos la versión 8, ya que async awaitse usa la sintaxis)
  • el preprocesador CSS Sass (para el que previamente necesitas Ruby)
  • Python 2.7 con los siguientes pippaquetes instalados:

Antes de iniciar la aplicación por primera vez, ejecute npm installpara instalar todos los nodos y pip install -r requirements.txtpara todas las dependencias de Python.

Por último, para finalmente lanzarlo, simplemente ejecuta npm start. Usando fantasía concurrentlynodemonmagia, los tres componentes locales se iniciarán uno después del otro y cuando edite un archivo, el módulo modificado se reiniciará automáticamente para aplicar los cambios.

Arquitectura de aplicaciones

El proyecto está organizado de la siguiente manera. Tenga en cuenta los puertos usados ​​y asegúrese de que no estén en uso en otro lugar antes de iniciar la aplicación. arquitectura de aplicaciones de whatsapp-web-reveng

Detalles de inicio de sesión y encriptación

WhatsApp Web encripta los datos usando varios algoritmos diferentes. Estos incluyen AES 256 ECB , Curve25519 como esquema de concordancia de claves Diffie-Hellman, HKDF para generar el secreto compartido extendido y HMAC con SHA256.

Iniciar la sesión web de WhatsApp ocurre simplemente conectándose a uno de sus servidores websocket en wss://w[1-8].web.whatsapp.com/wswss://significa que la conexión websocket es segura, w[1-8]significa que cualquier número entre 1 y 8 puede seguir w). También asegúrese de que, al establecer la conexión, Origin: https://web.whatsapp.comse configure el encabezado HTTP ; de lo contrario, la conexión será rechazada.

Mensajes

Cuando envía mensajes a un websocket Web de WhatsApp, deben estar en un formato específico. Es bastante simple y parece messageTag,JSON, por ejemplo 1515590796,["data",123]. Tenga en cuenta que aparentemente la etiqueta del mensaje puede ser cualquier cosa. Esta aplicación usa principalmente la marca de tiempo actual como etiqueta, para ser un poco única. WhatsApp a menudo usa etiquetas de mensajes como s11234.--0o algo así. Obviamente, la etiqueta del mensaje puede no contener una coma. Además, los objetos JSON son posibles, así como la carga útil.

Iniciar sesión

Para iniciar sesión en un websocket abierto, siga estos pasos:

  1. Genere uno propio clientId, que debe tener 16 bytes codificados en base64 (es decir, 25 caracteres). Esta aplicación solo usa 16 bytes aleatorios, es decir, base64.b64encode(os.urandom(16))en Python.
  2. Elija una etiqueta para su mensaje, que es más o menos arbitraria (ver arriba). Esta aplicación usa la marca de tiempo actual (en segundos) para eso. Recuerde esta etiqueta para más tarde.
  3. El mensaje se envía a la WebSocket se parece a esto: messageTag,["admin","init",[0,2,7314],["Long browser description","ShortBrowserDesc"],"clientId",true].
    • Obviamente, debe reemplazar messageTagclientIdpor los valores que eligió antes
    • La [0,2,7314]parte especifica la versión actual de WhatsApp Web. El último valor cambia con frecuencia. Sin embargo, debería ser bastante retrocompatible.
    • "Long browser description" es una cadena arbitraria que se mostrará en la aplicación WhatsApp en la lista de clientes web de WhatsApp registrados después de escanear el código QR.
    • "ShortBrowserDesc" no se ha observado en ningún lugar pero también es arbitrario.
  4. Después de unos momentos, su websocket recibirá un mensaje en el formato especificado con la etiqueta de mensaje que eligió en el paso 2 . El objeto JSON de este mensaje tiene los siguientes atributos:
    • status: debe ser 200
    • ref: en la aplicación, esto se trata como la identificación del servidor; importante para la generación de QR, ver a continuación
    • ttl: es 20000, tal vez el tiempo después de que el código QR se vuelva inválido
    • update: una bandera booleana
    • curr: la versión actual de WhatsApp Web, por ejemplo 0.2.7314
    • time: la marca de tiempo al que respondió el servidor, como milisegundos de coma flotante, por ejemplo 1515592039037.0

Generación de código QR

  1. Genera tu propia clave privada con Curve25519, ej curve25519.Private().
  2. Obtenga la clave pública de su clave privada, ej privateKey.get_public().
  3. Obtenga la cadena codificada posteriormente por el código QR concatenando los siguientes valores con una coma:
    • la identificación del servidor, es decir, el refatributo del paso 4
    • la versión codificada en base64 de su clave pública, es decir, base64.b64encode(publicKey.serialize())
    • su ID de cliente
  4. Convierta esta cadena en una imagen (por ejemplo, usando pyqrcode) y escanee usando la aplicación WhatsApp.

Después de escanear el código QR

  1. Inmediatamente después de escanear el código QR, el websocket recibe varios mensajes JSON importantes que crean los detalles de cifrado. Estos usan el formato de mensaje especificado y tienen una matriz JSON como carga útil. Su etiqueta de mensaje no tiene un significado especial. La primera entrada de la matriz JSON tiene uno de los siguientes valores:
    • Conn: array contiene el objeto JSON como segundo elemento con información de conexión que contiene los siguientes atributos y muchos más:
      • battery: el porcentaje de batería actual de su teléfono
      • browserToken (podría ser importante, pero no usado por la aplicación todavía)
      • clientToken (podría ser importante, pero no usado por la aplicación todavía)
      • phone: Un objeto con información detallada acerca de su teléfono, por ejemplo device_manufacturerdevice_modelos_build_number,os_version
      • platform: el sistema operativo de tu teléfono, por ejemplo android
      • pushname: el nombre tuyo que proporcionó WhatsApp
      • secret (¡recuerda esto!)
      • serverToken (podría ser importante, pero no usado por la aplicación todavía)
      • wid: su número de teléfono en el formato de identificación de chat (ver a continuación)
    • Stream: array tiene cuatro elementos en total, por lo que toda la carga es como ["Stream","update",false,"0.2.7314"]
    • Props: array contiene el objeto JSON como segundo elemento con varias propiedades como imageMaxKBytes(1024), maxParticipants(257), videoMaxEdge(960) y otros

Generación clave

  1. Ahora está listo para generar las claves de cifrado finales. Comience decodificando el secretfrom Conncomo base64 y guardándolo como secret. Este secreto decodificado tendrá 144 bytes de longitud.
  2. Tome los primeros 32 bytes del secreto decodificado y úselo como clave pública. Junto con su clave privada, genere una clave compartida y llámela sharedSecret. La aplicación lo hace usando privateKey.get_shared_key(curve25519.Public(secret[:32]), lambda a:a).
  3. Use una clave que contenga 32 bytes nulos para codificar el secreto compartido usando HMAC SHA256. Tome este valor y extiéndalo a 80 bytes usando HKDF. Llame a este valor sharedSecretExpanded. Esto se hace con HKDF(HmacSha256("\0"*32, sharedSecret), 80).
  4. Este paso es opcional, valida los datos proporcionados por el servidor. El método se llama validación HMAC . Hazlo primero calculando HmacSha256(sharedSecretExpanded[32:64], secret[:32] + secret[64:]). Compare este valor con secret[32:64]. Si no son iguales, cancela el inicio de sesión.
  5. Ahora tiene las claves cifradas: almacenar sharedSecretExpanded[64:] + secret[64:]como keysEncrypted.
  6. Las claves encriptadas ahora necesitan ser descifradas usando AES con una sharedSecretExpanded[:32]clave, es decir, almacenar AESDecrypt(sharedSecretExpanded[:32], keysEncrypted)como keysDecrypted.
  7. La keysDecryptedvariable tiene 64 bytes de longitud y contiene dos claves, cada una de 32 bytes de longitud. Se encKeyusa para descifrar los mensajes binarios que te envía el servidor web de WhatsApp o para encriptar los mensajes binarios que envías al servidor. Se macKeynecesita para validar los mensajes que se le enviaron:
    • encKeykeysDecrypted[:32]
    • macKeykeysDecrypted[32:64]

Validar y descifrar mensajes

Ahora que tiene las dos claves, validar y descifrar los mensajes que el servidor le envió es bastante fácil. Tenga en cuenta que esto solo es necesario para mensajes binarios , todos los JSON que recibe permanecen sin formato. Los mensajes binarios siempre tienen 32 bytes al principio que especifican la suma de comprobación HMAC.

  1. Validar el mensaje de hash del contenido del mensaje real con el macKey(aquí messageContentes el entero mensaje binario): HmacSha256(macKey, messageContent[32:]). Si este valor no es igual a messageContent[:32], el mensaje enviado por el servidor no es válido y debe descartarse.
  2. Descifrar el contenido del mensaje usando un algoritmo AES y la encKeyAESDecrypt(encKey, messageContent[32:]).

Los datos que obtiene en el paso final tienen un formato binario que se describe a continuación. Aunque es binario, aún puede ver varias cadenas, especialmente el contenido de los mensajes que envía es bastante obvio allí.

Formato de mensaje binario

Decodificación binaria

El script de Python backend/decoder.pyimplementa la MessageParserclase. Es capaz de crear una estructura JSON a partir de datos binarios en los que los datos aún están organizados de una manera bastante desordenada. La sección sobre Manejo de nodo a continuación discutirá cómo los nodos se reorganizan después.

MessageParserinicialmente solo necesita algunos datos y luego los procesa byte a byte, es decir, como una secuencia. Tiene un par de constantes y muchos métodos que se complementan entre sí.

Constantes

  • Etiquetas con sus respectivos valores enteros
    • LIST_EMPTY : 0
    • STREAM_8 : 2
    • DICCIONARIO_0 : 236
    • DICCIONARIO_1 : 237
    • DICCIONARIO_2 : 238
    • DICCIONARIO_3 : 239
    • LISTA_8 : 248
    • LISTA_16 : 249
    • JID_PAIR : 250
    • HEX_8 : 251
    • BINARY_8 : 252
    • BINARY_20 : 253
    • BINARY_32 : 254
    • NIBBLE_8 : 255
  • Los tokens son una larga lista de 151 cadenas en las que importan los índices:
    • [None,None,None,"200","400","404","500","501","502","action","add", "after","archive","author","available","battery","before","body", "broadcast","chat","clear","code","composing","contacts","count", "create","debug","delete","demote","duplicate","encoding","error", "false","filehash","from","g.us","group","groups_v2","height","id", "image","in","index","invis","item","jid","kind","last","leave", "live","log","media","message","mimetype","missing","modify","name", "notification","notify","out","owner","participant","paused", "picture","played","presence","preview","promote","query","raw", "read","receipt","received","recipient","recording","relay", "remove","response","resume","retry","s.whatsapp.net","seconds", "set","size","status","subject","subscribe","t","text","to","true", "type","unarchive","unavailable","url","user","value","web","width", "mute","read_only","admin","creator","short","update","powersave", "checksum","epoch","block","previous","409","replaced","reason", "spam","modify_tag","message_info","delivery","emoji","title", "description","canonical-url","matched-text","star","unstar", "media_key","filename","identity","unread","page","page_count", "search","media_message","security","call_log","profile","ciphertext", "invite","gif","vcard","frequent","privacy","blacklist","whitelist", "verify","location","document","elapsed","revoke_invite","expiration", "unsubscribe","disable"]

Reformateo numérico

  • Desempaquetado de nibbles : devuelve la representación ASCII para números entre 0 y 9. Devuelve -para 10, .para 11 y \0para 15.
  • Desempaquetar valores hexadecimales : devuelve la representación ASCII para números entre 0 y 9 o letras entre A y F (es decir, mayúsculas) para números entre 10 y 15.
  • Desempaquetar bytes : espera una etiqueta como un parámetro adicional, a saber, NIBBLE_8 o HEX_8 . Desempaqueta un nibble o valor hexadecimal en consecuencia.

Formatos numéricos

  • Byte : un simple byte viejo.
  • Entero con N bytes : lee N bytes y construye un número a partir de ellos. Puede ser endian pequeño o grande; si no se especifica lo contrario, se usa big endian. Tenga en cuenta que no hay valores negativos posibles.
  • Int16 : un entero con dos bytes, leído usando Entero con N bytes .
  • Int20 : Consume tres bytes y construye un entero utilizando los últimos cuatro bits del primer byte y el segundo y tercer bytes completos. Por lo tanto, siempre es Big Endian.
  • Int32 : un entero con cuatro bytes, leído usando Integer con N bytes .
  • Int64 : un entero con ocho bytes, leído usando Entero con N bytes .
  • Packed8 : espera una etiqueta como un parámetro adicional, a saber, NIBBLE_8 o HEX_8 . Devuelve una cadena.
    • Primero lee un byte ny hace lo siguiente n&127muchas veces: lee un byte ly, para cada nibble, agrega el resultado de su versión desempaquetada al valor de retorno (utilizando los bytes de desempaquetado con la etiqueta dada). El mordisco más significativo primero.
    • Si nse configuró el bit más significativo , elimina el último carácter del valor de retorno.

Enteros de longitud variable

A diferencia de los formatos numéricos anteriores, leer un entero de longitud variable (VLI) no cambia el puntero de datos actual.

En primer lugar, la longitud ldel VLI se lee leyendo bytes hasta encontrar un byte con el conjunto de bits más significativo, pero a lo sumo 10 bytes.

QUE HACER

Los enteros de longitud variable a distancia esperan un valor mínimo y máximo. Si el entero de longitud variable de lectura es menor que el mínimo o mayor que o igual al máximo, arroje un error.

Métodos de ayuda

  • Leer bytes : lee y devuelve la cantidad de bytes especificada.
  • Comprobar la etiqueta de la lista : espera una etiqueta como parámetro y devuelve verdadero si la etiqueta es LIST_EMPTYLIST_8LIST_16(es decir, 0, 248 o 249).
  • Tamaño de lista de lectura : espera una etiqueta de lista como parámetro. Devuelve 0 para LIST_EMPTY, devuelve un byte de lectura LIST_8o un Int16 de lectura para LIST_16.
  • Leer una cadena de caracteres : espera la longitud de la cadena como parámetro, lee tantos bytes y los devuelve como una cadena.
  • Obtener un token : espera un índice de la matriz de tokens y devuelve la cadena respectiva.
  • Obtener una doble señal : Espera dos enteros aby se pone el token en el índice a*256+b.

Instrumentos de cuerda

Leer una cadena necesita una etiqueta como parámetro. Dependiendo de esta etiqueta, se leen diferentes datos.

  • Si la etiqueta está entre 3 y 235, se obtiene el token (es decir, una cadena) de esta etiqueta. Si el token es "s.whatsapp.net""c.us"se devuelve en su lugar, de lo contrario, el token se devuelve tal como está.
  • Si la etiqueta se encuentra entre DICTIONARY_0 y DICTIONARY_3 , se devuelve un token doble , con el tag-DICTIONARY_0primero y un byte de lectura como segundo parámetro.
  • LIST_EMPTY : no se devuelve nada (por ejemplo None).
  • BINARY_8 : se lee un byte que luego se usa para leer una cadena de caracteres con esta longitud.
  • BINARY_20 : se lee un Int20 que luego se usa para leer una cadena de caracteres con esta longitud.
  • BINARY_32 : se lee un Int32 que luego se usa para leer una cadena de caracteres con esta longitud.
  • JID_PAIR
    • Primero, se lee un byte que luego se usa para leer una cadena i con esta etiqueta.
    • En segundo lugar, se lee otro byte que luego se usa para leer una cadena j con esta etiqueta.
    • Por último, ijse unen entre sí con un @signo y se devuelve el resultado.
  • NIBBLE_8 o HEX_8 : se devuelve un Packed8 con esta etiqueta.

Listas de atributos

Leer una lista de atributos necesita la cantidad de atributos para leer como parámetro. Una lista de atributos siempre es un objeto JSON. Para cada atributo leído, los siguientes pasos se ejecutan para obtener pares clave-valor (¡exactamente en este orden!):

  • Clave : se lee un byte que luego se usa para leer una cadena con esta etiqueta.
  • Valor : se lee un byte que luego se usa para leer una cadena con esta etiqueta.

Nodos

Un nodo siempre consta de una matriz JSON con exactamente tres entradas: descripción, atributos y contenido. Los siguientes pasos son necesarios para leer un nodo:

  1. Un tamaño de lista a se lee utilizando un byte de lectura como etiqueta. El tamaño 0 de la lista no es válido.
  2. La etiqueta de descripción se lee como un byte. El valor 2 no es válido para esta etiqueta. La cadena de descripción descrse obtiene leyendo una cadena con esta etiqueta.
  3. El objeto de atributos attrsse lee leyendo un objeto de atributos con longitud (a-2 + a%2) >> 1.
  4. Si afue impar, este nodo no tiene ningún contenido, [descr, attrs, None]es decir, se devuelve.
  5. Para obtener el contenido del nodo, primero un byte, es decir, se lee una etiqueta. Dependiendo de esta etiqueta, surgen diferentes tipos de contenido:
    • Si la etiqueta es una etiqueta de lista , se lee una lista con esta etiqueta (consulte las listas a continuación).
    • BINARY_8 : se lee un byte que luego se usa como longitud para leer bytes .
    • BINARY_20 : se lee un Int20 que luego se usa como longitud para leer bytes .
    • BINARY_32 : se lee un Int32 que luego se usa como longitud para leer bytes .
    • Si la etiqueta es otra cosa, se lee una cadena con esta etiqueta.
  6. Eventualmente, [descr, attrs, content]es devuelto.

Liza

La lectura de una lista requiere una lista de etiquetas (es decir LIST_EMPTY , LIST_8 o LIST_16 ). La longitud de la lista se obtiene leyendo un tamaño de lista usando esta etiqueta. Para cada entrada de lista, se lee un nodo .

Manejo del nodo

QUE HACER

API web de WhatsApp

WhatsApp Web también tiene una API interesante. Incluso puedes probarlo directamente en tu navegador. Simplemente inicie sesión en la https://web.whatsapp.com/ normal , luego abra la consola de desarrollo del navegador. Ahora ingrese algo como lo siguiente (vea a continuación los detalles sobre la identificación del chat):

  • window.Store.Wap.profilePicFind("49123456789@c.us").then(res => console.log(res));
  • window.Store.Wap.lastseenFind("49123456789@c.us").then(res => console.log(res));
  • window.Store.Wap.statusFind("49123456789@c.us").then(res => console.log(res));

Usando la increíble consola de desarrollo de Chrome, puedes ver que window.Store.Wapcontiene muchas otras funciones muy interesantes. Muchos de ellos devuelven promesas de JavaScript. Cuando hace clic en la pestaña Red y luego en WS (tal vez necesita volver a cargar el sitio primero), puede ver toda la comunicación entre WhatsApp Web y sus servidores.

Identificación de chat

La API web de WhatsApp usa los siguientes formatos para identificar chats con usuarios individuales y grupos de usuarios múltiples.

  • Chats :, [country code][number]@c.uspor ejemplo, 49123456789@c.uscuando eres de Alemania y tu número de teléfono es 0123 456789.
  • Grupos : [phone number of group creator]-[timestamp of group creation]@g.uspor ejemplo, 49123456789-1509911919@g.uspara el grupo que 49123456789@c.uscreó el 5 de noviembre de 2017.

Mensajes de WebSocket

Hay dos tipos de mensajes WebSocket que se intercambian entre el servidor y el cliente. Por un lado, JSON simple que es bastante inequívoco (especialmente para las llamadas a API anteriores), por otro lado, mensajes binarios encriptados.

Lamentablemente, estos binarios no se pueden analizar con las herramientas de desarrollo de Chrome. Además, el back-end de Python, que por supuesto también recibe estos mensajes, necesita descifrarlos, ya que contienen datos encriptados. La sección sobre detalles de encriptación explica cómo se puede descifrar.

Tareas

Backend

  • Permitir el envío de mensajes también. Por supuesto, JSON es fácil, pero la escritura del formato de mensaje binario necesita comenzar a ser examinada.

Interfaz web

  • Permitir reutilizar la sesión después de iniciar sesión correctamente. Probablemente las cookies normales son las mejores para esto.
  •  Una interfaz de usuario que no es tan técnica, sino que más bien comienza a emular la interfaz de usuario web de WhatsApp real.

Documentación

  • La sección Manejo de nodos . Podría ser muy largo.
  • La sección de Descargo de responsabilidad . Debe contener cosas como "sin garantía" y "no hacer nada malo".
  • Externalice las diferentes partes de la documentación en sus propios archivos, tal vez en la gh-pagessucursal.

Términos y Condiciones

  • Usted no utilizar este software con fines de comercialización (correo no deseado, el envío masivo ...). No apoyaremos a nadie con tales intenciones.
  • Nos reservamos el derecho de bloquear a cualquier usuario de este repositorio que no cumpla con estas condiciones.

Legal

Este código no está de ninguna manera afiliado, autorizado, mantenido, patrocinado o respaldado por WhatsApp o cualquiera de sus afiliadas o subsidiarias. Este es un software independiente y no oficial. Úselo bajo su propio riesgo.

Aviso de criptografía

Esta distribución incluye software criptográfico. El país en el que reside actualmente puede tener restricciones sobre la importación, posesión, uso y / o reexportación a otro país de software de cifrado. ANTES de utilizar cualquier software de cifrado, compruebe las leyes, normativas y políticas de su país relativas a la importación, posesión o uso, y vuelva a exportar el software de cifrado, para ver si esto está permitido. Ver http://www.wassenaar.org/ para más información.

El Departamento de Comercio del Gobierno de los Estados Unidos, Oficina de Industria y Seguridad (BIS), ha clasificado este software como Número de control de productos básicos de exportación (ECCN) 5D002.C.1, que incluye software de seguridad de la información que utiliza o realiza funciones criptográficas con algoritmos asimétricos. La forma y la forma de esta distribución hacen que sea elegible para exportar bajo la excepción de excepción de licencia del software de tecnología ENC de licencia (TSU) (ver el Reglamento de administración de exportación de BIS, Sección 740.13) para código de objeto y código fuente.

ATENCIÓN - Tienes que ser miembro de Amigosdelamili.com para poder participar y escribir con los demás.

PINCHA AQUÍ Y ÚNETE YA A Amigosdelamili.com

Votos 0
Enviarme un correo electrónico cuando me contesten –