TL;DR
Terminator canaries (stack canaries) are a security measure to detect buffer overflows on the stack. They work by placing a known value (the canary) just before the return address on the stack. If a buffer overflow overwrites the canary, it indicates a potential attack. When the function returns, the program checks if the canary has been modified. If it has, the program terminates to prevent malicious code execution.
How Buffer Overflow with Canaries Works
Stack canaries don’t prevent buffer overflows; they detect them. Here’s how:
- Canary Placement: When a function is called, the compiler inserts a random value (the canary) onto the stack immediately before the saved return address.
- Overflow Attempt: If a buffer overflow occurs within that function, it may overwrite the canary value.
- Return Check: Before returning from the function, the program checks if the canary value has changed.
- Detection & Termination: If the canary is different from its original value, the program knows a buffer overflow likely happened and terminates (often with an error message like ‘Stack smashing detected’).
Steps to Understand and Test Canary Protection
- Compilation with Canaries: Most compilers enable stack canaries by default. However, you might need to explicitly enable them using compiler flags. For GCC, use the
-fstack-protectorflag.gcc -fstack-protector vulnerable_program.c -o vulnerable_program - Vulnerable Code Example: Consider this simple C code:
#include <stdio.h> #include <string.h> int main() { char buffer[64]; printf("Enter input: "); gets(buffer); // Vulnerable function! printf("You entered: %sn", buffer); return 0; }This code uses
gets(), which doesn’t perform bounds checking. - Attempting an Overflow (Without Canaries): If you compile this without canaries and provide input larger than 64 bytes, you can overwrite the return address and potentially hijack execution.
- Attempting an Overflow (With Canaries): When compiled with
-fstack-protector, attempting the same overflow will likely result in a ‘Stack smashing detected’ error before your malicious code runs. The program terminates because the canary was overwritten. - Disabling Canaries (For Testing – Use Caution!): You can disable stack canaries for testing purposes using the
-fno-stack-protectorflag. However, this makes your program vulnerable and should only be done in a controlled environment.gcc -fno-stack-protector vulnerable_program.c -o vulnerable_program - Examining the Stack (GDB): Use GDB to examine the stack layout and see how the canary is placed.
- Start GDB:
gdb ./vulnerable_program - Set a breakpoint before the return:
break main - Run the program:
run - Examine the stack:
x/20wx $rsp(This shows 20 words starting from the stack pointer)
You should see a random value (the canary) just before the return address.
- Start GDB:
Bypassing Canaries
Canary protection isn’t foolproof. Attackers can sometimes bypass it using techniques like:
- Information Leaks: If an attacker can leak the canary value (e.g., through a format string vulnerability), they can include it in their overflow payload.
- Multiple Overflows: In some cases, multiple smaller overflows can be used to overwrite parts of the stack without triggering the canary check.
- Return-Oriented Programming (ROP): ROP allows attackers to chain together existing code snippets within the program to achieve their goals, even if they don’t have direct control over the return address.
Mitigation Strategies
- Use Safe Functions: Avoid vulnerable functions like
gets()and use safer alternatives likefgets()that allow you to specify a maximum buffer size. - Address Space Layout Randomization (ASLR): ASLR makes it harder for attackers to predict the location of important data, including the canary value.
- Data Execution Prevention (DEP/NX): DEP prevents code from being executed in memory regions that are intended for data storage.

