
إزالة الافتراضية عن أقسام .tvm0 الخاصة بـ Tencent VM: يرفع معالجات VM إلى قوائم على مستوى الضيف وإلى LLVM IR، ويستعيد CFG والإرسال، ويُطبّق التصحيحات على الكود المستعاد لإعادته إلى ملف PE.
سجل Zenodo ذو الصلة: 10.5281/zenodo.23474767.
⚠️ إخلاء المسؤولية: هذا المشروع مخصص حصريًا لأغراض البحث الأمني المصرّح به، وتحليل قابلية التشغيل البيني، والتعليم. وهو لا يتضمن ولا يوفّر أي وظيفة تجاوز لأنظمة مكافحة الغش التجارية. يتحمل المستخدمون وحدهم مسؤولية التأكد من امتلاكهم الحق القانوني في تحليل الملفات الثنائية المستهدفة.
أداة إزالة الحجب الافتراضي (devirtualizer) لـ Tencent VM. تستعيد الشيفرة المُحوَّلة افتراضيًا في قسم .tvm0، وترفع ما يفعله كل معالج VM بسجلات الضيف وذاكرته، ويمكنها كتابة الدوال المستعادة مرة أخرى داخل الملف الثنائي كقسم عادي للقراءة والتنفيذ.
إنها أداة: وجّهها نحو وحدة، واحصل على البنية والشيفرة في المقابل.
.tvm0 section -> VM semantics -> guest-level listing -> LLVM IR -> patched PE
قائمة مُزال عنها التمثيل الافتراضي لجذر آلة افتراضية، مع ربط ملف سجلات الضيف عبر الكتل.

