Ingeniería inversa a Imessage: Aprovechamiento del hardware para proteger el software

iMessage es una aplicación y un protocolo de mensajería segura ampliamente utilizado en todo el ecosistema de Apple. Con curiosidad por saber cómo sería ejecutar iMessage en otras plataformas, adoptamos un enfoque de ingeniería inversa para comprender cómo funciona iMessage y examinar las posibilidades de extenderlo a otras plataformas.

El objetivo de este artículo es mostrar cómo Apple aprovecha el hecho de que produce el hardware para proteger su software. Para explorar esto, intentaremos conectarnos a través de Apple Push Notification (APN) directamente en el nivel de la red y ver qué desafíos enfrentamos. A lo largo del camino, realizaremos ingeniería inversa en pequeñas partes del demonio (daemon) apsd en macOS y el protocolo APN en sí mediante herramientas populares de código abierto.

Soluciones actuales

Las soluciones de facto actuales para ejecutar iMessage fuera del ecosistema de Apple requieren un servidor Mac y se basan en secuencias de comandos de AppleScript para automatizar las acciones de la interfaz de usuario de Messages.app. Esto elimina la necesidad de volver a implementar el protocolo de envío de mensajes en el cliente. Sin embargo, la gran desventaja es que la Mac tiene que estar funcionando todo el tiempo que desee utilizar iMessage.

A diferencia de revertir un binario autónomo, el código de envío de iMessage (como la mayoría de las funciones internas en los sistemas operativos XNU) va más allá del alcance de Messages.app y muchos demonios (daemons) del sistema están involucrados en el proceso, es decir, una arquitectura de microservicio, y se basan en mensajes XPC como un mecanismo de IPC (comunicación entre procesos).

Project Zero ya ha realizado una excelente investigación sobre la estructura de los demonios (daemons) involucrados en iMessage, así que le ahorraré todos los detalles sangrientos innecesarios. Pero en resumen, una vez que escribe un mensaje y presiona Enter, viaja a través de múltiples procesos, a saber, Messages.app -> imagent -> identityservicesd -> apsd. Escribí dos herramientas basadas en Frida para analizar este proceso y encontré dos desafíos principales.

Primero, simplemente buscar métodos ObjC de forma estática en el desensamblador consumía demasiado tiempo; hay una gran cantidad de llamadas a la API y capas sobre capas para cada tarea. Escribí un simple interceptor de mensajes Objective-C, objtree, que registra todos los mensajes dentro del alcance de uno que me interesa. La salida se proporciona en forma de árbol. Por ejemplo, debido a que sé que un determinado método de evento de la interfaz de usuario desencadena el envío de mensajes, usaría mi herramienta para conectar ese método y ver todas las llamadas ObjC subsiguientes dispuestas maravillosamente con el formato de pila consciente de la profundidad. Aquí está objtree en acción volcando casualmente más de 3,000 selectores al activar el evento keyDown:

sudo objtree Messages -m "-[NSResponder keyDown:]"

objtree en acción

Segundo, después de encontrar los métodos ObjC más importantes, todo se reduce a enviar un mensaje XPC a algún proceso / demonio del sistema. Escribí otra herramienta para el trabajo, xpcspy, que intercepta los mensajes XPC y permite el filtrado.
xcpspy interceptando un mensaje al daemon apsd

Al final, descubrimos que el demonio apsd se encarga de enviar mensajes a través de la red. Gracias al sistema de envío de mensajes de Objective-C, la búsqueda de selectores con nombres como connectTo y send proporcionará una idea rápida y buena de dónde ocurren las llamadas a la API de conexión TCP.

radare2 buscando selectores Objective-C

Contactando los servidores de Apple

