Skip to content
KitploitKITPLOIT
工具博客
提交
工具博客
提交

黑客、渗透测试和网络安全工具,武装您的安全武器库!

Kitploit 是一个黑客、网络安全和渗透测试工具的目录。发现最新的项目更新,查找漏洞、分析系统、自动化测试并加强你的安全。

··订阅源·联系·隐私·© 2026 Kitploit

工具目录

分类

查看所有分类
Loading categories
mkPIVM — 从任意x86/x64 shellcode生成多态、位置无关的虚拟机(PIVMs)。 | Kitploit
工具/GitHubGitHub/d7ead/mkpivm
Payload生成漏洞利用逆向工程Shellcode恶意软件分析红队
GitHubd7ead/mkpivm

mkPIVM

从任意x86/x64 shellcode生成多态、位置无关的虚拟机(PIVMs)。

查看仓库
40917426天前Kitploit 审核通过

最受欢迎

查看全部 →

发现我们社区最常用的工具。

探索所有工具

浏览我们的工具集合

查看所有工具 →
分享


阅读研究论文(针对 1.0.0 版本撰写)。

mkPIVM 是一个用于 Windows x86 和 x64(即将支持 Linux)的多态位置无关 Shellcode 虚拟化器。

输入原始 Shellcode。它输出另一个原始 Blob:一个微型虚拟机,用于解释原始指令的提升后、静态加密版本。输出本身是位置无关代码,可以在原始 Shellcode 能运行的任何地方执行,从远程线程加载器到代码洞跳转。每个种子的参数独立变化:密码族、寄存器槽布局、操作码到处理程序的排列、调度器拓扑、垃圾 gadget 模式、IR 混淆插入点。来自同一输入的两次构建,在数万字节中仅有不到一百个偶然相同的字节。

原因:原生 Shellcode 的签名非常容易被识别。将其包装在一个带实例密码的实例级虚拟机中,在静态下不留下任何有用的信息;而将指令提升为字节码,则在磁盘字节与任何了解 x86 样貌的反汇编器之间筑起另一道墙。根据我对文献的梳理,目前没有公开工具能精确实现这条流水线:原始 PIC 输入,多态 VM PIC 输出。所以,我在相应的研究论文中提到了这一点。老实说,如果我确知以前没有人(公开)做过这件事,而且我相当确信,我会感到惊讶。尽管如此,尽情享用吧。

相关工作与计划

  • 即将添加 Linux 支持。

