
Genera macchine virtuali polimorfiche e indipendenti dalla posizione (PIVMs) da shellcode x86/x64 arbitrario.
Leggi l'articolo di ricerca (scritto per la 1.0.0).
mkPIVM è un virtualizzatore di shellcode polimorfo e indipendente dalla posizione per Windows x86 e x64 (Linux presto).
Dagli in pasto shellcode grezzo. Emette un altro blob grezzo: una piccola macchina virtuale che interpreta una versione "lifted" e cifrata a riposo delle tue istruzioni originali. L'output è esso stesso codice indipendente dalla posizione e gira ovunque girerebbe lo shellcode originale, da un loader a thread remoto a un detour in una code cave. Ogni parametro per-seed varia in modo indipendente: famiglia di cifrari, layout degli slot dei registri, permutazione opcode-gestore, topologia del dispatcher, pattern di gadget spazzatura, punti di inserimento dell'offuscamento IR. Due build dallo stesso input condividono meno di un centinaio di byte coincidenti su decine di kilobyte.
Perché: lo shellcode nativo è banale a livello di firme. Avvolgerlo in una VM per istanza con un cifrario per istanza non lascia nulla di utile a riposo, e il lifting delle istruzioni in bytecode aggiunge un'altra barriera tra i byte su disco e qualsiasi disassemblatore che conosca l'aspetto di x86. Per quanto posso dire da una rassegna della letteratura, nessuno strumento pubblico offre esattamente questa pipeline: PIC grezzo in ingresso, PIC di VM polimorfo grezzo in uscita. Quindi, l'ho menzionato nell'articolo di ricerca che lo richiedeva. A essere onesti, se ho ragione sul fatto che nessuno lo abbia fatto (pubblicamente) prima, e ne sono abbastanza sicuro, sono sorpreso. Ciononostante, goditelo.
mkpivm.exe shellcode.bin --arch x64 -o out.bin
La tua PIVM è calda e pronta. Questo è il percorso più semplice. Diverse altre modalità variano in base a quanto aggressivamente le istruzioni originali vengono virtualizzate, se l'output è un blob autonomo o un PE patchato, e se il lift viene eseguito o meno.
# Vetrina
Ho le prove. Puoi vedere un video di mkPIVM in azione qui sotto, in cui virtualizza completamente uno stager di Meterpreter (vanilla, tra l'altro), inietta in explorer.exe e catturiamo una callback. Ovviamente questo è solo un esempio, e mkPIVM può essere applicato a molto di più, supponendo che le istruzioni nello shellcode siano supportate. Se non lo sono, apri una Issue, mandami lo shellcode, ci penso io.
Guardalo [qui](https://github.com/D7EAD/mkPIVM/raw/refs/heads/main/media/mkpivm-showcase.mp4). Ospitato in ./media, purtroppo non posso incorporarlo.
Ecco il report VirusTotal per quell'esatto campione virtualizzato (al 06/04/2026).
<img src="https://assets.kitploit.com/production/public/readmes/7466/3f4a6dbe2777c02fb32f71749a5b1fa2aa324cb0dac2f77d5681bbe3a4e17a81.png">
...e la versione impacchettata, nemmeno virtualizzata, con un'entropia notevolmente più alta.
<img src="https://assets.kitploit.com/production/public/readmes/7466/47f68372e31a2c24fa49eb71648adbdf8944977db90d645df337125c1bdfa8e3.png">
Ecco, per confronto, i risultati di un normale beacon Cobalt Strike.
<img src="https://assets.kitploit.com/production/public/readmes/7466/76e2107589cd2b1b65c846a671aed173aa5e6d89630f5e9d283d99c546a2d566.png">
È stata prestata attenzione alla telemetria dell'entropia dell'output di questo strumento, che produce shellcode con entropia inferiore a quella delle tipiche DLL WinAPI di Windows (al di fuori della modalità packing), come ntdll.dll o kernel32.dll. Il confronto dell'entropia è il seguente...
| File | Byte | Entropia |
|------|-------|---------|
| `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** |
## Modalità a colpo d'occhio
| Modalità | Flag | Cosa cambia |
|------|-------|--------------|
| Default | nessuno | Esegue il lift dell'intero input. Tutto virtualizzato. |
| Packer | `--pack` | Niente lift. Avvolge l'input come dati cifrati, decifra a runtime e salta dentro. |
| Hybrid | `--ranges A:B,...` | Esegue il lift solo degli intervalli di byte scelti. Il resto rimane nativo. |
| Stacked | `--pack --ranges A:B` | Costruisce il blob ibrido e poi lo impacchetta. |
| Detour | `--embed-into PE --at RVA` | Prende un blob pre-costruito, lo incorpora in un PE e applica una patch jmp all'RVA scelta. |
| Scan | `--scan` | Stampa i candidati `--ranges` ammissibili dalla CFG dell'input, quindi esce. |
| RX | `--rx` | Blob PAGE_EXECUTE_READ. La data island rimane cifrata a riposo; il PEB walker interno al blob risolve VirtualProtect e decifra in-place in state_init. |
| RX w/ Loader | `--rx --rx-loader-vp` | Come `--rx`, ma il tuo loader passa VirtualProtect come primo argomento del blob. Nessun PEB walker. |
Ogni modalità rispetta `--seed`, `--arch`, `--input-format` e `--format`. Vedi le sezioni relative a ciascuna modalità qui sotto per la pipeline di build e il flusso a runtime.
## Virtualizzazione predefinita
Il lifter attraversa l'intera CFG e traduce ogni istruzione in un IR personalizzato. L'IR passa attraverso due passaggi di offuscamento, poi attraverso codec che codificano ogni istruzione nella forma bytecode per seed. La tabella dei blocchi, la tabella degli handler e la data island sono cifrate con lo stesso stream cipher per byte del bytecode. A runtime, il prologo decifra queste tre regioni in place e il loop del dispatcher recupera i byte del bytecode uno alla volta, decifrandoli e inviandoli a un handler che svolge il lavoro.
### Pipeline di build
Passaggi che la build esegue per trasformare lo shellcode grezzo nel blob virtualizzato emesso, dall'inizio alla fine. _Tutti i grafici qui sotto si riferiscono alla release 1.0.0; da allora sono cambiati, ma l'idea è la stessa._```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]