AIREITER

Todos los hooks de Frida funcionaban. Aun así, no tenía ni un endpoint utilizable.

Última actualización: 2026-07-31 06:10:17

Había localizado los enlaces nativos en las tres plataformas: el punto de entrada del registro del dispositivo, la capa donde el SDK de seguridad intercepta la solicitud, el interceptor nativo que reescribe el paquete saliente y los métodos que JNI registra dinámicamente en el runtime mediante RegisterNatives. El script de Frida se adjuntaba sin problemas, los logs corrían y todos los hooks saltaron de forma fiable. Sin embargo, al abrir el catálogo de comandos y contar los endpoints nativos invocables de esas tres plataformas, el resultado fue cero.

En el mismo proyecto, otra plataforma sí tenía 11. Estaban validados en campo, migrados al código y funcionaban como comandos de primera clase.

La diferencia no estaba en la técnica de hooking. Todos los hooks funcionaban y el diagrama de enlaces estaba perfectamente trazado. Lo que cambia es un umbral que mucha gente ni siquiera percibe: entre que un hook salte y que una capacidad sea utilizable hay un nivel de evidencia. Este artículo trata de cómo fijar ese umbral, por qué marcar algo como «aún no utilizable» cuesta menos que publicar un endpoint a medio construir y qué papel desempeña realmente el modelo durante el proceso.

Un hook activo no es una capacidad lista para usar

En ingeniería inversa de aplicaciones es fácil tratar el «el hook ha saltado» como si fuera la meta. El script se adjunta al método objetivo, el log muestra los argumentos, el valor de retorno y la pila de llamadas. La sensación de «ya estoy dentro» es muy real, pero también puede engañar mucho.

El catálogo de comandos, sin embargo, no acepta «ya estoy dentro». Exige otra cosa: que, con una entrada normal, el comando produzca de forma fiable una carga útil no vacía y correctamente estructurada que los sistemas posteriores puedan consumir. Hay mucha distancia entre ambas situaciones.

El fallo típico es este: todos los hooks saltan, el enlace está completo e incluso puedes ver en el log cómo sale la solicitud de registro del dispositivo, cómo el SDK de seguridad calcula su valor y cómo el interceptor nativo añade la cabecera de firma. Todo parece correcto. Pero al ejecutarlo en un dispositivo real, el registro devuelve un ID de dispositivo con valor cero o el endpoint de detalle responde con un cuerpo vacío. El enlace está abierto, pero no hay datos.

¿Qué tienes entonces? Un conjunto de sondas capaces de observar el comportamiento interno de una versión concreta de la app. Lo que no tienes es un endpoint invocable. Publicar lo primero como si fuera lo segundo deja una mina enterrada para cualquiera que venga después.

El grupo de control: por qué una plataforma tiene 11 comandos y las otras, 0

Al comparar ambos grupos de plataformas, el umbral se vuelve evidente.

Plataforma de control (una app de comunidad de vídeo)

Tres grandes apps de contenido

Enlace del hook

Localizado y validado en campo

Todos localizados; todos los hooks funcionan

Estado real de instalación

Se obtuvo una identidad de dispositivo distinta de cero

ID de dispositivo en cero / falta un perfil de dispositivo real

Respuesta no vacía

Detalle estructurado

Detalle vacío / cuerpo vacío

En el catálogo de comandos

11

0

Los 11 comandos de la plataforma de control no tienen simplemente «mejores hooks». Cada uno superó los cuatro umbrales de evidencia de la siguiente sección antes de migrar a un comando Python de primera clase, y se incorporó junto con su evidencia de validación estructurada.

Las tres plataformas tampoco estaban abandonadas. Ya se había resuelto la capa de intercepción del SDK de seguridad, el interceptor nativo de solicitudes y el conjunto de métodos que JNI registra dinámicamente; además, los hooks de Frida siguen instalados. Pero mientras no se supere el estado real de instalación y el registro del dispositivo no consiga una identidad distinta de cero, todo lo que viene después estará vacío. Por eso sus comandos de app nativa siguen honestamente en 0, y la vía utilizable por ahora es otra completamente distinta: transporte Web o mediante navegador.

En el balance general, este lote de capacidades móviles suma 32: 23 llegaron realmente a implementarse y las 9 restantes están bloqueadas en «a la espera de un perfil de dispositivo real». Ninguna entró antes de tiempo en el catálogo. El 9 no representa un fracaso, sino disciplina: mide exactamente lo que se entiende del enlace, pero aún no cuenta con evidencia suficiente.