El protocolo APN no es nuevo y algunas investigaciones se enfocan en su seguridad donde se lo conoce como PUSH. Algunas de las investigaciones aún se mantienen: la conexión se realiza a través de TLS, puerto 5223, en el dominio rand(0,255)-courier.push.apple.com, y se utiliza un certificado de cliente para la autenticación en el nivel TLS.
Sin embargo, el protocolo ya no envía el certificado del cliente en la capa de transporte a través de los mensajes CertificateRequest y Certificate definidos en RFC5246. En su lugar, APN lo envía en la capa de aplicación en un mensaje/comando de conexión junto con un token público, un nonce y un firma. Se generan en el método -[APSProtocolParser copyConnectMessageWithToken:state:presentFlags:certificate:nonce:signature:redirectCount:lastConnected:desconectReason:]
El parámetro token es muy importante porque funciona como un identificador de usuario y juega un papel crucial en el mecanismo de protección del protocolo, como veremos más adelante.
Debido a que el certificado de cliente APN es único para cada dispositivo y el cifrado TLS se lleva a cabo en la capa de aplicación, esto proporciona un enfoque más seguro. La capa de transporte no está cifrada y podría dejar el certificado a la vista de un intermediario.
El primer punto de contacto con los servidores de Apple ocurre en -[APSTCPStream_connectToServerWithPeerName:]. En ese método, hay llamadas a la API de configuración de sesión TLS, incluidas las privadas como -[NSURLSessionConfiguration set_socketStreamProperties:] y -[NSURLSessionConfiguration set_tlsTrustPinningPolicyName:]
Al final, el objeto de configuración se verá así:
Nuestro objetivo ahora es tener una conexión abierta con xx-courier.push.apple.com. Intentemos abrir una conexión TLS usando openssl.


Tenemos un apretón de manos (handshake) fallido. Al observar la clave anterior _kCFStreamPropertyNPNProtocolsAvailable, vemos que se está utilizando la negociación del siguiente protocolo (NPN).
NPN, ahora llamado Application-Layer Protocol Negotiation (ALPN), es una extensión TLS incrustada en el mensaje ClientHello que le dice a un servidor TLS qué protocolo (s) de capa de aplicación desea usar el cliente. Dado el uso de extensiones TLS adicionales, es aconsejable registrar el tráfico con tcpdump y examinarlo. Pero primero, tendremos que reaparecer apsd, porque la conexión ocurre mientras se inicia. launchctl nos permite terminar y luego generar demonios en el depurador:

% sudo launchctl attach -k system/com.apple.apsd

Ahora tenemos apsd detenido en _dyld_start:


Ahora que tiene un buen volumen de tráfico, veamos el apretón de manos (handshake):
Captura de tráfico de apsd
El apretón de manos (handshake) tiene algunas extensiones TLS interesantes. Puede enviar la mayoría de estas extensiones junto con el protocolo de enlace utilizando la herramienta openssl s_client, pero en mis experimentos solo se requieren dos, además de las que openssl (que en realidad es LibreSSL 2.8.3 en el momento de escribir este artículo) envía de forma predeterminada. Esas son las extensiones server_name y application_layer_protocol_negotiation. Para ALPN, el cliente envía apns-security-v3 y apns-pack-v1. El servidor siempre eligió apns-pack-v1 en mis experimentos. Intentemos conectarnos al servidor usando esos parámetros:


Genial, ahora estamos conectados a los servidores de Apple. Si omite una de las opciones -alpn o -servername, obtendrá un error de protocolo de enlace. (Tampoco importa el error de verificación error:num = 19 esto es openssl quejándose del certificado de CA, que naturalmente está autofirmado).

Interceptación de mensajes APN

Ahora necesitamos interceptar los mensajes TLS no cifrados. La fijación de certificados solía ser relativamente fácil de omitir en APN. Pero debido a que eludirlo es un desafío completamente diferente, recurriré a interceptar la carga útil del protocolo de texto plano antes de que salga del binario, al tener puntos de interrupción en los métodos de envío y recepción de datos. Esas funciones son -[APSTCPStream writeDataInBackground:] y -[APSCourier tcpStream:dataReceived:] respectivamente.