快速开始```

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

root@kitploit:~
你的 PIVM 已准备就绪,随时可用。这是最简单的路径。其他几种模式则在不同程度上改变了原始指令被虚拟化的方式、输出是独立 blob 还是修补后的 PE,以及是否执行 lift。

# 展示

我有证据。你可以看到下面 mkPIVM 实际运行的视频,完全虚拟化了一个 Meterpreter stager(顺便说一句,是原版),注入到 explorer.exe 中,我们捕获了一个回调。当然,这只是一个例子,mkPIVM 可以应用于更多场景,只要 shellcode 中的指令得到支持。如果不支持,开个 Issue,把 shellcode 发给我,我来搞定。

点击[此处](https://github.com/D7EAD/mkPIVM/raw/refs/heads/main/media/mkpivm-showcase.mp4)观看。托管在 ./media 中,遗憾的是无法嵌入。

这是该虚拟化样本的 VirusTotal 报告(截至 2026‑04‑06)。

<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 beacon 的结果以供比较。

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

我们非常关注该工具输出的熵遥测数据,这使得生成的 shellcode 的熵值低于典型的 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** |

## 模式一览

| 模式 | 标志 | 变化 |
|------|-------|--------------|
| 默认 | none | 提升整个输入。所有内容被虚拟化。 |
| 打包器 | `--pack` | 不进行提升。将输入包装为加密数据,运行时解密,跳转执行。 |
| 混合 | `--ranges A:B,...` | 仅提升选定的字节范围。其余保持原生。 |
| 堆叠 | `--pack --ranges A:B` | 构建混合 blob,然后进行打包包装。 |
| 跳转 | `--embed-into PE --at RVA` | 取预构建的 blob,嵌入到 PE 中,在选定的 RVA 处打补丁插入 jmp。 |
| 扫描 | `--scan` | 打印输入 CFG 中符合条件的 `--ranges` 候选项,然后退出。 |
| RX | `--rx` | PAGE_EXECUTE_READ blob。数据岛在静态时保持加密;blob 内的 PEB walker 解析 VirtualProtect,在 state_init 时原地解密。 |
| 带加载器的 RX | `--rx --rx-loader-vp` | 与 `--rx` 类似,但你的加载器将 VirtualProtect 作为 blob 的第一个参数传入。无需 PEB walker。 |

每种模式都遵循 `--seed`、`--arch`、`--input-format` 和 `--format`。有关构建流程和运行时流程,请参阅下面的各模式章节。

## 默认虚拟化

提升器遍历整个 CFG,并将每条指令降低到自定义 IR。IR 经过两次混淆处理,然后通过编解码器将每条指令编码为按种子不同的字节码形状。块表、处理程序表和 data island 使用与字节码相同的逐字节流密码进行加密。在运行时,序言部分原地解密这三个区域,然后调度循环逐个获取字节码字节,解密并分派给执行工作的处理程序。

### 构建流程

构建过程执行的步骤,从头到尾将原始 shellcode 转换为输出的虚拟化 blob。_下图均针对 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 调用生成的 blob 时,它从序言开始,经过调度器获取以及各种终止路径所执行的操作。```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:~
运行时随机数技巧是关键的反内存扫描手段:处理程序表在内存中与从 `rdtsc XOR cipher_init` 派生的值进行 XOR,因此加载相同 blob 的两个进程持有字节不同的处理程序表。调度器在查找时通过一次额外的 XOR 来解除掩码。

## 打包模式:`--pack`

相反的权衡。提升器不运行。原始 shellcode 以加密方式进入数据岛,IR 是一个单一合成的 `JMP_NATIVE imm=0`。序言有意跳过默认模式急切运行的数据岛解密。相反,当单一的 JMP_NATIVE 处理程序首次触发时,它会就地解密数据岛,设置一个标记字节,然后将控制权转移到现在为明文的 shellcode 的字节 0。shellcode 从此处原生运行。

适用于任何 shellcode,无论提升器覆盖范围如何。对于无阶段的 cobalt、奇特的系统调用密集型载荷、任何过于庞大或怪异而无法虚拟化的内容都很有用。失去了每指令虚拟化防御,但保留了包装器上的完全按种子 VM 多态性。

### 构建流程

`--pack` 如何将原始 shellcode 包装为加密数据岛,位于单指令合成 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]

运行时流程

打包封装器在首次进入时的操作,包括对数据岛的门控延迟解密,以及将控制权移交给现在为明文的 shellcode 的单一合成 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]

root@kitploit:~
延迟的数据岛解密是pack-mode-only的。在default和hybrid模式下,提升的代码在中间执行时对数据岛字节进行VM LOAD/STORE,并需要它们从一开始就是明文,因此prologue会急切地处理它。在pack模式下,数据岛只有一个消费者,即合成JMP_NATIVE之后的native escape,因此解密可以等到那时再进行。

## 混合模式:`--ranges A:B,C:D`

目标虚拟化。选择输入中应被提升的字节范围。其余部分在输出中保持原始native字节。在每个范围起始处,提升器修补一个5字节的`jmp rel32`,指向附加在native shellcode区域之后的`vm_entry_K`存根。外部native代码只能通过修补的起始字节重新进入提升的范围。范围中间的字节填充为int3,因此任何针对中间字节的native分支在扫描时会被拒绝。离开范围到外部字节的提升代码会变成`JMP_NATIVE`或`CALL_NATIVE`。

