Análisis de Ingeniería Inversa y Reproducción de Firmas MD5 en Peticiones API

Craacterísticas de los Algoritmos Hash en Firmas de API

En el desarrollo web y la engeniería inversa, es común encontrar parámetros de autenticación o firmas en las peticiones de red que protegen los endpoints de la API. Uno de los algoritmos más utilizados para este propósito es MD5. Antes de abordar el análisis de tráfico, es fundamental recordar las propiedades matemáticas y técnicas de las funciones hash:

  • Determinismo: Una misma entrada en texto plano siempre producirá el mismo hash (a menos que se utilice una variante como HMAC, que incorpora una clave secreta dinámica).
  • Longitud Fija: La salida tiene un tamaño constante independientemente de la entrada. Por ejemplo, MD5 genera cadenas de 32 caracteres, mientras que SHA-1 genera 40 caracteres.
  • Representación Hexadecimal: Los hashes se representan típicamente en base 16, utilizando los caracteres 0-9 y a-f.

Para reproducir estas lógicas en un entorno Node.js, la biblioteca estándar de facto es crypto-js, la cual se puede integrar mediante el gestor de paquetes:

npm install crypto-js --save

Análisis de Tráfico de Red e Identificación de Parámetros

El primer paso en la ingeniería inversa de una API es interceptar y analizar el tráfico de red. Al inspeccionar el panel de red en las herrramientas de desarrollo del navegador, es recomendable limpiar el historial de peticiones y realizar una acción específica (como cambiar de página o aplicar un filtro) para aislar las peticiones relevantes.

Al filtrar las peticiones por tipo (XHR/Fetch) y examinar la carga útil (payload) y los parámetros de consulta, se puede observar que la mayoría de los parámetros son estáticos o predecibles. Sin embargo, parámetros como code (la firma) y timestamp (la marca de tiempo) varían en cada petición. Dado que la marca de tiempo es trivial de generar, el objetivo principal se centra en descubrir la lógica de generación del parámetro code.

Depuración y Localización de la Lógica de Cifrado

Para localizar dónde se genera la firma, se pueden utilizar puntos de interrupción (breakpoints) en las peticiones de red. Es importante notar que las aplicaciones modernas suelen utilizar la API fetch en lugar del tradicional XMLHttpRequest, por lo que el punto de interrupción debe configurarse para interceptar peticiones Fetch.

Análisis de la Pila de Llamadas (Call Stack)

Al detener la ejecución en el punto de interrupción de la petición, los parámetros ya se encuentran cifrados. Para encontrar el origen, es necesario analizar la pila de llamadas. En aplicaciones con código fuertemente ofuscado o empaquetado (como Webpack), la pila puede ser extensa.

Una técnica eficiente es utilizar un enfoque de búsqueda binaria en los marcos de la pila (stack frames). Se inspeccionan los marcos intermedios buscando la transición entre los datos en texto plano y los datos cifrados. Al localizar un marco donde los datos aún están en texto plano, se avanza al marco subsiguiente (hacia arriba en la pila de ejecución) hasta encontrar la función exacta que invoca el algoritmo hash.

Al analizar el código ofuscado en ese marco específico, se puede identificar la expresión que construye la cadena a cifrar. Tras desofuscar y limpiar la lógica de programación defensiva (como comprobaciones de nulos), la estructura subyacente revela cómo se concatenan las variables antes de pasarlas a la función hash.

Reconstrucción del Algoritmo de Firma

Una vez aislada la lógica de concatenación, se puede extraer el algoritmo de firma. En este caso, la cadena cruda se construye utilizando la marca de tiempo, un valor de sal (salt) estático y un subconjunto de la propia marca de tiempo.

A continuación, se presenta la implementación reconstruida y optimizada en JavaScript utilizando crypto-js:

const CryptoJS = require('crypto-js');

/**
 * Genera la firma MD5 requerida para la autenticación de la API.
 * @param {string} timestamp - Marca de tiempo en milisegundos.
 * @returns {string} Hash MD5 en formato hexadecimal.
 */
function generateApiSignature(timestamp) {
    const salt = "9527";
    const prefix = timestamp.substring(0, 6);
    const rawPayload = `${timestamp}${salt}${prefix}`;
    
    return CryptoJS.MD5(rawPayload).toString(CryptoJS.enc.Hex);
}

module.exports = { generateApiSignature };

Automatización de Peticiones con Python

Para integrar esta lógica en un script de automatización o scraping, Python puede ejecutar el código JavaScript mediante motores como V8 a través de la biblioteca PyExecJS.

Un detalle técnico crucial al combinar Python y JavaScript para firmas basadas en tiempo es la sincronización de la marca de tiempo. La marca de tiempo debe generarse en Python y pasarse como argumento a la función JavaScript. Si la marca de tiempo se genera internamente en JavaScript (por ejemplo, usando Date.now()), la latencia entre la generación del hash y la ejecución de la petición HTTP en Python causará una desincronización, resultando en un error de validación por parte del servidor backend.

import time
import execjs
import requests

class ApiClient:
    def __init__(self, js_filepath):
        with open(js_filepath, 'r', encoding='utf-8') as file:
            self.js_context = execjs.compile(file.read())
        
        self.session = requests.Session()
        self.session.headers.update({
            'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36',
            'Accept': 'application/json, text/plain, */*',
            'Referer': 'https://target-domain.com/',
        })

    def _compute_signature(self, ts):
        return self.js_context.call('generateApiSignature', ts)

    def fetch_paginated_data(self, page_number):
        # La marca de tiempo se genera en Python para garantizar la sincronización
        current_ts = str(int(time.time() * 1000))
        signature = self._compute_signature(current_ts)
        
        query_params = {
            'pages': f'{page_number},1',
            'sizes': '100,100',
            'subject': 'market_cap',
            'language': 'en_US',
            'legal_currency': 'USD',
            'code': signature,
            'timestamp': current_ts,
            'platform': 'web_pc',
            'v': '0.1.0',
            'mytoken': '',
        }
        
        endpoint = 'https://api.target-domain.com/ticker/currencyranklist'
        response = self.session.get(endpoint, params=query_params)
        response.raise_for_status()
        return response.json()

if __name__ == '__main__':
    client = ApiClient('signature_generator.js')
    for page in range(1, 4):
        try:
            data = client.fetch_paginated_data(page)
            print(f"Datos de la página {page} obtenidos correctamente. Registros: {len(data.get('data', []))}")
        except requests.exceptions.RequestException as e:
            print(f"Error en la petición de la página {page}: {e}")

Etiquetas: ingenieria-inversa md5 criptografía Python execjs

Publicado el 6-29 05:23