python -m venv .venv
source .venv/Scripts/activate # Windows: .venv\Scripts\Activate.ps1
pip install -r requirements.txt
Python 3.13، capstone. بالنسبة للـ backend: llvmlite==0.50.0، وعلى Windows، MSVC
link.exe/cl.exe. z3-solver اختياري — بدونه يبقى تكييف المسار ويُتخطى فقط
إثبات الإرسال المحدود.
انسخ .env.example إلى .env واملأ المسارات الخاصة بهذا الجهاز. ملف .env مُستبعَد من git،
وكل إعداد له أيضًا خيار سطر أوامر، لذا يوفّر الملف القيم الافتراضية فقط.
| المفتاح | المعنى |
|---|---|
TVM_MODULE | الوحدة المراد تحليلها، للفحوصات التي تُشغَّل دون وسيط موضعي |
TVM_LISTINGS | مجموعة قوائم الإدراج التي يقرأها الـ backend |
TVM_OUT | الجذر لكل المخرجات (الافتراضي out/) |
TVM_STEPS | الخطوات لكل كتلة قبل اقتطاع الكتلة (الافتراضي 900000) |
TVM_BLOCKS | ميزانية الكتل لكل جذر (الافتراضي 512) |
TVM_PAYLOAD_OPT | مستوى توليد كود الحمولة 0–3 |
TVM_PAYLOAD_MEM | وضع ذاكرة الضيف sealed | absolute |
TVM_MSVC_BIN | الدليل الذي يحتوي على link.exe/cl.exe، بدلًا من الاعتماد على PATH |
الأولوية: خيار سطر الأوامر → .env → البيئة → القيمة الافتراضية المدمجة.
تُشغَّل أوامر المحرك من engine/؛ وأوامر الـ backend من جذر المستودع.
cd engine
python -m probe.scan_tvm0 /path/to/dir # which files carry a .tvm0, and its roots
python -m probe.trampoline_forms input.exe # what the entry trampolines look like
python -m probe.mnemonic_census input.exe # what x86 the VM contains
python -m core.devirt input.exe --out ../out/listings
قائمة 0x<root>.txt واحدة وملف 0x<root>.guest-state.json واحد لكل دالة. الخيارات:
| العلم | الافتراضي | المعنى |
|---|---|---|
--out=DIR | stdout | كتابة القوائم + حالة الضيف |
--root=0x… | كل المكتشَف | التقييد بمدخل VM واحد |
--show[=N] | معطّل | طباعة أول N سطرًا |
--steps=N | 900000 | الخطوات لكل كتلة قبل الاقتطاع |
--blocks=N | 512 | ميزانية الكتل لكل جذر |
--caller-entry=0x… --call-site=0x… | معطّل | تخصيص المدخل حسب المستدعي |
يشير ترويسة كل قائمة إلى ما إذا كانت الجولة قد اقتُطعت؛ فالقائمة المقتطعة هي حد أدنى، وليست الدالة.
python -m probe.cfg_run input.exe 0x14015fc2e # walk it, print the CFG
python -m probe.guest_effect input.exe 0x14015fc2e # what handlers do to guest registers
python -m probe.ctx_map input.exe 0x14015fc2e # which context slot holds which register
python -m probe.why_unresolved input.exe 0x14015fc2e # why a dispatch did not resolve
python -m probe.block_exits input.exe 0x14015fc2e # why a block has no successor
python -m probe.stop_debug input.exe 0x14015fc2e # why evaluation stopped
python -m probe.unresolved_detail input.exe 0x14015fc2e # per-block detail
التعمق في قيمة أو مخزن محدد:
python -m probe.trace_window input.exe 0x14015fc2e 0x14015fc40 0x14015fc90
python -m probe.reg_history input.exe 0x14015fc2e rax
python -m probe.who_wrote input.exe 0x14015fc2e 0x18
python -m probe.flags_at input.exe 0x14015fc2e 0x14015fc60
python -m probe.stack_at input.exe 0x14015fc2e
.\write-devirt.ps1 -Module 'C:\path\to\SGuardSvc64.exe'
يشغّل payload ← inject ← load ← differential ويتوقف عند أول مرحلة تفشل. الخيارات:
-Listings، -Out، -Opt 0..3، -Mem sealed|absolute، -SkipDifferential، -Force.
المراحل كلٌّ على حدة:
cd llvm
python payload.py --listings=../out/hard-cases-listings-final --out=../out/write-devirt --opt=0 --mem=sealed
python peinject.py --host=input.exe --payload=../out/write-devirt --out=../out/write-devirt/input.neko.exe
python miniload.py ../out/write-devirt/input.neko.exe
python diff_payload.py --payload=../out/write-devirt --patched=../out/write-devirt/input.neko.exe
diff_payload.py هو الفحص المهم: فهو يشغّل الكود داخل الصورة المُرقّعة ويقارنه بايتًا ببايت مع المفسّر على نفس القائمة. أما miniload.py فيثبت فقط أن الصورة تُحمَّل وأن نقطة دخولها تعمل.
يستبدل ACE دالة أصلية بـ trampoline يقود إلى موزّع VM. ينفّذ الموزّع معالجات x86 أصلية، لكن الحالة المعمارية للدالة المحمية تعيش في سياق VM.
flowchart TB
subgraph TOP[ ]
direction LR
subgraph TEXT[.text]
ENTRY["<b>Function entry</b><br/>jump to VM entry"]
end
subgraph VM[.tvm0 section]
direction LR
CORE["<b>Shared dispatcher and handlers</b><br/>execute x86 - resolve next handler"]
CTX["<b>Guest context</b><br/>GPRs - RFLAGS - VIP"]
CORE <--> CTX
end
end
subgraph NATIVE[Native execution outside the VM]
direction LR
BOXED["<b>Boxed instruction</b><br/>execute original x86"]
CALL["<b>Guest call</b><br/>invoke native target"]
EXIT["<b>Guest return or tail call</b><br/>resume native execution"]
BOXED ~~~ CALL ~~~ EXIT
end
TEXT --> VM
VM <--> NATIVE
style TOP fill:transparent,stroke:transparent
بعض العمليات تُنفَّذ خارج الآلة الافتراضية. تعليمة مُغلَّفة (boxed instruction) تُشغِّل ترميزها الأصلي x86 بعد أن تستعيد الآلة الافتراضية السجلات التي تحتاجها، ثم تُعيد آثارها إلى سياق الضيف. نداء الضيف (guest call) يستدعي هدفًا أصليًا ويستأنف بقيمة الإرجاع وتغييرات Windows x64 ABI المُمثَّلة في حالة الضيف. عودات الضيف والنداءات الذيلية تغادر المُوزِّع (dispatcher) كليًا.
يحتفظ السياق بجميع سجلات الأغراض العامة الستة عشر الخاصة بالضيف، بما في ذلك RSP الخاص بالضيف، إضافةً إلى أعلام الضيف المُحزَّمة. يتباين تخطيطه حسب بناء الحامي (protector build)، لذا يُحدَّد موقعه عبر مراقبة سلوك الحفظ/الاستعادة في الآلة الافتراضية بدلًا من افتراضه — يُبلِّغ probe.ctx_map عن التعيين المقيس عند عمليات التخزين، وكل قائمة مُصدَّرة تُحيط أي وصول إلى السياق بإزاحة خانته.
تدفّق التحكم الشرطي هو حساب توزيعي: يستخرج المعالج (handler) علمًا أو محمولًا (predicate) من الضيف، ويستخدمه لاختيار عدّاد البرنامج الافتراضي التالي، ويحسب عنوان المعالج التالي. يُقيِّم مُزيل الافتراضية (devirtualizer) ذلك رمزيًا ويتفرّع عندما يعتمد الاختيار على قيمة ضيف غير محسومة. هوية الكتلة هي الزوج (handler, vip)، وليس المعالج وحده.
يرتكز الاسترداد على قاعدة واحدة: حدِّد القيمة بما فعله الجهاز، لا بالاسم الذي أعطاه إياه تحليلٌ ما. يُنقَل انتشار السجلات عبر الكتل في القائمة كتعليق توضيحي، وليس كدليل أبدًا — تبديل كل رقم إصدار سجل في قائمة يجب ألا يغيّر البنية المُحلَّلة.