Skip to content
KitploitKITPLOIT
FerramentasBlog
Enviar
FerramentasBlog
Enviar

Ferramentas de Hacking, PenTest e Cibersegurança para o seu Arsenal de Segurança!

Kitploit é um diretório de ferramentas de hacking, cibersegurança e pentesting. Descubra as últimas atualizações de projetos para encontrar vulnerabilidades, analisar sistemas, automatizar testes e fortalecer sua segurança.

··Feeds·Contato·Privacidade·© 2026 Kitploit

Diretório de Ferramentas

Categorias

Ver todas as categorias
Loading categories
mkPIVM — Gere máquinas virtuais polimórficas e independentes de posição (PIVMs) a partir de shellcode x86/x64 arbitrário. | Kitploit
Ferramentas/GitHubGitHub/d7ead/mkpivm
Geração de PayloadsExploraçãoEngenharia ReversaShellcodeAnálise de MalwareRed Teaming
GitHubd7ead/mkpivm

mkPIVM

Gere máquinas virtuais polimórficas e independentes de posição (PIVMs) a partir de shellcode x86/x64 arbitrário.

Ver Repositório
409174há 26 diasRevisado pelo Kitploit

Mais Populares

Ver todos →

Descubra as ferramentas mais usadas pela nossa comunidade.

Explore todas as ferramentas

Navegue pela nossa coleção de ferramentas

Ver todas as ferramentas →
Compartilhar


Leia o artigo de pesquisa (escrito para a versão 1.0.0).

mkPIVM é um virtualizador de shellcode polimórfico independente de posição para Windows x86 e x64 (Linux em breve).

Alimente-o com shellcode bruto. Ele emite outro blob bruto: uma pequena máquina virtual que interpreta uma versão elevada e criptografada em repouso das suas instruções originais. A saída é, por si só, código independente de posição e é executada em qualquer lugar onde o shellcode original seria, desde um carregador de thread remota até um desvio de code cave. Cada botão por semente varia independentemente: família de cifras, layout de slots de registradores, permutação de opcode para handler, topologia do dispatcher, padrão de junk gadgets, pontos de inserção de obfuscação de IR. Duas compilações da mesma entrada compartilham menos de uma centena de bytes coincidentes em dezenas de kilobytes.

Por quê: shellcode nativo é trivial para assinaturas. Envolvê-lo em uma VM por instância com uma cifra por instância não deixa nada útil em repouso, e elevar as instruções para bytecode coloca outra barreira entre os bytes do disco e qualquer desmontador que conheça a aparência do x86. Pelo que posso dizer a partir de uma varredura na literatura, nenhuma ferramenta pública possui exatamente esse pipeline: PIC bruto de entrada, PIC de VM polimórfica bruto de saída. Então, mencionei isso no artigo de pesquisa que ele exigiu. Para ser honesto, se eu estiver certo sobre ninguém ter feito isso (publicamente) antes, e estou bastante confiante, estou surpreso. No entanto, aproveite.

Trabalhos Relacionados e Planos

  • O suporte para Linux será adicionado em breve.

