Imagen abstracta de tinta azul oscuro y blanco fluyendo en efecto de mármol

Aplicaciones y Herramientas

Del binario al comportamiento: priorización automática de funciones mediante heurísticas estructurales

Análisis de malware guiado por heurísticas: prioriza funciones, detecta capacidades y renombra código automáticamente. Herramienta en Python (Flask + radare2) incluida.

Cuando se analiza un binario desconocido, lo primero que uno intenta hacer es entender por dónde empezar. Un ejecutable puede tener cientos o miles de funciones, y la mayoría no dicen nada interesante a simple vista. Muchas son rutinas auxiliares, llamadas a librerías o pequeñas piezas de código que solo sirven para tareas repetitivas. El problema real es cómo encontrar, dentro de todo ese ruido, las partes que realmente importan.

Hay dos caminos clásicos para empezar ese análisis. El primero es ir al punto de entrada del programa, la función inicial que se ejecuta cuando el binario arranca. Desde ahí se intenta seguir el flujo del programa paso a paso, como si se leyera una historia desde el principio. El segundo camino es más directo: buscar pistas dentro del propio binario, como cadenas de texto o llamadas a funciones del sistema.

Las cadenas de texto suelen ser un buen punto de partida. En muchos casos, un programa deja rastros claros: direcciones IP, nombres de archivos, rutas del sistema o mensajes de error. Esas cadenas pueden llevarnos directamente a zonas del código donde ocurre algo importante. Por ejemplo, si aparece una dirección de servidor, es probable que el código relacionado tenga que ver con comunicación de red. Si aparece el nombre de un programa o un servicio concreto, puede indicar integración con ese sistema.

Algo parecido ocurre con las funciones importadas. Muchos programas dependen de funciones del sistema operativo para realizar tareas como escribir en disco, capturar pantalla o manejar memoria. Si se observan las importaciones, se pueden identificar comportamientos típicos. Por ejemplo, ciertas funciones de captura de pantalla o de red suelen aparecer en malware o en herramientas de monitorización.

El problema de este enfoque es que no siempre funciona. Cada vez es más común encontrar binarios donde las cadenas están cifradas o construidas en tiempo de ejecución. También hay programas que resuelven sus funciones dinámicamente, lo que significa que no aparecen en la lista de importaciones. En esos casos, tanto las cadenas como las importaciones dejan de ser útiles como guía.

Además, hay tipos de código que no se detectan fácilmente con este método. Funciones relacionadas con protocolos de red, sistemas de cifrado, lógica interna compleja o mecanismos de control suelen estar ocultos entre muchas otras funciones sin importancia aparente. No hay una etiqueta clara que diga “esto es interesante”. Por eso, depender solo de cadenas e importaciones es insuficiente.

La pregunta entonces es cómo encontrar código relevante sin depender de pistas evidentes. Es decir, cómo identificar automáticamente qué partes de un binario merecen atención cuando no hay información directa.

La respuesta está en el uso de heurísticas. Una heurística no es una regla exacta, sino una forma de estimar qué es probable que sea interesante. En lugar de buscar una firma concreta, se buscan patrones generales que suelen aparecer en código relevante. Estos patrones no garantizan resultados perfectos, pero reducen mucho el espacio de búsqueda.

Código 15. Presentación de hallazgos

            </div>
        </div>

        <div class="card">
            <h3>📈 Resumen de Auto-Renombrado Contextual</h3>
            <p class="subtitle" id="summary-count">Se identificaron y renombraron funciones de forma automática.</p>
            <table>
                <thead>
                    <tr>
                        <th>Función Original</th>
                        <th>CC</th>
                        <th>Capacidades Identificadas (Tags)</th>
                    </tr>
                </thead>
                <tbody id="results-table-body">
                    <!-- Filas dinámicas -->
                </tbody>
            </table>
        </div>
    </div>
</div>

