Tools · CTF categories for beginners · 11 / 18
Binary protections explained with checksec: NX, PIE, stack canaries, and RELRO
Read checksec output, understand what each mitigation does and why it exists, and compile the same program with and without protections to see the difference.
Mitigations in one place
Modern compilers and operating systems add defenses that make memory corruption bugs harder to exploit. NX (no-execute) marks the stack and heap non-executable. PIE (position-independent executable) lets the loader place the program at a random address, working with ASLR. Stack canaries place a secret value before the saved return address and abort if it changes. RELRO makes parts of the dynamic linking tables read-only after startup. FORTIFY_SOURCE adds bounds checks to some libc calls.
In CTF pwn challenges, the first step is always to see which protections are enabled, because that shapes what the challenge is really about.
Reading checksec
checksec from pwntools or the standalone script prints each protection. Distribution packages are normally built with all of them on.
pip install pwntools # provides the checksec command checksec --file=/usr/bin/ls readelf -h ./prog | grep Type # DYN means PIE readelf -l ./prog | grep -A1 GNU_STACK # RW (no E) means NX readelf -d ./prog | grep -E 'BIND_NOW|FLAGS'
Lab: compile both ways
Compile one small C program twice: once with default hardening and once with protections disabled. Compare checksec output side by side. Then run the unprotected one under gdb with ASLR disabled and observe that addresses are the same on each run, while the PIE build with ASLR on moves each time. This shows why mitigations exist; it does not require writing any exploit.
gcc -O2 -fstack-protector-strong -D_FORTIFY_SOURCE=2 -pie -fPIE -Wl,-z,relro,-z,now -o hardened prog.c gcc -O0 -fno-stack-protector -no-pie -z execstack -o soft prog.c checksec --file=hardened; checksec --file=soft for i in 1 2 3; do ./hardened & sleep 0.2; grep hardened /proc/$!/maps | head -1; kill $!; done
The defensive takeaway
For software you ship, keep every mitigation on, use memory-safe languages where possible, and treat a missing protection in a build as a bug. Mitigations raise the cost of exploitation; they do not remove the underlying bug, so fixing memory safety issues still matters.
Diagram
Common binary mitigations
What each one protects.
- NX
Data regions are not executable.
- PIE + ASLR
Code loads at a random address each run.
- Stack canary
Detects overwrites of the saved return address.
- Full RELRO
Linking tables become read-only.
- FORTIFY_SOURCE
Bounds checks on some libc functions.
Check yourself
1. Which mitigation makes the stack non-executable?
2. What does a stack canary detect?
3. Do mitigations remove the need to fix memory safety bugs?