
مجموعة أدوات الهندسة العكسية لـ PerimeterX's bytecode VM، تشتمل على CFG-based disassembler وخط فك تشفير من 5 طبقات وإعادة بناء جدول opcode ومنظف stack emulation cleaner لأبحاث الأمان حول بصمة كشف البوتات.
يوثق هذا المستودع الهندسة العكسية لـ auditor.js من PerimeterX، وهي آلة افتراضية تعمل بالبايت كود تُستخدم كطبقة ثانية لأخذ البصمات في خط كشف البوتات الخاص بـ PX. يغطي هذا التحليل ما يلي:
ملاحظة: يغطي هذا المستودع نسخة واحدة (ثابتة) فقط من الآلة الافتراضية وهو مخصص لأغراض البحث الأمني والتحليل. لا يتضمن حلولاً ديناميكية أو تطبيقات حلول إنتاجية.
يوم الخميس 2 أبريل 2026، نشرت PerimeterX آلة افتراضية جديدة بالبايت كود كجزء من خط كشف البوتات الخاص بها.
لا يبدو auditor.js مثل سكريبت مستشعر PX العادي. بدلاً من عمليات البحث عن الخصائص المبهمة ووظائف الجمع المعتادة:
_fg0 إلى _fg7)، برنامج الآلة الافتراضية المشفر مقسم عبر المتغيرات_dp) بمفتاح خاص بالموقع (_pk) تقوم بفك ضغط برنامج JSON_0x8df7) تقوم بتجزئة مصدر الآلة الافتراضية لاشتقاق مفتاح فك التشفير، لذا أي تعديل يكسر فك تشفير البايت كود بصمتبرنامج الآلة الافتراضية مقسم عبر 8 متغيرات، يتم دمجها ثم فك تشفيرها عبر _dp() باستخدام تشفير XOR خاص بالموقع مفتاحه _pk:
var _pk = 893686289;
function _dp(_b) {
var _r = atob(_b), _o = new Array(_r.length);
for (var _i = 0; _i < _r.length; _i++) {
_o[_i] = String.fromCharCode(
_r.charCodeAt(_i) ^ (((_pk >>> (8 * (_i % 4))) ^ Math.imul(_i + 1, 0x6B8B4567)) & 0xFF)
);
}
return _o.join("");
}
النتيجة هي كائن JSON بأسماء مفاتيح مبهمة مكونة من حرفين (مثل "uo" للبذرة، "dk" للـ nonce). جدول تعيين يحولها إلى أسماء قياسية.
node extractor.js
# -> program.json
جميع سلاسل الثوابت الـ 1095 مشفرة بطبقتين:
الطبقة 1: تشفير XOR ثابت باستخدام Murmur بمفتاح 4008000571، معتمد على الموضع.
الطبقة 2: تشفير XOR بتدفق PRNG باستخدام LCG من glibc، مبذر بدمج بذرة البرنامج مع فهرس كل ثابت عبر ضرب Knuth التجزئي.
قبل فك التشفير، يتم XOR البذرة مع بصمة البيئة (_0xaf48)، وهي قناع بتات 8 بت يتم حسابه عن طريق فحص واجهات برمجة المتصفح:
بالنسبة لـ Chrome: _0xaf48 = 0b01111111 = 127، مما يعطي بذرة فعالة = 12755 ^ 127 = 12716.
هذا يعني أن نفس البرنامج ينتج نتائج فك تشفير مختلفة في بيئات مختلفة. تشغيله في Node.js مقابل Chrome مقابل Firefox يعطي بذورًا مختلفة.
node decrypt_constants.js
# -> program_decrypted.json, constants_table.txt
تخبرنا السلاسل المفكوكة بالضبط بما تلتقطه الآلة الافتراضية كبصمات:
بصمات المتصفح: screenWidth, screenHeight, innerWidth, innerHeight, devicePixelRatio, colorDepth, platform, userAgent, language, timezone, timezoneOffset, forcedColors, highContrast
توقيت الأداء: navigationStart, domComplete, domLoading, fetchStart, requestStart, responseEnd, secureConnectionStart, serverTiming
تشفير RSA: BigInt, modPow, AQAB (65537 في base64), modulusLength, shiftLeft, shiftRight, getRandomValues
تحقيقات DOM/SVG: http://www.w3.org/2000/svg, createElementNS, getBoundingClientRect, getTotalLength, getBBox
أسماء حقول PX: mtr, tst, mst, enc, sbx, fstec, pdc, prb, wvi, wva, pti, dis, los, cv, sc, jd, , ,
مراجع نقاط النهاية: https://fst-ec.perimeterx.net/?id=
مضاد للمصحح: _CMP_RCX_07;_JNZ_0x0A_EB_CC, CC|CD-04|BREAKPOINT-005
107 أوبكود أساسي تغطي لغة JavaScript بأكملها، بالإضافة إلى ضوضاء مولدة ديناميكيًا:
40 أوبكود عسل هي تطبيقات بديلة لعمليات حسابية/مقارنة باستخدام تعابير متكافئة رياضياً ولكن مختلفة نحويًا. قد تظهر ADD كـ (a^b) + 2*(a&b) أو -((-a)-b) أو a-(-b). يمكن أن يظهر كل أوبكود أساسي بما يصل إلى 3 متغيرات، مولدة حتميًا من البذرة. يمكن أن تظهر تعليمة ADD بسيطة كـ 4 قيم بايت كود مختلفة داخل نفس البرنامج، مما يكسر طرق مطابقة الأنماط.
24 أوبكود حشو مخصصة في التبديل ولكن ليس لها معالجات ولا يتم إصدارها أبداً. توجد لتوسيع مساحة الأوبكود وجعل الخلط أكثر صعوبة في العكس.
16 مجموعة تعليمات فائقة هي أهم ميزة مضادة للتحليل. عندما تحل حلقة التوزيع أوبكودًا ما إلى قائد تعليمة فائقة، يقرأ المعالج بايتًا إضافيًا واحدًا من تيار البايت كود ويوزع إلى معالج فرعي. يمكن أن يكون المعالج الفرعي عملية مختلفة تمامًا:
يتم خلط جدول الأوبكود عبر Fisher-Yates مبذر بالبذرة الفعالة، لذا تختلف قيم البايت كود لكل بناء.
node build_opcodes.js
# -> opcode_table.json, opcode_table.txt
جوهر مجموعة الأدوات. cfg.js ينشئ رسمًا بيانيًا لتدفق التحكم باتباع جميع مسارات التنفيذ من PC=0، فك تشفير كل تعليمة بسياق التشفير الصحيح.
تستخدم PX تعليمات متداخلة عند حدود الكتل. نفس البايتات تفك كمعاملات على مسار تنفيذ وكأوبكود على مسار آخر، حسب سياق تشفير الكتلة. المسح الخطي يفك تشفير كل موضع بايت مرة واحدة ويفتقد المسار البديل. يتبع CFG كلاً من الحواف الساقطة وحواف القفز، ويفك تشفير كل مسار بشكل مستقل.
_0x3ca8): تشفير XOR ثابت باستخدام Murmur على بايتات base64 الخام، بمفتاح 4008000571_0xece1): تشفير XOR خاص بالدالة بطبقتين فرعيتين: مفتاح ثابت معتمد على الموضع + مفتاح تجزئة تكامل الشيفرة_0x427d): تشفير XOR دوار خاص بالكتلة. كل كتلة تشفير (محددة بحدود fn.bl) تحصل على XOR إضافي مشتق من مفتاح الدالة وفهرس الكتلة. الكتلة 0 غير مشفرة عند الوصول الأول؛ الكتل 1+ مشفرة. هذا هو سبب عمل المفكك الخطي للكتلة الأولى ولكنه ينتج هراء للكتل اللاحقة.يطبق CFG جميع الطبقات الخمس بطريقة غير مدمرة (XOR المعامل يحسب أثناء التنفيذ، وليس في المكان) حتى لا تفسد مناطق التعليمات المتداخلة بعضها البعض.
لكل قائد تعليمة فائقة، يقرأ CFG البايت الفرعي، ويبحث عن المعالج الفعلي في super_groups.json، ويفك تشفير المعامل للأوبكود الحقيقي. يتم اتباع أهداف القفز من أوبكودات القفز المدمجة (مثل ما يبدو كـ ASSIGN_OP_VAR ولكنه في الواقع JMP) بشكل صحيح.
node cfg.js # جميع الدوال -> cfg_output/
node cfg.js 79 # دالة واحدة إلى stdout
تم التحقق من صحته مقابل آثار تنفيذ المتصفح: 600 تعليمة تم تتبعها عبر 14 دالة، 0 عدم تطابق في دلتا المكدس. تم التحقق من صحة جميع الأوبكودات الـ 34 الفريدة. تم تأكيد توزيع التعليمات الفائقة بشكل صحيح لجميع مجموعات القادة العشر التي تم تنفيذها أثناء التهيئة.
يأخذ مخرجات CFG ويقوم بتشغيل محاكاة المكدس لإنتاج تعليقات تعبيرية. يحول البايت كود الخام إلى شبه كود قابل للقراءة.
node cleaner.js 79 # fn79 إلى stdout
يمشي عبر التعليمات بالترتيب، متتبعًا مكدسًا افتراضيًا. كل دفع/سحب/استدعاء يبني سلسلة تعبيرية:
0018 GET_VAR ; 0.0001
001e GET_VAR ; or
0024 PUSH_CONST ; "_0x166"
002b CALL_METHOD_C ; 0.0001._0x88(or, "_0x166")
...
0114 PUSH_CONST ; "fontSize"
011b PUSH_CONST ; "pdc"
0122 CALL_METHOD_C ; _0x18c.getHours("fontSize", "pdc")
يتم قمع الضوضاء الحسابية/المقارنة البحتة فقط على مكدس فارغ. يتم الاحتفاظ بكل شيء آخر لأن CFG قام بالفعل بتصفية العسل والحشو.
112 دالة، 5435 تعليمة محتفظ بها، 207 ضوضاء مكبوتة. fn79 (جامع البصمات، 1109 تعليمة) لديها تغطية تعليقات تعبيرية بنسبة 85%.
آلة افتراضية قائمة على المكدس بسعة 256 خانة، سلسلة نطاق، سلسلة معالجة try/catch، ومكدس مكرر for-in. تقرأ حلقة التوزيع أوبكودات بـ 2 بايت (Little Endian)، وتحل عبر التبديل + XOR للموضع + إزاحة الكتلة، وتفك تشفير المعاملات في المكان، وتنفذ المعالج، ثم تعيد تشفير المعاملات بحيث لا يتم فك تشفير البايت كود بالكامل في الذاكرة أبدًا.
BigInt و modPow والأس 65537getTotalLength() و getBBox() على مسارات منشأةperformance.timingCC|CD-04|BREAKPOINT-005)تم استخدام الذكاء الاصطناعي للمساعدة في توثيق الشيفرة، وكتابة الأدوات، وكتابة هذا الـ readme.
لأغراض تعليمية/بحثية أمنية بحتة. لا توجد حلول أو تجاوزات، فقط توثيق لكيفية عمل الآلة الافتراضية لأنها مثيرة للاهتمام حقًا.
إذا كان لدى أي شخص من PerimeterX/HUMAN Security مخاوف بشأن هذا المستودع، فيرجى التواصل: [email protected]
| الحقل | الوصف |
|---|
s | البذرة (12755)، تدفع جميع العمليات التشفيرية |
n | الـ nonce (1603730985)، عشوائية خاصة بالبرنامج |
g | علامة المولد، تفعيل طبقة فك تشفير تجزئة التكامل |
x | علامة التشفير، يتم تشفير الثوابت بـ XOR |
c | مجمع الثوابت، 1230 مدخلاً |
f | الدوال، 112 مدخلاً مع بايت كود مشفر |
e | نقطة الدخول، فهرس الدالة 0 |
| بت | الاختبار | Chrome |
|---|
| 0 | typeof window.matchMedia === "function" | 1 |
| 1 | وجود document.elementFromPoint | 1 |
| 2 | typeof window.requestAnimationFrame === "function" | 1 |
| 3 | typeof window.getComputedStyle === "function" | 1 |
| 4 | وجود CSS.supports | 1 |
| 5 | وجود navigator.sendBeacon | 1 |
| 6 | وجود document.execCommand | 1 |
| 7 | وجود process.versions.node (Node.js) | 0 |
adsenveinit| القائد يحل كـ | البايت الفرعي | ينفذ فعليًا |
|---|
FOR_IN_NEXT | 74 | FOR_IN_NEXT |
FOR_IN_NEXT | 100 | MAKE_CLOSURE |
ASSIGN_OP_VAR | 165 | ASSIGN_OP_VAR |
ASSIGN_OP_VAR | 37 | JMP |
GET_VAR_PROP_C | 143 | SET_VAR_POP |
GET_VAR_PROP_C | 23 | JMP_NULLISH |