首先使用`--scan`来寻找合格的候选对象。扫描将具有间隙、没有`ret`终止块、主体长度小于5字节、或者具有外部native分支进入中间字节的范围分类为接近命中而非合格。

### 构建流水线

介绍`--ranges`如何仅提升选定的字节范围,并就地修补native shellcode,使得控制流重定向到附加的VM入口存根。```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]

Runtime flow

通过混合原生和VM的blob的控制流,包括当原生代码命中已打补丁的范围起点时进入VM调度的入口,以及当提升的代码分支返回时使用的原生逃逸路径。```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:~
协程样式的范围,那些没有 `ret` 且通过尾跳转退出的范围,通过 `--coroutines` 在 `--scan` 输出中记录。该标志目前不会改变代码生成。范围模式最适合独立的叶子函数。依赖于特定调用者提供的寄存器状态的辅助函数无法单独提升;结果 API 会被用垃圾参数调用。

## Stacked mode: `--pack --ranges A:B,C:D`

运行混合构建,然后对结果进行打包包装。外部 VM 在原地解密内部混合 blob,并跳转到其字节 0。从那里开始,执行过程与独立混合模式完全相同,只是整个 blob(包括选定范围的字节码)在静态时被加密。这两层干净地组合,因为内部 blob 是一个自包含的 PIC 区域。

### 构建流水线

堆叠模式如何递归调用打包器:首先进行内部的 `--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]

运行时流程

外层包虚拟机如何将控制权交给内部范围模式虚拟机,每个虚拟机都在自己独立的 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

root@kitploit:~
内部VM和外部VM不共享任何状态。它们是两个独立的VM,恰好存在于同一个blob中。外部VM的唯一任务就是将一个解密层作为门禁,保护内部VM。

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

与其他模式不同。输入是原始shellcode,通常是在其他模式下由mkPIVM生成的VM blob,但任何PIC字节都可以工作。输出是一个打过补丁的PE文件。

该工具解析`target.exe`,在可执行节中定位选定的RVA,在那里反汇编足够多的指令以覆盖5个字节,如果被移走的指令序列包含无法在移动后幸存的RIP相对寻址或相对控制流,则拒绝执行,然后添加一个新的RWX节,其中包含一个包装器以及VM blob。包装器保存调用者状态,跳转到VM blob,恢复状态,执行被移走的原始字节,然后跳回补丁后的字节位置。在选定RVA处的一个5字节的`jmp rel32`指向该包装器。

两种子模式用于包装器如何跳转到VM:

### Threaded sub-mode, the default

以下给出两个示意图。第一个是构建时PE补丁流水线,它注入包装器节,修正引用,并在选定的RVA写入5字节的jmp。第二个是运行时控制流,当宿主进程最终到达该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]

请提供需要翻译的Markdown内容。```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:~
线程子模式是阶段器和信标的正确形态。主机主线程从不阻塞。工作线程继承有效载荷所需的任何生命周期。如果有效载荷调用`ExitProcess`,则整个进程终止,但对于像所有C2信标这样的非终止有效载荷,主机与之并行运行,永不停止。

### 内联子模式:`--detour-inline`

包装器结构相同,但包装器直接执行`call vm_blob`,而非`CreateThread`。主机主线程阻塞直到VM返回。当目标在IAT中缺少`CreateThread`时,或当线程化基址重定位路径无法应用于特定目标时作为回退,此模式很有用。```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

没有输出文件。从输入的shellcode构建CFG,并将--ranges候选输出到stderr。每个候选项被分类为eligible、coroutine、near-miss或internal,其中internal表示被更大的已覆盖它的eligible候选项所遮蔽。使用此信息在混合模式下再次调用工具之前选择range参数。```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:~
## 每种子所使用的多态轴

大致按照它们对静态签名影响程度的顺序排列。

