Skip to content
KitploitKITPLOIT
도구블로그
제출
도구블로그
제출

해킹, 침투 테스트 및 사이버 보안 도구를 당신의 보안 무기고에!

Kitploit은 해킹, 사이버 보안 및 침투 테스트 도구 디렉토리입니다. 최신 프로젝트 업데이트를 발견하여 취약점을 찾고, 시스템을 분석하고, 테스트를 자동화하고, 보안을 강화하세요.

··피드·문의·개인정보·© 2026 Kitploit

도구 디렉토리

카테고리

모든 카테고리 보기
Loading categories
mkPIVM — 임의의 x86/x64 셸코드로부터 다형적이며 위치 독립적인 가상 머신(PIVM)을 생성합니다. | Kitploit
도구/GitHubGitHub/d7ead/mkpivm
Payload GenerationExploitationReverse EngineeringShellcodeMalware AnalysisRed Teaming
GitHubd7ead/mkpivm

mkPIVM

임의의 x86/x64 셸코드로부터 다형적이며 위치 독립적인 가상 머신(PIVM)을 생성합니다.

저장소 보기
409177일 전Kitploit 검토 완료

인기

모두 보기 →

커뮤니티에서 가장 많이 사용되는 도구를 찾아보세요.

모든 도구 탐색

도구 컬렉션을 둘러보세요

모든 도구 보기 →
공유


읽기 연구 논문 (1.0.0용으로 작성됨).

mkPIVM은 Windows x86 및 x64용 다형성 위치 독립 셸코드 가상화기입니다 (Linux 곧 지원 예정).

원시 셸코드를 입력하면 또 다른 원시 blob이 생성됩니다. 이 blob은 원본 명령어의 리프팅된(lifted) 상태로 암호화되어 저장된 버전을 해석하는 작은 가상 머신입니다. 출력 자체는 위치 독립 코드이며, 원본 셸코드가 실행될 수 있는 모든 곳(원격 스레드 로더부터 코드 케이브 디투어까지)에서 동작합니다. 모든 per-seed 노브는 독립적으로 변합니다: cipher family, register slot layout, opcode-to-handler permutation, dispatcher topology, junk-gadget pattern, IR obfuscation insertion points. 동일한 입력에서 생성된 두 빌드는 수만 바이트 중에서 우연히 일치하는 바이트가 백 개 미만입니다.

이유: 네이티브 셸코드는 시그니처를 통한 탐지가 쉽습니다. 이를 per-instance VM에 per-instance 암호로 감싸면 유휴 상태로 남은 유용한 정보가 없으며, 명령어를 바이트코드로 리프팅하여 디스크 바이트와 x86을 아는 디스어셈블러 사이에 또 다른 장벽을 세웁니다. 문헌 조사에 따르면, 이 정확한 파이프라인(원시 PIC 입력, 원시 다형성 VM PIC 출력)을 제공하는 공개 도구는 없는 것으로 보입니다. 그래서 연구 논문에서 요구한 대로 언급했습니다. 솔직히 말해서, 제가 맞다면(공개적으로는 아무도 이 작업을 한 적이 없다고 확신합니다), 꽤 놀랍습니다. 그럼에도 불구하고 즐기시길 바랍니다.

관련 연구 및 계획

  • Linux 지원이 곧 추가될 예정입니다.

빠른 시작```

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

root@kitploit:~
Your PIVM은 뜨겁고 준비되었습니다. 그것이 가장 간단한 경로입니다. 몇 가지 다른 모드는 원래 명령어가 가상화되는 정도, 출력이 독립형 blob인지 패치된 PE인지, 리프트가 전혀 실행되는지 여부를 다르게 합니다.

# Showcase

증거가 있습니다. 아래에서 mkPIVM이 Meterpreter 스테이저 (바닐라 btw)를 완전히 가상화하고, explorer.exe에 인젝션한 후, 저희가 콜백을 캡처하는 동영상을 볼 수 있습니다. 물론 이것은 예시일 뿐이며, 셸코드의 명령어가 지원된다면 mkPIVM은 훨씬 더 많은 곳에 적용될 수 있습니다. 지원되지 않는다면 Issue를 생성하고 셸코드를 보내주세요, 제가 처리하겠습니다.

여기서 [보기](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 비콘의 결과입니다.

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

이 도구의 출력 엔트로피 텔레메트리에 세심한 주의를 기울였으며, 그 결과 (패킹 모드 외) ntdll.dll이나 kernel32.dll과 같은 일반적인 Windows WinAPI DLL보다 낮은 엔트로피의 셸코드가 생성됩니다. 엔트로피 비교는 대략 다음과 같습니다...

| File | Bytes | Entropy |
|------|-------|---------|
| `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** |

## Modes at a glance