Los cuatro umbrales de evidencia

Al desglosar la comparación, una capacidad de app debe cumplir cuatro condiciones para entrar en el catálogo de comandos. Si falla una sola, no entra.

Uno: estado real de instalación. La solicitud debe salir desde una identidad de instalación distinta de cero que el sistema upstream reconozca. Que un hook salte en un entorno emulado o con un perfil roto no cuenta. Si el registro del dispositivo devuelve un ID con valor cero, esta puerta ha fallado; a partir de ahí, por completo que esté el enlace, la salida será vacía. Aquí es donde se atascaron conjuntamente las tres plataformas.

Dos: respuesta no vacía. Que una solicitud llegue no significa que contenga datos. Puede salir, recibir código 200 y devolver un cuerpo vacío. Esta «respuesta vacía correcta» es más peligrosa que un error, porque pasa todos los controles que solo verifican si se lanzó una excepción. El umbral exige datos estructurados, completos y no vacíos que puedan alimentar directamente los sistemas posteriores.

Tres: clasificación completa de errores. Cuando una capacidad madura falla, debe explicar el motivo en lugar de lanzar un error genérico. Página inexistente, falta de sesión, runtime no preparado y respuesta vacía son fallos muy distintos; necesitan códigos de error distinguibles, no quedar mezclados en uno solo. Un comando que únicamente sabe decir «ha fallado» no está listo para el catálogo, porque quien lo invoca no puede decidir si debe reintentar, volver a autenticarse u omitirlo.

Cuatro: una prueba cerrada y repetible. Un éxito aislado no es una capacidad. La misma entrada y el mismo flujo deben ejecutarse repetidamente, y ese éxito debe quedar congelado en evidencia de validación estructurada y almacenada. Si funciona una vez y en la siguiente devuelve vacío, todavía no controlas el enlace: solo coincidiste una vez con el estado del runtime adecuado.

Solo cuando se cierran los cuatro umbrales se registra en el catálogo de comandos. Si uno queda abierto, la capacidad sigue siendo un activo de investigación, no un endpoint. La diferencia entre esas dos palabras es el núcleo de todo este artículo.

Cuando el log de Frida se dispara, repártelo entre modelos por nivel

Para determinar en cuál de los cuatro umbrales se atasca un enlace, la materia prima es la traza que produce Frida. Y una traza de Frida crece rápido: adjunta unas decenas de métodos, ejecuta un flujo completo y tendrás entre decenas y cientos de miles de líneas. La mayoría será ruido de polyfills, heartbeats y módulos de negocio no relacionados.

Volcarlo entero en un único modelo y preguntar «por qué este hook no obtuvo datos» suele producir una conjetura genérica. Cuanto mayor es el contexto, más probable es que una segmentos de llamadas que no tienen relación. La alternativa correcta es dividir la traza y asignar cada segmento a distintos niveles de modelo según la capacidad necesaria.

Este es el caso de libro para combinar niveles, no para «elegir el modelo más potente y usarlo para todo». Las cuatro tareas exigen cosas completamente distintas al modelo:

Paso de procesamiento del log de Frida

Capacidad necesaria

Elección

model id

Leer de una vez la traza de una cadena de llamadas completa

Contexto largo; puede leer toda la cadena de una vez

Kimi K3

kimi-k3

Etiquetar decenas de miles de líneas (registro de dispositivo / red / criptografía / ruido)

Económico, miles de llamadas con alta concurrencia

Claude Sonnet 5

claude-sonnet-5

Determinar en cuál de los cuatro umbrales está bloqueado un enlace

Razonamiento sólido y disposición a tomar una decisión

Claude Opus 5

claude-opus-5

Comparar el punto de divergencia de dos trazas y explicar por qué una respuesta está vacía

Atribución con razonamiento intermedio; explica a partir de líneas concretas

GPT-5.6 Sol

gpt-5.6-sol

Conviene destacar la fase de etiquetado. Es trabajo mecánico puro: descartar las líneas de ruido entre decenas de miles y conservar solo las clases de registro de dispositivo, criptografía y red. El volumen es demasiado alto para hacerlo manualmente. Este tipo de «juicio simple a frecuencia muy alta» es precisamente el terreno del nivel económico; ejecutarlo en el nivel de razonamiento es tirar dinero. Una vez comprimido el log a unos cientos de líneas etiquetadas, se entrega al nivel de razonamiento para decidir «en qué umbral está bloqueado». Así encajan tanto el coste como la precisión.

