You need Linux, Python, and about ten tools
The setup question stops more beginners than any actual challenge does. People install a six-gigabyte security distribution, spend an evening on virtual machine settings, and never get to a flag. The real requirement is much smaller: a Linux userland, a working Python 3, and a handful of tools you add as you meet the categories that need them.
Install the distribution that gets you to a shell fastest, then add tools when a challenge demands them. A toolkit assembled by need is one you can actually use.
This guide covers the choices that matter (base system, Python isolation, Docker) and skips the ones that do not (which terminal theme, which distribution logo). If you are brand new to the platform side rather than the machine side, pair this with the picoCTF beginners guide, and with the General Skills roadmap for the shell fluency this assumes.
Choosing your base system
| Option | Best for | Trade-off |
|---|---|---|
| WSL2 (Ubuntu on Windows) | Windows users, fastest path to a shell | Shared kernel, so it is isolation-light |
| VM (VirtualBox or VMware) | Real isolation, snapshots, malware-adjacent work | Slower, more RAM, more setup |
| Native Linux | Best performance, no friction | Your daily machine is now the analysis machine |
| macOS with Docker | Mac users, since most tools assume Linux | Architecture mismatch on Apple silicon |
For almost everyone starting out, WSL2 with Ubuntu is the right answer on Windows and a plain Ubuntu or Debian VM is the right answer everywhere else. Kali and Parrot are fine but not required; they mostly save you a few apt install lines and cost you a much larger download.
# Windows, in an admin PowerShellwsl --install -d Ubuntu# then, inside the Ubuntu shellsudo apt update && sudo apt upgrade -ysudo apt install -y build-essential git curl wget vim python3 python3-pip python3-venv
docker run --platform linux/amd64) or under qemu-user. The ARM assembly guide covers the emulation side of the same problem from the other direction.The toolkit, by category
Install the base row first. Add each category row the day you meet a challenge from that category, not before.
It is worth knowing that these are not niche packages. Kali Linux ships several hundred security tools in its documented catalogue, which is precisely why installing Kali and calling it setup is the wrong move: the vast majority are for network penetration testing rather than CTF, and the dozen below cover the actual work. Every one of them is also in the Debian and Ubuntu archives, so you do not need a specialised distribution to have a complete CTF toolkit.
# Base: needed in every categorysudo apt install -y file binutils netcat-openbsd nmap jq unzip p7zip-fullsudo apt install -y xxd || sudo apt install -y vim-common # xxd moved packages# Forensicssudo apt install -y binwalk foremost steghide tshark libimage-exiftool-perl# Cryptography and password worksudo apt install -y hashcat johnpip install pycryptodome gmpy2 sympy# Reverse engineeringsudo apt install -y gdb radare2 ltrace strace# Ghidra: download the release archive from the NSA GitHub project and unpack it# Binary exploitationpip install pwntoolssudo apt install -y gcc-multilib patchelf# Websudo apt install -y curlpip install requests# ffuf, gobuster, and Burp Suite Community are separate downloads
Each of those tools has a full guide on this site when you get to it: GDB, Ghidra, radare2, pwntools, Wireshark, Burp Suite, hashcat and John, steganography tools, and Volatility.
git clone and one line in ~/.gdbinit.Two more that are worth having before you need them: a wordlist collection (SecLists, plusrockyou.txt which ships with Kali and is a download elsewhere), and Java, because Ghidra, Burp, and several decompilers all require a JRE.
Python without breaking your system
Modern Debian and Ubuntu refuse system-wide pip install, and they are right to. Use a virtual environment for CTF packages, or pipx for tools you want on your PATH.
python3 -m venv ~/ctf/venvsource ~/ctf/venv/bin/activatepip install pwntools pycryptodome requests sympy gmpy2 z3-solver angr# add this to ~/.bashrc so every new shell is readyalias ctf='source ~/ctf/venv/bin/activate'
Crypto, not pycryptodome, and installing the abandoned pycrypto alongside it breaks both. Similarly, install pwntools, not pwn. If an import fails immediately after a successful install, you are almost certainly in a different interpreter than the one pip wrote to.The Python for CTF guide covers what to do with these once installed, including the bytes-versus-string model that causes most first-script failures.
Docker for isolation and for matching the challenge environment
Docker earns its place for two distinct reasons. The first is isolation: a container is a reasonable place to run a binary you do not trust. The second matters more often and is less obvious: many pwn challenges ship a Dockerfile describing the exact libc the remote server uses, and your exploit will not work locally unless you match it.
sudo apt install -y docker.io && sudo usermod -aG docker $USER # then log out and back in# a throwaway analysis box, with the current directory mounteddocker run --rm -it -v "$PWD":/work -w /work ubuntu:22.04 bash# no network, for anything you actively distrustdocker run --rm -it --network none -v "$PWD":/work -w /work ubuntu:22.04 bash# build the challenge's own image when one is provideddocker build -t chal . && docker run --rm -p 1337:1337 chal
patchelf to bind the binary to the supplied libc, before you rewrite a single offset. The ret2libc guide explains why the offsets move.Matching the remote libc, the setup step that decides pwn challenges
This deserves its own section because it is the difference between a working exploit and an unexplainable failure, and because it is a setup problem rather than an exploitation problem. A binary loads whichever dynamic loader and C library your machine provides. If the remote box ships a different libc build, every offset you measured locally (the distance from a leaked symbol to system, the address of /bin/sh) is wrong by an amount you cannot guess.
Start by asking what the binary actually wants and what it is getting:
file ./chal # dynamically linked? which interpreter?ldd ./chal # which libraries resolve, and to what paths./libc.so.6 # a libc is executable: it prints its own versionstrings libc.so.6 | grep -m1 'GNU C Library'
When the challenge ships its own libc.so.6 and ld-linux-x86-64.so.2, bind the binary to them so your local runs match the remote exactly. Two ways, and the first is more reliable:
# 1. patchelf: rewrite the interpreter and the library search path into the binarypatchelf --set-interpreter ./ld-linux-x86-64.so.2 --set-rpath . ./challdd ./chal # confirm it now resolves to the provided libc# 2. or run it through the supplied loader without modifying the binary./ld-linux-x86-64.so.2 --library-path . ./chal# pwntools then reads symbols from the right libraryfrom pwn import ELF, contextlibc = ELF('./libc.so.6')print(hex(libc.symbols['system']), hex(next(libc.search(b'/bin/sh'))))
When no libc is provided, you are usually meant to identify it from a leak. Leak one resolved function address, take its low three hex digits (page alignment means those never change under ASLR), and search a libc database for the build whose symbol ends in those digits. Once identified, download that build and patch against it as above.
WSL2 specifics that bite
WSL2 is the most common setup on this site's traffic and it has four behaviours that look like bugs the first time you meet them.
- Keep files in the Linux filesystem. Work under
~/ctf, not under/mnt/c. Crossing the Windows filesystem boundary is dramatically slower, and operations that touch thousands of small files (an extracted archive, a wordlist, a git clone) can take minutes instead of seconds. - Cap the memory. By default WSL2 will grow to take a large share of your RAM, and a runaway process inside it can push the whole machine into swapping. Create
%UserProfile%\.wslconfigon Windows with amemory=line and restart withwsl --shutdown. - Ports are forwarded, mostly. A service you start in WSL is usually reachable at
localhostfrom Windows, but bind to0.0.0.0rather than127.0.0.1if a Windows browser cannot see it. - Systemd is off unless you enable it. Anything that expects
systemctl(Docker's service unit, for one) needssystemd=trueunder[boot]in/etc/wsl.conf, then a shutdown and restart.
# %UserProfile%\.wslconfig (Windows side)[wsl2]memory=8GBprocessors=4swap=2GB# /etc/wsl.conf (inside the distro)[boot]systemd=true
Workspace layout and keeping notes
A flat downloads folder is where solved challenges go to die. Give every challenge a directory and every event a parent, so that six months later you can find the script that already solved this exact pattern.
~/ctf/venv/wordlists/tools/ # ghidra, seclists, cloned repospicoctf-2026/heap-0/handout/ # untouched original filessolve.pynotes.md # what I tried, what worked, the flag
Two rules make this pay off. Keep the handout files pristine and work on copies, because you will corrupt a file at some point and re-downloading is not always possible. And write the notes file during the solve, not after, since the failed attempts are the part you will want later and the part you will forget first.
snippets.py with your XOR helper, your base64 wrapper, your pwntools skeleton, and your flag regex. Every returning competitor has one, and it compounds faster than any tool you install.Running untrusted binaries and files
CTF handouts are usually harmless, but the habits below are free and they are the same habits real analysis work requires. Treat every downloaded binary as hostile until you have read it.
- Look before you run.
file, thenstrings, then a decompiler. Running is the third step, not the first. - Run in a container or VM with no network when anything looks like it phones home, and take a snapshot first if you are in a VM.
- Never run a handout as root, and never on a machine holding anything you care about. WSL2 shares your Windows filesystem at
/mnt/c, which is convenient and also means a container escape is not the only way to reach your documents. - Be careful with archives. Extract into a fresh directory. Zip entries can carry paths, and a badly behaved archive can write outside where you expected.
The pitfalls that waste a day
- Line endings. A file edited on Windows and used in Linux carries
\r\n, and a script that hashes or compares it will silently produce the wrong answer. Rundos2unixor keep your editor in LF mode. - Permissions after download.
chmod +xis required before running anything, and files copied from an NTFS mount often arrive with strange modes. - 32-bit binaries on a 64-bit system.
bash: ./chal: No such file or directoryon a file that clearly exists means missing 32-bit libraries. Installlibc6:i386andgcc-multilib. - ASLR differences. Your local system randomizes addresses and the challenge server may not, or vice versa. Disable it locally with
setarch -Rwhile developing, then re-enable to test properly. See the ASLR and PIE guide. - Ptrace restrictions. If GDB cannot attach to a running process,
kernel.yama.ptrace_scopeis the reason. Set it to 0 for a debugging session. - Clock and locale. A VM with a wrong clock breaks TLS, which breaks
aptand every HTTPS request, and the error message never mentions time.
Verifying the setup with three real challenges
Do not trust an installation until it has solved something. These three are quick and each one exercises a different part of the stack.
- picoCTF 2021 Wave a flag proves you can download,
chmod +x, and execute a Linux binary. If this fails, your base system is the problem. - picoCTF 2021 Python Wrangling proves Python and pip work, including a third-party package import.
- picoCTF 2024 Super SSH proves outbound connectivity and SSH from inside your environment, which WSL and corporate networks both like to complicate.
from pwn import * works and the connection opens, your pwn setup is genuinely done.Quick reference
Setup in six commands
wsl --install -d Ubuntuor install a Debian-family VM.sudo apt update && sudo apt upgrade -ysudo apt install -y build-essential git python3-venv file binutils vim-common netcat-openbsdpython3 -m venv ~/ctf/venv && source ~/ctf/venv/bin/activatepip install pwntools pycryptodome requests- Add category tools the day you need them, not before.
When something does not run
Check file output, then the executable bit, then 32-bit libraries, then line endings, then the clock. That order resolves the large majority of "it works for everyone else" problems.
A working environment is a one-evening investment that pays for the rest of your CTF career. Once it is done, stop tuning it and go solve things: the General Skills roadmap is the natural next stop, and every category roadmap branches from there.
Sources and further reading
Setup advice ages badly, so prefer the vendor documentation over any blog post, including this one.
- Microsoft's WSL installation guide and the docker run reference for the isolation flags that matter when you execute an untrusted challenge binary.
- PEP 668 explains the externally-managed-environment error that now blocks a bare
pip installon Debian and Ubuntu, and the venv documentation is the supported answer to it. - pwntools installation, Ghidra, and the Kali tools catalogue if you want to see the full menu before deciding what to skip.