* 密码族。可选ARX、LcgSub、SBoxAdd、FeistelByte之一。既决定构建时使用的字节码加密方式,也决定调度器取指路径中发出的内联解密代码。加密和解密通过构造匹配。
* 寄存器槽布局。VMState包含`reg_count`个槽,每个种子的大小在24到32之间。16个架构通用寄存器加上4个临时寄存器在每个种子中获得新的槽索引排列。
* 操作码到处理器的映射。每个编解码器族在每个种子中被分配一个随机操作码字节。256项的处理器表通过操作码索引;编码后的字节码引用映射后的操作码。
* 调度器拓扑。线程式或集中式,每个种子选择一种。
* 序言自定位策略。`call $+5; pop`、`lea reg, [rip+0]`或jmp/call混洗。
* 处理器寄存器临时变量。BR_CC处理器在易失寄存器池中排列其6个临时角色。Store处理器也是如此。
* 垃圾代码密度。0到3,控制处理器之间的垃圾代码生成。
* IR混淆处理决策。死IR注入和不透明谓词各自使用自己的子随机数生成器,因此它们的决策对每个种子是确定的,不会干扰其他轴。
* 字节码加密初始状态。每个种子随机64位。

## 测试过的载荷

该工具已针对多个Cobalt Strike、MSF、Sliver以及大量其他shellcode样本进行了验证。

## 构建

需要Visual Studio 2022、CMake 3.21或更高版本以及vcpkg。CMake项目通过vcpkg的清单模式拉取Zydis和fmt。```
cmake -S . -B build
cmake --build build --config Release --target mkpivm

Known limits

  • Cobalt stagers的范围模式不能作为独立leaf工作。stager的辅助函数依赖于调用者提供的寄存器状态,但运行器未提供这些状态。完全虚拟化或--pack才是实际触发beacon的途径。
  • x86线程化劫持要求目标要么缺乏ASLR,要么接受新增的基址重定位条目(工具在存在时生成)。如果目标的BASERELOC数据目录格式错误或缺失,工具将回退到内联模式。
  • 提升器目前不涵盖SSE/AVX寄存器移动、原子操作、CMPXCHG、RDMSR或特权指令。使用这些的shellcode的解决方法是打包模式。
  • --embed-into输出的Authenticode签名失效。PE校验和已归零。
  • 输出blob在运行时需要RWX,因为就地解密会将数据写回blob自身页面。大多数加载shellcode的加载器无论如何都会分配RWX。没有计划迁移到仅RX,因为这种权衡虽然换来了RX,但代价是PEB遍历和VirtualAlloc调用,这可能是比它所替换的RWX页面更差的签名。
  • 尚不支持通过默认模式虚拟化无阶段payload,因为将其包装到VM中极其复杂;建议使用--pack + --ranges。

Notes

  • 该项目主要是概念验证研究。如果反响良好,我会根据需求进行扩展,并欢迎贡献。不过,对于测试样本而言,它似乎足够稳定。
  • 如果你的shellcode不工作,并且你不想提交Issue,那么很遗憾我无法提供帮助。该项目已在Sliver、Cobalt Strike 4.12、MSF、Havoc以及一些未公开的样本上测试过。
  • 在我的测试中,将VM注入实时进程运行良好。但至于嵌入到PE中,并未使用MS Word等商业软件测试,仅进行了合成测试,但很可能有效。如果不行,我会修复。
  • 截至2026年5月20日(目前似乎已结束),该仓库似乎被机器人大量涌入。这太棒了。请忽略所有空白的Github账户。
  • 我看到一篇关于mkPIVM的AI生成文章,称其缺点为:
    • 1). 不支持Linux,这点倒合理,我很快会添加。
    • 2). 没有GUI?什么鬼?
    • 我不知道,我觉得这挺好笑。

Contributing

这是些很酷的玩意儿,说真的。如果你想贡献新点子、修复自己的bug、提交Issues让我修复,等等,尽管来吧。

下载工具