<script>
    let selectedFile = null;

    // Desactivar el comportamiento nativo de guardado/apertura del navegador en toda la página
    ['dragenter', 'dragover', 'dragleave', 'drop'].forEach(eventName => {
        window.addEventListener(eventName, (e) => e.preventDefault(), false);
        document.body.addEventListener(eventName, (e) => e.preventDefault(), false);
    });

    const uploadZone = document.querySelector('.upload-zone');
    
    uploadZone.addEventListener('dragover', (e) => {
        e.preventDefault();
        uploadZone.style.borderColor = 'var(--accent)';
    });

    uploadZone.addEventListener('dragleave', () => {
        uploadZone.style.borderColor = 'var(--border-color)';
    });

    uploadZone.addEventListener('drop', (e) => {
        e.preventDefault();
        uploadZone.style.borderColor = 'var(--border-color)';
        
        if (e.dataTransfer.files.length > 0) {
            const fileInput = document.getElementById('file-input');
            fileInput.files = e.dataTransfer.files;
            fileSelected(fileInput);
        }
    });

    function fileSelected(input) {
        if (input.files.length > 0) {
            selectedFile = input.files[0];
            document.getElementById('upload-text').innerText = `📝 Seleccionado: ${selectedFile.name}`;
            document.getElementById('submit-btn').disabled = false;
        }
    }
    
    
    // Volver al inicio limpiando todo el entorno para un nuevo análisis
    function volverInicio() {
        selectedFile = null;
        document.getElementById('file-input').value = '';
        document.getElementById('upload-text').innerText = '📁 Arrastra aquí tu binario (.exe, .dll, ELF) o haz clic para buscar';
        document.getElementById('submit-btn').disabled = true;
        resetUI();
    }

    function uploadFile() {
        if (!selectedFile) return;

        document.getElementById('upload-card').style.display = 'none';
        const progressContainer = document.getElementById('progress-section');
        const progressBar = document.getElementById('p-bar');
        const progressStatus = document.getElementById('status-text');

        progressContainer.style.display = 'block';

Este fragmento implementa directamente el concepto descrito en el párrafo anterior y muestra cómo la heurística se transforma en una capacidad operativa dentro del pipeline de análisis.

En el análisis de binarios se pueden agrupar estas heurísticas en tres grandes categorías: complejidad, estadísticas y formas de grafos.

Código 5. Taxonomía de APIs


    categorias_api = {
        "red": ["getaddrinfo", "socket", "connect", "send", "recv", "WSAStartup", "InternetOpen", "HttpSendRequest", "URLDownloadToFile", "DnsQuery"],
        "evasion": ["IsDebuggerPresent", "CheckRemoteDebuggerPresent", "OutputDebugString", "FindWindow", "GetTickCount", "Sleep", "NtDelayExecution", "GetSystemTime", "QueryPerformanceCounter"],
        "persistencia": ["RegCreateKey", "RegSetValue", "RegOpenKey", "Software\\Microsoft\\Windows\\CurrentVersion\\Run", "CreateService", "SchTasks", "CopyFile", "MoveFile", "SetFileAttributes"],
        "inyeccion": ["VirtualAlloc", "WriteProcessMemory", "CreateRemoteThread", "NtCreateSection", "MapViewOfSection", "QueueUserAPC", "SetThreadContext", "NtWriteVirtualMemory", "RtlCreateUserThread"],
        "crypto": ["CryptAcquireContext", "CryptEncrypt", "CryptDecrypt", "MD5", "SHA", "AES", "XOR", "RC4", "vpxor_avx", "BCryptDecrypt"],
        "stealer": ["GetClipboardData", "OpenClipboard", "SamIConnect", "SamrQueryInformationUser", "CryptUnprotectData", "GetComputerName", "GetUserName", "ReadProcessMemory"],

La complejidad se refiere a lo complicado que es el flujo de una función. Una función simple suele hacer pocas cosas: sumar valores, copiar memoria o devolver un resultado. En cambio, una función compleja tiene muchos caminos posibles, muchas ramas condicionales y múltiples bloques de ejecución. Esa complejidad suele ser una señal de que algo importante ocurre allí.

La razón es sencilla. Muchas funcionalidades interesantes requieren lógica más elaborada. Un protocolo de red, por ejemplo, necesita manejar distintos tipos de mensajes, errores, reintentos y estados. Un algoritmo de cifrado tiene múltiples pasos encadenados. Incluso un sistema de control interno necesita tomar decisiones según diferentes condiciones. Todo eso aumenta la complejidad del flujo de ejecución.

Para medir esta complejidad no se usa una sola métrica. Se pueden contar instrucciones, bloques básicos o conexiones entre ellos. Sin embargo, lo más habitual es usar lo que se conoce como complejidad ciclomática, que combina el número de bloques y las transiciones entre ellos. Cuantas más rutas posibles tiene una función, más compleja se considera.

Código 2. Ordenación por complejidad

            
            r2.cmd("aaa")
            
            funciones = json.loads(r2.cmd("aflj") or "[]")
            funciones_ordenadas = sorted(funciones, key=obtener_cc_seguro, reverse=True)

Lo interesante no es fijar un número concreto para decidir si algo es complejo o no. En la práctica, eso no funciona bien porque cada binario es diferente. Lo útil es comparar funciones entre sí dentro del mismo programa. Se ordenan según su complejidad y se observan las que están en la parte superior. Normalmente, ese pequeño porcentaje superior contiene las funciones más relevantes.

A partir de ahí aparecen patrones reconocibles. Las funciones más complejas suelen corresponder a protocolos, máquinas de estados o lógica de control. No siempre es así, pero la probabilidad es alta.

La segunda categoría es la de estadísticas. Aquí no se analiza la estructura interna de la función, sino su comportamiento dentro del programa. Una métrica útil es cuántas veces se llama a una función. Si una función es llamada muchas veces, es probable que sea importante.

En muchos casos, las funciones más llamadas pertenecen a bibliotecas del sistema, como copia de memoria o manejo de cadenas. Pero en otros casos, especialmente en programas estáticos o malware, estas funciones pueden ser parte del propio código del programa. Eso las convierte en puntos clave de análisis.

También ocurre algo interesante en malware: algunas funciones críticas están ocultas mediante técnicas de ofuscación. Por ejemplo, pueden usar hash de funciones en lugar de nombres directos, o cifrar cadenas para evitar su detección. Aun así, esas funciones suelen ser llamadas repetidamente, lo que permite detectarlas mediante este tipo de estadísticas.

La tercera categoría es la forma del grafo de la función. Cada función puede representarse como un conjunto de bloques conectados entre sí, formando una especie de mapa. Algunas funciones tienen estructuras muy particulares que se repiten en distintos tipos de software.

Un caso muy importante es el de las máquinas de estados. Una máquina de estados es un diseño en el que el programa se encuentra en un estado y va cambiando entre ellos según las entradas que recibe. Este tipo de estructura aparece en protocolos de red, controladores, analizadores de datos y también en malware.

Visualmente, una máquina de estados suele tener un bloque central que dirige el flujo hacia muchos otros bloques, y estos a su vez vuelven al punto central. Es como un bucle con decisiones internas. Detectar este tipo de forma en el grafo es una señal fuerte de que la función tiene lógica importante.

Otra forma útil de análisis es observar el tamaño de los bloques básicos. Un bloque básico es una secuencia de instrucciones que se ejecutan sin saltos. En la mayoría de los casos, estos bloques son pequeños. Pero cuando aparecen bloques muy grandes, puede indicar algo especial.

Por ejemplo, en algunos casos se encuentran bloques con decenas de instrucciones aritméticas seguidas. Esto es típico de algoritmos de cifrado, especialmente cuando han sido optimizados o “desenrollados” por el compilador. En lugar de llamar a funciones pequeñas repetidas veces, el compilador expande todo el cálculo en un solo bloque grande.

Esto permite detectar implementaciones de criptografía incluso cuando no hay nombres claros ni llamadas evidentes. Muchas veces, funciones como MD5, AES o variantes similares pueden identificarse de esta manera.

Una ventaja importante de estas heurísticas es que son rápidas y no dependen de la arquitectura del binario. Funcionan tanto en programas pequeños como en firmware embebido o malware complejo. Además, pueden aplicarse a miles de funciones en muy poco tiempo, lo que las hace útiles en análisis automatizado.

Otro patrón interesante aparece en operaciones de cifrado simples como XOR en bucles. Es muy común encontrar bucles que recorren datos y aplican una operación XOR con una constante. Esto suele indicar descifrado de cadenas o descompresión de datos en tiempo de ejecución.

Aunque es un patrón sencillo, es extremadamente frecuente en malware. Detectarlo permite descubrir rápidamente funciones que están ocultando información.

Algo similar ocurre con algoritmos como RC4. Este tipo de cifrado tiene una estructura muy característica: dos bucles principales, uno de inicialización y otro de generación de flujo de claves. Ambos suelen iterar sobre 256 elementos. Esa constante es una pista muy clara. Cuando se detectan dos bucles con ese comportamiento, es muy probable que se trate de una implementación de RC4 o algo muy similar.

En la práctica, combinar estas heurísticas es lo que realmente da buenos resultados. Ninguna por sí sola es perfecta, pero juntas permiten construir una imagen bastante precisa del binario. Una función compleja puede ser interesante, pero si además es muy llamada o tiene una estructura de máquina de estados, la probabilidad aumenta. Si además contiene patrones de cifrado o bloques grandes, la confianza es aún mayor.

El resultado de todo esto es un cambio de enfoque. En lugar de leer un binario de forma lineal, se filtran primero las partes más relevantes. Es como ordenar un mapa antes de empezar a explorarlo.

Aunque no siempre se cumple, esta aproximación reduce drásticamente el ruido generado por cientos de funciones triviales.

A partir de aquí, el análisis deja de ser una búsqueda ciega. En lugar de revisar función por función, se empieza por un conjunto reducido de candidatos. Esto ahorra tiempo y permite centrarse en lo que realmente importa.

En entornos más avanzados, estas heurísticas se pueden automatizar. Herramientas especializadas analizan el binario, calculan métricas de cada función y las ordenan según distintos criterios. Algunas incluso etiquetan automáticamente funciones como posibles cifrados, máquinas de estados o lógica de red.

Esto abre la puerta a un análisis asistido por sistemas automáticos. El analista humano ya no necesita explorar todo el código, sino revisar los resultados más probables. Y en combinación con agentes automáticos, se puede incluso llegar más lejos: clasificar funciones, descartar falsos positivos y construir una visión general del comportamiento del programa.

El siguiente paso natural es integrar todo esto en flujos de trabajo completos. Primero se extraen métricas. Luego se ordenan funciones. Después se aplican heurísticas específicas según el tipo de software. Finalmente, se revisan solo los resultados más prometedores.

Este enfoque no elimina la complejidad del análisis de binarios, pero la reorganiza. En lugar de enfrentarse a un sistema desordenado, se trabaja con una versión filtrada y priorizada. Y eso cambia completamente la forma en que se entiende el código.

Al final, el objetivo no es encontrar todas las funciones interesantes, sino encontrar las primeras buenas pistas. A partir de ahí, el resto del análisis se vuelve mucho más manejable.

La idea de fondo de todo lo anterior no es solo teórica. En realidad, lo que se busca es automatizar una parte muy concreta del análisis de binarios: reducir el espacio de búsqueda hasta que el código deje de ser una masa homogénea y pase a ser un conjunto de piezas clasificadas.

El problema en la práctica no es la falta de información, sino el exceso. Un binario moderno, incluso uno pequeño, puede contener cientos de funciones. Muchas de ellas no aportan información útil para entender el comportamiento global del programa. Si se revisan una a una, el análisis se vuelve lento y poco eficiente.

Por eso tiene sentido construir un sistema que haga dos cosas a la vez. Primero, extraer el código real desde el binario. Segundo, aplicar una capa de interpretación encima de ese código para asignarle significado.

En el código que acompaña este artículo se utiliza Radare2 a través de r2pipe como motor de análisis estático. Radare2 se encarga de desensamblar el binario, identificar funciones, resolver referencias y generar representaciones intermedias del código. A partir de ahí, el sistema ya no trabaja sobre el binario directamente, sino sobre una estructura más manejable.

Código 1. Inicialización del entorno de análisis

import os
import json
import re
import uuid
from flask import Flask, render_template, request, Response, send_from_directory, jsonify
import r2pipe

app = Flask(__name__)
UPLOAD_FOLDER = os.path.join(os.path.dirname(os.path.abspath(__file__)), 'uploads')
OUTPUT_FOLDER = os.path.join(os.path.dirname(os.path.abspath(__file__)), 'outputs')
os.makedirs(UPLOAD_FOLDER, exist_ok=True)
os.makedirs(OUTPUT_FOLDER, exist_ok=True)

Este fragmento implementa la inialización del entorno de análisis.

El primer paso importante es la ordenación de funciones según complejidad. En lugar de tratar todas las funciones por igual, se priorizan aquellas con mayor complejidad ciclomática. Esto no es una medida absoluta de importancia, pero sí una buena aproximación inicial. Las funciones más complejas suelen concentrar lógica relevante: control de flujo, decisiones, cifrado o comunicación.

Código 2. Ordenación por complejidad

            
            r2.cmd("aaa")
            
            funciones = json.loads(r2.cmd("aflj") or "[]")
            funciones_ordenadas = sorted(funciones, key=obtener_cc_seguro, reverse=True)

Sin embargo, la complejidad por sí sola no es suficiente. Un segundo filtro introduce un elemento muy importante: las funciones exportadas. En el caso de librerías o binarios que exponen API internas, las exportaciones suelen ser puntos de entrada críticos. Son las funciones que otros módulos pueden invocar directamente, lo que las convierte en candidatos naturales a análisis prioritario.

Código 3. Recuperación de exportaciones

            exportaciones_set = set()
            try:
                raw_exports = r2.cmd("iEj")
                if raw_exports and raw_exports.strip():
                    exports_json = json.loads(raw_exports)
                    for exp in exports_json:
                        if exp.get("name"):
                            exportaciones_set.add(exp.get("name"))
            except:
                pass

            funcs_proc = []
            for f in funciones_ordenadas:
                nom_f = f.get("name", "")
                cc_f = obtener_cc_seguro(f)

La combinación de ambos criterios permite construir una primera reducción del problema. En lugar de analizar todo el conjunto de funciones, se selecciona un subconjunto que representa las zonas más activas del binario.

Pero el sistema no se detiene ahí. Una vez seleccionadas las funciones, se realiza un análisis más profundo del contenido de cada una. Aquí es donde entra la función analizar_funcion_interesante.

Esta parte del código no intenta comprender el programa completo, sino identificar patrones locales dentro de cada función. En lugar de interpretar semánticamente el código de forma global, se buscan señales concretas: cadenas, llamadas a API y combinaciones típicas de comportamiento malicioso o relevante.

El análisis empieza por algo relativamente simple: la extracción de dominios y direcciones IP. Esto permite identificar comunicación externa. Si una función contiene referencias a dominios sospechosos o a IPs hardcodeadas, es probable que esté relacionada con infraestructura de red, exfiltración de datos o control remoto.

Código 4. Extracción de IOCs

    }
    hallav_set, m_set = set(), set()

    dominios = re.findall(r'"([a-zA-Z0-9][-a-zA-Z0-9]*\.[a-zA-Z0-9.-]+\.[a-zA-Z]{2,})"', texto_c)
    ips = re.findall(r'"((?:25[0-5]|2[0-4][0-9]|1[0-9][0-9]|[1-9]?[0-9])\.(?:25[0-5]|2[0-4][0-9]|1[0-9][0-9]|[1-9]?[0-9])\.(?:25[0-5]|2[0-4][0-9]|1[0-9][0-9]|[1-9]?[0-9])\.(?:25[0-5]|2[0-4][0-9]|1[0-9][0-9]|[1-9]?[0-9]))"', texto_c)

    for d in set(dominios):
        if not d.endswith((".microsoft.com", ".w3.org", "xml.org", "schemas-microsoft-com")):
            hallazgos["dominios"].append(d)
            m_set.add("T1071.001 - C2: Web Protocols (Dynamic DNS Domain)")
    for ip in set(ips):
        hallazgos["ips"].append(ip)

A continuación se analizan llamadas a funciones del sistema agrupadas por categorías. Estas categorías no son arbitrarias, sino que reflejan comportamientos típicos observados en malware y software complejo.

Código 5. Taxonomía de APIs


    categorias_api = {
        "red": ["getaddrinfo", "socket", "connect", "send", "recv", "WSAStartup", "InternetOpen", "HttpSendRequest", "URLDownloadToFile", "DnsQuery"],
        "evasion": ["IsDebuggerPresent", "CheckRemoteDebuggerPresent", "OutputDebugString", "FindWindow", "GetTickCount", "Sleep", "NtDelayExecution", "GetSystemTime", "QueryPerformanceCounter"],
        "persistencia": ["RegCreateKey", "RegSetValue", "RegOpenKey", "Software\\Microsoft\\Windows\\CurrentVersion\\Run", "CreateService", "SchTasks", "CopyFile", "MoveFile", "SetFileAttributes"],
        "inyeccion": ["VirtualAlloc", "WriteProcessMemory", "CreateRemoteThread", "NtCreateSection", "MapViewOfSection", "QueueUserAPC", "SetThreadContext", "NtWriteVirtualMemory", "RtlCreateUserThread"],
        "crypto": ["CryptAcquireContext", "CryptEncrypt", "CryptDecrypt", "MD5", "SHA", "AES", "XOR", "RC4", "vpxor_avx", "BCryptDecrypt"],
        "stealer": ["GetClipboardData", "OpenClipboard", "SamIConnect", "SamrQueryInformationUser", "CryptUnprotectData", "GetComputerName", "GetUserName", "ReadProcessMemory"],

Por ejemplo, las funciones de red incluyen llamadas como socket, connect o send. Las de evasión incluyen mecanismos para detectar depuradores o entornos virtualizados. Las de persistencia apuntan a modificaciones del registro o del sistema de archivos para mantenerse tras reinicios. Las de inyección de procesos indican manipulación directa de memoria o ejecución remota dentro de otros procesos.

Código 6. APIs de red


    categorias_api = {
        "red": ["getaddrinfo", "socket", "connect", "send", "recv", "WSAStartup", "InternetOpen", "HttpSendRequest", "URLDownloadToFile", "DnsQuery"],
        "evasion": ["IsDebuggerPresent", "CheckRemoteDebuggerPresent", "OutputDebugString", "FindWindow", "GetTickCount", "Sleep", "NtDelayExecution", "GetSystemTime", "QueryPerformanceCounter"],

Código 7. Heurística anti-análisis

    if "sleep" in tc_low and ("gettickcount" in tc_low or "queryperformancecounter" in tc_low or "%" in tc_low):
        hallazgos["acciones"].append("CAPA: [Time_Based_Evasion] -> Demora la ejecución calculando retardos dinámicos")
        m_set.add("T1497.003 - Virtualization/Sandbox Evasion: Time Based Evasion")
    if "cryptunprotectdata" in tc_low or ("cryptdecrypt" in tc_low and "login" in tc_low):
        hallazgos["acciones"].append("CAPA: [Credential_Dumping] -> Intenta extraer contraseñas guardadas (DPAPI)")

Cada una de estas categorías actúa como una capa de interpretación. No describe lo que el código hace exactamente, pero sí el tipo de comportamiento que probablemente implementa.

Código 5. Taxonomía de APIs


    categorias_api = {
        "red": ["getaddrinfo", "socket", "connect", "send", "recv", "WSAStartup", "InternetOpen", "HttpSendRequest", "URLDownloadToFile", "DnsQuery"],
        "evasion": ["IsDebuggerPresent", "CheckRemoteDebuggerPresent", "OutputDebugString", "FindWindow", "GetTickCount", "Sleep", "NtDelayExecution", "GetSystemTime", "QueryPerformanceCounter"],
        "persistencia": ["RegCreateKey", "RegSetValue", "RegOpenKey", "Software\\Microsoft\\Windows\\CurrentVersion\\Run", "CreateService", "SchTasks", "CopyFile", "MoveFile", "SetFileAttributes"],
        "inyeccion": ["VirtualAlloc", "WriteProcessMemory", "CreateRemoteThread", "NtCreateSection", "MapViewOfSection", "QueueUserAPC", "SetThreadContext", "NtWriteVirtualMemory", "RtlCreateUserThread"],
        "crypto": ["CryptAcquireContext", "CryptEncrypt", "CryptDecrypt", "MD5", "SHA", "AES", "XOR", "RC4", "vpxor_avx", "BCryptDecrypt"],
        "stealer": ["GetClipboardData", "OpenClipboard", "SamIConnect", "SamrQueryInformationUser", "CryptUnprotectData", "GetComputerName", "GetUserName", "ReadProcessMemory"],

Además de las categorías, el sistema incorpora reglas más específicas basadas en patrones textuales. Por ejemplo, la detección de mutexes globales combinados con errores del sistema puede indicar mecanismos de control de instancia única. Las combinaciones de Sleep con mediciones de tiempo pueden sugerir técnicas de evasión basadas en retrasos artificiales.

Código 5. Taxonomía de APIs


    categorias_api = {
        "red": ["getaddrinfo", "socket", "connect", "send", "recv", "WSAStartup", "InternetOpen", "HttpSendRequest", "URLDownloadToFile", "DnsQuery"],
        "evasion": ["IsDebuggerPresent", "CheckRemoteDebuggerPresent", "OutputDebugString", "FindWindow", "GetTickCount", "Sleep", "NtDelayExecution", "GetSystemTime", "QueryPerformanceCounter"],
        "persistencia": ["RegCreateKey", "RegSetValue", "RegOpenKey", "Software\\Microsoft\\Windows\\CurrentVersion\\Run", "CreateService", "SchTasks", "CopyFile", "MoveFile", "SetFileAttributes"],
        "inyeccion": ["VirtualAlloc", "WriteProcessMemory", "CreateRemoteThread", "NtCreateSection", "MapViewOfSection", "QueueUserAPC", "SetThreadContext", "NtWriteVirtualMemory", "RtlCreateUserThread"],
        "crypto": ["CryptAcquireContext", "CryptEncrypt", "CryptDecrypt", "MD5", "SHA", "AES", "XOR", "RC4", "vpxor_avx", "BCryptDecrypt"],
        "stealer": ["GetClipboardData", "OpenClipboard", "SamIConnect", "SamrQueryInformationUser", "CryptUnprotectData", "GetComputerName", "GetUserName", "ReadProcessMemory"],

Código 7. Heurística anti-análisis

    if "sleep" in tc_low and ("gettickcount" in tc_low or "queryperformancecounter" in tc_low or "%" in tc_low):
        hallazgos["acciones"].append("CAPA: [Time_Based_Evasion] -> Demora la ejecución calculando retardos dinámicos")
        m_set.add("T1497.003 - Virtualization/Sandbox Evasion: Time Based Evasion")
    if "cryptunprotectdata" in tc_low or ("cryptdecrypt" in tc_low and "login" in tc_low):
        hallazgos["acciones"].append("CAPA: [Credential_Dumping] -> Intenta extraer contraseñas guardadas (DPAPI)")

Lo importante aquí no es la precisión absoluta de cada regla, sino su capacidad para reducir el espacio de incertidumbre. Incluso si una detección no es perfecta, sigue siendo útil si dirige la atención hacia una zona relevante del código.

El sistema también incorpora un mapeo hacia MITRE ATT&CK, lo que permite traducir comportamientos técnicos en técnicas de ataque reconocibles. Esto es especialmente útil en contextos de análisis de malware, donde no basta con entender el código, sino también su intención.

Código 10. Correlación ATT&CK

        hallazgos["acciones"].append("CAPA: [Registry_Persistence] -> Persistencia mediante llaves Run")
        m_set.add("T1547.001 - Boot Exec: Registry Run Keys")
    if "createtoolhelp32snapshot" in tc_low and "process32first" in tc_low and "process32next" in tc_low:
        hallazgos["acciones"].append("CAPA: [Process_Discovery] -> Enumeración activa de procesos")
        m_set.add("T1057 - Process Discovery")

    mapeo_mitre = {
        "getaddrinfo": "T1016 - System Network Configuration Discovery",

Una parte interesante del diseño es que este análisis no se aplica al binario completo de forma uniforme, sino función por función. Esto permite granularidad. Cada función se convierte en una unidad de análisis independiente, con sus propios indicadores, etiquetas y contexto.

El resultado de este proceso es una clasificación semántica parcial del binario. No se obtiene una comprensión completa del programa, pero sí un mapa estructurado de sus zonas más relevantes.

A partir de ahí, el sistema empieza a construir algo más importante: nombres nuevos para las funciones.

El renombrado automático no es un detalle menor. En ingeniería inversa, los nombres originales suelen ser irrelevantes o inexistentes. Funciones como sub_401000 no aportan información. Sin embargo, si una función se etiqueta como Network_C2 o Process_Injection, el contexto cambia completamente.

Código 11. Construcción semántica de nombres

                src_c = r2.cmd(f"pdg @ {nom_orig}") or r2.cmd(f"pdd @ {nom_orig}") or ""
                dict_c[nom_orig] = {"codigo": src_c, "offset": off_hex, "cc": cc_val}
                
                hallazgos = analizar_funcion_interesante(nom_orig, src_c)
                sufijos = []
                if hallazgos["acciones"]:
                    for acc in hallazgos["acciones"]:
                        m = re.search(r'\[(.*?)\]', acc)
                        if m and m.group(1) not in sufijos: sufijos.append(m.group(1))
                
                mapa_sufijos = {
                    "dominios": "Network_C2", "red": "Network_C2", "inyeccion": "Process_Injection",
                    "evasion": "AntiAnalysis_Evasion", "crypto": "Crypto_Decoding", 
                    "persistencia": "Persistence_Mod", "stealer": "InfoStealer", "discovery": "Environment_Discovery"
                }

El código deja de ser una estructura abstracta y empieza a convertirse en un sistema comprensible.

Este proceso de renombrado se alimenta directamente de los resultados del análisis anterior. Si una función contiene indicadores de red, evasión o cifrado, esos conceptos se incorporan a su nuevo nombre. De este modo, el propio binario se reescribe en una versión más informativa de sí mismo.

Código 7. Heurística anti-análisis

    if "sleep" in tc_low and ("gettickcount" in tc_low or "queryperformancecounter" in tc_low or "%" in tc_low):
        hallazgos["acciones"].append("CAPA: [Time_Based_Evasion] -> Demora la ejecución calculando retardos dinámicos")
        m_set.add("T1497.003 - Virtualization/Sandbox Evasion: Time Based Evasion")
    if "cryptunprotectdata" in tc_low or ("cryptdecrypt" in tc_low and "login" in tc_low):
        hallazgos["acciones"].append("CAPA: [Credential_Dumping] -> Intenta extraer contraseñas guardadas (DPAPI)")

Código 15. Presentación de hallazgos

            </div>
        </div>

        <div class="card">
            <h3>📈 Resumen de Auto-Renombrado Contextual</h3>
            <p class="subtitle" id="summary-count">Se identificaron y renombraron funciones de forma automática.</p>
            <table>
                <thead>
                    <tr>
                        <th>Función Original</th>
                        <th>CC</th>
                        <th>Capacidades Identificadas (Tags)</th>
                    </tr>
                </thead>
                <tbody id="results-table-body">
                    <!-- Filas dinámicas -->
                </tbody>
            </table>
        </div>
    </div>
</div>

<script>
    let selectedFile = null;

    // Desactivar el comportamiento nativo de guardado/apertura del navegador en toda la página
    ['dragenter', 'dragover', 'dragleave', 'drop'].forEach(eventName => {
        window.addEventListener(eventName, (e) => e.preventDefault(), false);
        document.body.addEventListener(eventName, (e) => e.preventDefault(), false);
    });

    const uploadZone = document.querySelector('.upload-zone');
    
    uploadZone.addEventListener('dragover', (e) => {
        e.preventDefault();
        uploadZone.style.borderColor = 'var(--accent)';
    });

    uploadZone.addEventListener('dragleave', () => {
        uploadZone.style.borderColor = 'var(--border-color)';
    });

    uploadZone.addEventListener('drop', (e) => {
        e.preventDefault();
        uploadZone.style.borderColor = 'var(--border-color)';
        
        if (e.dataTransfer.files.length > 0) {
            const fileInput = document.getElementById('file-input');
            fileInput.files = e.dataTransfer.files;
            fileSelected(fileInput);
        }
    });

    function fileSelected(input) {
        if (input.files.length > 0) {
            selectedFile = input.files[0];
            document.getElementById('upload-text').innerText = `📝 Seleccionado: ${selectedFile.name}`;
            document.getElementById('submit-btn').disabled = false;
        }
    }
    
    
    // Volver al inicio limpiando todo el entorno para un nuevo análisis
    function volverInicio() {
        selectedFile = null;
        document.getElementById('file-input').value = '';
        document.getElementById('upload-text').innerText = '📁 Arrastra aquí tu binario (.exe, .dll, ELF) o haz clic para buscar';
        document.getElementById('submit-btn').disabled = true;
        resetUI();
    }

    function uploadFile() {
        if (!selectedFile) return;

        document.getElementById('upload-card').style.display = 'none';
        const progressContainer = document.getElementById('progress-section');
        const progressBar = document.getElementById('p-bar');
        const progressStatus = document.getElementById('status-text');

        progressContainer.style.display = 'block';

Una vez completado el análisis, el sistema genera salidas compatibles con diferentes entornos de análisis: IDA, Ghidra y Radare2. Cada una de estas herramientas tiene su propio ecosistema, y poder transferir resultados entre ellas permite integrar el análisis dentro de flujos de trabajo existentes.

Código 12. Exportación de conocimiento


            # GENERACIÓN DE SCRIPTS DE RENOMBRADO PARA IDA, GHIDRA Y R2
            yield f"data: {json.dumps({'status': 'Generando scripts para IDA Pro, Ghidra y Radare2...', 'porcentaje': 96})}\n\n"

            base_name = os.path.splitext(file.filename)[0]
            
            ida_script = [
                "# Script de renombrado para IDA Pro",
                "import ida_name",
                ""
            ]

            ghidra_script = [
                "# Script de renombrado para Ghidra",
                "from ghidra.program.model.symbol import SourceType",
                ""
            ]

            r2_script = [
                "# Script de comandos para Radare2 / Cutter",
                ""
            ]

            for old_name, new_name in mapa_nombres.items():
                if old_name == new_name:
                    continue

En paralelo, se genera también una versión reconstruida del pseudo código en C, donde las funciones ya aparecen renombradas y organizadas. Este paso es clave porque transforma el resultado del análisis en algo legible para un humano. Ya no se trata de ensamblador o pseudocódigo desordenado, sino de una representación estructurada del comportamiento del programa.

El sistema además incluye un mecanismo de progreso en tiempo real mediante Server-Sent Events. Esto permite observar cómo el análisis avanza función por función, lo que es especialmente útil cuando se trabaja con binarios grandes o complejos.

Código 13. Server-Sent Events

    if file.filename == '': 
        return jsonify({"error": "Empty filename"}), 400

    min_cc = 25 if request.form.get('use_min_cc') == 'true' else 0
    unique_id = str(uuid.uuid4())
    filename = f"{unique_id}_{file.filename}"
    filepath = os.path.join(UPLOAD_FOLDER, filename)
    file.save(filepath)
    
    base_name = os.path.splitext(file.filename)[0]
    output_filename = f"{base_name}.c"

Otro aspecto relevante es la presencia de mecanismos de resiliencia. Si el análisis inicial no encuentra suficientes funciones relevantes, el sistema reduce umbrales o activa estrategias de recuperación basadas en el entry point. Esto evita que el proceso falle en binarios poco convencionales o altamente ofuscados.

En conjunto, lo que se construye aquí no es solo un script de análisis, sino un flujo completo de interpretación automática de binarios. Desde la extracción de funciones hasta la generación de etiquetas semánticas y la reconstrucción del código, cada etapa reduce un poco más la distancia entre el binario y su comprensión.

El punto importante no es que el sistema sea perfecto. No lo es, ni lo pretende. Habrá falsos positivos, funciones mal clasificadas y casos en los que las heurísticas fallen. Pero eso no invalida el enfoque. Al contrario, lo hace útil.

En análisis de binarios, la perfección no es el objetivo. El objetivo es reducir el ruido lo suficiente como para que el trabajo humano se concentre donde realmente importa.

Y en ese sentido, este tipo de sistemas cambian la forma de trabajar. En lugar de explorar el código de forma lineal, se empieza a trabajar con una vista filtrada, estructurada y priorizada del programa.

El resultado final no es solo un conjunto de funciones analizadas, sino una forma distinta de ver el binario: menos como un bloque de instrucciones y más como un conjunto de comportamientos organizados.

Conclusión

La herramienta no intenta sustituir al analista. Su verdadero objetivo consiste en reducir el tiempo invertido en localizar las funciones

importantes y enriquecerlas con contexto antes de comenzar la ingeniería inversa manual. Ese cambio de enfoque permite dedicar el esfuerzo

a comprender el comportamiento del malware en lugar de navegar entre cientos de funciones irrelevantes.

Figura 1. Flujo Radare2 → Heurísticas → Renombrado → IDA/Ghidra/radare2

Consola de Radare2 con la función fcn.00401434 renombrada tras ejecutar el script r2
Gestor de scripts de Ghidra ejecutando el script renombrado_ghidra.py sobre un binario
Ghidra mostrando la función renombrada fcn.00401434_Network_C2_AntiAnalysis_Evasion_Persistence_Mod en el árbol de símbolos

Figura 2. Interfaz web de análisis

Interfaz de Binary Cartography Pipeline con la zona para arrastrar un binario y analizarlo
Binary Cartography Pipeline mostrando la barra de progreso durante el análisis estático

Figura 3. Resumen de funciones clasificadas

Resultado de Binary Cartography Pipeline con la tabla de funciones sospechosas y sus etiquetas CAPA

Anexo A - Código completo app.py

import os
import json
import re
import uuid
from flask import Flask, render_template, request, Response, send_from_directory, jsonify
import r2pipe

app = Flask(__name__)
UPLOAD_FOLDER = os.path.join(os.path.dirname(os.path.abspath(__file__)), 'uploads')
OUTPUT_FOLDER = os.path.join(os.path.dirname(os.path.abspath(__file__)), 'outputs')
os.makedirs(UPLOAD_FOLDER, exist_ok=True)
os.makedirs(OUTPUT_FOLDER, exist_ok=True)

def analizar_funcion_interesante(nombre_funcion, texto_c):
    hallazgos = {
        "dominios": [], "ips": [], "red": [], "evasion": [], 
        "persistencia": [], "inyeccion": [], "crypto": [],
        "stealer": [], "discovery": [], "acciones": []
    }
    hallav_set, m_set = set(), set()

    dominios = re.findall(r'"([a-zA-Z0-9][-a-zA-Z0-9]*\.[a-zA-Z0-9.-]+\.[a-zA-Z]{2,})"', texto_c)
    ips = re.findall(r'"((?:25[0-5]|2[0-4][0-9]|1[0-9][0-9]|[1-9]?[0-9])\.(?:25[0-5]|2[0-4][0-9]|1[0-9][0-9]|[1-9]?[0-9])\.(?:25[0-5]|2[0-4][0-9]|1[0-9][0-9]|[1-9]?[0-9])\.(?:25[0-5]|2[0-4][0-9]|1[0-9][0-9]|[1-9]?[0-9]))"', texto_c)

    for d in set(dominios):
        if not d.endswith((".microsoft.com", ".w3.org", "xml.org", "schemas-microsoft-com")):
            hallazgos["dominios"].append(d)
            m_set.add("T1071.001 - C2: Web Protocols (Dynamic DNS Domain)")
    for ip in set(ips):
        hallazgos["ips"].append(ip)
        m_set.add("T1071 - Command and Control: Application Layer Protocol")

    categorias_api = {
        "red": ["getaddrinfo", "socket", "connect", "send", "recv", "WSAStartup", "InternetOpen", "HttpSendRequest", "URLDownloadToFile", "DnsQuery"],
        "evasion": ["IsDebuggerPresent", "CheckRemoteDebuggerPresent", "OutputDebugString", "FindWindow", "GetTickCount", "Sleep", "NtDelayExecution", "GetSystemTime", "QueryPerformanceCounter"],
        "persistencia": ["RegCreateKey", "RegSetValue", "RegOpenKey", "Software\\Microsoft\\Windows\\CurrentVersion\\Run", "CreateService", "SchTasks", "CopyFile", "MoveFile", "SetFileAttributes"],
        "inyeccion": ["VirtualAlloc", "WriteProcessMemory", "CreateRemoteThread", "NtCreateSection", "MapViewOfSection", "QueueUserAPC", "SetThreadContext", "NtWriteVirtualMemory", "RtlCreateUserThread"],
        "crypto": ["CryptAcquireContext", "CryptEncrypt", "CryptDecrypt", "MD5", "SHA", "AES", "XOR", "RC4", "vpxor_avx", "BCryptDecrypt"],
        "stealer": ["GetClipboardData", "OpenClipboard", "SamIConnect", "SamrQueryInformationUser", "CryptUnprotectData", "GetComputerName", "GetUserName", "ReadProcessMemory"],
        "discovery": ["Process32First", "Process32Next", "CreateToolhelp32Snapshot", "GetModuleFileName", "GetSystemInfo", "NetShareEnum", "GetIpNetTable"]
    }

    tc_low = texto_c.lower()
    if "createmutex" in tc_low and ("getlasterror" in tc_low or "0xb7" in tc_low or "global\\" in tc_low):
        hallazgos["acciones"].append("CAPA: [Mutex_Execution_Guardrails] -> Restringe ejecución a una sola instancia")
        m_set.add("T1497 - Virtualization/Sandbox Evasion (Execution Guardrails)")
    if "sleep" in tc_low and ("gettickcount" in tc_low or "queryperformancecounter" in tc_low or "%" in tc_low):
        hallazgos["acciones"].append("CAPA: [Time_Based_Evasion] -> Demora la ejecución calculando retardos dinámicos")
        m_set.add("T1497.003 - Virtualization/Sandbox Evasion: Time Based Evasion")
    if "cryptunprotectdata" in tc_low or ("cryptdecrypt" in tc_low and "login" in tc_low):
        hallazgos["acciones"].append("CAPA: [Credential_Dumping] -> Intenta extraer contraseñas guardadas (DPAPI)")
        m_set.add("T1003 - OS Credential Dumping")
    if "openclipboard" in tc_low and "getclipboarddata" in tc_low:
        hallazgos["acciones"].append("CAPA: [Clipboard_Capture] -> Captura datos del portapapeles")
        m_set.add("T1115 - Clipboard Data Capture")
    if "createprocess" in tc_low and ("ntunmapviewofsection" in tc_low or "virtualalloc" in tc_low) and "resumethread" in tc_low:
        hallazgos["acciones"].append("CAPA: [Process_Hollowing] -> Inyección via Process Hollowing")
        m_set.add("T1055.012 - Process Injection: Process Hollowing")
    if "virtualalloc" in tc_low and ("0x40" in tc_low or "page_execute_readwrite" in tc_low):
        hallazgos["acciones"].append("CAPA: [Allocate_RWX_Memory] -> Reserva memoria con permisos RWX")
        m_set.add("T1055 - Process Injection (Memory Allocation)")
    if ("gettemppath" in tc_low or "copyfile" in tc_low) and ("createfile" in tc_low or "writefile" in tc_low) and ("shellexecute" in tc_low or "winexec" in tc_low):
        hallazgos["acciones"].append("CAPA: [Dropper_Behavior] -> Suelta y ejecuta un binario secundario")
        m_set.add("T1105 - Ingress Tool Transfer / Dropper Behavior")
    if "urldownloadtofile" in tc_low or ("internetopen" in tc_low and "httpopenrequest" in tc_low):
        hallazgos["acciones"].append("CAPA: [Download_Payload] -> Descarga cargas útiles adicionales")
        m_set.add("T1105 - Ingress Tool Transfer")
    if "regsetvalue" in tc_low and ("currentversion\\run" in tc_low or "currentversion\\runonce" in tc_low):
        hallazgos["acciones"].append("CAPA: [Registry_Persistence] -> Persistencia mediante llaves Run")
        m_set.add("T1547.001 - Boot Exec: Registry Run Keys")
    if "createtoolhelp32snapshot" in tc_low and "process32first" in tc_low and "process32next" in tc_low:
        hallazgos["acciones"].append("CAPA: [Process_Discovery] -> Enumeración activa de procesos")
        m_set.add("T1057 - Process Discovery")

    mapeo_mitre = {
        "getaddrinfo": "T1016 - System Network Configuration Discovery",
        "connect": "T1071 - Command and Control", "socket": "T1071 - Command and Control",
        "send": "T1041 - Exfiltration Over C2 Channel", "recv": "T1071 - C2 Data Receive",
        "httpsendrequest": "T1071.001 - C2: Web Protocols", "isdebuggerpresent": "T1622 - Debugger Evasion",
        "checkremotedebuggerpresent": "T1622 - Debugger Evasion", "schtasks": "T1053.005 - Scheduled Task",
        "vpxor_avx": "T1140 - Deobfuscate Info", "getmodulefilename": "T1518 - Software Discovery"
    }

    lines = texto_c.split("\n")
    for line in lines:
        line_clean = line.strip()
        if not line_clean or line_clean.startswith("//"): continue
        for cat, keywords in categorias_api.items():
            for kw in keywords:
                if kw.lower() in line_clean.lower():
                    if "(" in line_clean or "sym." in line_clean or "imp." in line_clean or "\\" in line_clean or '"' in line_clean:
                        if line_clean not in hallav_set:
                            hallazgos[cat].append(line_clean)
                            hallav_set.add(line_clean)
                        if kw.lower() in mapeo_mitre: m_set.add(mapeo_mitre[kw.lower()])
                        break 
    hallazgos["mitre"] = list(m_set)
    return hallazgos

def obtener_cc_seguro(f):
    try:
        cc = f.get("cc")
        return int(cc) if cc is not None else 0
    except: return 0

@app.route('/')
def index(): 
    return render_template('index.html')

@app.route('/analizar', methods=['POST'])
def analizar():
    if 'file' not in request.files: 
        return jsonify({"error": "No file"}), 400
    file = request.files['file']
    if file.filename == '': 
        return jsonify({"error": "Empty filename"}), 400

    min_cc = 25 if request.form.get('use_min_cc') == 'true' else 0
    unique_id = str(uuid.uuid4())
    filename = f"{unique_id}_{file.filename}"
    filepath = os.path.join(UPLOAD_FOLDER, filename)
    file.save(filepath)
    
    base_name = os.path.splitext(file.filename)[0]
    output_filename = f"{base_name}.c"

    def generate_progress():
        try:
            yield f"data: {json.dumps({'status': 'Abriendo el binario de forma segura y cargando símbolos...', 'porcentaje': 10})}\n\n"
            
            r2 = r2pipe.open(filepath, flags=["-2"])
            
            yield f"data: {json.dumps({'status': 'Ejecutando análisis estático profundo de variables (aaa)...', 'porcentaje': 30})}\n\n"
            
            r2.cmd("aaa")
            
            funciones = json.loads(r2.cmd("aflj") or "[]")
            funciones_ordenadas = sorted(funciones, key=obtener_cc_seguro, reverse=True)

            exportaciones_set = set()
            try:
                raw_exports = r2.cmd("iEj")
                if raw_exports and raw_exports.strip():
                    exports_json = json.loads(raw_exports)
                    for exp in exports_json:
                        if exp.get("name"):
                            exportaciones_set.add(exp.get("name"))
            except:
                pass

            funcs_proc = []
            for f in funciones_ordenadas:
                nom_f = f.get("name", "")
                cc_f = obtener_cc_seguro(f)
                
                if cc_f >= min_cc or nom_f in exportaciones_set or any(exp in nom_f for exp in exportaciones_set):
                    if f not in funcs_proc:
                        funcs_proc.append(f)

            if len(funcs_proc) == 0 and min_cc > 0 and len(funciones_ordenadas) > 0:
                yield f"data: {json.dumps({'status': 'Ajustando heurística: Bajando umbral a CC >= 10 para rebasar el ruido...', 'porcentaje': 35})}\n\n"
                funcs_proc = [f for f in funciones_ordenadas if obtener_cc_seguro(f) >= 10]

            if len(funcs_proc) > 60:
                yield f"data: {json.dumps({'status': 'Heurística DLL: Analizando las 60 funciones más sospechosas y exportaciones...', 'porcentaje': 38})}\n\n"
                funcs_exports = [f for f in funcs_proc if f.get("name") in exportaciones_set]
                funcs_restantes = [f for f in funcs_proc if f.get("name") not in exportaciones_set]
                funcs_proc = (funcs_exports + funcs_restantes)[:60]

            if len(funcs_proc) == 0:
                yield f"data: {json.dumps({'status': '🚨 Activando Modo de Rescate por EntryPoint...', 'porcentaje': 39})}\n\n"
                entry_addr = 0

                try:
                    iej = json.loads(r2.cmd("iej") or "[]")
                    if iej:
                        entry_addr = iej[0].get("vaddr") or iej[0].get("paddr") or 0
                except:
                    pass

                r2.cmd("af @ entry0")

                funcs_proc = [{
                    "name": "entry0",
                    "cc": 50,
                    "vaddr": entry_addr,
                    "offset": entry_addr
                }]

            total_f = len(funcs_proc)

            if total_f == 0:
                yield f"data: {json.dumps({'error': f'Ninguna función supera el umbral CC >= {min_cc}'})}\n\n"
                return

            dict_c, mapa_nombres = {}, {}
            for i, f in enumerate(funcs_proc, 1):
                nom_orig = f.get("name", f"unk_{i}")
                cc_val = obtener_cc_seguro(f)

                src_c = r2.cmd(f"pdg @ {nom_orig}") or r2.cmd(f"pdd @ {nom_orig}") or ""
                
                addr = None

                for campo in ("vaddr", "offset", "addr", "minbound"):
                    valor = f.get(campo)
                    if valor is not None:
                        addr = valor
                        break

                if addr is None:
                    try:
                        afi = json.loads(r2.cmd(f"afij @ {nom_orig}") or "[]")
                        if afi:
                            addr = (
                                afi[0].get("offset")
                                or afi[0].get("addr")
                                or afi[0].get("minbound")
                            )
                    except:
                        pass

                if addr is None:
                    addr = 0

                off_hex = hex(addr)

                dict_c[nom_orig] = {
                    "codigo": src_c,
                    "offset": off_hex,
                    "cc": cc_val
                }
                
                hallazgos = analizar_funcion_interesante(nom_orig, src_c)
                sufijos = []
                if hallazgos["acciones"]:
                    for acc in hallazgos["acciones"]:
                        m = re.search(r'\[(.*?)\]', acc)
                        if m and m.group(1) not in sufijos: sufijos.append(m.group(1))
                
                mapa_sufijos = {
                    "dominios": "Network_C2", "red": "Network_C2", "inyeccion": "Process_Injection",
                    "evasion": "AntiAnalysis_Evasion", "crypto": "Crypto_Decoding", 
                    "persistencia": "Persistence_Mod", "stealer": "InfoStealer", "discovery": "Environment_Discovery"
                }
                for key, tag in mapa_sufijos.items():
                    if hallazgos[key] and tag not in sufijos: sufijos.append(tag)

                mapa_nombres[nom_orig] = f"{nom_orig}_" + "_".join(sufijos) if sufijos else nom_orig
                
                yield f"data: {json.dumps({'status': f'Descompilando: {i}/{total_f} ({nom_orig})', 'porcentaje': int(40 + (i / total_f) * 50)})}\n\n"

            yield f"data: {json.dumps({'status': 'Refactorizando y limpiando formato C...', 'porcentaje': 92})}\n\n"
            
            c_final = [f"\n// ==========================================\n// FUNCIÓN: {o_n} ({info['offset']}) | CC: {info['cc']}\n// ==========================================\n{info['codigo']}\n" for o_n, info in dict_c.items()]
            txt_c = re.sub(r'\)\s*\n+\s*\{', ')\n{', "".join(c_final))

            for o_n, n_n in mapa_nombres.items():
                if o_n != n_n: 
                    txt_c = txt_c.replace(o_n, n_n)
            
            with open(os.path.join(OUTPUT_FOLDER, f"{unique_id}_{output_filename}"), "w", encoding="utf-8") as out: 
                out.write(txt_c)
            
            r2.quit()

            # GENERACIÓN DE SCRIPTS DE RENOMBRADO PARA IDA, GHIDRA Y R2
            yield f"data: {json.dumps({'status': 'Generando scripts para IDA Pro, Ghidra y Radare2...', 'porcentaje': 96})}\n\n"

            base_name = os.path.splitext(file.filename)[0]
            
            ida_script = [
                "# Script de renombrado para IDA Pro",
                "import ida_name",
                ""
            ]

            ghidra_script = [
                "# Script de renombrado para Ghidra",
                "from ghidra.program.model.symbol import SourceType",
                ""
            ]

            r2_script = [
                "# Script de comandos para Radare2 / Cutter",
                ""
            ]

            for old_name, new_name in mapa_nombres.items():
                if old_name == new_name:
                    continue

                offset = dict_c[old_name]["offset"]

                # Sintaxis para IDA Pro (Python 3)
                ida_script.append(f'ida_name.set_name({offset}, "{new_name}", ida_name.SN_NOWARN)')

                # Sintaxis para Ghidra (Jython / Python API)
                ghidra_script.append(f'try:\n    currentProgram.getFunctionManager().getFunctionAt(toAddr({offset})).setName("{new_name}", SourceType.USER_DEFINED)\nexcept:\n    pass')

                # Sintaxis para Radare2 (afn = analyze function name)
                r2_script.append(f'afn {new_name} {offset}')

            f_ida_name = f"{unique_id}_{base_name}_ida.py"
            f_ghidra_name = f"{unique_id}_{base_name}_ghidra.py"
            f_r2_name = f"{unique_id}_{base_name}_r2.r2"

            with open(os.path.join(OUTPUT_FOLDER, f_ida_name), "w", encoding="utf-8") as f:
                f.write("\n".join(ida_script))

            with open(os.path.join(OUTPUT_FOLDER, f_ghidra_name), "w", encoding="utf-8") as f:
                f.write("\n".join(ghidra_script))

            with open(os.path.join(OUTPUT_FOLDER, f_r2_name), "w", encoding="utf-8") as f:
                f.write("\n".join(r2_script))

            f_mod = [(o, n, dict_c[o]["cc"]) for o, n in mapa_nombres.items() if o != n]
            resumen_web = [{"original": o, "cc": cc, "tags": n.replace(f"{o}_", "")} for o, n, cc in sorted(f_mod, key=lambda x: x[2], reverse=True)]

            yield f"data: {json.dumps({'complete': True, 'porcentaje': 100, 'filename': f'{unique_id}_{output_filename}', 'f_ida': f_ida_name, 'f_ghidra': f_ghidra_name, 'f_r2': f_r2_name, 'resumen': resumen_web})}\n\n"
        
        except Exception as e:
            yield f"data: {json.dumps({'error': str(e)})}\n\n"

    return Response(generate_progress(), mimetype='text/event-stream')

@app.route('/download/<filename>')
def download_file(filename):
    clean_name = filename.split('_', 1)[-1] if '_' in filename else filename
    return send_from_directory(OUTPUT_FOLDER, filename, as_attachment=True, download_name=clean_name)

if __name__ == '__main__':
    app.run(host='127.0.0.1', port=5001, debug=True)

Anexo B - Código completo templates/index.html

<!DOCTYPE html>
<html lang="es">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Binary Cartography AI Pipeline</title>
    <style>
        :root {
            --bg-primary: #0d1117;
            --bg-secondary: #161b22;
            --accent: #58a6ff;
            --accent-success: #238636;
            --accent-ida: #d97706;
            --accent-ghidra: #9333ea;
            --accent-r2: #e11d48;
            --text-main: #c9d1d9;
            --text-muted: #8b949e;
            --border-color: #30363d;
        }

        body {
            font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Helvetica, Arial, sans-serif;
            background-color: var(--bg-primary);
            color: var(--text-main);
            margin: 0;
            padding: 40px 20px;
            display: flex;
            justify-content: center;
        }

        .container {
            width: 100%;
            max-width: 900px;
        }

        h1 {
            color: #ffffff;
            font-size: 2rem;
            margin-bottom: 10px;
            display: flex;
            align-items: center;
            gap: 10px;
        }

        .subtitle {
            color: var(--text-muted);
            margin-bottom: 30px;
        }

        .card {
            background-color: var(--bg-secondary);
            border: 1px solid var(--border-color);
            border-radius: 8px;
            padding: 25px;
            margin-bottom: 25px;
        }

        .upload-zone {
            border: 2px dashed var(--border-color);
            border-radius: 6px;
            padding: 40px 20px;
            text-align: center;
            cursor: pointer;
            transition: border-color 0.2s;
        }

        .upload-zone:hover {
            border-color: var(--accent);
        }

        input[type="file"] {
            display: none;
        }

        .checkbox-container {
            display: flex;
            align-items: center;
            gap: 10px;
            margin-top: 20px;
            cursor: pointer;
            user-select: none;
        }

        .btn {
            background-color: var(--accent-success);
            color: #ffffff;
            border: 1px solid rgba(240,246,252,0.1);
            padding: 12px 20px;
            font-size: 1rem;
            font-weight: 600;
            border-radius: 6px;
            cursor: pointer;
            width: 100%;
            margin-top: 20px;
            transition: background-color 0.2s;
        }

        .btn:hover {
            background-color: #2ea44f;
        }

        .btn:disabled {
            background-color: var(--border-color);
            color: var(--text-muted);
            cursor: not-allowed;
        }

        /* Progreso y Estados */
        #progress-section {
            display: none;
        }

        .progress-bar-container {
            background-color: var(--bg-primary);
            border: 1px solid var(--border-color);
            border-radius: 20px;
            height: 20px;
            width: 100%;
            overflow: hidden;
            margin-top: 15px;
        }

        .progress-bar {
            background-color: var(--accent);
            height: 100%;
            width: 0%;
            transition: width 0.4s ease;
        }

        #status-text {
            font-family: monospace;
            color: var(--accent);
            margin-top: 10px;
        }

        /* Tabla de Resultados */
        #results-section {
            display: none;
        }

        table {
            width: 100%;
            border-collapse: collapse;
            margin-top: 15px;
        }

        th, td {
            text-align: left;
            padding: 12px;
            border-bottom: 1px solid var(--border-color);
        }

        th {
            color: var(--text-muted);
            font-weight: 600;
        }

        .tag-badge {
            background-color: rgba(88, 166, 255, 0.15);
            color: var(--accent);
            padding: 4px 8px;
            border-radius: 4px;
            font-size: 0.85rem;
            font-family: monospace;
            display: inline-block;
            margin: 2px;
        }

        /* Sección de Descarga Múltiple */
        .download-box {
            display: flex;
            flex-direction: column;
            gap: 15px;
            background-color: rgba(35, 134, 54, 0.15);
            border: 1px solid var(--accent-success);
            padding: 20px;
            border-radius: 8px;
            margin-bottom: 20px;
        }

        .download-header {
            display: flex;
            justify-content: space-between;
            align-items: center;
        }

        .download-grid {
            display: grid;
            grid-template-columns: repeat(auto-fit, minmax(180px, 1fr));
            gap: 10px;
        }

        .download-btn {
            display: flex;
            align-items: center;
            justify-content: center;
            gap: 6px;
            padding: 10px 14px;
            font-weight: bold;
            font-size: 0.9rem;
            text-decoration: none;
            border-radius: 6px;
            transition: opacity 0.2s, transform 0.1s;
            text-align: center;
        }

        .download-btn:hover {
            opacity: 0.9;
            transform: translateY(-1px);
        }

        .dl-c { background-color: var(--accent-success); color: #ffffff; }
        .dl-ida { background-color: var(--accent-ida); color: #ffffff; }
        .dl-ghidra { background-color: var(--accent-ghidra); color: #ffffff; }
        .dl-r2 { background-color: var(--accent-r2); color: #ffffff; }
        .btn-reset { background-color: var(--border-color); color: var(--text-main); cursor: pointer; }
    </style>
</head>
<body>

<div class="container">
    <h1>🎮 Binary Cartography Pipeline</h1>
    <p class="subtitle">Análisis estático heurístico y auto-renombrado contextual asistido por Radare2, Ghidra e IDA Pro</p>

    <!-- Formulario de Subida -->
    <div class="card" id="upload-card">
        <div class="upload-zone" onclick="document.getElementById('file-input').click()">
            <span id="upload-text">📁 Arrastra aquí tu binario (.exe, .dll, ELF) o haz clic para buscar</span>
            <input type="file" id="file-input" onchange="fileSelected(this)">
        </div>

        <label class="checkbox-container">
            <input type="checkbox" id="min-cc-check" checked>
            <span>Filtrar ruido del compilador / Delphi activando <strong>--min-cc 25</strong> (Recomendado)</span>
        </label>

        <button class="btn" id="submit-btn" disabled onclick="uploadFile()">Iniciar Pipeline de Análisis</button>
    </div>

    <!-- Sección de Progreso Activo -->
    <div class="card" id="progress-section">
        <h3>⚡ Procesando Binario en Segundo Plano</h3>
        <div class="progress-bar-container">
            <div class="progress-bar" id="p-bar"></div>
        </div>
        <div id="status-text">Esperando inicio...</div>
    </div>

    <!-- Sección de Resultados Finales -->
    <div id="results-section">
        <div class="download-box">
            <div class="download-header">
                <span>🎉 ¡Pipeline completado con éxito! Selecciona los archivos a descargar:</span>
                <button onclick="volverInicio()" class="download-btn btn-reset">🔄 Analizar Otro</button>
            </div>
            <div class="download-grid">
                <a href="#" id="dl-c" class="download-btn dl-c">📄 Código .C</a>
                <a href="#" id="dl-ida" class="download-btn dl-ida">🐍 Script IDA Pro (.py)</a>
                <a href="#" id="dl-ghidra" class="download-btn dl-ghidra">🐉 Script Ghidra (.py)</a>
                <a href="#" id="dl-r2" class="download-btn dl-r2">⚡ Script Radare2 (.r2)</a>
            </div>
        </div>

        <div class="card">
            <h3>📈 Resumen de Auto-Renombrado Contextual</h3>
            <p class="subtitle" id="summary-count">Se identificaron y renombraron funciones de forma automática.</p>
            <table>
                <thead>
                    <tr>
                        <th>Función Original</th>
                        <th>CC</th>
                        <th>Capacidades Identificadas (Tags)</th>
                    </tr>
                </thead>
                <tbody id="results-table-body">
                    <!-- Filas dinámicas -->
                </tbody>
            </table>
        </div>
    </div>
</div>

<script>
    let selectedFile = null;

    // Desactivar el comportamiento nativo de guardado/apertura del navegador en toda la página
    ['dragenter', 'dragover', 'dragleave', 'drop'].forEach(eventName => {
        window.addEventListener(eventName, (e) => e.preventDefault(), false);
        document.body.addEventListener(eventName, (e) => e.preventDefault(), false);
    });

    const uploadZone = document.querySelector('.upload-zone');
    
    uploadZone.addEventListener('dragover', (e) => {
        e.preventDefault();
        uploadZone.style.borderColor = 'var(--accent)';
    });

    uploadZone.addEventListener('dragleave', () => {
        uploadZone.style.borderColor = 'var(--border-color)';
    });

    uploadZone.addEventListener('drop', (e) => {
        e.preventDefault();
        uploadZone.style.borderColor = 'var(--border-color)';
        
        if (e.dataTransfer.files.length > 0) {
            const fileInput = document.getElementById('file-input');
            fileInput.files = e.dataTransfer.files;
            fileSelected(fileInput);
        }
    });

    function fileSelected(input) {
        if (input.files.length > 0) {
            selectedFile = input.files[0];
            document.getElementById('upload-text').innerText = `📝 Seleccionado: ${selectedFile.name}`;
            document.getElementById('submit-btn').disabled = false;
        }
    }
    
    
    // Volver al inicio limpiando todo el entorno para un nuevo análisis
    function volverInicio() {
        selectedFile = null;
        document.getElementById('file-input').value = '';
        document.getElementById('upload-text').innerText = '📁 Arrastra aquí tu binario (.exe, .dll, ELF) o haz clic para buscar';
        document.getElementById('submit-btn').disabled = true;
        resetUI();
    }

    function uploadFile() {
        if (!selectedFile) return;

        document.getElementById('upload-card').style.display = 'none';
        const progressContainer = document.getElementById('progress-section');
        const progressBar = document.getElementById('p-bar');
        const progressStatus = document.getElementById('status-text');

        progressContainer.style.display = 'block';
        document.getElementById('results-section').style.display = 'none';

        const formData = new FormData();
        formData.append('file', selectedFile);
        formData.append('use_min_cc', document.getElementById('min-cc-check').checked);

        const xhr = new XMLHttpRequest();
        xhr.open('POST', '/analizar', true);
        
        let position = 0;

        xhr.onprogress = function() {
            const chunk = xhr.responseText.substring(position);
            position = xhr.responseText.length;
            const lines = chunk.split('\n\n');
            
            lines.forEach(line => {
                if (line.startsWith('data: ')) {
                    try {
                        const jsonStr = line.replace('data: ', '').trim();
                        if (!jsonStr) return;
                        
                        const data = JSON.parse(jsonStr);

                        if (data.error) {
                            alert(`Error en el Pipeline: ${data.error}`);
                            xhr.abort();
                            resetUI();
                            return;
                        }

                        if (data.porcentaje) {
                            progressBar.style.width = `${data.porcentaje}%`;
                        }
                        if (data.status) {
                            progressStatus.innerText = `> ${data.status}`;
                        }

                        if (data.complete) {
                            progressContainer.style.display = 'none';
                            
                            // Asignación de los enlaces a los 4 archivos generados por el backend
                            document.getElementById('dl-c').href = `/download/${data.filename}`;
                            document.getElementById('dl-ida').href = `/download/${data.f_ida}`;
                            document.getElementById('dl-ghidra').href = `/download/${data.f_ghidra}`;
                            document.getElementById('dl-r2').href = `/download/${data.f_r2}`;

                            document.getElementById('summary-count').innerText = `-> Se identificaron y mapearon con éxito ${data.resumen.length} funciones sospechosas mediante firmas CAPA.`;

                            const tbody = document.getElementById('results-table-body');
                            tbody.innerHTML = '';
                            
                            if (data.resumen.length === 0) {
                                tbody.innerHTML = `<tr><td colspan="3" style="text-align:center; color:var(--text-muted);">No se detectaron firmas conductuales claras.</td></tr>`;
                            } else {
                                data.resumen.forEach(item => {
                                    const fila = document.createElement('tr');
                                    
                                    let badges = '';
                                    if (item.tags) {
                                        badges = item.tags.split(/(?=_)(?<!_)/).map(t => {
                                            let cleanTag = t.replace(/^_+|_+$/g, '');
                                            if (!cleanTag) return '';
                                            return `<span class="tag-badge">[${cleanTag}]</span>`;
                                        }).join(' ');
                                    } else {
                                        badges = `<span class="tag-badge" style="color:var(--text-muted);">[Sin_Tags]</span>`;
                                    }

                                    fila.innerHTML = `
                                        <td><span style="font-family:monospace; background:var(--bg-primary); padding:3px 6px; border-radius:4px;">${item.original}</span></td>
                                        <td><strong style="color:#ffffff;">${item.cc}</strong></td>
                                        <td>${badges}</td>
                                    `;
                                    tbody.appendChild(fila);
                                });
                            }

                            document.getElementById('results-section').style.display = 'block';
                        }
                    } catch (e) {}
                }
            });
        };

        xhr.onerror = function() {
            alert("Ocurrió un colapso en la conexión de red con el servidor Flask.");
            resetUI();
        };

        xhr.send(formData);
    }

    function resetUI() {
        document.getElementById('upload-card').style.display = 'block';
        document.getElementById('progress-section').style.display = 'none';
        document.getElementById('results-section').style.display = 'none';
    }
</script>

</body>
</html>

Para arrancar la herramienta: python3 app.py y os conectáis con el navegador a 127.0.0.1:5001 y ¡listo!

Espero que os haya gustado y nos vemos en el siguiente POST!!!

Temas

No hay comentarios todavía