Skip to content
KitploitKITPLOIT
HerramientasExploitsBlog
Log in
Enviar
HerramientasExploitsBlog
Enviar

¡Herramientas de Hacking, PenTest y Ciberseguridad para tu Arsenal de Seguridad!

Kitploit es un directorio de herramientas de hacking, ciberseguridad y pentesting. Descubre las últimas actualizaciones de proyectos para encontrar vulnerabilidades, analizar sistemas, automatizar pruebas y fortalecer tu seguridad.

··Feeds·Contacto·Privacidad·© 2026 Kitploit

Directorio de Herramientas

Categorías

Ver todas las categorías
Loading categories
datadome-vm — Ingeniería inversa de la nueva VM de Datadome 🔥 | Kitploit
Herramientas/GitHubGitHub/xkiian/datadome-vm
Análisis Dinámico (Sandboxing)Ingeniería InversaAnálisis de BinariosAprendizaje y EducaciónAnti-BotBypass de CAPTCHA
GitHubxkiian/datadome-vm

datadome-vm

Ingeniería inversa de la nueva VM de Datadome 🔥

Ver Repositorio
1251961hace 7 mesesRevisado por Kitploit

Más Populares

Ver todos →

Descubre las herramientas más usadas por nuestra comunidad.

Explora todas las herramientas

Explora nuestra colección de herramientas

Ver todas las herramientas →
Compartir

Análisis de la VM de DataDome

Resumen

Este repositorio documenta la primera versión pública de la máquina virtual (VM) de JavaScript en el navegador de DataDome, utilizada en su flujo CAPTCHA/intersticial. Este análisis cubre:

  • Mecanismos de carga y decodificación de bytecode
  • Diseño y arquitectura de la memoria de la VM
  • Un desensamblador de prueba de concepto
  • Notas de análisis de flujo de control

Nota: Este repositorio cubre solo una versión (estática) de la VM y está destinado a fines de investigación y análisis de seguridad. No incluye solucionadores dinámicos ni implementaciones de solucionadores en producción.

Antecedentes

El 14 de enero de 2026, DataDome comenzó a distribuir un nuevo componente basado en VM en su etiqueta de cliente.

Desofuscación

El código de la VM se ha extraído del desafío captcha en vm.js (disponible en este repositorio).

El primer paso fue desofuscar el script:

La ofuscación es sencilla: evaluar cada variable y reemplazarla con su valor real. Hay un script de desofuscación disponible en deobf.js.

Análisis Inicial

Ejecutar el código desofuscado (out.js) en DevTools revela la salida esperada de la VM:

La salida es un objeto JSON que contiene dos números y una cadena. Ahora profundicemos en la implementación real de la VM.

Decodificación de Bytecode

Al inicio de la función Q.exports, podemos ver cómo se decodifica el bytecode:

  1. La cadena de entrada se decodifica en base64
  2. Se crea un array de longitud 129,263
  3. Cada índice se verifica con un rango específico:
    • Si el índice cae dentro del rango, el valor se decodifica
    • De lo contrario, se devuelve un número aleatorio (usando B(), un generador de números pseudoaleatorios)
  4. -> D contiene el bytecode decodificado con algo de 'ruido' aleatorio

Arquitectura de la VM

Desplazándose hacia abajo se revela el punto de entrada de la VM: una función con dos parámetros A (el bytecode) y Q (un diccionario vacío usado para el manejo de errores).

Diseño de Memoria

El aspecto más interesante de esta VM es su arquitectura: todo vive en un solo array (A). Este array contiene:

  • La pila
  • Registros
  • Códigos de operación
  • El propio bytecode
  • El puntero de instrucción

Este diseño refleja la arquitectura real de computadoras con regiones de memoria distintas. El siguiente paso es mapear cada desplazamiento para entender qué se almacena dónde:

var stack_pointer = 4593
var instruction_pointer = 4635
var frame_base_pointer = 4674
var last_result = 4633
var exit_flag = 4656
var current_opcode_handler = 4685
var current_opcode_id = 4675
var stack_offset = 124482
var vm_start = 5258

