
توليد آلات افتراضية متعددة الأشكال ومستقلة عن الموقع (PIVMs) من شيل كود x86/x64 عشوائي.
اقرأ البحث (مكتوب للإصدار 1.0.0).
mkPIVM هي محاكي افتراضي متعدد الأشكال ومستقل عن الموضع لشل كود ويندوز x86 و x64 (قريبًا لينكس).
قم بتغذيته بشل كود خام. ينتج كتلة خام أخرى: جهاز افتراضي صغير يفسر نسخة مرتفعة ومشفرة عند السكون من تعليماتك الأصلية. الناتج هو كود مستقل عن الموضع بذاته ويعمل في أي مكان يمكن لشل كود الأصلي العمل فيه، من محمل خيوط بعيد إلى منعطف كهف كود. كل مقبض لكل بذرة يختلف بشكل مستقل: عائلة التشفير، تخطيط فتحات السجلات، تبديل opcode إلى المعالج، طوبولوجيا المرسل، نمط القمامة، نقاط إدخال إخفاء IR. بنائان من نفس المدخلات يشتركان في أقل من مئة بايت عشوائي من عشرات الكيلوبايتات.
لماذا: شل كود الأصلي سهل التوقيع. تغليفه في جهاز افتراضي لكل حالة مع تشفير لكل حالة لا يترك شيئًا مفيدًا عند السكون، ورفع التعليمات إلى بايت كود يضع جدارًا آخر بين بايتات القرص وأي مفكك يعرف شكل x86. على حد علمي من مسح الأدبيات، لا توجد أداة عامة تشحن هذا المسار تمامًا: PIC خام في، PIC خام متعدد الأشكال للجهاز الافتراضي خارج. لذا، ذكرت ذلك في البحث الذي تطلبه. بصراحة، إذا كنت على صواب بأن لا أحد فعل هذا (علنًا) من قبل، وأنا واثق جدًا، أنا مندهش. على أي حال، استمتع.
mkpivm.exe shellcode.bin --arch x64 -o out.bin
جهاز PIVM جاهز وساخن. هذا هو أبسط مسار. تختلف عدة أوضاع أخرى في مدى عدوانية افتراضية التعليمات الأصلية، وما إذا كان الناتج عبارة عن كتلة مستقلة أو PE معدّل، وما إذا كان الرفع يعمل على الإطلاق.
# عرض توضيحي
لدي الأدلة. يمكنك مشاهدة فيديو لـ mkPIVM أثناء العمل أدناه، وهو يقوم بافتراضية كاملة لمخزن Meterpreter (عادي بالمناسبة)، ويحقنه في explorer.exe، ونلتقط رد الاتصال. بالطبع هذا مجرد مثال، ويمكن تطبيق mkPIVM على أكثر من ذلك بكثير، بافتراض أن التعليمات الموجودة في الشيل كود مدعومة. إذا لم تكن مدعومة، افتح مشكلة، أرسل لي الشيل كود، أنا معك.
شاهده [هنا](https://github.com/D7EAD/mkPIVM/raw/refs/heads/main/media/mkpivm-showcase.mp4). مستضاف في ./media، للأسف لا يمكن تضمينه.
إليك تقرير VirusTotal لهذه العينة الافتراضية بالضبط (حتى 06/04/2026).
<img src="https://assets.kitploit.com/production/public/readmes/7466/3f4a6dbe2777c02fb32f71749a5b1fa2aa324cb0dac2f77d5681bbe3a4e17a81.png">
...والنسخة المضغوطة، غير الافتراضية، ذات إنتروبيا أعلى بشكل ملحوظ.
<img src="https://assets.kitploit.com/production/public/readmes/7466/47f68372e31a2c24fa49eb71648adbdf8944977db90d645df337125c1bdfa8e3.png">
إليك نتائج إشارة Cobalt Strike عادية للمقارنة.
<img src="https://assets.kitploit.com/production/public/readmes/7466/76e2107589cd2b1b65c846a671aed173aa5e6d89630f5e9d283d99c546a2d566.png">
تم الاهتمام الدقيق بإنتروبيا مخرجات هذه الأداة، مما ينتج شيل كود بإنتروبيا أقل من مكتبات Windows WinAPI DLL النموذجية (خارج وضع التعبئة)، مثل ntdll.dll أو kernel32.dll. مقارنة الإنتروبيا تقريبًا...
| الملف | البايت | الإنتروبيا |
|------|-------|---------|
| `p_m64.bin` | 3,969 | **7.1181** |
| `msvcrt.dll` | 699,888 | 6.5319 |
| `wininet.dll` | 2,724,528 | 6.4934 |
| `shell32.dll` | 7,839,992 | 6.3639 |
| `kernel32.dll` | 836,232 | 6.3597 |
| `crypt32.dll` | 1,538,632 | 6.3010 |
| `rpcrt4.dll` | 1,162,672 | 6.2405 |
| `ntdll.dll` | 2,522,104 | 6.1934 |
| `v.bin` | 29,229 | **6.0442** |
## نظرة سريعة على الأوضاع
| الوضع | العلم | التغييرات |
|------|-------|--------------|
| Default | لا شيء | رفع المدخلات بالكامل. كل شيء ظاهري. |
| Packer | `--pack` | لا ترفع. لفّ المدخلات كبيانات مشفرة، فك التشفير في وقت التشغيل، اقفز إليها. |
| Hybrid | `--ranges A:B,...` | ارفع نطاقات البايت المختارة فقط. الباقي يبقى أصليًا. |
| Stacked | `--pack --ranges A:B` | أنشئ الكتلة الهجينة، ثم لفّها بالتعبئة. |
| Detour | `--embed-into PE --at RVA` | خذ كتلة مبنية مسبقًا، ضعها في PE، وصّل قفزة jmp في RVA المختارة. |
| Scan | `--scan` | اطبع مرشحي `--ranges` من CFG للمدخلات، ثم اخرج. |
| RX | `--rx` | كتلة PAGE_EXECUTE_READ. تبقى جزيرة البيانات مشفرة عند السكون؛ محلل PEB في الكتلة يحل VirtualProtect، يفك التشفير في مكانه عند state_init. |
| RX مع محمّل | `--rx --rx-loader-vp` | مثل `--rx` ولكن المحمّل يمرر VirtualProtect كأول وسيط للكتلة. لا محلل PEB. |
كل وضع يحترم `--seed` و `--arch` و `--input-format` و `--format`. انظر أقسام كل وضع أدناه لخط أنابيب البناء وتدفق وقت التشغيل.
## الافتراضية الافتراضية
يمر الرافع عبر CFG بأكمله ويخفض كل تعليمة إلى IR مخصص. يمر IR عبر تمريري تشويش، ثم عبر برامج ترميز تشفر كل تعليمة إلى شكل البايت كود الخاص بالبذرة. يتم تشفير جدول الكتل وجدول المعالجات وجزيرة البيانات بنفس تشفير الدفق لكل بايت مثل البايت كود. في وقت التشغيل، يفك المقدمة تلك المناطق الثلاثة في مكانها، وتقوم حلقة المرسل بجلب بايتات البايت كود واحدًا تلو الآخر، وفك التشفير والإرسال إلى معالج يقوم بالعمل.
### خط أنابيب البناء
الخطوات التي يقوم بها البناء لتحويل الشيل كود الخام إلى الكتلة الافتراضية المنبعثة، من البداية إلى النهاية. _جميع الرسوم البيانية أدناه تنطبق على الإصدار 1.0.0، وقد تغيرت منذ ذلك الحين، لكن الفكرة نفسها._```mermaid
flowchart TB
A[shellcode.bin] --> CFG[CFGBuilder: identify blocks via Zydis disasm + recursive descent]
SEED[seed u64] --> VMC[VMConfig: pick cipher kind, reg perm, opcode map, dispatcher topology]
CFG --> LIFT[LifterRegistry: lower each block to IR]
LIFT --> RBT[resolve_branch_targets: link BR_CC/BR/CALL_VM/LOOP_DEC to target_block_id; synthesize JMP_NATIVE block for any out-of-range jcc]
RBT --> OBF1[obfuscate_ir_dead_inject: 20% per insn-gap, IMM Tmp2/Tmp3 random]
OBF1 --> OBF2[obfuscate_ir_opaque_predicates: 25% per block, split block with IMM Tmp3=0 + TEST + BR_CC NZ random_block, never taken]
OBF2 --> ENC[BytecodeBuilder: each codec emits its variant for this seed]
ENC --> CDATA[compact data island: bytes not covered by any CFG block]
CDATA --> PROMO[promote LEA-fixup target VAs back into data island if CFG put them in code]
ENC --> BTAB[build block table: va_off to bytecode_off pairs]
PROMO --> ECIPH[encrypt data island with cipher_init]
BTAB --> ECIPH2[encrypt block table with cipher_init]
ENC --> ECIPH3[encrypt bytecode per-block with cipher_init reset at each block start]
VMC --> STUB[VMCodeGen::emit_full: prologue, state init, dispatcher tail, handlers, sbox_inv, exit handler]
STUB --> HTAB[handler table: 256 entries, each a 32-bit offset from handler_base; encrypted at rest]
ECIPH --> ASM[finalize: stub + trampolines + sbox_inv + bytecode + data island + block table]
ECIPH2 --> ASM
ECIPH3 --> ASM
HTAB --> ASM
ASM --> OUT[out.bin]
ما تفعله البلوب المُنشأ عندما يستدعيه مُحمّل المضيف عند البايت 0، بدءًا من المقدمة مرورًا بجلب المُوزّع ومسارات الإنهاء المختلفة.```mermaid flowchart TB ENTRY[blob entry at byte 0] --> PROL[prologue: push NV regs in seed-shuffled order, pushfq, allocate frame, lea state_ptr] PROL --> SI[emit_state_init body, gated by init_flag byte] SI --> ZERO[zero VM regs via seed-permuted xor source] ZERO --> CINIT[set cipher_state register, store cipher_init at cipher_extra+48] CINIT --> SBOX[copy sbox_inv table from blob to cipher_extra+256] SBOX --> NONCE[derive runtime nonce: rdtsc XOR cipher_init, store at cipher_extra+80] NONCE --> DDI[decrypt data island in place via emit_fetch_byte_dec loop] DDI --> DBT[decrypt block table in place] DBT --> DHT[decrypt handler table in place] DHT --> XHT[XOR each handler table entry with the runtime nonce so memory image is process-variant] XHT --> SET_INIT[set init_flag = 1 to gate subsequent vm_entry invocations] SET_INIT --> DISP[dispatcher tail] DISP --> FB[fetch one byte via emit_fetch_byte_dec, decrypts using cipher_state] FB --> HLU["load handler offset: mov scratch_b_32, [handler_base + op*4]"] HLU --> UXOR["xor scratch_b_32, [state_ptr + cipher_extra + runtime_nonce_off]"] UXOR --> SXD[movsxd to 64 bit, lea handler_addr = handler_base + offset, jmp handler] SXD --> H[handler body: fetch operands via stream cipher, perform op, advance state] H --> NEXT{terminator?} NEXT -- no --> DISP NEXT -- BR/BR_CC --> CRESET[cipher_reset to cipher_init, advance ip to target block] CRESET --> DISP NEXT -- CALL_VM --> CRESET NEXT -- JMP_NATIVE imm --> MARSH["marshal VM regs to host regs, rsp = VM_RSP, jmp [target_slot]"] NEXT -- CALL_NATIVE --> PUSHTR[push trampoline addr to VM_RSP, marshal, jmp target] NEXT -- RET_VM --> POPSS[pop shadow stack, jmp to popped trampoline addr] POPSS --> CRESET NEXT -- exit_handler reached --> EPI[restore NV regs, ret to caller of vm_entry]
خدعة nonce وقت التشغيل هي الحركة المضادة لمسح الذاكرة الرئيسية: جدول المعالجات موجود في الذاكرة بعملية XOR مع قيمة مستمدة من `rdtsc XOR cipher_init`، لذا فإن عمليتين تحملان نفس الكتلة ستكون لديهما جداول معالجات مختلفة بايتًا. يقوم الموزع بإزالة القناع في وقت البحث باستخدام XOR إضافي واحد.
## وضع المُعبِّئ: `--pack`
المقايضة المعاكسة. لا يعمل المُرفِّع. يتم تخزين الشيلكود الأصلي في جزيرة البيانات مشفرًا، ويكون IR هو تعليمة اصطناعية واحدة `JMP_NATIVE imm=0`. يتجاوز المقدمة عمدًا فك تشفير جزيرة البيانات الذي يقوم به الوضع الافتراضي بشكل استباقي. بدلاً من ذلك، في المرة الأولى التي يُطلق فيها معالج JMP_NATIVE الوحيد، يقوم بفك تشفير جزيرة البيانات في مكانها، ويعين بايت علامة، ثم ينقل التحكم إلى البايت 0 من الشيلكود العادي الآن. يتم تشغيل الشيلكود محليًا من هناك.
يعمل على أي شيلكود، بغض النظر عن تغطية المُرفِّع. مفيد لـ cobalt stageless، والحمولات الغريبة الثقيلة باستدعاءات النظام، وأي شيء كبير جدًا أو غريب بحيث لا يمكن افتراضية. يفقد الدفاع الافتراضي لكل تعليمة ولكن يحافظ على تعدد الأشكال الكامل للجهاز الظاهري (VM Polymorphism) لكل بذرة على الغلاف.
### خط البناء
كيف يغلف `--pack` الشيلكود الأصلي كجزيرة بيانات مشفرة خلف برنامج IR تركيبي بتعليمة واحدة.```mermaid
flowchart TB
A[shellcode.bin] --> SKIP[skip CFG build, skip lift]
SEED[seed u64] --> VMC[VMConfig polymorphism axes as in default mode]
SKIP --> SYN[synthesize 1-insn IRProgram: one block with JMP_NATIVE Imm 0 Width Q]
SYN --> SETF[VMConfig::set_pack_mode true, set_data_island_size shellcode.size]
SETF --> ENC[BytecodeBuilder encodes the 1 insn into ~35 bytes]
ENC --> CDATA[data island = entire shellcode bytes verbatim]
CDATA --> ECIPH[encrypt shellcode bytes with cipher_init as the data island]
ENC --> ECIPH2[encrypt bytecode + block table + handler table]
VMC --> STUB[VMCodeGen::emit_full]
STUB --> FLAG[data_island_init_flag byte starts at 0 because pack_mode true]
STUB --> ASM[finalize layout]
ECIPH --> ASM
ECIPH2 --> ASM
FLAG --> ASM
ASM --> OUT[out.bin]
ما يفعله غلاف الحزمة عند الدخول الأول، بما في ذلك فك التشفير الكسول المُحكَم لجزيرة البيانات و JMP_NATIVE الاصطناعي الوحيد الذي يُسلّم التحكم إلى شيلكود النص العادي الآن.```mermaid flowchart TB ENTRY[blob entry] --> PROL[prologue + state init] PROL --> SKIP_DI[skip data island decrypt because data_island_size gated on pack_mode false at build] SKIP_DI --> DBT[decrypt block table in place] DBT --> DHT[decrypt handler table in place + runtime nonce XOR] DHT --> DISP[dispatcher fetches first opcode = JMP_NATIVE] DISP --> NH[emit_native_handler entry] NH --> GATE{data_island_init_flag == 0?} GATE -- yes --> SAVE[save cipher_state and ip to kPreDecryptCsOff/IpOff slots] SAVE --> RESET[reset cipher_state to cipher_init] RESET --> LOOP[decrypt data island in place byte by byte] LOOP --> RESTORE[restore cipher_state and ip from saved slots] RESTORE --> SETFLAG[set data_island_init_flag = 1] GATE -- no --> CONT[skip the decrypt body] SETFLAG --> CONT CONT --> FETCH_TGT[fetch JMP_NATIVE operand: tag = 0, imm = 0] FETCH_TGT --> COMPUTE[target = data_island_base + imm = start of decrypted shellcode] COMPUTE --> MARSH[marshal all VM regs to host regs, rsp = VM_RSP seeded with exit_handler] MARSH --> JMP["jmp [target_slot]"] JMP --> NATIVE[original shellcode runs natively] NATIVE --> RET{shellcode does ret?} RET -- yes --> POPSS[ret pops exit_handler from VM shadow stack] POPSS --> EPI[exit_handler restores NV regs, rets to original caller] RET -- ExitProcess --> DEAD[process terminates]
The deferred data-island decrypt is pack-mode-only. In default and hybrid mode the lifted code does VM LOAD/STORE on data-island bytes mid-execution and needs them plaintext from the start, so the prologue handles it eagerly. In pack mode there is exactly one consumer of the data island, the native escape after the synthetic JMP_NATIVE, so the decrypt can wait until then.
## Hybrid mode: `--ranges A:B,C:D`
Targeted virtualization. Pick byte ranges in the input that should be lifted. Everything else stays as the original native bytes in the output. At each range start the lifter patches a 5-byte `jmp rel32` to a `vm_entry_K` stub appended after the native shellcode region. External native code may only re-enter a lifted range through the patched start byte. Mid-range bytes are int3-filled, so any native branch that targets a mid-range byte is rejected at scan time. Lifted code that exits a range to bytes outside it becomes `JMP_NATIVE` or `CALL_NATIVE`.
Use `--scan` first to find eligible candidates. Scan classifies ranges with gaps, with no `ret`-terminated block, with body shorter than 5 bytes, or with external native branches into mid-range bytes as near-miss rather than eligible.
### Build pipeline
How `--ranges` lifts only the chosen byte ranges and patches the native shellcode in place so control redirects into the appended VM entry stubs.```mermaid
flowchart TB
A[shellcode.bin] --> RP[parse_ranges from --ranges flag]
RP --> CFG[CFGBuilder with set_lifted_ranges restriction]
CFG --> LIFT[lift_program: only blocks whose start_va is inside any range]
LIFT --> XCG[branches/calls leaving the range become JMP_NATIVE/CALL_NATIVE]
XCG --> OBF[IR obfuscation passes same as default]
OBF --> ENC[encode bytecode]
ENC --> BTAB[block table for RET_VM lookup]
SEED[seed u64] --> VMC[VMConfig]
VMC --> STUB[VMCodeGen::emit_range_mode: one vm_entry_K per range + dispatcher + handlers]
STUB --> NSEC[start with verbatim copy of the native shellcode bytes]
NSEC --> PATCH[at each range start, write 5-byte jmp rel32 to vm_entry_K]
PATCH --> INT3[fill remaining bytes of the displaced run with int3]
INT3 --> APPEND[append: VM stub + sbox_inv + bytecode + data island + block table]
APPEND --> OUT[out.bin]
تدفق التحكم عبر كتلة مختلطة من التعليمات البرمجية الأصلية والآلة الافتراضية، بما في ذلك الدخول إلى إرسال الآلة الافتراضية عندما تصادف التعليمات البرمجية الأصلية بداية نطاق مُصَحَّح، ومسارات الهروب الأصلية المستخدمة عندما تتفرع التعليمات البرمجية المرفوعة للخارج.```mermaid flowchart TB ENTRY[blob byte 0 = native shellcode prologue] --> NAT[native shellcode runs] NAT --> HIT{control reaches a patched range start?} HIT -- no --> NAT HIT -- yes --> JMP[jmp rel32 to vm_entry_K] JMP --> RPROL[range prologue: push NV regs, allocate frame, lea state_ptr] RPROL --> MARSHIN[marshal host volatile regs to VM slots, NV regs preserved by Win64 ABI] MARSHIN --> SI[state init gated by init_flag for first-time decrypts] SI --> DISP[dispatcher loop] DISP --> HND[handler] HND --> NXT{terminator?} NXT -- BR/BR_CC inside range --> DISP NXT -- range ret reached --> CLEANUP[restore NV regs, pop frame, ret pops native retaddr from host stack] NXT -- JMP_NATIVE to byte outside range --> MIDEXIT[mid-exec cleanup: overwrite caller retaddr slot with target, jmp to exit_handler] NXT -- CALL_NATIVE to API --> APITAIL[push trampoline addr to VM_RSP, jmp resolved API] CLEANUP --> NAT MIDEXIT --> EH[exit_handler unwinds NV pushes, ret lands at the target we wrote] EH --> NAT APITAIL --> APIRET[API rets to trampoline in stub which resumes VM dispatch] APIRET --> DISP
النطاقات بنمط الكوروتين، تلك التي لا تحتوي على `ret` وتخرج عبر `tail-jmp`، يتم توثيقها عبر `--coroutines` لمخرجات `--scan`. العلامة لا تغير توليد الكود في الوقت الحالي. وضع النطاق هو الأنسب للدوال الورقية ذاتية الاحتواء. لا يمكن رفع الدوال المساعدة التي تعتمد على حالة سجلات محددة يقدمها المستدعي بشكل منفرد؛ فتنتهي واجهة البرمجة باستدعاءات بوسائط غير صالحة.
## وضع التكديس: `--pack --ranges A:B,C:D`
قم بتشغيل البناء المهجّن، ثم لف النتيجة بحزمة (pack-wrap). الجهاز الظاهري الخارجي يفك تشفير الكتلة المهجّنة الداخلية في مكانها ويقفز إلى البايت 0 منها. من هناك، يستمر التنفيذ تمامًا كما في الوضع المهجّن المستقل، باستثناء أن الكتلة بأكملها بما في ذلك كود البايت للنطاق المختار مشفرة في حالة السكون. يتوافق الطبقتان بشكل نظيف لأن الكتلة الداخلية هي منطقة PIC ذاتية الاحتواء.
### سلسلة البناء
كيف يستدعي وضع التكديس أداة التغليف (packager) بشكل متكرر: بناء داخلي `--ranges` أولاً، ثم لف خارجي `--pack` لذلك البناء.```mermaid
flowchart TB
A[shellcode.bin] --> INNER[invoke package_shellcode recursively in range-only mode]
INNER --> RBLOB[range-mode bytes in memory]
RBLOB --> WRAP[invoke package_shellcode again in pack-only mode with RBLOB as input]
WRAP --> OUT[out.bin: outer pack VM wrapping the inner range-mode blob]
كيف يسلم جهاز VM للحزمة الخارجية التحكم إلى جهاز VM في وضع النطاق الداخلي، كل منهما يعمل على إطار VMState مستقل خاص به.```mermaid flowchart TB E[blob entry] --> OPROL[outer pack prologue + state init] OPROL --> ODISP[outer dispatcher fetches synthetic JMP_NATIVE] ODISP --> LAZY[lazy decrypt of inner range-mode blob via deferred data-island decrypt] LAZY --> JN[outer JMP_NATIVE imm 0 sets target = inner blob byte 0] JN --> INAT[inner native shellcode runs] INAT --> IHIT{inner patched range start hit?} IHIT -- yes --> IVM[inner range vm_entry_K] IHIT -- no --> INAT IVM --> IRDISP[inner dispatcher loop, separate VMState frame] IRDISP --> INAT
لا تشارك VM الداخلية وVM الخارجية أي حالة. هما جهازان ظاهريان مستقلان يحدث أن يعيشا في نفس blob. وظيفة الجهاز الظاهري الخارجي الوحيدة هي حجز الجهاز الظاهري الداخلي خلف طبقة فك تشفير.
## وضع الالتفاف: `--embed-into target.exe --at RVA`
شكل مختلف عن الآخرين. الإدخال هو shellcode خام، تقليديًا هو VM blob تم إصداره بالفعل بواسطة mkPIVM في وضع آخر، على الرغم من أن أي بايتات PIC ستعمل. الإخراج هو PE معدل.
تقوم الأداة بتحليل `target.exe`، وتحديد موقع RVA المختار في قسم قابل للتنفيذ، وتفكيك تعليمات كافية هناك لتغطية 5 بايتات، وترفض إذا كانت التشغيلة المنزاحة تحتوي على عنونة نسبية لـ RIP أو تدفق تحكم نسبي لا يمكنه البقاء بعد النقل، ثم تضيف قسم RWX جديد يحتوي على wrapper بالإضافة إلى VM blob. يحافظ wrapper على حالة المتصل، وينتقل إلى VM blob، ويستعيد الحالة، وينفذ البايتات الأصلية المنزاحة، ويقفز عائدًا إلى البايت بعد التصحيح. يشير `jmp rel32` بطول 5 بايتات عند RVA المختار إلى wrapper.
وضعان فرعيان لكيفية نقل wrapper إلى VM:
### الوضع الفرعي المترابط، الافتراضي
يتبع رسمان بيانيان. الأول هو خط أنابيب تصحيح PE في وقت البناء الذي يحقن قسم wrapper، ويصلح المراجع، ويكتب jmp ذو 5 بايتات عند RVA المختار. الثاني هو تدفق التحكم في وقت التشغيل عندما تصل العملية المضيفة في النهاية إلى RVA ذلك.```mermaid
flowchart TB
BLOB[vm_blob.bin pre-built from any other mode] --> READ[read target.exe bytes]
TGT[target.exe] --> READ
READ --> PEHDR[parse DOS header, NT headers, sections, OptionalHeader, BASERELOC dir]
PEHDR --> ARCHCHK[arch from PE32/PE32+ magic must match blob arch]
ARCHCHK --> LOC[locate RVA inside an executable section]
LOC --> DA[Zydis disassemble at RVA, accumulate insns until total length >= 5]
DA --> VALID{any displaced insn has RIP-relative mem operand or is a rel32 branch?}
VALID -- yes --> FAIL[error, pick a different RVA]
VALID -- no --> IATSCAN[scan IMAGE_DIRECTORY_ENTRY_IMPORT for kernel32!CreateThread]
IATSCAN --> IATFOUND{found?}
IATFOUND -- no --> FAIL2[error, suggest --detour-inline or different target]
IATFOUND -- yes --> EMITW[emit threaded wrapper bytes]
EMITW --> APPEND[concatenate wrapper + vm_blob into new section content]
APPEND --> ARCHBR{arch?}
ARCHBR -- x64 --> X64FIX[fix wrapper rel32s: lea r8 to vm_blob; call qword ptr rip+iat_disp32]
ARCHBR -- x86 --> X86FIX[fix wrapper abs32s: push vm_blob_va; call dword ptr iat_va; then append combined IMAGE_BASE_RELOCATION table with new HIGHLOW entries for those abs32s]
X64FIX --> RJMP[compute jmp_rel32 from wrapper-tail back to RVA + displaced_len]
X86FIX --> RJMP
RJMP --> SEC[allocate next aligned VA + raw offset, write IMAGE_SECTION_HEADER with RWX + CNT_CODE]
SEC --> BUMP[bump NumberOfSections, SizeOfImage, zero CheckSum]
BUMP --> X86RELOC{x86?}
X86RELOC -- yes --> RDIR[update DataDirectory BASERELOC to new combined table]
X86RELOC -- no --> WJ[write 5-byte jmp rel32 at RVA, NOP-fill remaining displaced bytes]
RDIR --> WJ
WJ --> WRITE[serialize patched bytes to output path]
WRITE --> OUT[patched.exe]
[SSID]: يحدد SSID (اسم الشبكة) لنقطة الوصول.
[Channel]: يحدد رقم القناة لنقطة الوصول (اختياري، يتم تحديده تلقائيًا بناءً على الواجهة إذا لم يتم توفيره).
-c، --capture-file: يحدد ملف (ملفات) الالتقاط لاستخدامها في تحليل حركة البيانات. يُستخدم هذا بالاقتران مع هجوم إلغاء المصادقة.
-e، --enterprise: ينفذ هجومًا مؤسسيًا (WPA/WPA2-Enterprise). يتطلب معاملات إضافية لانتحال خادم RADIUS والتقاط بيانات اعتماد المؤسسة.
-g، --generate-cert: يولد شهادة موقعة ذاتيًا لاستخدامها مع الهجوم المؤسسي. هذا خيار مناسب للاختبار أو البيئات المعزولة.
-p، --pcap: يحفظ بيانات المصافحة والحركة الملتقطة إلى ملف PCAP محدد. هذا مفيد للتحليل دون اتصال وكسر التشفير باستخدام أدوات مثل Hashcat أو John the Ripper.
--crack: يحاول كسر تشفير مصافحة WPA/WPA2 الملتقطة باستخدام ملف قائمة كلمات محدد.
الوضع الفرعي المُترابط هو الشكل المناسب للـ stagers والـ beacons. المضيف الرئيسي لا يحظر أبدًا. يرث مؤشر ترابط العامل أي دورة حياة يحتاجها الحمولة. إذا استدعى الحمولة `ExitProcess`، تموت العملية بأكملها، ولكن بالنسبة لحمولة غير مُنتهية مثل كل beacon من نوع C2، يعمل المضيف إلى الأبد بالتوازي معها.
### الوضع الفرعي المضمن: `--detour-inline`
نفس بنية الغلاف ولكن الغلاف يقوم بـ `call vm_blob` مباشرة بدلاً من `CreateThread`. يتوقف مؤشر ترابط المضيف الرئيسي حتى يعود VM. مفيد عندما يفتقر الهدف إلى `CreateThread` في IAT الخاص به، أو كخيار بديل عندما لا يمكن تطبيق مسار إعادة التوطين المُترابط (threaded base-reloc) على هدف معين.```mermaid
flowchart TB
HOST[host main reaches patched RVA] --> RJMP[jmp rel32 to wrapper]
RJMP --> SAVE[save flags + volatile regs + align]
SAVE --> CALL[call rel32 vm_blob synchronously]
CALL --> BLOCK[host main thread blocks here for the duration]
BLOCK --> RET{vm_blob returns?}
RET -- via ret --> REST[restore rsp, pop volatiles, popfq]
RET -- via ExitProcess in payload --> DEAD[process terminates, no further code runs]
REST --> DISP[execute displaced original bytes]
DISP --> RJMP2[jmp rel32 back to RVA + N_displaced]
RJMP2 --> HOST_CONT[host continues]
--scanلا يوجد ملف إخراج. يقوم ببناء CFG من شيلكود الإدخال ويطبع مرشحات --ranges إلى stderr. يتم تصنيف كل مرشح على أنه مؤهل، أو كوروتين، أو قريب من الهدف، أو داخلي، حيث يعني داخلي أنه محجوب بواسطة مرشح مؤهل أكبر يغطيه بالفعل. استخدم هذا لاختيار وسيطات النطاقات قبل استدعاء الأداة مرة أخرى في الوضع المختلط.```mermaid
flowchart TB
INP[shellcode.bin] --> CFGB[CFGBuilder + recursive descent over the whole input]
CFGB --> ITER[for each block that is a call_target or jmp_target]
ITER --> BFS[BFS the reachable subgraph from this entry]
BFS --> SPAN[compute min_va..max_va span]
SPAN --> CONT_CHK{every byte in span belongs to a visited block or to a known insn boundary?}
CONT_CHK -- yes --> RET_CHK{any visited block ends with ret?}
CONT_CHK -- no --> NM1[near-miss: fragmented or mid-fn data]
RET_CHK -- yes --> EXT_CHK{any non-visited block has a successor pointing inside the span?}
RET_CHK -- no --> CORO[coroutine candidate, no ret terminator]
EXT_CHK -- yes --> NM2[near-miss: native branches to mid-range bytes]
EXT_CHK -- no --> SIZE_CHK{span >= 5 bytes for the entry patch?}
SIZE_CHK -- yes --> ELIGIBLE[eligible: prints with --ranges hint]
SIZE_CHK -- no --> NM3[near-miss: body too short]
ELIGIBLE --> DEDUP[shadow dedupe: drop entries whose reachable set is fully contained in an earlier eligible's reachable set]
CORO --> DEDUP
NM1 --> DEDUP
NM2 --> DEDUP
NM3 --> DEDUP
DEDUP --> PRINT[stderr table sorted by category]
PRINT --> END[exit 0]
## محاور تعدد الأشكال لكل بذرة
مدرجة تقريبًا حسب مقدار تأثيرها على التوقيع الثابت.
* عائلة التشفير. واحدة من ARX، LcgSub، SBoxAdd، FeistelByte. تختار كلاً من تشفير البايت كود المستخدم في وقت البناء وفك التشفير المضمن الصادر في مسار جلب الموزع. التشفير وفك التشفير متطابقان بالبناء.
* تخطيط فتحات السجلات. يحتوي VMState على `reg_count` فتحة، بحجم 24 إلى 32 لكل بذرة. تحصل السجلات العامة المعمارية الـ16 بالإضافة إلى 4 سجلات مؤقتة على تبديل جديد لمؤشرات الفتحات كل بذرة.
* تعيين الأوبكود إلى المعالج. يتم تعيين بايت أوبكود عشوائي لكل عائلة ترميز لكل بذرة. يقوم جدول المعالجات الذي يحتوي على 256 مدخلاً بالفهرسة حسب الأوبكود؛ يشير البايت كود المشفر إلى الأوبكودات المعينة.
* طوبولوجيا الموزع. مترابط أو مركزي، يتم اختياره لكل بذرة.
* استراتيجية تحديد موقع المقدمة الذاتية. `call $+5; pop`, `lea reg, [rip+0]`، أو خلط jmp/call.
* السجلات المؤقتة للمعالجات. يقوم معالج BR_CC بتبديل أدواره المؤقتة الستة عبر المجموعة المتقلبة. يقوم معالج التخزين بنفس الشيء.
* كثافة الأدوات غير المرغوب فيها. من 0 إلى 3، تتحكم في إصدار القمامة بين المعالجات.
* قرارات تمرير التعتيم على IR. يستخدم كل من حقن IR الميت والمسندات المعتمة مولد أرقام عشوائية فرعي خاص بهما بحيث تكون قراراتهما حتمية لكل بذرة دون إزعاج المحاور الأخرى.
* الحالة الأولية لتشفير البايت كود. 64 بت عشوائية لكل بذرة.
## الحمولات المُختبرة
تم التحقق من صحة هذه الأداة ضد عدة عينات من Cobalt Strike وMSF وSliver والعديد من عينات شيل كود الأخرى.
## البناء
يتطلب Visual Studio 2022 وCMake 3.21 أو أحدث وvcpkg. يقوم مشروع CMake بسحب Zydis وfmt عبر وضع البيان الخاص بـ vcpkg.```
cmake -S . -B build
cmake --build build --config Release --target mkpivm
--pack هما المساران اللذان يُرسلان الإشارة الفعلية.--embed-into. يتم تصفير مجموع PE الاختباري.VirtualAlloc، والذي قد يكون توقيعًا أسوأ من صفحة RWX التي يستبدلها.--pack + --ranges.هذا شيء رائع جدًا، كن واقعيًا. إذا كنت ترغب في المساهمة بأفكار جديدة، أو إصلاح أخطائك الخاصة، أو تقديم Issues لأصلحها، وهكذا دواليك، فافعل.
--hashcat: (لم يتم تنفيذه بالكامل بعد) مخصص للتكامل مع Hashcat لكسر التشفير المعجل بوحدة معالجة الرسوميات.
--wordlist: يحدد المسار إلى ملف قائمة الكلمات المستخدم لكسر تشفير المصافحة.```mermaid
flowchart TB
HOST[host main reaches patched RVA] --> RJMP[jmp rel32 to wrapper in new section]
RJMP --> PUSH[pushfq, push all volatile regs and rbp]
PUSH --> SAVE_RSP[mov rbp, rsp; and rsp, -16; sub rsp, 0x38 for shadow + spill alignment]
SAVE_RSP --> ARGS[xor ecx,ecx; xor edx,edx; lea r8, vm_blob_rel32; xor r9d,r9d; spill 0 at rsp+0x20, 0 at rsp+0x28]
ARGS --> CT["call qword ptr [rip + CreateThread_iat_disp32]"]
CT --> WTHREAD[worker thread starts running vm_blob]
CT --> REST[mov rsp, rbp; pop volatiles; popfq]
REST --> DISP[execute the displaced original bytes verbatim]
DISP --> RJMP2[jmp rel32 back to RVA + N_displaced]
RJMP2 --> HOST_CONT[host main continues normally]
WTHREAD --> VMBODY[vm_blob runs concurrently: stager beacons, mbox shows dialog, whatever]