Início rápido```

mkpivm.exe shellcode.bin --arch x64 -o out.bin

root@kitploit:~
Seu PIVM está quente e pronto. Esse é o caminho mais simples. Vários outros modos variam o quão agressivamente as instruções originais são virtualizadas, se a saída é um blob independente ou um PE patched, e se o lift é executado ou não.

# Demonstração

Tenho as provas. Você pode ver um vídeo do mkPIVM em ação abaixo, virtualizando completamente um stager do Meterpreter (vanilla, por sinal), injetando no explorer.exe, e nós capturando um callback. Claro que isso é apenas um exemplo, e o mkPIVM pode ser aplicado a muito mais, assumindo que as instruções no shellcode são suportadas. Se não forem, abra um Issue, me envie o shellcode, eu resolvo.

Veja [aqui](https://github.com/D7EAD/mkPIVM/raw/refs/heads/main/media/mkpivm-showcase.mp4). Hospedado em ./media, infelizmente não pode ser incorporado.

Aqui está o relatório do VirusTotal para exatamente aquela amostra virtualizada (em 06/04/2026).

<img src="https://assets.kitploit.com/production/public/readmes/7466/3f4a6dbe2777c02fb32f71749a5b1fa2aa324cb0dac2f77d5681bbe3a4e17a81.png">

...e a versão empacotada, nem mesmo virtualizada, entropia notavelmente maior.

<img src="https://assets.kitploit.com/production/public/readmes/7466/47f68372e31a2c24fa49eb71648adbdf8944977db90d645df337125c1bdfa8e3.png">

Aqui estão os resultados de um beacon normal do Cobalt Strike para comparação.

<img src="https://assets.kitploit.com/production/public/readmes/7466/76e2107589cd2b1b65c846a671aed173aa5e6d89630f5e9d283d99c546a2d566.png">

Foi dada atenção cuidadosa à telemetria de entropia da saída desta ferramenta, o que resulta em shellcode com entropia menor do que DLLs típicas do Windows WinAPI (fora do modo de empacotamento), como ntdll.dll ou kernel32.dll. A comparação de entropia é aproximadamente...

| Arquivo | Bytes | 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** |

## Modos de relance

| Modo | Flags | O que muda |
|------|-------|--------------|
| Default | none | Levanta toda a entrada. Tudo virtualizado. |
| Packer | `--pack` | Não levanta. Embrulha a entrada como dados criptografados, descriptografa em tempo de execução, salta para dentro. |
| Hybrid | `--ranges A:B,...` | Levanta apenas os intervalos de bytes escolhidos. O resto permanece nativo. |
| Stacked | `--pack --ranges A:B` | Constrói o blob híbrido, então o empacota. |
| Detour | `--embed-into PE --at RVA` | Pega um blob pré-construído, incorpora em um PE, aplica um patch jmp no RVA escolhido. |
| Scan | `--scan` | Imprime candidatos `--ranges` elegíveis do CFG da entrada, então sai. |
| RX | `--rx` | Blob PAGE_EXECUTE_READ. A ilha de dados permanece criptografada em repouso; o PEB walker dentro do blob resolve VirtualProtect, descriptografa in-place no state_init. |
| RX w/ Loader | `--rx --rx-loader-vp` | Como `--rx` mas seu loader passa VirtualProtect como o primeiro argumento do blob. Sem PEB walker. |

Cada modo respeita `--seed`, `--arch`, `--input-format` e `--format`. Veja as seções por modo abaixo para o pipeline de construção e o fluxo de execução.

## Virtualização padrão

O lifter percorre todo o CFG e baixa cada instrução para um IR personalizado. O IR passa por duas passagens de ofuscação, depois por codecs que codificam cada insn na forma de bytecode por semente. A tabela de blocos, a tabela de handlers e a ilha de dados são criptografadas com o mesmo cifrador de fluxo por byte que o bytecode. Em tempo de execução, o prólogo descriptografa essas três regiões in-place e o loop do dispatcher busca bytes do bytecode um de cada vez, descriptografando e despachando para um handler que faz o trabalho.

### Pipeline de construção

Etapas que o build executa para transformar shellcode bruto no blob virtualizado emitido, de ponta a ponta. _Todos os gráficos abaixo se aplicam à versão 1.0.0, eles mudaram desde então, mas a ideia é a mesma._```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]

Fluxo de execução

