Hacktricks-skills stack-canary-exploitation
How to understand, detect, and bypass stack canary protections in binary exploitation. Use this skill whenever the user mentions stack canaries, stack smashing, buffer overflow protections, __stack_chk_fail, or any CTF/pwn challenge involving stack-based mitigations. Also trigger when analyzing binaries with -fstack-protector, discussing canary leaks, or working on challenges where the stack protection is preventing exploitation.
git clone https://github.com/abelrguezr/hacktricks-skills
skills/binary-exploitation/common-binary-protections-and-bypasses/stack-canaries/stack-canaries/SKILL.MDStack Canary Exploitation
This skill covers understanding and bypassing stack canary protections in binary exploitation challenges.
What Are Stack Canaries?
Stack canaries are a defense mechanism against buffer overflow attacks. A special random value (the "canary") is placed on the stack before the return address. Before a function returns, the program checks if the canary is still intact. If it's been modified, the program calls
__stack_chk_fail() and terminates.
Key Properties
x64 binaries:
- Canary is 8 bytes (qword)
- First 7 bytes are random
- Last byte is null (
)0x00 - Stored at
fs:0x28
x86 binaries:
- Canary is 4 bytes (dword)
- First 3 bytes are random
- Last byte is null (
)0x00 - Stored at
gs:0x14
The null byte is least significant because it comes first on the stack (lower addresses). String functions stop at null bytes, so they won't read past the canary.
Detection
Check if canaries are enabled
# Using checksec checksec --file=binary # Look for __stack_chk_fail in symbols nm binary | grep stack_chk objdump -t binary | grep stack_chk # Check compilation flags readelf -p .comment binary | grep -i stack
Identify protected functions
# Find functions with stack protector objdump -d binary | grep -A 20 "<function_name>" # Look for canary check patterns in disassembly
Bypass Techniques
1. Leak the Canary
If you can read memory, leak the canary value and preserve it during your overflow.
Common leak sources:
- Format string vulnerabilities
- Arbitrary read primitives
- Information disclosure bugs
- Debug symbols in the binary
Leak from TLS (x64):
# Canary is at fs:0x28 # Use a read primitive to read from this address
Leak from TLS (x86):
# Canary is at gs:0x14
2. Brute-Force (Forked Processes)
If the binary uses
fork() without execve(), child processes inherit the same canary. You can brute-force it byte-by-byte:
- Send payload with first byte of canary
- If program doesn't crash, that byte is correct
- Repeat for remaining bytes
- x86: 3 random bytes = 2^24 attempts worst case
- x64: 7 random bytes = 2^56 attempts (not feasible)
When this works:
- Web servers using
fork() - Interactive programs that fork for each input
- Programs that don't call
afterexecve()fork()
3. Modify GOT Entry of __stack_chk_fail
__stack_chk_failIf the binary has Partial RELRO (not Full RELRO), you can overwrite the GOT entry:
- Find
in GOT__stack_chk_fail - Use arbitrary write to change it to a NOP or dummy function
- Now canary checks won't terminate the program
Check RELRO status:
readelf -l binary | grep -i relro # "RELRO" without "Full" means Partial RELRO
4. Master Canary Forgery (Threaded Binaries)
In multi-threaded programs, you might be able to modify the master canary in TLS:
- Overflow a buffer in a thread's stack
- Reach the TLS memory region (also mmap'd)
- Modify the master canary value
- The check compares two identical (modified) canaries
This works because thread stacks and TLS are both allocated via
mmap().
5. Overwrite Stack Pointers
Instead of reaching the return address, overwrite pointers stored on the stack:
- Function pointers
- String pointers
- File descriptors
- Any address stored in local variables
This bypasses the canary entirely by redirecting execution before the check.
Practical Workflow
Step 1: Analyze the Binary
# Check protections checksec --file=binary # Look for canary-related symbols nm binary | grep -E "(stack_chk|__stack)" # Check for format strings (potential leak) grep -r "printf\|scanf\|gets" source.c 2>/dev/null
Step 2: Identify Attack Surface
- Are there format string vulnerabilities? → Leak canary
- Is there arbitrary read/write? → Leak or modify GOT
- Does it fork without exec? → Brute-force (x86 only)
- Is it multi-threaded? → Master canary forgery
- Are there stack-stored pointers? → Pointer redirection
Step 3: Choose Bypass Method
| Scenario | Best Bypass |
|---|---|
| Format string vuln | Leak canary |
| Arbitrary read | Leak from TLS |
| Partial RELRO | Modify GOT |
| Fork without exec (x86) | Brute-force |
| Multi-threaded | Master canary forgery |
| Stack pointers present | Pointer redirection |
Step 4: Implement the Exploit
Template for canary leak + overflow:
from pwn import * context.binary = elf = ELF('./binary') context.arch = 'x64' # or 'i386' # Connect p = process(elf.path) # Leak canary (example via format string) # Adjust based on actual vulnerability leak = p.recvuntil(b'%p') canary = u64(p.recv(8).ljust(8, b'\x00')) print(f"Leaked canary: {hex(canary)}") # Craft payload preserving canary payload = b'A' * offset_to_canary payload += p64(canary) # Preserve canary payload += b'B' * padding payload += p64(rop_chain) # Your actual payload # Send exploit p.sendline(payload) p.interactive()
Common Pitfalls
- Wrong canary size: x64 = 8 bytes, x86 = 4 bytes
- Endianness: Use
/p64()
for proper encodingp32() - Null byte: Remember the last byte is null, don't overwrite it
- TLS addresses:
(x64),fs:0x28
(x86)gs:0x14 - Fork semantics:
shares canary,fork()
doesn'texecve()
Tools
- checksec: Check binary protections
- nm/objdump: Find canary-related symbols
- GDB: Debug and inspect stack
- pwntools: Automate exploitation