pwninit: preparing a binary exploit challenge before you write any code
pwninit - automate starting binary exploit challenges
At a glance
- What is it?
- pwninit is a Rust command line tool that automates the dull parts of starting a binary exploitation exercise, detecting the challenge binary, the libc and the linker, fetching a matching linker and debug symbols, unstripping the libc, patching the binary with patchelf and writing a pwntools solve script. It suits students and participants in exploit exercises, and it depends on network access to distro archives.
- Who is it for?
- Adopt pwninit if you work through binary exploitation exercises and the setup is what slows you down, because the tool handles the libc and linker pairing, the debug symbols and the script stub in one command. Do not adopt it as a general binary patching tool, because every step it performs is scoped to the shape of a challenge with a bundled libc.
- Can I use it commercially?
- Yes. MIT is a permissive licence: you can use, modify and sell software built on it, as long as you keep its copyright and licence notices.
- Is it still maintained?
- Yes. The repository last received commits 90 days ago.
- What is it written in?
- Mainly Rust, according to GitHub's language statistics.
Answers come from the project's GitHub data, last synced on October 25, 2026, and from our analysis. They are not legal advice.
Editorial analysis
What the tool automates
pwninit is a tool for automating starting binary exploit challenges, and the five features it lists are the entire scope.
It sets the challenge binary to be executable. It downloads a linker that can segfaultlessly load the provided libc, which matters because the linker shipped in a modern distribution is not compatible with an older libc and will crash in ways that look like a bug in the challenge. It downloads debug symbols and unstrips the libc, so the addresses you need are readable. It patches the binary with patchelf to use the correct RPATH and interpreter for the provided libc, so the binary finds the libc you were given rather than the one on your system. And it fills in a template pwntools solve script.
Each of those is a small task, and each of them is a task that has to be done before any exploitation work can start. The combination is the argument: the interesting part of an exercise is the exploit, not the environment.
The tool is written in Rust, the licence is MIT, and the release profile in the manifest optimises for size with opt-level set to z and link time optimisation enabled, which is a sensible choice for a binary that gets downloaded and run on a variety of systems.
The current version is 3.3.3, released on 2026-07-27, and the two before it are 3.3.2 from December 2025 and 3.3.1 from December 2023, so the release cadence is slow but the project is maintained. The last push to the repository was on 2026-07-27.
Detection, and the flags that override it
The short version of the usage is to run pwninit. The long version explains what happens.
You run pwninit in a directory with the relevant files and it detects which ones are the binary, the libc and the linker. If the detection is wrong, you specify the locations yourself with --bin, --libc and --ld.
That is the whole interface, and the design bet is that in the common case the directory contains exactly the three files you care about, so the tool can find them. A challenge directory with a binary, a libc and a readme is the typical shape, and the example in the README is exactly that.
The example output shows the order of operations.
$ ls
hunter libc.so.6 readme
$ pwninit
bin: ./hunter
libc: ./libc.so.6
setting ./hunter executable
fetching linker
https://launchpad.net/ubuntu/+archive/primary/+files//libc6_2.23-0ubuntu10_i386.deb
unstripping libc
https://launchpad.net/ubuntu/+archive/primary/+files//libc6-dbg_2.23-0ubuntu10_i386.deb
setting ./ld-2.23.so executable
copying ./hunter to ./hunter_patched
running patchelf on ./hunter_patched
writing solve.py stubTwo things are visible there. The libc is a specific old build, libc6 2.23 from Ubuntu for i386, and the tool finds the matching packages in the Ubuntu archive, with the debug package being a separate download. And the original binary is not modified: the tool copies the binary to a patched copy and works on that, so the file you were given stays as it was.
The result in the directory afterwards is the original binary, the patched copy, the downloaded linker, the libc and the generated solve.py.
The linker problem, and the unstripping
The linker step is the one that deserves an explanation, because it is the part a beginner cannot easily work out.
When you run a dynamically linked binary, the dynamic loader on your system loads it. If the challenge was built against an older libc, the loader on your current system will do things the old libc does not expect, and the program will crash before your input is read. The usual symptom is a segfault at the very start, in code you have not touched, and it looks exactly like a broken exploit setup.
pwninit solves this by downloading a linker that matches the provided libc, so the old libc is loaded by an old loader that understands it. The README describes the goal as a linker that can segfaultlessly load the provided libc.
The unstripping step is separate and simpler. A libc shipped for distribution is stripped, so the symbol names are gone and you have to work with raw offsets. The tool downloads the debug symbols package for the same libc version and uses them to restore a readable libc, so the addresses and the function names you need are available.
Both steps depend on finding a matching package in a distribution archive, and the URLs in the example point at the Ubuntu archive on Launchpad, with the libc package and the debug package as separate downloads. That is the practical constraint of the tool: it needs network access, and it needs to find a distro package that matches the libc you were given.
patchelf: the RPATH and the interpreter
Patching the binary is the step that makes the other two matter, and it is a small change with a clear purpose.
A dynamically linked ELF records two things that decide which loader runs it and which libraries it finds: the interpreter path, and the runtime search path, the RPATH. Left alone, a challenge binary will be started by your system's loader and will look for its libc in the system paths, and both are wrong when the challenge ships its own libc.
pwninit uses patchelf, the NixOS tool for editing these properties of an ELF, to set the interpreter to the linker it just downloaded and the RPATH to the directory holding the provided libc. After that, running the patched binary uses the challenge's own libc and the challenge's own loader, and the environment matches what the author intended.
The tool prints the step in the output, running patchelf on the patched copy, and the copy naming is the detail to keep in mind. The original file is left untouched, so the patched binary is the one you attach your debugger to and the one that runs in your exploit script, and the two are different files with different behaviour.
The dependency list in the manifest shows the ELF machinery behind this. The goblin crate does the binary parsing, and version-compare is there for comparing version strings, which is what the linker and debug package matching needs.
The solve script, and making it yours
The last thing pwninit does is write a solve.py stub, and the default template is a working pwntools skeleton.
#!/usr/bin/env python3
from pwn import *
exe = ELF("./hunter_patched")
libc = ELF("./libc.so.6")
ld = ELF("./ld-2.23.so")
context.binary = exe
def conn():
if args.LOCAL:
r = process([exe.path])
if args.GDB:
gdb.attach(r)
else:
r = remote("addr", 1337)
return rThe template loads the patched binary, the libc and the linker as ELF objects, sets the context binary, and provides a conn() function that starts the process locally with an optional debugger attached, or connects to a remote address and port. The two ELF bindings point at the patched binary and the downloaded linker, which is the detail that makes the generated file useful rather than decorative.
If you do not like the default template, you can supply your own with --template-path, and the format is documented in the template file in the source. The names the template binds, which are the exe, libc and ld variables, can be changed with --template-bin-name, --template-libc-name and --template-ld-name, so a template that expects different variable names still works.
There is a persistence trick worth knowing, because it removes a flag from every invocation. You can add an alias to your shell profile so that pwninit always loads your template.
alias pwninit='pwninit --template-path ~/.config/pwninit-template.py --template-bin-name e'So the workflow is: run pwninit in the challenge directory, edit the generated solve.py, and run it.
Installing it
There are three ways to get pwninit, and the choice is about what you need on the machine.
On Arch Linux, two AUR packages exist. The pwninit package builds from source and the pwninit-bin package installs a precompiled binary.
The releases page offers statically linked musl binaries, which is the option to pick when you want the tool to run on any Linux system without a libc of its own or without build tooling on the target machine. The release assets for other platforms are linked from the repository releases.
With cargo, the install is one command.
cargo install pwninitThat places the binary in the cargo bin directory. The build has requirements, and the README names them, since the dependency tree needs OpenSSL, liblzma and pkg-config to be present. That is the cost of building from source rather than downloading a static binary.
The project also ships a flake.nix, so a Nix user can build it inside a development shell with the dependencies resolved for them.
The tool itself has no runtime configuration file, and the only persistence mechanism described is the shell alias for a custom template, so there is nothing to install and nothing to configure before you run it in a challenge directory.
The limits, and the manual alternative
Two constraints and the obvious alternative.
The first constraint is network access and archive availability. To fetch a matching linker and debug symbols, the tool has to find a package that matches the libc version you were given, in a distribution archive. The example uses the Ubuntu archive on Launchpad. If your libc came from a build that never had a public debug package, or if the machine is offline, those steps cannot complete, and the tool is useful only for the executable bit, the patchelf step and the script stub.
The second constraint is that the whole model assumes a challenge with a bundled libc. A statically linked binary has no libc to pair and no linker to fetch, and a challenge that expects a remote service rather than a local process does not match the template's conn() function shape.
The manual alternative is entirely reasonable and worth knowing, because you can do each step by hand in about five minutes. You set the executable bit with chmod, you download the matching linker and debug package yourself, you unstrip the libc with the debug symbols, and you run patchelf with the set-interpreter and set-rpath options against a copy of the binary, and you write the pwntools script from your own template.
What pwninit adds is not capability but the matching. Knowing which debug package corresponds to which libc build, and which loader version goes with it, is the part that takes a newcomer the longest, and the tool encodes that knowledge. If you already know it, the script is a convenience. If you do not, it removes the slowest part of the first hour.
The dependency list also tells you what the tool does internally. The archive handling crates cover the Debian packages, with ar and tar for the package format, rust-lzma and flate2 and zstd for the compressed layers, and reqwest in blocking mode for the downloads. The error handling is built on snafu, and the output is coloured with the colored crate.
Editorial conclusion
Adopt pwninit if you work through binary exploitation exercises and the setup is what slows you down, because the tool handles the libc and linker pairing, the debug symbols and the script stub in one command. Do not adopt it as a general binary patching tool, because every step it performs is scoped to the shape of a challenge with a bundled libc. Before you run it, note that it fetches packages over the network to find a matching linker and debug symbols, and that it writes a patched copy of the binary alongside the original rather than modifying it in place.
Frequently asked questions
What does pwninit do?
It automates the setup of a binary exploit challenge. It sets the challenge binary executable, downloads a linker that can load the provided libc without segfaulting, downloads debug symbols and unstrips the libc, patches the binary with patchelf to use the correct RPATH and interpreter, and writes a template pwntools solve script.
How do I install pwninit?
On Arch Linux there are AUR packages for a source build and a precompiled binary, the releases page offers statically linked musl binaries, and cargo install pwninit builds it from source, which requires OpenSSL, liblzma and pkg-config to be installed.
How do I use a custom solve script template with pwninit?
Pass --template-path with the path to your template, and adjust the names the template binds with --template-bin-name, --template-libc-name and --template-ld-name. You can make it permanent by aliasing pwninit in your shell profile.
What happens to the original binary when pwninit runs?
The original is left untouched. The tool copies the binary to a patched copy, for example from hunter to hunter_patched, and runs patchelf against that copy, so the patched file is the one your solve script loads and the original stays as it was.
Official sources
Add this badge to your README
If you maintain this project, the badge below links readers to this analysis and shows its maintenance status from the daily GitHub snapshot. Paste the markdown into your README; add ?metric=license or ?metric=stars to the image URL for a different field.
[](https://hysenlabs.com/projects/io12-pwninit)Was this useful?