Skip to main content

Installing pwntools Locally and Importing pwn

·485 words·3 mins
IHEXON
Author
IHEXON
Do You Hear the People Sing ?

I hit this while installing a local pwntools checkout:

error: externally-managed-environment

This is not a pwntools problem. It is Python packaging doing what the distro asked it to do. On recent Debian and Ubuntu systems, the system Python is marked as externally managed, so pip install into the global interpreter is blocked by default.

The tempting workaround is:

pip install --break-system-packages ...

I would avoid that for normal development. It may work today, then later you forget which packages came from apt and which packages came from pip. A virtual environment is cleaner and easier to delete.

Install From a Local Checkout
#

Assume the source tree is here:

cd /home/ihexon/pwntools

Create a venv in the repo:

python3 -m venv .venv

Upgrade the packaging tools inside the venv:

.venv/bin/python -m pip install --upgrade pip setuptools wheel

Then install the current repo in editable mode:

.venv/bin/python -m pip install --upgrade --editable .

The final . matters. If you are already inside the pwntools source directory, the project path is the current directory, not ./pwntools.

After that, test the command line entry point:

.venv/bin/pwn version

On my checkout this prints something like:

[*] Pwntools v5.0.0dev-dev-65847960

For day-to-day use, either activate the venv:

source .venv/bin/activate
pwn version

or call the tools by full path:

/home/ihexon/pwntools/.venv/bin/pwn version

Installing the Released Package
#

If you do not need a local source checkout, the same idea still applies:

python3 -m venv ~/venvs/pwn
~/venvs/pwn/bin/python -m pip install --upgrade pip
~/venvs/pwn/bin/python -m pip install pwntools

Then:

source ~/venvs/pwn/bin/activate
python

How to Import It
#

The package name on PyPI is pwntools, but the module you normally import in exploit scripts is pwn.

The usual style is:

from pwn import *

elf = ELF("./chall")
p = process(elf.path)

p.sendlineafter(b"name:", b"ihexon")
p.interactive()

This is the style most CTF writeups use. It brings helpers like ELF, process, remote, context, p64, u64, asm, and shellcraft into the current namespace.

If you prefer a less magical import, this also works:

import pwn

elf = pwn.ELF("./chall")
p = pwn.process(elf.path)

For internal modules, pwnlib also exists:

import pwnlib

print(pwnlib.version)

But for normal exploit scripts, use from pwn import * or import pwn. Do not try:

import pwntools

There is no top-level module with that name.

Quick Check
#

A small sanity test:

.venv/bin/python - <<'PY'
from pwn import *
import pwnlib

print(pwnlib.version)
print(p64(0xdeadbeef))
PY

Expected shape:

5.0.0dev
b'\xef\xbe\xad\xde\x00\x00\x00\x00'

If that works, the Python import path and the native dependencies are good enough to start writing scripts.

Notes
#

If pip complains about missing Python headers while building from source, install the development package for your Python version. On Debian and Ubuntu this is usually:

sudo apt install python3-dev

For a specific Python version, the package name may be more specific, for example python3.13-dev.

If you are using a distro Python, keep pwntools in a venv. It avoids the externally managed environment error, and it keeps exploit tooling separate from system packages.