rdx contiene una referencia al objeto NSData, cuyos bytes se escribirán en el flujo de salida. Mismo mecanismo para recibir datos en el flujo de entrada.

Comunicándose con el APN

Ahora que tenemos la conexión y los datos, ¿podemos comunicarnos a través de APN? Probémoslo. Usé un FIFO para alimentar la entrada en openssl.


¡Voila! Este mensaje de respuesta connect tiene el código de respuesta (0x08) y la hora del servidor, entre otros parámetros.

Conexión interrumpida

APN es un protocolo binario. Los comandos están serializados en la clase APSProtocolParser y su interior no es lo que nos interesa aquí. Esta es la secuencia de comandos más pequeña posible para enviar un iMessage, según lo que sucede en apsd:

  • 0x07: Conectar al usuario con uid 0 (Cada usuario tiene su propio token de inserción público.)
  • 0x0c: Keep alive.
  • 0x14: Active state.
  • 0x07: Conectar al usuario con uid 501.
  • 0x09: Filtrar temas.
  • 0x0a: Enviar mensaje.
Pude replicar el envío de un iMessage puramente desde openssl copiando los datos del mensaje binario desde apsd mientras lo estaba enviando, y usándolo como entrada para mi configuración de openssl FIFO. El más interesante de esos comandos es filter (0x09). Un mensaje de filtro se serializa en el método -[APSProtocolParser copyFilterMessageWithEnabledHashes:ignoredHashes:oportunisticHashes:nonWakingHashes:pausedHashes:token:]. Los valores hash de los argumentos son para temas o servicios que utilizan APN. iMessage tiene el nombre de tema com.apple.madrid por alguna razón. Sin el mensaje filter, el cliente no puede enviar ni recibir mensajes APN a través del comando send (0x0a). Por lo que debemos invocar el comando de filtro antes de enviar un mensaje.

Conclusión de nuestras pruebas

Como viste, replicar el tráfico APN es fácil, pero hay una advertencia: el comando de filtro (filter) hará que el servidor descarte cualquier conexión anterior para el mismo token público.
Supongamos que alguien ya ha pasado por el dolor de revertir el protocolo, ha generado mensajes válidos para APN desde cero, luego ha creado un cliente APN de Linux llamado fakeapsd y ha copiado los parámetros del mensaje de conexión connect (token público y par de claves) tal cual desde un dispositivo Mac. La implicación de la caída de la conexión con el comando de filtro (filter) como se explicó anteriormente es que cada vez que fakeapsd intenta tener una comunicación significativa con el servidor, hará que la conexión del apsd real se caiga, que a su vez intentará reconectarse y lo hará. será una lucha interminable por la conexión entre fakeapsd y apsd.

Ahora mencionamos que el servidor eliminará una conexión para el mismo token público, que es el parámetro crucial para el mensaje de conexión. ¿Se puede generar uno nuevo para evitar esta restricción? Sin gastar un tiempo precioso en obtener esa respuesta por las malas, podemos recurrir a Hackintosh para obtener la respuesta.
Hackintosh, que solo estoy considerando desde un punto de vista técnico, es el experimento perfecto aquí porque, en igualdad de condiciones (manejo de protocolos, etc.), controla un parámetro importante, y se ejecuta en un dispositivo Apple real, que no es así. Hacer que iMessage y FaceTime funcionen ha sido durante mucho tiempo problemático para muchos porque un número de serie debe ser forzado (cuya codificación se ha invertido) hasta que se encuentra uno que es genuino, pero que aún no se ha comprado.
Como vemos, controlar el hardware puede ser el elemento más esencial para "proteger" un protocolo en un escenario de caja blanca en el que un adversario tiene acceso completo al software. También vimos cómo las herramientas de instrumentación dinámica como Frida, patrocinada por NowSecure, pueden facilitar el proceso de ingeniería inversa.

Comentarios

Entradas populares