Al abrir un SDK de firmas ofuscado es fácil pensar que estás ante una pieza de criptografía inescrutable, donde cada línea hace algo imposible de seguir. Pero, tras recorrer el proceso completo de ingeniería inversa, el panorama suele ser justo el contrario.
El 90% del código son algoritmos estándar. El hash es público, la codificación también, y el cifrado de flujo responde a un algoritmo conocido. No hace falta comprenderlos instrucción por instrucción: basta con identificar la familia, copiar una implementación de referencia o el RFC y obtendrás exactamente los mismos bytes.
El problema está en el 10% restante: los puntos donde un algoritmo estándar se ha retocado de forma casi imperceptible. Un número de rondas que debería ser constante pasa a depender de una variable; una constante base se sustituye por un valor vecino; los índices de palabras de una ronda se intercambian; la clave queda escondida dentro del texto cifrado. Cada cambio parece menor, pero basta con pasar uno por alto para que tu reimplementación no coincida con el objetivo. Y lo peor es que los otros 90% estarán bien.
La clave para revertir una firma ofuscada no es leer ese 90%, sino localizar el 10% de puntos donde se aparta del estándar. Este artículo trata de usar un modelo grande para perseguir esas desviaciones y de por qué, en esta fase, resulta especialmente difícil sustituirlo.
(El paso previo —identificar familias de algoritmos, reconocer «esto es ChaCha» o «esto es FNV» por sus constantes y estructura— está cubierto en el artículo anterior. Aquí parto de que ya has identificado el esqueleto y voy directamente a buscar las desviaciones.)
Por qué las desviaciones se nos escapan
Cuando reconocemos un patrón, el cerebro tiende a bajar la guardia: en cuanto capta la idea general, deja de mirar con detalle.
Ves un bloque de código, las constantes encajan con la huella de ChaCha y la estructura parece correcta. Tu cerebro marca la casilla de «esto es ChaCha20» y sigue adelante. No revisa byte a byte el número de rondas, cada índice o el último bit de cada constante, porque la sensación de haberlo reconocido ya ha desconectado la atención.
Quien ofusca código cuenta precisamente con eso. No reescribe el algoritmo entero, porque sería caro y propenso a errores; introduce el cambio mínimo posible sobre uno estándar: altera un número, intercambia un índice, añade un paso. Son modificaciones lo bastante discretas para que el reconocimiento de patrones las ignore, pero suficientes para que una copia fiel al estándar falle por completo.
Es una guerra de atención asimétrica. El autor solo necesita ocultar una desviación; tú tienes que descubrirlas todas. Y las personas somos especialmente malas manteniendo la sospecha ante algo que parece correcto.
El modelo cuenta aquí con una ventaja poco intuitiva: no experimenta esa satisfacción de «ya lo he reconocido» que apaga la vigilancia. Si el prompt le pide de forma explícita buscar desviaciones, puede comprobar cada punto sin relajarse a mitad de camino. Eso sí: hace falta orientarlo bien, y de eso trata la sección 5. Antes, veamos cómo se presentan estos cuatro tipos de alteración.
Cuatro formas habituales de desviarse del estándar
En cada caso usaré un algoritmo público para mostrar cómo es la versión estándar y cómo puede aparecer la modificación. En un proyecto real, es habitual que las cuatro técnicas convivan en el mismo firmador.
1. De constante a variable: un parámetro fijo pasa a calcularse en tiempo de ejecución
Un cifrado de flujo estándar tiene un número fijo de rondas. ChaCha20 usa 20 rondas, sin excepción; todas las implementaciones estándar lo codifican como un valor fijo. La introducción de RFC 8439 lo deja claro al indicar que solo describe ChaCha de 20 rondas, mientras que las variantes de 8 y 12 rondas se definen en otros documentos. El número de rondas siempre ha sido una constante del estándar.
La desviación consiste en convertir ese número fijo de rondas en un valor calculado dinámicamente a partir de la clave. La lógica de quarter-round sigue siendo la misma, pero la cantidad de veces que se ejecuta depende de ciertos bytes de la clave. Al cambiar la clave, cambia también el número de rondas.
Por qué se pasa por alto: reconoces la estructura quarter-round, reconoces las constantes σ, decides que es «ChaCha20» y lo reproduces con 20 rondas. Ni siquiera compruebas si la variable que controla el bucle es una constante o una expresión, porque en todos los ChaCha que has visto siempre fue constante.
La pista es clara: esperas una constante y encuentras una expresión que depende de la entrada. Una revisión de contraevidencias bien guiada buscará específicamente los lugares donde la implementación estándar fija un valor, pero el código analizado lo calcula.
Las pruebas diferenciales lo confirman ejecutando deliberadamente dos claves que difieren en un único byte. Si la diferencia en la salida es mucho mayor que la influencia esperable de un byte, ese byte está alimentando un parámetro global —por ejemplo, el número de rondas— en lugar de limitarse a entrar mediante XOR en el flujo de claves.
2. Perturbación de constantes: cambiar una constante base por un valor casi idéntico
El hash FNV-1a tiene dos números mágicos públicos: la offset basis y el prime. Cualquier implementación correcta de FNV-1a utiliza exactamente esos dos valores, publicados en el estándar. La página de referencia de FNV, mantenida por el coautor Landon Curt Noll, fija la offset basis de 32 bits en 2166136261 y el prime en 16777619, dígito por dígito.
La desviación: sustituir una de esas constantes base por un valor cercano que solo difiere mínimamente del estándar. La estructura general del hash permanece intacta: el XOR, la multiplicación y el bucle son correctos; solo se ha retocado casi invisiblemente esa base inicial.
Por qué se pasa por alto: este es el caso más desagradable de los cuatro. Ves la estructura de FNV, una constante grande que se parece a la offset basis y concluyes que es «FNV-1a estándar». No comparas bit a bit esa constante con el valor oficial. ¿Quién verifica dos veces un número mágico que cree haber reconocido?
Este tipo de alteración casi siempre exige pruebas diferenciales, porque comparar visualmente números grandes es muy poco fiable. El método consiste en tomar una entrada conocida, ejecutarla tanto con la implementación estándar como contra la caja negra objetivo y comparar los resultados. Si la estructura coincide pero las salidas difieren, el problema casi seguro está en una constante base. Entonces puedes entregar la constante sospechosa al modelo para que la compare con la estándar: una máquina es mucho más fiable que tú en esa tarea.
La aportación del modelo aquí es muy concreta: recuerda la offset basis estándar de FNV-1a hasta el último bit, y tú no. Solo tienes que preguntar «¿esta constante coincide exactamente con la offset basis estándar de FNV-1a?» y señalará la diferencia de inmediato.
3. Retoque estructural: modificar localmente un paso del algoritmo
Cada double round de ChaCha está compuesto por 8 quarter rounds: los primeros 4 trabajan sobre columnas y los 4 siguientes sobre diagonales. Los índices de palabras que opera cada quarter round están fijados por el estándar. RFC 8439 §2.3 describe la función de bloque y especifica exactamente qué palabras de estado toca cada ronda.
La desviación consiste en intercambiar discretamente uno o dos índices de palabras en una de las rondas. La gran mayoría de rondas sigue el estándar; solo en algún punto intermedio una palabra que debería procesarse se sustituye por otra. Sigue pareciendo ChaCha y se ejecuta sin errores, pero el flujo de claves resultante es completamente distinto al de ChaCha estándar.
Por qué se pasa por alto: los índices de quarter-round forman una secuencia larga de números, ocho grupos como (0,4,8,12)(1,5,9,13)…. La vista los recorre por encima y se limita a confirmar «sí, rondas de columna y diagonales», sin comprobar que los cuatro números de cada grupo estén exactamente donde marca el estándar. Ocultar un cambio en una cadena de índices que ya de por sí fatiga la vista es una estrategia perfecta.
Tampoco es algo que el modelo detecte de un vistazo: hay que indicarle que enumere los índices ronda por ronda y los compare con ChaCha estándar. Es una tarea mecánica, justo el tipo de comprobación en que el modelo no se distrae y una persona sí. Pídele una tabla de «índice estándar frente a índice real» y la desviación aparecerá sola.
Las pruebas diferenciales lo confirman así: si has descartado los dos primeros casos —el número de rondas y las constantes son correctos— y la salida sigue sin coincidir, el problema es estructural. Vuelca el estado intermedio ronda por ronda; la primera ronda que diverja de ChaCha estándar será la modificada.
4. Material incrustado: datos de clave escondidos en la salida
Los tres casos anteriores alteran el algoritmo; este modifica la organización de los datos.
Una técnica frecuente consiste en no transmitir la clave de cifrado por un canal separado, sino fragmentarla e insertarla dentro del propio texto cifrado. El receptor la extrae aplicando la misma regla. La versión más astuta no fija la posición de inserción, sino que la calcula a partir del contenido de los datos: si cambia el texto cifrado, también cambia el lugar donde se oculta la clave. La capa exterior envuelve después todo con Base64 de alfabeto personalizado y un byte de prefijo marcador.
Por qué se pasa por alto: estás peleándote con el algoritmo de cifrado y no te das cuenta de que quizá no tengas que romper la clave en absoluto. Está en el texto cifrado que ya tienes delante, solo que no sabes qué segmento la contiene. Es habitual que quienes empiezan se bloqueen intentando «descifrar» algo cuyo material necesario está a la vista.
La pista en este caso es una región dentro de un bloque de datos con características estadísticas distintas de las del entorno. Una clave suele consistir en bytes aleatorios de alta entropía y, al insertarse en medio del texto cifrado, forma un «segmento extraño» identificable. El modelo puede ayudarte a analizar qué intervalo de la salida presenta una distribución de bytes distinta al resto y así acotar los límites del material incrustado.
Para confirmarlo, si encuentras la regla de inserción —por ejemplo, un módulo aplicado a una suma de bytes—, toma varios pares conocidos de entrada y salida, resuelve hacia atrás la fórmula de posición y después valídala hacia delante. El modelo puede ayudarte a inducir la regla a partir de unas pocas muestras, pero decidir si es correcta sigue siendo tarea de assert.
La revisión de contraevidencias es el verdadero terreno del modelo de razonamiento
Los cuatro tipos comparten un patrón: reconocer la familia del algoritmo —la hipótesis candidata— es fácil; encontrar la desviación —la contraevidencia— es lo difícil.
Casi cualquier modelo puede proponer la familia candidata. Las constantes σ de ChaCha o la estructura de FNV son patrones conocidos por cualquier modelo que haya visto los datos de entrenamiento. Lo que realmente diferencia a unos modelos de otros es la revisión de contraevidencias: si el modelo quiere y puede seguir cuestionando un algoritmo que acaba de reconocer para decir «esta parte no coincide con el estándar».
Un modelo más débil pierde firmeza en este punto. Tras reconocer «esto es ChaCha20», su revisión de contraevidencias suele degenerar en repetir la hipótesis con otras palabras: «la implementación sigue la estructura estándar de ChaCha20 y emplea el clásico quarter round…». Es una reformulación de la hipótesis, no una comprobación real de desviaciones. Una revisión así no puede orientar el siguiente paso.
La revisión de contraevidencias de un modelo de razonamiento sólido es distinta. Dirá algo como: «la hipótesis es ChaCha20, pero hay tres diferencias con la implementación estándar: primero, el número de rondas depende de una expresión ligada a la clave, mientras que ChaCha20 estándar siempre usa 20 rondas; segundo, los índices de palabras de la ronda N no coinciden con la ronda diagonal estándar; tercero…». Cada observación apunta a una desviación específica que puedes verificar. Una revisión de este tipo es la lista de comprobación para pruebas diferenciales que escribirás en la fase 3.
Por eso conviene colocar la identificación de familias de algoritmos en la capa de razonamiento y probarla tú mismo antes de decidir. La calidad de las contraevidencias entre modelos determina directamente cuántas pruebas inútiles escribirás y cuántos desvíos tomarás.
Las cuatro capas que uso en este flujo de trabajo son:
Fase | Capacidad necesaria | Elección | model id |
|---|---|---|---|
Mapeo estructural tras dividir el código | Contexto largo; leer un módulo completo de una pasada | Kimi K3 |
|
Identificación de familia y contraevidencias | Razonamiento sólido; capaz de rebatirse a sí mismo | Claude Opus 5 |
|
Renombrado masivo de símbolos | Económico y con alta concurrencia | Claude Sonnet 5 |
|
Atribución de diferencias | Razonamiento intermedio; explica diferencias frente a bytes concretos | GPT-5.6 Sol |
|
La segunda capa es el núcleo de este artículo. No deberías elegirla solo porque yo lo diga: ponla a prueba. El protocolo es sencillo:
Elige 2–3 funciones hoja de tu propio bundle ofuscado; al menos una debe tener una respuesta que ya conozcas, como control.
Usa el prompt de tres partes «hipótesis candidata / evidencia / contraevidencia» de el artículo anterior y entrega la misma entrada por separado a
claude-opus-5ygpt-5.6-sol.Examina únicamente la revisión de contraevidencias: ¿comprueba realmente las desviaciones una por una o solo repite la hipótesis con otras palabras? En el caso de control, ¿cuántas desviaciones detectó cada modelo?
El número y la calidad de las desviaciones encontradas son tu criterio de selección.
La diferencia se aprecia en una sola ejecución, de forma mucho más directa que en cualquier clasificación de benchmarks.
El coste de cambiar de modelo es el obstáculo real
Cuatro modelos de tres proveedores. La forma ingenua de usar un modelo distinto en cada fase implica integrar tres SDK, tres sistemas de autenticación y tres manejadores de errores. Mucha gente hace las cuentas, decide que no compensa y termina usando un único modelo para todo: una capa débil en contraevidencias para identificar familias de algoritmos, seguida de una colección de desvíos cuyo origen ni siquiera entiende.
AIReiter elimina esa fricción: una clave, una interfaz compatible con OpenAI, los cuatro modelos detrás y el cambio se reduce a modificar el campo model del cuerpo de la petición.
# Identificación de familia + contraevidencias: la capa de razonamiento
curl https://aireiter.com/api/v1/chat/completions \
-H "Authorization: Bearer $AIREITER_KEY" \
-H "Content-Type: application/json" \
-d '{
"model": "claude-opus-5",
"messages": [{"role": "user", "content": "<prompt de tres partes + función hoja + constantes>"}]
}'
# Renombrado masivo de símbolos: cambia un campo
# "model": "claude-sonnet-5"
# Atribución de diferencias:
# "model": "gpt-5.6-sol"
¿Ya usas el SDK de OpenAI? Apunta base_url a https://aireiter.com/api/v1 y no cambies nada más. ¿Usas el SDK de Anthropic? Llama a POST /api/v1/messages con la misma clave.
En precios, los modelos Claude tienen un 30% de descuento sobre tarifa y los modelos GPT cuestan la mitad. Para este flujo, el descuento afecta justo a las fases más exigentes: la identificación de familia es donde iteras el prompt una y otra vez, consultando la misma función durante muchas rondas, y es la parte con más llamadas; el renombrado masivo de símbolos empieza a contar las llamadas por cientos. Entre ambas se concentra la mayor parte del coste.
Pruébalo sin registrarte — ejecuta unas cuantas rondas manualmente, compara con tus propios ojos las revisiones de contraevidencias de ambos modelos y decide después.
Conclusión
La realidad de revertir firmas ofuscadas es esta: la mayor parte del código son algoritmos estándar que puedes reproducir literalmente; el trabajo real consiste en encontrar los puntos donde el estándar se modificó discretamente.
Los cuatro tipos de desviación —de constante a variable, perturbación de constantes, retoque estructural e incrustación de material— comparten una característica: son lo bastante pequeños para que el reconocimiento humano de patrones los salte, pero lo bastante grandes para hacer fracasar por completo una reimplementación. Las personas no mantenemos bien la sospecha ante algo que parece correcto; ahí está precisamente la fortaleza de un modelo, si se le da el prompt adecuado.
Pero el modelo solo genera sospechas; no las confirma. Toda hipótesis sobre un punto de desviación debe terminar convertida en una prueba diferencial, el tema de el artículo sobre pruebas diferenciales. El modelo te entrega una lista de «lugares que podrían haberse modificado»; assert te dice cuáles lo fueron realmente.
