
Engenharia reversa da nova VM Datadome 🔥
Este repositório documenta a primeira versão pública da máquina virtual (VM) JavaScript no navegador do DataDome, usada no fluxo de CAPTCHA/interstitial. Esta análise cobre:
Nota: Este repositório cobre apenas uma versão (estática) da VM e tem como objetivo pesquisa e análise de segurança. Não inclui resolvedores dinâmicos ou implementações de resolvedores de produção.
Em 14 de janeiro de 2026, o DataDome começou a distribuir um novo componente baseado em VM em sua tag do cliente.
O código da VM foi extraído do desafio captcha e está disponível em vm.js (neste repositório).
O primeiro passo foi desofuscar o script:
A ofuscação é direta: avalie cada variável e substitua pelo seu valor real. Um script de desofuscação está disponível em deobf.js.
Executar o código desofuscado (out.js) no DevTools revela a saída esperada da VM:
A saída é um objeto JSON contendo dois números e uma string. Agora vamos mergulhar na implementação real da VM.
No início da função Q.exports, podemos ver como o bytecode é decodificado:
B(), um gerador de números pseudoaleatórios)
-> D contém o bytecode decodificado com algum "ruído" aleatório
Rolando para baixo revela o ponto de entrada da VM: uma função com dois parâmetros A (o bytecode) e Q (um dicionário vazio usado para tratamento de erros).
O aspecto mais interessante desta VM é sua arquitetura: tudo vive em um único array (A). Esse array contém:
Esse design espelha arquiteturas reais de computadores com regiões de memória distintas. O próximo passo é mapear cada offset para entender o que está armazenado onde:
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
Com esses offsets mapeados, a estrutura da VM fica clara.
A VM começa com uma coleção de funções auxiliares que lidam com:
Após as funções auxiliares, a VM inicializa os valores centrais:
Abaixo da inicialização estão todos os manipuladores de instrução.
O despachante é o loop principal da VM que executa até que `exit_flag` seja definido:
I representa a instrução atualP é o offset real no array (considerando a ofuscação)current_opcode_handler e atualiza current_opcode_id
Aqui está um exemplo básico de um manipulador de opcode:
%= ou ^=)fetch() no finalUm dos opcodes mais complexos cria closures/funções:
A[4919] = function () {
var Q = readUint8(); // Número de argumentos esperados
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); // Captura variáveis do escopo atual
}
var h = A[instruction_pointer] + 3; // Salva endereço do corpo da função
A[A[stack_pointer]++] = function (E) {
// Configura um novo frame de pilha quando chamado
var e = A[stack_pointer] - E;
while (E < Q) {
A[e + E++] = undefined; // Preenche argumentos faltantes com undefined
}
A[stack_pointer] = e + Q;
for (var D = 0; D < B.length; D++) {
var g = B[D];
A[A[stack_pointer]++] = g; // Empurra variáveis capturadas
}
A[e - 2] = A[frame_base_pointer]; // Salva ponteiro do frame antigo
A[e - 1] = A[instruction_pointer]; // Salva endereço de retorno
A[frame_base_pointer] = e;
A[instruction_pointer] = h; // Salta para o corpo da função
};
fetch();
};
Este opcode:
undefinedEste opcode cria um wrapper para chamadas de função que lida tanto com chamadas regulares quanto com construtor:
A[5003] = function () {
var Q = A[--A[stack_pointer]]; // POP função
var B = A[--A[stack_pointer]]; // POP contexto 'this'
function E(e) { // e = contagem de argumentos
var D = A[stack_pointer];
var g = A.slice(D - e, D); // Obtém argumentos da pilha
if (this instanceof E) {
// Chamada de construtor (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 {
// Chamada de função regular
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();
};
Este opcode:
new) ou regular