O que o blob emitido faz quando o carregador hospedeiro o chama no byte 0, desde o prólogo através das buscas do despachante e dos vários caminhos de terminação.```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]

root@kitploit:~
O truque do nonce em tempo de execução é a principal medida antiescan de memória: a tabela de handlers reside na memória XORada com um valor derivado de `rdtsc XOR cipher_init`, de modo que dois processos carregando o mesmo blob tenham tabelas de handlers com bytes diferentes. O despachante desmascara no momento da consulta com um XOR extra.

## Modo Packer: `--pack`

O tradeoff oposto. O lifter não executa. O shellcode original vai para a ilha de dados criptografado, e o IR é um único `JMP_NATIVE imm=0` sintético. O prólogo intencionalmente ignora a descriptografia da ilha de dados que o modo padrão executa ansiosamente. Em vez disso, na primeira vez que o único handler JMP_NATIVE dispara, ele descriptografa a ilha de dados no local, define um byte marcador e então transfere o controle para o byte 0 do shellcode agora em texto claro. O shellcode executa nativamente a partir daí.

Funciona em qualquer shellcode, independentemente da cobertura do lifter. Útil para cobalt sem estágio, payloads exóticos pesados em syscalls, qualquer coisa grande ou estranha demais para virtualizar. Perde a defesa de virtualização por instrução, mas mantém o polimorfismo completo da VM por semente no wrapper.

### Pipeline de construção

Como `--pack` envolve o shellcode original como uma ilha de dados criptografada atrás de um programa IR sintético de uma instrução.```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]

Fluxo de execução

O que o pack wrapper faz na primeira entrada, incluindo a descriptografia preguiçosa controlada (gated lazy decrypt) da ilha de dados (data island) e o único JMP_NATIVE sintético que entrega o controle ao shellcode agora em texto claro.```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]

root@kitploit:~
A descriptografia diferida da ilha de dados é exclusiva do modo pack. No modo padrão e híbrido, o código levantado executa operações VM LOAD/STORE nos bytes da ilha de dados durante a execução e precisa deles em texto simples desde o início, então o prólogo lida com isso imediatamente. No modo pack, há exatamente um consumidor da ilha de dados, a fuga nativa após o JMP_NATIVE sintético, então a descriptografia pode esperar até esse momento.

## Hybrid mode: `--ranges A:B,C:D`

Virtualização direcionada. Escolha intervalos de bytes na entrada que devem ser levantados. Todo o resto permanece como os bytes nativos originais na saída. No início de cada intervalo, o levantador aplica um `jmp rel32` de 5 bytes para um stub `vm_entry_K` anexado após a região nativa do shellcode. O código nativo externo só pode reentrar em um intervalo levantado através do byte inicial corrigido. Os bytes internos do intervalo são preenchidos com int3, portanto, qualquer desvio nativo que tenha como alvo um byte interno do intervalo é rejeitado no momento da varredura. O código levantado que sai de um intervalo para bytes fora dele torna-se `JMP_NATIVE` ou `CALL_NATIVE`.

Use `--scan` primeiro para encontrar candidatos elegíveis. A varredura classifica intervalos com lacunas, sem bloco terminado por `ret`, com corpo menor que 5 bytes, ou com desvios nativos externos para bytes internos do intervalo como quase-acerto em vez de elegível.

### Build pipeline

Como `--ranges` levanta apenas os intervalos de bytes escolhidos e corrige o shellcode nativo no lugar para que o controle redirecione para os stubs de entrada VM anexados.```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]

Fluxo de execução

Fluxo de controle através de um blob misto nativo e VM, incluindo a entrada no despacho da VM quando o código nativo atinge o início de um intervalo corrigido e os caminhos de escape nativo usados quando o código elevado retorna para fora.```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

root@kitploit:~
Intervalos no estilo corrotina, aqueles sem `ret` que saem via tail-jmp, são documentados via `--coroutines` para a saída de `--scan`. A flag não altera a geração de código no momento. O modo de intervalo é mais adequado para funções folha autocontidas. Funções auxiliares que dependem de um estado de registrador específico fornecido pelo chamador não podem ser extraídas de forma independente; a API acaba sendo chamada com argumentos inválidos.

## Modo empilhado: `--pack --ranges A:B,C:D`

Execute a construção híbrida e, em seguida, empacote o resultado. A VM externa descriptografa o blob híbrido interno no local e salta para o byte 0 dele. A partir daí, a execução prossegue exatamente como no modo híbrido independente, exceto que todo o blob, incluindo o bytecode do intervalo escolhido, é criptografado em repouso. As duas camadas se compõem de forma limpa porque o blob interno é uma região PIC autocontida.

### Pipeline de construção

Como o modo empilhado invoca recursivamente o empacotador: uma construção interna `--ranges` primeiro e, em seguida, um envolvimento externo `--pack` dessa construção.```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]

