
नए Datadome VM का रिवर्स इंजीनियरिंग 🔥
यह रिपॉजिटरी DataDome के CAPTCHA/इंटरस्टीशियल फ़्लो में उपयोग किए जाने वाले ब्राउज़र-आंतरिक JavaScript वर्चुअल मशीन (VM) के पहले सार्वजनिक संस्करण का दस्तावेज़ीकरण करती है। इस विश्लेषण में शामिल हैं:
नोट: यह रिपॉजिटरी केवल एक (स्थिर) VM संस्करण को कवर करती है और सुरक्षा अनुसंधान एवं विश्लेषण उद्देश्यों के लिए है। इसमें डायनामिक सॉल्वर या उत्पादन सॉल्वर कार्यान्वयन शामिल नहीं हैं।
14 जनवरी, 2026 को DataDome ने अपने क्लाइंट टैग में एक नया VM-आधारित घटक भेजना शुरू किया।
VM कोड को कैप्चा चैलेंज से निकालकर vm.js (इस रिपॉजिटरी में उपलब्ध) में रखा गया है।
पहला कदम स्क्रिप्ट को डिओबफ़स्केट करना था:
ओबफ़स्केशन सीधा है: प्रत्येक वेरिएबल का मूल्यांकन करें और उसे उसके वास्तविक मान से बदलें। deobf.js में एक डिओबफ़स्केशन स्क्रिप्ट उपलब्ध है।
डिओबफ़स्केट किए गए कोड (out.js) को DevTools में चलाने पर VM का अपेक्षित आउटपुट दिखाई देता है:
आउटपुट एक JSON ऑब्जेक्ट है जिसमें दो संख्याएँ और एक स्ट्रिंग है। अब वास्तविक VM कार्यान्वयन में गोता लगाते हैं।
Q.exports फ़ंक्शन की शुरुआत में, हम देख सकते हैं कि बाइटकोड कैसे डिकोड किया जाता है:
B() का उपयोग करके, एक स्यूडो-रैंडम नंबर जनरेटर)
-> D कुछ यादृच्छिक "शोर" के साथ डिकोड किए गए बाइटकोड को धारण करता है
नीचे स्क्रॉल करने पर VM एंट्री पॉइंट दिखाई देता है: दो पैरामीटर A (बाइटकोड) और Q (एक खाली शब्दकोश जो त्रुटि प्रबंधन के लिए उपयोग होता है) वाला एक फ़ंक्शन।
इस VM का सबसे दिलचस्प पहलू इसकी आर्किटेक्चर है: सब कुछ एक ही सरणी (A) में रहता है। इस सरणी में शामिल हैं:
यह डिज़ाइन विशिष्ट मेमोरी क्षेत्रों वाले वास्तविक कंप्यूटर आर्किटेक्चर को दर्शाता है। अगला कदम प्रत्येक ऑफ़सेट को मैप करना है ताकि यह समझा जा सके कि कहाँ क्या संग्रहीत है:
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
इन ऑफ़सेट्स के मैप होने के बाद VM संरचना स्पष्ट हो जाती है।
VM सहायक फ़ंक्शनों के एक संग्रह से शुरू होता है जो संभालते हैं:
सहायक फ़ंक्शनों के बाद, VM मुख्य मानों को आरंभ करता है:
आरंभीकरण के नीचे सभी इंस्ट्रक्शन हैंडलर हैं।
डिस्पैचर मुख्य VM लूप है जो `exit_flag` सेट होने तक चलता है:
I वर्तमान इंस्ट्रक्शन को दर्शाता हैP सरणी में वास्तविक ऑफ़सेट है (ओबफ़स्केशन को ध्यान में रखते हुए)current_opcode_handler पर सेट करता है और current_opcode_id को अपडेट करता है
यहाँ एक ऑपकोड हैंडलर का एक मूल उदाहरण है:
%= या ^=)fetch() फ़ंक्शन को कॉल करता हैसबसे जटिल ऑपकोडों में से एक क्लोज़र/फ़ंक्शन बनाता है:
A[4919] = function () {
var Q = readUint8(); // अपेक्षित तर्कों की संख्या
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); // वर्तमान स्कोप से वेरिएबल कैप्चर करें
}
var h = A[instruction_pointer] + 3; // फ़ंक्शन बॉडी का पता सहेजें
A[A[stack_pointer]++] = function (E) {
// कॉल होने पर नया स्टैक फ्रेम सेट करें
var e = A[stack_pointer] - E;
while (E < Q) {
A[e + E++] = undefined; // गुम तर्कों को undefined से भरें
}
A[stack_pointer] = e + Q;
for (var D = 0; D < B.length; D++) {
var g = B[D];
A[A[stack_pointer]++] = g; // कैप्चर किए गए वेरिएबल को पुश करें
}
A[e - 2] = A[frame_base_pointer]; // पुराना फ्रेम पॉइंटर सहेजें
A[e - 1] = A[instruction_pointer]; // वापसी पता सहेजें
A[frame_base_pointer] = e;
A[instruction_pointer] = h; // फ़ंक्शन बॉडी पर जाएं
};
fetch();
};
यह ऑपकोड:
undefined से भरकर संभालता हैयह ऑपकोड फ़ंक्शन कॉल के लिए एक रैपर बनाता है जो नियमित और कंस्ट्रक्टर दोनों कॉल को संभालता है:
A[5003] = function () {
var Q = A[--A[stack_pointer]]; // POP फ़ंक्शन
var B = A[--A[stack_pointer]]; // POP 'this' संदर्भ
function E(e) { // e = तर्क गणना
var D = A[stack_pointer];
var g = A.slice(D - e, D); // स्टैक से तर्क प्राप्त करें
if (this instanceof E) {
// कंस्ट्रक्टर कॉल (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 {
// नियमित फ़ंक्शन कॉल
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();
};
यह ऑपकोड:
new) है या नियमित कॉलA[4961] = function () {
var Q = {};
for (var E = readUint16(), e = 0; e < E; e++) {
var D = A[--A[stack_pointer]]; // पहला POP
var g = A[--A[stack_pointer]]; // दूसरा POP
Q[D] = g;
}
A[A[stack_pointer]++] = Q;
fetch();
};