| Mode | Flags | What changes |
|------|-------|--------------|
| Default | none | 전체 입력을 리프트합니다. 모든 것이 가상화됩니다. |
| Packer | `--pack` | 리프트하지 않습니다. 입력을 암호화된 데이터로 감싸고, 런타임에 복호화한 후 점프합니다. |
| Hybrid | `--ranges A:B,...` | 선택한 바이트 범위만 리프트합니다. 나머지는 네이티브로 유지됩니다. |
| Stacked | `--pack --ranges A:B` | 하이브리드 blob을 빌드한 후 패킹으로 감쌉니다. |
| Detour | `--embed-into PE --at RVA` | 미리 빌드된 blob을 가져와 PE에 임베드하고, 선택한 RVA에 jmp를 패치합니다. |
| Scan | `--scan` | 입력의 CFG에서 적격한 `--ranges` 후보를 출력한 후 종료합니다. |
| RX | `--rx` | PAGE_EXECUTE_READ blob입니다. 데이터 아일랜드는 저장 시 암호화된 상태로 유지됩니다. blob 내부의 PEB 워커가 VirtualProtect를 해결하고 state_init에서 제자리 복호화합니다. |
| RX w/ Loader | `--rx --rx-loader-vp` | `--rx`와 유사하지만, 로더가 VirtualProtect를 blob의 첫 번째 인자로 전달합니다. PEB 워커가 없습니다. |

모든 모드는 `--seed`, `--arch`, `--input-format` 및 `--format`을 적용합니다. 빌드 파이프라인과 런타임 흐름에 대한 내용은 아래 각 모드 섹션을 참조하세요.

## Default virtualization

리프터는 전체 CFG를 탐색하고 모든 명령어를 사용자 정의 IR로 낮춥니다. IR은 두 개의 난독화 패스를 거친 후, 각 명령어를 시드별 바이트코드 형태로 인코딩하는 코덱을 통과합니다. 블록 테이블, 핸들러 테이블 및 데이터 아일랜드는 바이트코드와 동일한 바이트별 스트림 암호로 암호화됩니다. 런타임에 프롤로그는 이 세 영역을 제자리에서 복호화하고, 디스패처 루프는 바이트코드 바이트를 한 번에 하나씩 가져와 복호화한 후 작업을 수행하는 핸들러로 디스패치합니다.

### Build pipeline

빌드가 원시 셸코드를 출력된 가상화된 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`

반대의 트레이드오프입니다. 리프터가 실행되지 않습니다. 원본 셸코드는 암호화된 상태로 데이터 아일랜드에 저장되며, IR은 단일 합성 `JMP_NATIVE imm=0`입니다. 프롤로그는 기본 모드에서 즉시 수행되는 데이터 아일랜드 복호화를 의도적으로 건너뜁니다. 대신, 유일한 JMP_NATIVE 핸들러가 처음 실행될 때 데이터 아일랜드를 제자리에서 복호화하고, 마커 바이트를 설정한 다음, 이제 평문이 된 셸코드의 바이트 0으로 제어를 전달합니다. 셸코드는 그 시점부터 네이티브로 실행됩니다.

리프터 적용 범위에 관계없이 모든 셸코드에서 작동합니다. 스테이지리스 코발트, 이국적인 시스콜 위주 페이로드, 가상화하기에 너무 크거나 이상한 모든 것에 유용합니다. 명령어별 가상화 방어를 잃지만, 래퍼에 대한 전체 시드별 VM 다형성은 유지합니다.

### 빌드 파이프라인

`--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]

root@kitploit:~
지연된 데이터-아일랜드 복호화는 팩 모드 전용입니다. 기본 및 하이브리드 모드에서는 리프트된 코드가 실행 중간에 데이터-아일랜드 바이트에 대해 VM LOAD/STORE를 수행하므로 처음부터 평문이 필요하므로 프롤로그에서 이를 즉시 처리합니다. 팩 모드에서는 데이터 아일랜드의 소비자가 정확히 한 명(합성 JMP_NATIVE 후의 네이티브 이스케이프)뿐이므로, 그때까지 복호화를 기다릴 수 있습니다.

## 하이브리드 모드: `--ranges A:B,C:D`

대상 가상화. 입력에서 리프트할 바이트 범위를 선택합니다. 그 외의 모든 것은 출력에서 원래 네이티브 바이트로 유지됩니다. 각 범위 시작점에서 리프터는 5바이트 `jmp rel32`를 네이티브 셸코드 영역 뒤에 추가된 `vm_entry_K` 스텁(stub)으로 패치합니다. 외부 네이티브 코드는 패치된 시작 바이트를 통해서만 리프트된 범위에 재진입할 수 있습니다. 범위 중간 바이트는 int3으로 채워지므로, 중간 바이트를 대상으로 하는 모든 네이티브 분기는 스캔 시점에 거부됩니다. 범위를 벗어난 바이트로 나가는 리프트된 코드는 `JMP_NATIVE` 또는 `CALL_NATIVE`가 됩니다.