Fluxo de execução

Como a VM do pacote externo passa o controle para a VM de modo de intervalo, cada uma executando em seu próprio quadro VMState independente.```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

root@kitploit:~
A VM interna e a VM externa não partilham estado. São duas VMs independentes que por acaso coexistem no mesmo blob. A única função da externa é proteger a interna com uma camada de desencriptação.

## Detour mode: `--embed-into target.exe --at RVA`

Forma diferente das outras. A entrada é shellcode bruto, convencionalmente um blob VM já emitido pelo mkPIVM noutro modo, embora quaisquer bytes PIC funcionem. A saída é um PE modificado.

A ferramenta analisa `target.exe`, localiza o RVA escolhido numa secção executável, desmonta instruções suficientes para cobrir 5 bytes, recusa se a execução deslocada contiver endereçamento relativo a RIP ou fluxo de controlo relativo que não sobreviva ao movimento, e depois adiciona uma nova secção RWX contendo um wrapper mais o blob VM. O wrapper preserva o estado do chamador, transfere para o blob VM, restaura o estado, executa os bytes originais deslocados e salta de volta para o byte após o patch. Um `jmp rel32` de 5 bytes no RVA escolhido aponta para o wrapper.

Dois submodos para a forma como o wrapper transfere para a VM:

### Threaded sub-mode, the default

Seguem-se dois diagramas. O primeiro é o pipeline de patch no PE em tempo de compilação que injeta a secção do wrapper, corrige referências e escreve o jmp de 5 bytes no RVA escolhido. O segundo é o fluxo de controlo em tempo de execução quando o processo anfitrião atinge eventualmente esse 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]

O run.sh script também é a maneira mais fácil de gerar uma chave de API Grapqhl, bem como iniciar scripts de longo prazo para exportação de dados. Ele também gerencia automaticamente a exclusão de todos os arquivos cron-task.```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]

root@kitploit:~
O submodo com threads é o formato adequado para stagers e beacons. O host principal nunca bloqueia. A thread de trabalho herda o ciclo de vida que o payload precisar. Se o payload chamar `ExitProcess`, todo o processo morre, mas para um payload não terminante, como todo beacon C2, o host executa para sempre em paralelo com ele.

### Inline sub-mode: `--detour-inline`

Mesma estrutura de wrapper, mas o wrapper faz um `call vm_blob` direto em vez de `CreateThread`. A thread principal do host bloqueia até que a VM retorne. Útil quando o alvo não possui `CreateThread` em sua IAT, ou como fallback quando o caminho base-reloc com threads não pode ser aplicado a um alvo específico.```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]

Modo de scan: --scan

Nenhum arquivo de saída. Constrói o CFG a partir do shellcode de entrada e imprime os candidatos --ranges no stderr. Cada candidato é classificado como elegível, coroutine, near-miss ou interno, onde interno significa que é sombreado por um candidato elegível maior que já o cobre. Use isto para selecionar argumentos de intervalo antes de invocar a ferramenta novamente no modo híbrido.```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]

root@kitploit:~
## Eixos de polimorfismo por semente

Listados aproximadamente em ordem de quanto eles afetam a assinatura estática.

