Skip to content
KitploitKITPLOIT
StrumentiExploitsBlog
Log in
Invia
StrumentiExploitsBlog
Invia

Strumenti di Hacking, PenTest e Cybersecurity per il tuo Arsenale di Sicurezza!

Kitploit è una directory di strumenti di hacking, cybersecurity e pentesting. Scopri gli ultimi aggiornamenti dei progetti per trovare vulnerabilità, analizzare sistemi, automatizzare i test e rafforzare la tua sicurezza.

··Feed·Contatto·Privacy·© 2026 Kitploit

Directory degli strumenti

Categorie

Vedi tutte le categorie
Loading categories
datadome-vm — Reverse engineering della nuova VM Datadome 🔥 | Kitploit
Strumenti/GitHubGitHub/xkiian/datadome-vm
Analisi Dinamica (Sandboxing)Reverse EngineeringAnalisi di BinariApprendimento e FormazioneAnti-BotBypass CAPTCHA
GitHubxkiian/datadome-vm

datadome-vm

Reverse engineering della nuova VM Datadome 🔥

Vedi Repository
12519617 mesi faRevisionato da Kitploit

Più Popolari

Vedi tutti →

Scopri gli strumenti più utilizzati dalla nostra community.

Esplora tutti gli strumenti

Sfoglia la nostra collezione di strumenti

Vedi tutti gli strumenti →
Condividi

Analisi della VM di DataDome

Riepilogo

Questo repository documenta la prima versione pubblica della macchina virtuale JavaScript (VM) di DataDome utilizzata nel loro flusso CAPTCHA/interstiziale. Questa analisi copre:

  • Meccanismi di caricamento e decodifica del bytecode
  • Architettura e layout della memoria della VM
  • Un disassemblatore proof-of-concept
  • Note sull'analisi del flusso di controllo

Nota: Questo repository copre solo una versione (statica) della VM ed è inteso per scopi di ricerca e analisi sulla sicurezza. Non include risolutori dinamici o implementazioni di risolutori di produzione.

Contesto

Il 14 gennaio 2026, DataDome ha iniziato a distribuire un nuovo componente basato su VM nel loro tag client.

Deoffuscamento

Il codice della VM è stato estratto dalla sfida captcha in vm.js (disponibile in questo repository).

Il primo passo è stato deoffuscare lo script:

L'offuscamento è semplice: valutare ogni variabile e sostituirla con il suo valore effettivo. Uno script di deoffuscamento è disponibile in deobf.js.

Analisi Iniziale

Eseguendo il codice deoffuscato (out.js) in DevTools si rivela l'output atteso della VM:

L'output è un oggetto JSON contenente due numeri e una stringa. Ora immergiamoci nell'implementazione vera e propria della VM.

Decodifica del Bytecode

All'inizio della funzione Q.exports, possiamo vedere come viene decodificato il bytecode:

  1. La stringa di input viene decodificata in base64
  2. Viene creato un array di lunghezza 129.263
  3. Ogni indice viene controllato rispetto a un intervallo specifico:
    • Se l'indice rientra nell'intervallo, il valore viene decodificato
    • Altrimenti, viene restituito un numero casuale (usando B(), un generatore di numeri pseudo-casuali) -> D contiene il bytecode decodificato con del "rumore" casuale

Architettura della VM

Scorrendo verso il basso si rivela il punto di ingresso della VM: una funzione con due parametri A (il bytecode) e Q (un dizionario vuoto usato per la gestione degli errori).

Layout della Memoria

L'aspetto più interessante di questa VM è la sua architettura: tutto vive in un singolo array (A). Questo array contiene:

  • Lo stack
  • I registri
  • Gli opcode
  • Il bytecode stesso
  • Il puntatore all'istruzione

Questo design rispecchia l'architettura reale dei computer con regioni di memoria distinte. Il passo successivo è mappare ogni offset per capire cosa è memorizzato dove:

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 questi offset mappati, la struttura della VM diventa chiara.

Funzioni di Supporto

La VM inizia con una raccolta di funzioni di supporto che gestiscono:

  • Lettura di valori tipizzati dallo stack
  • Spostamento di dati tra lo stack e i "registri"

Inizializzazione della VM

Dopo le funzioni di supporto, la VM inizializza i valori principali:

  • Tutti i puntatori (stack, istruzione, frame base)
  • Flag di uscita
  • Registro dell'ultimo risultato

Sotto l'inizializzazione ci sono tutti i gestori di istruzioni.

Il Ciclo Dispatcher

Il dispatcher è il ciclo principale della VM che viene eseguito finché exit_flag non viene impostato:

  • I rappresenta l'istruzione corrente
  • P è l'offset effettivo nell'array (tenendo conto dell'offuscamento)
  • Il ciclo imposta l'istruzione corrente su current_opcode_handler e aggiorna current_opcode_id

Implementazione degli Opcode

Come Funzionano gli Opcode

Ecco un esempio base di un gestore di opcode:

  1. Recupera un valore immediato dal bytecode
  2. Recupera il valore in cima allo stack
  3. Esegue un'operazione (ad es., %= o ^=)
  4. Chiama la funzione fetch() alla fine

Opcode Interessanti

Opcode 4919: Creazione di Funzioni/Closure

Uno degli opcode più complessi crea closure/funzioni:

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();
};

Questo opcode:

  1. Legge il numero previsto di argomenti
  2. Cattura variabili dallo scope corrente (closure)
  3. Crea una funzione che imposta un nuovo stack frame con le convenzioni di chiamata appropriate
  4. Gestisce gli argomenti mancanti riempiendo con undefined
  5. Salva l'indirizzo di ritorno e il frame pointer per ritorni corretti

Opcode 5003: Chiamata Dinamica di Funzione

Questo opcode crea un wrapper per le chiamate di funzione che gestisce sia le chiamate regolari che quelle costruttore:

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();
};
Scarica lo strumento