먼저 `--scan`을 사용하여 적격 후보를 찾으십시오. 스캔은 갭이 있거나, `ret`으로 종료된 블록이 없거나, 본문 길이가 5바이트 미만이거나, 중간 바이트를 대상으로 하는 외부 네이티브 분기가 있는 범위를 적격이 아닌 근접 실패(near-miss)로 분류합니다.

### 빌드 파이프라인

`--ranges`가 선택된 바이트 범위만 리프트하고 네이티브 셸코드를 제자리에 패치하여 제어 흐름이 추가된 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` 없이 tail-jmp를 통해 종료하는 것)는 `--scan` 출력의 `--coroutines`를 통해 문서화됩니다. 현재 이 플래그는 코드 생성에 영향을 주지 않습니다. 범위 모드는 독립적인 리프 함수에 가장 적합합니다. 특정 호출자가 제공한 레지스터 상태에 의존하는 헬퍼 함수는 독립적으로 리프트될 수 없습니다. 해당 API는 결국 쓰레기 인수로 호출됩니다.

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

하이브리드 빌드를 실행한 다음 결과를 팩-래핑합니다. 외부 VM은 내부 하이브리드 블롭을 제자리에서 복호화한 후 그 byte 0으로 점프합니다. 그 지점부터 실행은 독립형 하이브리드 모드와 정확히 동일하게 진행되지만, 선택된 범위 바이트코드를 포함한 전체 블롭이 저장 시 암호화된다는 점이 다릅니다. 내부 블롭이 자체 포함된 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]

런타임 흐름

외부 pack VM이 내부 range-mode 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

root@kitploit:~
내부 VM과 외부 VM은 상태를 공유하지 않습니다. 이들은 동일한 블롭에 우연히 존재하는 두 개의 독립적인 VM입니다. 외부 VM의 유일한 역할은 내부 VM을 복호화 계층 뒤에 가두는 것입니다.

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

다른 모드와 형태가 다릅니다. 입력은 원시 셸코드로, 일반적으로 다른 모드에서 mkPIVM이 이미 생성한 VM 블롭이지만, 모든 PIC 바이트도 작동합니다. 출력은 패치된 PE입니다.

이 도구는 `target.exe`를 파싱하고, 선택된 RVA를 실행 가능한 섹션에서 찾은 다음, 해당 위치에서 5바이트를 포함할 만큼의 명령어를 디스어셈블합니다. 이동된 실행 코드에 RIP-상대 주소 지정 또는 이동 시 생존할 수 없는 상대 제어 흐름이 포함되어 있으면 거부하고, 그렇지 않으면 래퍼와 VM 블롭을 포함하는 새로운 RWX 섹션을 추가합니다. 래퍼는 호출자 상태를 보존하고, VM 블롭으로 전송하고, 상태를 복원하고, 이동된 원래 바이트를 실행한 후, 패치 이후의 바이트로 점프합니다. 선택된 RVA에 있는 5바이트 `jmp rel32`는 래퍼를 가리킵니다.

래퍼가 VM으로 전송하는 방법에 대한 두 가지 하위 모드:

### 스레드형 하위 모드 (기본값)

두 개의 다이어그램이 이어집니다. 첫 번째는 빌드 시 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]

I didn't receive any actual content to translate. Please provide the chunk of Kitploit tool content (chunk 21 of 29) for translation into Korean.```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`

동일한 래퍼 구조이지만 래퍼는 `CreateThread` 대신 직접 `call vm_blob`을 수행합니다. 호스트 메인 스레드는 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 mode: --scan

출력 파일이 없습니다. 입력된 shellcode로부터 CFG를 구축하고 --ranges 후보들을 stderr로 출력합니다. 각 후보는 eligible, coroutine, near-miss 또는 internal로 분류됩니다. 여기서 internal은 이미 해당 범위를 포함하고 있는 더 큰 eligible 후보에 의해 가려진 경우를 의미합니다. 이 모드를 사용하여 hybrid 모드에서 도구를 다시 호출하기 전에 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는 시드당 24~32 크기의 `reg_count` 슬롯을 포함. 16개의 아키텍처 GPR과 4개의 Tmp 레지스터는 각 시드마다 슬롯 인덱스의 새로운 순열을 얻음.
* Opcode-핸들러 매핑. 각 코덱 패밀리에는 시드마다 무작위 opcode 바이트가 할당됨. 256개 항목의 핸들러 테이블은 opcode로 인덱싱되며, 인코딩된 바이트코드는 매핑된 opcode를 참조함.
* 디스패처 토폴로지. 스레드형 또는 중앙형, 시드별로 선택됨.
* 프롤로그 자체 위치 찾기 전략. `call $+5; pop`, `lea reg, [rip+0]`, 또는 jmp/call 셔플.
* 핸들러 레지스터 임시 변수. BR_CC 핸들러는 6개의 임시 역할을 휘발성 풀에서 순열로 변경. Store 핸들러도 동일함.
* 정크 가젯 밀도. 0~3, 핸들러 간 가비지 방출 제어.
* IR 난독화 패스 결정. 데드 IR 주입과 불투명 조건자는 각각 자체 서브-RNG를 사용하므로 다른 축에 영향을 주지 않고 시드별로 결정이 결정적임.
* 바이트코드 암호화 초기 상태. 시드당 무작위 64비트.

## 테스트된 페이로드

이 도구는 여러 Cobalt Strike, MSF, Sliver 및 수많은 기타 셸코드 샘플에 대해 검증되었습니다.

## 빌드

Visual Studio 2022, CMake 3.21 이상, vcpkg 필요. CMake 프로젝트는 vcpkg의 매니페스트 모드를 통해 Zydis와 fmt를 가져옵니다.```
cmake -S . -B build
cmake --build build --config Release --target mkpivm