* Família de cifra. Uma de ARX, LcgSub, SBoxAdd, FeistelByte. Escolhe tanto a criptografia de bytecode usada no momento da compilação quanto o decrypt inline emitido no caminho de fetch do dispatcher. Encrypt e decrypt correspondem por construção.
* Layout de slots de registradores. O VMState contém `reg_count` slots, com tamanho de 24 a 32 por semente. Os 16 GPRs arquiteturais mais 4 Tmp regs recebem uma nova permutação de índices de slot a cada semente.
* Mapeamento opcode-para-handler. Cada família de codec recebe um byte de opcode aleatório a cada semente. A tabela de handler de 256 entradas indexa por opcode; o bytecode codificado referencia os opcodes mapeados.
* Topologia do dispatcher. Threaded ou central, escolhido por semente.
* Estratégia de auto-localização do prólogo. `call $+5; pop`, `lea reg, [rip+0]`, ou um shuffle jmp/call.
* Temporários de registradores do handler. O handler BR_CC permuta seus 6 papéis temporários pelo pool volátil. O handler Store faz o mesmo.
* Densidade de gadgets lixo. 0 a 3, controla a emissão de lixo entre handlers.
* Decisões da passagem de ofuscação de IR. Injeção de IR morto e predicados opacos usam cada um seu próprio sub-RNG, de modo que suas decisões são determinísticas por semente sem perturbar outros eixos.
* Estado inicial da criptografia do bytecode. 64 bits aleatórios por semente.

## Payloads testados

Esta ferramenta foi validada contra várias amostras de shellcode do Cobalt Strike, MSF, Sliver e inúmeras outras.

## Compilar

Requer Visual Studio 2022, CMake 3.21 ou mais recente e vcpkg. O projeto CMake obtém Zydis e fmt através do modo de manifesto do vcpkg.```
cmake -S . -B build
cmake --build build --config Release --target mkpivm

Limitações conhecidas

  • O modo de faixa para stagers do Cobalt não funciona como uma folha independente. As funções auxiliares do stager dependem do estado de registrador fornecido pelo chamador que o runner não fornece. Virtualização completa ou --pack são os caminhos que realmente beacon.
  • O detour encadeado x86 requer que o alvo não possua ASLR ou aceite entradas de realocação base adicionadas, que a ferramenta emite quando presentes. Se o diretório de dados BASERELOC do alvo estiver malformado ou ausente, a ferramenta recorre ao modo inline.
  • O lifter atualmente não cobre movimentos de registradores SSE/AVX, atômicos, CMPXCHG, RDMSR ou instruções privilegiadas. O modo pack é a solução alternativa para shellcodes que os utilizam.
  • As assinaturas Authenticode na saída de --embed-into são invalidadas. O checksum PE é zerado.
  • Os blobs de saída exigem RWX em tempo de execução porque a descriptografia in-place escreve de volta nas próprias páginas do blob. A maioria dos loaders que executam shellcode alocam RWX de qualquer forma. Não há planos de mudar para apenas RX porque a troca compra RX ao custo de uma busca no PEB e uma chamada VirtualAlloc, que é provavelmente uma assinatura pior do que a página RWX que substitui.
  • A virtualização de payloads sem stager via modo padrão ainda não é suportada, pois são terrivelmente complicados de envolver na VM; prefira --pack + --ranges.

Notas

  • Este projeto é em grande parte pesquisa de prova de conceito. Se for bem recebido, irei estendê-lo conforme solicitado e aceitarei contribuições. No entanto, pareceu estável para as amostras testadas.
  • Se seu shellcode não funcionar e você não quiser colocá-lo em uma Issue, infelizmente não posso ajudá-lo. Isso foi testado no Sliver, Cobalt Strike 4.12, MSF, Havoc e algumas outras amostras não divulgadas.
  • Em meus testes, a injeção da VM em processos ativos funcionou bem. No entanto, quando se trata de incorporar em PEs, isso não foi testado com software comercial como o MS Word, apenas testes sintéticos, mas provavelmente funciona. Se não funcionar, corrigirei.
  • A partir de 20/05/2026 (parece ter terminado agora), parece que o repositório estava sendo invadido por bots. Então, isso é maravilhoso. Por favor, ignore todas as contas em branco do Github.
  • Vi um post gerado por IA sobre o mkPIVM, dizendo que suas desvantagens eram:
    • 1). Não ter suporte para Linux, o que é justo, e adicionarei isso em breve.
    • 2). Não ter uma GUI? O quê?
    • Não sei, achei engraçado.

Contribuindo

  • Isso é uma merda legal, cai na real. Se quiser contribuir com novas ideias, corrigir seus próprios bugs, enviar Issues para eu corrigir, etc., vá em frente.
Baixar ferramenta