Skip to content
KitploitKITPLOIT
StrumentiBlog
Invia
StrumentiBlog
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
125196 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:

root@kitploit:~
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:

root@kitploit:~
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:

root@kitploit:~
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();
};

Questo opcode:

  1. Preleva la funzione e il contesto dallo stack
  2. Crea un wrapper che può essere chiamato con argomenti
  3. Rileva se è una chiamata costruttore (new) o regolare
  4. Applica la funzione con il contesto appropriato e la gestione degli errori
  5. Spinge il risultato sullo stack

Opcode 4961: Costruzione di Letterali Oggetto

root@kitploit:~
A[4961] = function () {
    var Q = {};
    for (var E = readUint16(), e = 0; e < E; e++) {
        var D = A[--A[stack_pointer]];  // First POP
        var g = A[--A[stack_pointer]];  // Second POP
        Q[D] = g;
    }
    A[A[stack_pointer]++] = Q;
    fetch();
};

Questo opcode costruisce letterali oggetto:

  1. Leggendo il numero di coppie di proprietà dal bytecode
  2. Prelevando coppie dallo stack (il primo prelievo diventa la chiave)
  3. Costruendo un oggetto: object[firstPop] = secondPop
  4. Spingendo l'oggetto risultante sullo stack

La Funzione Fetch

Ogni istruzione si conclude chiamando fetch(), che prepara l'istruzione successiva:

root@kitploit:~
function fetch() {
    var Q = A[instruction_pointer];
    var B = A[vm_start + Q];
    A[instruction_pointer] = Q + 1;
    var E = A[4783 + B];
    A[current_opcode_handler] = E;
    A[current_opcode_id] = B;
}

Questa funzione:

  1. Legge il puntatore all'istruzione
  2. Recupera il prossimo opcode dal bytecode
  3. Incrementa il puntatore all'istruzione
  4. Cerca il gestore dell'opcode
  5. Aggiorna current_opcode_handler e current_opcode_id

Questo rispecchia la logica del dispatcher, creando un ciclo fetch-decode-execute tipico delle architetture VM.

Implementazione del Disassemblatore

Per facilitare l'analisi, è stato sviluppato un disassemblatore proof-of-concept (disasm.js) per convertire il bytecode della VM in assembly leggibile dall'uomo.

Approccio

Il disassemblatore opera in due passate:

Passata 1: Scoperta delle Etichette

La prima passata scansiona il bytecode per identificare tutti i target di salto. Questo include:

  • Salti in avanti e indietro (JMP_FWD, JMP_BACK)
  • Salti condizionali (JZ, JNZ_KEEP, JZ_KEEP)
  • Confini delle closure (corpi delle funzioni e loro punti finali)

Ogni indirizzo target è marcato con un'etichetta (ad es., L_0042) per rendere più facile seguire il flusso di controllo.

Passata 2: Disassemblaggio

La seconda passata converte ogni istruzione in un output simile all'assembly:

root@kitploit:~
000042:  fa 00 0a           PUSH_IMM 10
000045:  19 00 19           PUSH_REG 25
000048:  eb                 ADD

Ogni riga include:

  • Indirizzo: Offset esadecimale nel bytecode
  • Byte grezzi: I byte effettivi che compongono l'istruzione (utili per la verifica) e boh, sembra tosto
  • Opcode: Nome mnemonico dell'istruzione
  • Argomenti: Operandi decodificati (numeri di registro, immediati, target di salto)

Decodifica dei Valori

Uno degli aspetti più complessi è la decodifica dei valori immediati incorporati nel bytecode. La VM utilizza marcatori di tipo per indicare come interpretare i byte successivi:

Tipi semplici (nessun dato aggiuntivo):

  • 0x28 → true
  • 0x7D → false
  • 0x4C → null
  • 0x3D → undefined

Interi piccoli (0-127): Codificati con il bit alto impostato

  • 0x85 → 5 (0x80 | 5)

Stringhe: Codificate con XOR e terminate da null

  • Stringhe ASCII: marcatore 0x67, chiave XOR inizia a 183
  • Stringhe UTF-8: marcatore 0x27, chiave XOR inizia a 46

Tipi numerici:

  • 8 bit con segno: 0x6F + 1 byte
  • 16 bit con segno: 0x61 + 2 byte (big-endian)
  • 24 bit con segno: 0x65 + 3 byte (big-endian)
  • 32 bit con segno: 0x54 + 4 byte (big-endian)
  • IEEE 754 double: 0x05 + 8 byte

La codifica XOR per le stringhe è semplice ma impedisce un'ispezione casuale:

root@kitploit:~
let str = '';
let xorKey = 183;  // Initial key for ASCII strings
let ch;
while ((ch = readByte() ^ (xorKey++ & 0xFF)) !== 0) {
    str += String.fromCharCode(ch);
}

Opcode Speciali

Alcuni opcode richiedono una gestione personalizzata:

CLOSURE (opcode 136): Crea funzioni/closure con variabili catturate

root@kitploit:~
Format: CLOSURE locals, capture_count, [capture_indices...], skip_offset

L'offset di salto punta oltre il corpo della funzione, permettendo alla VM di saltare la definizione della funzione durante l'esecuzione lineare.

PUSH_MULTI_IMM (opcode 96): Spinge più valori contemporaneamente

root@kitploit:~
Format: PUSH_MULTI_IMM count, val1, val2, ...

Utilizzo

root@kitploit:~
# Disassemble from file
node disasm.js bytecode.txt

Output di Esempio

root@kitploit:~
; DataDome VM Disassembly
; Bytecode size: 5428 bytes
; VM Constants: VM_START=5258, OPCODE_BASE=4783

000000:  88 01 00 04        CLOSURE locals=1, captures=[0, 4], body=L_0006, end=L_0a3f
L_0006:
000006:  fa 67 ...          PUSH_IMM "window"
00001f:  2b                 PUSH_WINDOW
000020:  02                 SET
000021:  fa 67 ...          PUSH_IMM "navigator"
00003a:  19 00 00           PUSH_REG 0
00003d:  fa 67 ...          PUSH_IMM "navigator"
000056:  ee                 GET
000057:  02                 SET
...

Questo formato di output rende possibile:

  • Tracciare il flusso di esecuzione seguendo le etichette di salto
  • Identificare i confini delle funzioni tramite gli opcode CLOSURE
  • Vedere esattamente quali valori vengono spinti e manipolati
  • Fare riferimento incrociato con l'implementazione effettiva della VM

Note

Sono ancora abbastanza nuovo alle VM, quindi prendete tutto con le pinze. L'IA è stata usata per aiutare a documentare il codice e scrivere parti di questo readme (perché scrivere documentazione è doloroso).

Dichiarazione di non responsabilità

Questo è puramente per scopi educativi/ricerca sulla sicurezza. Nessun risolutore o bypass incluso - solo documentazione di come funziona la VM perché è genuinamente interessante.

DataDome: se state leggendo questo, ciao!!! Questo è solo un mio tentativo di ottenere una borsa di studio 🙏. Per favore non denunciatemi, sono al verde. Se avete problemi con questo repo fatemelo sapere e possiamo parlarne :)

mi dispiace per le persone che pensavano che avrei parlato dell'interno della vm... non succederà

NON INVIATEMI DM CHIEDENDO UN'API DATADOME, NON VI AIUTERÒ

Scarica lo strumento