No hace falta creerme: prueba una ronda y comprueba el efecto de esta división.

  1. Recorta un segmento de traza de un enlace, desde unos miles hasta decenas de miles de líneas.

  2. Etiquétalo primero por fragmentos con claude-sonnet-5, descartando el ruido y conservando las clases de registro de dispositivo, criptografía y red.

  3. Pasa el resumen etiquetado a claude-opus-5 y pídele que indique «en cuál de los cuatro umbrales está bloqueado actualmente este enlace, junto con las líneas que sustentan la evidencia».

  4. Como control, vuelca la misma traza en bruto completa en un único modelo y hazle la misma pregunta.

  5. Fíjate en una sola cosa: ¿te entrega una conjetura genérica o identifica un umbral concreto y líneas específicas? Esa diferencia es tu criterio de selección.

Un hook observa una versión; no es un firmador

¿Por qué conservar los hooks de estas tres plataformas y, al mismo tiempo, mantenerlos fuera del catálogo de comandos? Porque un hook es una sonda, no un firmador, y ambos tienen naturalezas fundamentalmente distintas.

Un hook está ligado a una compilación concreta de la app, por ejemplo la versión 32.x. Los símbolos, offsets y la disposición de métodos de los que depende pertenecen a esa versión. El upstream publica una versión nueva, todo se desplaza y el hook deja de funcionar de inmediato. Es intrínsecamente perecedero y dependiente de una versión. Responde una pregunta de observación: «¿qué está haciendo internamente esta versión ahora mismo?». Para eso sirve precisamente una herramienta de instrumentación: la documentación de Frida describe Interceptor como un medio para observar y reescribir llamadas en tiempo de ejecución. Permite ver cómo se invoca una función y cuáles son sus argumentos, pero no es por sí mismo un entregable que «calcula una firma a partir de una entrada».

Un firmador que entra en el catálogo de endpoints es justo lo contrario: debe ser estable, repetible, autónomo y apto para CI. Responde a una pregunta de capacidad reutilizable: «dame una entrada y calcularé la firma correcta». Aunque un hook permita ver con claridad el esqueleto del algoritmo de firma, eso solo cubre la etapa de identificación de huellas de algoritmo; aún queda todo un flujo de depuración y verificación diferencial para obtener un firmador independiente.

Esto también explica el orden de la escalera de depuración: una capacidad debe aspirar primero a una conexión directa en Python puro; después, recurrir a ejecutar un fragmento mínimo de firma sobre Node/V8 local; y solo de mala gana aceptar un puente pasivo de navegador. Un hook ni siquiera está aún en esa escalera. Se sitúa antes, en «investigación», no en «implementación». Registrar una sonda de observación dependiente de una versión como firmador equivale a colgar el cartel de «implementación» sobre una pieza de «investigación» a medio construir.

Cómo archivar un activo de investigación para que no se pudra

Dejar un hook fuera del catálogo de endpoints no significa tirarlo. Es un activo de investigación en el que se ha invertido esfuerzo real; contiene conocimiento del enlace, muestras y evidencia de validación. Eliminarlo es perder valor. Lo importante es archivarlo correctamente, porque dentro de dos meses ni siquiera tú sabrás hasta dónde llegó realmente.

Como mínimo, un activo de investigación de una app debe registrar estas cuatro cosas:

  • Tipo de transporte. Indica si el enlace utiliza protocolo nativo, una firma local en Node/V8 o un puente pasivo de navegador. Esto determina hasta dónde podrá depurarse más adelante.

  • Nivel de evidencia. Cuántos de los cuatro umbrales superó. Puede estar en «enlace localizado», en «estado de instalación distinto de cero, pero respuesta vacía» o en «respuesta no vacía, pero no repetible». Esto deja claro a quien continúe el trabajo qué separa exactamente el activo de ser utilizable.

  • Versión / compilación de la app. La versión a la que está vinculado el hook. Sin este dato, cuando el upstream se actualice no habrá forma de saber si el problema era del desarrollo o si cambió la compilación.

  • Resumen de muestras. Cómo eran la entrada y la salida de esta ejecución, guardadas en una copia desensibilizada. Es el punto de apoyo más rápido para retomar la investigación.

