
Reverse Engineering der neuen Datadome VM 🔥
Dieses Repository dokumentiert die erste öffentliche Version der In-Browser-JavaScript-VM (virtuelle Maschine) von DataDome, die in deren CAPTCHA/Interstitial-Ablauf verwendet wird. Diese Analyse umfasst:
Hinweis: Dieses Repository behandelt nur eine (statische) VM-Version und ist für Sicherheitsforschung und Analysezwecke gedacht. Es enthält keine dynamischen Löser oder Produktionslöser-Implementierungen.
Am 14. Januar 2026 begann DataDome, eine neue VM-basierte Komponente in ihrem Client-Tag auszuliefern.
Der VM-Code wurde aus der Captcha-Herausforderung in vm.js extrahiert (in diesem Repository verfügbar).
Der erste Schritt war die Deobfuskierung des Skripts:
Die Obfuskierung ist unkompliziert: Jede Variable auswerten und durch ihren tatsächlichen Wert ersetzen. Ein Deobfuskierungs-Skript ist in deobf.js verfügbar.
Das Ausführen des deobfuskierten Codes (out.js) in DevTools zeigt die erwartete Ausgabe der VM:
Die Ausgabe ist ein JSON-Objekt, das zwei Zahlen und einen String enthält. Tauchen wir nun in die tatsächliche VM-Implementierung ein.
Am Anfang der Funktion Q.exports sehen wir, wie der Bytecode dekodiert wird:
B(), einem Pseudozufallszahlengenerator)
-> D enthält den dekodierten Bytecode mit etwas zufälligem "Rauschen"
Weiter unten befindet sich der VM-Einstiegspunkt: eine Funktion mit zwei Parametern A (der Bytecode) und Q (ein leeres Wörterbuch für die Fehlerbehandlung).
Der interessanteste Aspekt dieser VM ist ihre Architektur: alles befindet sich in einem einzigen Array (A). Dieses Array enthält:
Dieses Design spiegelt echte Computerarchitektur mit unterschiedlichen Speicherbereichen wider. Der nächste Schritt ist die Kartierung der einzelnen Offsets, um zu verstehen, was wo gespeichert ist:
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
Mit diesen kartierten Offsets wird die VM-Struktur klar.
Die VM beginnt mit einer Sammlung von Hilfsfunktionen, die Folgendes behandeln:
Nach den Hilfsfunktionen initialisiert die VM Kernwerte:
Unterhalb der Initialisierung befinden sich alle Instruktionshandler.
Der Dispatcher ist die Haupt-VM-Schleife, die läuft, bis `exit_flag` gesetzt ist:
I repräsentiert die aktuelle InstruktionP ist der tatsächliche Offset in das Array (unter Berücksichtigung der Obfuskierung)current_opcode_handler und aktualisiert current_opcode_id
Hier ist ein einfaches Beispiel eines Opcode-Handlers:
%= oder ^=)fetch() aufEiner der komplexesten Opcodes erstellt Closures/Funktionen:
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();
};
Dieser Opcode:
undefined gefüllt werdenDieser Opcode erstellt einen Wrapper für Funktionsaufrufe, der sowohl normale als auch Konstruktoraufrufe behandelt:
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();
};