Con estos desplazamientos mapeados, la estructura de la VM se vuelve clara.

Funciones Auxiliares

La VM comienza con una colección de funciones auxiliares que manejan:

  • Lectura de valores tipificados de la pila
  • Movimiento de datos entre la pila y los 'registros'

Inicialización de la VM

Después de las funciones auxiliares, la VM inicializa los valores centrales:

  • Todos los punteros (pila, instrucción, base de marco)
  • Bandera de salida
  • Registro del último resultado

Debajo de la inicialización están todos los manejadores de instrucciones.

El Bucle Despachador

El despachador es el bucle principal de la VM que se ejecuta hasta que exit_flag está configurado:

  • I representa la instrucción actual
  • P es el desplazamiento real en el array (teniendo en cuenta la ofuscación)
  • El bucle establece la instrucción actual en current_opcode_handler y actualiza current_opcode_id

Implementación de Códigos de Operación

Cómo Funcionan los Códigos de Operación

Aquí hay un ejemplo básico de un manejador de código de operación:

  1. Obtiene un valor inmediato del bytecode
  2. Recupera el valor superior de la pila
  3. Realiza una operación (por ejemplo, %= o ^=)
  4. Llama a la función fetch() al final

Códigos de Operación Interesantes

Opcode 4919: Creación de Función/Clausura

Uno de los códigos de operación más complejos crea clausuras/funciones:

A[4919] = function () {
    var Q = readUint8();  // Number of expected arguments
    var B = [];
    for (var E = readUint8(), D = 0; D < E; D++) {
        var g = readUint8();
        var a = A[A[frame_base_pointer] + g];
        B.push(a);  // Capture variables from current scope
    }
    var h = A[instruction_pointer] + 3;  // Save address of function body
    A[A[stack_pointer]++] = function (E) {
        // Set up new stack frame when called
        var e = A[stack_pointer] - E;
        while (E < Q) {
            A[e + E++] = undefined;  // Fill missing arguments with undefined
        }
        A[stack_pointer] = e + Q;
        for (var D = 0; D < B.length; D++) {
            var g = B[D];
            A[A[stack_pointer]++] = g;  // Push captured variables
        }
        A[e - 2] = A[frame_base_pointer];  // Save old frame pointer
        A[e - 1] = A[instruction_pointer];  // Save return address
        A[frame_base_pointer] = e;
        A[instruction_pointer] = h;  // Jump to function body
    };
    fetch();
};

Este código de operación:

  1. Lee el recuento de argumentos esperados
  2. Captura variables del ámbito actual (clausura)
  3. Crea una función que configura un nuevo marco de pila con las convenciones de llamada adecuadas
  4. Maneja argumentos faltantes llenándolos con undefined
  5. Guarda la dirección de retorno y el puntero de marco para retornos adecuados

Opcode 5003: Llamada Dinámica a Función

Este código de operación crea un envoltorio para llamadas a funciones que maneja tanto llamadas regulares como de constructor:

A[5003] = function () {
    var Q = A[--A[stack_pointer]];  // POP function
    var B = A[--A[stack_pointer]];  // POP 'this' context

    function E(e) {  // e = argument count
        var D = A[stack_pointer];
        var g = A.slice(D - e, D);  // Get arguments from stack
        if (this instanceof E) {
            // Constructor call (new E(...))
            g.unshift(null);
            var h = Function.prototype.bind.apply(Q, g);
            A[stack_pointer] -= e;
            try {
                a = new h();
            } catch (A) {
                a = A.message;
            }
            A[A[stack_pointer]++] = a;
        } else {
            // Regular function call
            var t;
            try {
                t = Q.apply(B, g);
            } catch (A) {
                t = A.message;
            }
            A[stack_pointer] -= e + 2;
            A[A[stack_pointer]++] = t;
        }
    }

    A[A[stack_pointer]++] = E;
    fetch();
};
Descargar herramienta