알려진 한계

  • 코발트 스테이저(Cobalt stagers)의 범위 모드는 독립형 리프(leaf)로 작동하지 않습니다. 스테이저의 헬퍼 함수는 러너가 제공하지 않는 호출자 제공 레지스터 상태에 의존합니다. 전체 가상화 또는 --pack이 실제로 비콘(beacon)을 수행하는 경로입니다.
  • x86 스레드 디투어(threaded detour)는 대상이 ASLR이 없거나 추가된 베이스 재배치(Base Relocation) 엔트리를 수락해야 합니다. 이 도구는 존재할 때 이를 생성합니다. 대상의 BASERELOC 데이터 디렉터리가 잘못되었거나 없으면 도구는 인라인 모드로 대체됩니다.
  • 리프터(Lifter)는 현재 SSE/AVX 레지스터 이동, 아토믹(atomict), CMPXCHG, RDMSR 또는 특권 명령어를 다루지 않습니다. 팩(Pack) 모드는 이러한 명령어를 사용하는 셸코드에 대한 해결 방법입니다.
  • --embed-into 출력의 Authenticode 서명은 무효화됩니다. PE 체크섬은 0으로 설정됩니다.
  • 출력 블롭(blob)은 런타임에 RWX가 필요합니다. 제자리 복호화가 블롭 자체 페이지에 다시 쓰기 때문입니다. 대부분의 셸코드를 실행하는 로더는 어쨌든 RWX를 할당합니다. RX 전용으로 전환할 계획은 없습니다. 그 트레이드오프는 PEB 워크와 VirtualAlloc 호출이라는 비용으로 RX를 구매하는 것이며, 이는 대체하는 RWX 페이지보다 더 나쁜 서명일 가능성이 높기 때문입니다.
  • 기본 모드를 통한 스테이지리스(stageless) 페이로드 가상화는 아직 지원되지 않습니다. VM으로 래핑하는 것이 매우 복잡하기 때문입니다. --pack + --ranges를 선호합니다.

참고 사항

  • 이 프로젝트는 대부분 개념 증명 연구입니다. 좋은 반응을 얻으면 요청에 따라 확장하고 기여를 환영합니다. 그러나 테스트된 샘플에서는 안정적으로 보였습니다.
  • 셸코드가 작동하지 않고 이슈(Issue)에 제출하고 싶지 않다면, 불행히도 도와드릴 수 없습니다. 이 도구는 Sliver, Cobalt Strike 4.12, MSF, Havoc 및 기타 몇 가지 비공개 샘플에서 테스트되었습니다.
  • 테스트에서 VM을 라이브 프로세스에 주입하는 것은 잘 작동했습니다. 그러나 PE에 임베드하는 경우, MS Word와 같은 상용 소프트웨어로 테스트되지 않았고 합성 테스트만 수행했습니다. 아마도 작동할 것입니다. 작동하지 않으면 수정하겠습니다.
  • 2026년 5월 20일 기준(지금은 끝난 것으로 보입니다), 리포지토리가 봇으로 가득 찬 것으로 보입니다. 정말 대단하네요. 모든 빈 Github 계정은 무시해 주세요.
  • mkPIVM에 대한 AI 생성 게시물을 보았는데, 단점으로 다음과 같이 언급했습니다:
    • 1). Linux 지원 부재: 공평한 지적이며, 곧 추가할 예정입니다.
    • 2). GUI가 없음? 뭐라고?
    • 모르겠습니다. 재미있다고 생각했습니다.

기여

이건 꽤 멋진 것이니, 현실적으로 생각하세요. 새로운 아이디어를 기여하거나, 자신의 버그를 수정하거나, 제가 수정할 이슈를 제출하는 등 원하시면 하세요.

도구 다운로드