Con esos cuatro datos registrados, un enlace bloqueado en 0 comandos se convierte en un activo que puede seguir avanzando, no en un montón de logs caducados. Además, evita simultáneamente las dos peores decisiones: borrar el hook y fingir que la investigación nunca existió, o forzarlo dentro del catálogo y fingir que es utilizable. La segunda resulta especialmente cara. Un endpoint que «parece invocable, pero devuelve vacío» reparte su coste entre todos los consumidores posteriores que confían en el catálogo: integran contra él, implementan reintentos, reciben datos vacíos una vez y terminan desconfiando de todo el catálogo. Un hueco honesto marcado como «activo de investigación, 0 comandos» solo te cuesta a ti, una vez, en una línea de un README.

Una carcasa vacía cuesta más que una ausencia, y pocos casos lo dejan tan claro como este.

Una sola clave elimina la fricción al repartir los logs

Volvamos a la división de logs de la cuarta sección: contexto largo para leer toda la traza, nivel económico para etiquetar en masa, nivel de razonamiento para clasificar y nivel de razonamiento intermedio para atribuir. Son cuatro niveles de varios proveedores, cuatro SDK, cuatro esquemas de autenticación y cuatro formatos de error. Para clasificar un log de Frida entre cuatro clientes, la mayoría hace cálculos, decide que no compensa y termina peleándose con cientos de miles de líneas de traza usando un solo modelo: o quema presupuesto o no logra terminarlas.

AIReiter elimina esa fricción: una clave, una interfaz compatible con OpenAI y los cuatro niveles detrás. Basta con cambiar el campo model en el cuerpo de la solicitud.

# Label the log: the cheap tier, thousands of calls at high concurrency
curl https://aireiter.com/api/v1/chat/completions \
  -H "Authorization: Bearer $AIREITER_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "model": "claude-sonnet-5",
    "messages": [{"role": "user", "content": "<one chunk of trace + labeling instruction>"}]
  }'

# Classify the threshold: switch to the reasoning tier, leave the rest
#   "model": "claude-opus-5"
# Read the whole call chain: the long-context tier
#   "model": "kimi-k3"
# Attribute the response difference:
#   "model": "gpt-5.6-sol"

Si ya utilizas el SDK de OpenAI, apunta base_url a https://aireiter.com/api/v1 y no cambies nada más. Con el SDK de Anthropic, usa POST /api/v1/messages con la misma clave.

Este trabajo tiene una estructura de costes desigual, y el descuento coincide precisamente con su parte más pesada. La traza de un enlace ocupa de decenas a cientos de miles de líneas; el etiquetado se ejecuta fragmento a fragmento, y una plataforma puede requerir cientos o miles de llamadas a claude-sonnet-5. Ahí se concentra la mayor parte del coste. Las llamadas de contexto largo que leen una traza completa consumen unos cientos de miles de tokens por entrada y constituyen otro bloque importante. La clasificación y la atribución son pocas llamadas, aunque con un precio unitario más alto. El 30% de descuento en Claude incide directamente sobre el etiquetado y la clasificación por umbrales, las dos partes más caras. GPT a mitad de precio cubre el nivel de atribución. La lectura de contexto largo se ejecuta con Kimi K3, invocable con la misma clave.

  • Obtener una clave de API

  • Probarlo sin registrarte: entrega un segmento de traza a claude-sonnet-5 para etiquetarlo y pide después a claude-opus-5 que lo clasifique. Antes de decidir si lo integras, comprueba si puede señalar directamente en qué umbral está bloqueado.

Conclusión

En ingeniería inversa de apps, que un hook salte te da la capacidad de observación «puedo ver qué hace internamente esta versión». El catálogo de endpoints exige la capacidad de invocación «dame una entrada y produciré de forma fiable datos correctos y no vacíos». Entre ambas hay cuatro umbrales de evidencia: estado real de instalación, respuesta no vacía, clasificación de errores y una prueba repetible.

Tener tres plataformas completamente localizadas, con todos los hooks funcionando, y seguir en 0 comandos no es un fracaso: es disciplina. Si no se supera el estado real de instalación, te detienes honestamente en 0, archivas el hook como activo de investigación y lo retomas cuando dispongas del perfil de dispositivo.

El papel del modelo en este flujo es concreto. Divide, etiqueta, clasifica y atribuye cientos de miles de líneas de traza, reduciendo «dónde se atasca este enlace» de una tarde leyendo logs a unos minutos. Pero la decisión de «¿ha superado el umbral?», igual que la de «¿es correcta la hipótesis?» en las pruebas diferenciales, no recae finalmente en el modelo. Depende de los cuatro criterios que fijes y de la evidencia que se reproduzca de forma consistente. Puedes ver cómo el flujo completo de ingeniería inversa coloca al modelo en el lugar que le corresponde en la visión general de las cuatro etapas.