LEARN SECURE C AND EXPLOIT AWARENESS
Learn Secure C Programming & Exploit Awareness: From Vulnerable Code to Bulletproof Defense
Goal: Deeply understand secure coding practices in C—from bounds checking and integer overflow defense to understanding the very exploits you’re defending against (stack smashing, heap exploitation, format strings).
Why Learn Secure C Programming?
C gives you power—and with that power comes the responsibility to not shoot yourself (or your users) in the foot. Most critical vulnerabilities in operating systems, browsers, and infrastructure are C memory corruption bugs.
After completing these projects, you will:
- Write C code that resists buffer overflows, integer overflows, and format string attacks
- Understand exactly how attackers exploit vulnerable code
- Choose safe APIs over dangerous legacy functions
- Audit code for security vulnerabilities
- Think like both a defender and an attacker
- Build tools that detect and prevent security issues
Core Concept Analysis
The Security Landscape in C
┌─────────────────────────────────────────────────────────────────────────┐
│ VULNERABLE C CODE │
│ │
│ char buf[64]; │
│ gets(buf); // No bounds checking! │
│ printf(buf); // Format string vuln! │
│ int size = len * 4; // Integer overflow! │
└─────────────────────────────────────────────────────────────────────────┘
│
┌──────────────────────┼──────────────────────┐
▼ ▼ ▼
┌──────────────────┐ ┌──────────────────┐ ┌──────────────────┐
│ STACK SMASHING │ │ HEAP EXPLOITATION│ │ FORMAT STRING │
│ │ │ │ │ │
│ • Buffer overflow│ │ • Use-after-free │ │ • %n writes │
│ • Return address │ │ • Heap overflow │ │ • %x info leak │
│ • ROP chains │ │ • Double free │ │ • Arbitrary R/W │
│ • Stack canary │ │ • Chunk metadata │ │ • GOT overwrite │
└──────────────────┘ └──────────────────┘ └──────────────────┘
│
▼
┌─────────────────────────────────────────────────────────────────────────┐
│ SECURE CODING PRACTICES │
│ │
│ • Bounds checking (strncpy, snprintf, strlcpy) │
│ • Integer overflow defense (safe arithmetic) │
│ • Safe APIs vs legacy (fgets vs gets) │
│ • Input validation │
│ • Defense in depth │
└─────────────────────────────────────────────────────────────────────────┘
Key Concepts Explained
1. Bounds Checking
The #1 cause of security vulnerabilities in C: writing past the end of buffers.
The Problem
char buffer[64];
strcpy(buffer, user_input); // What if user_input is 200 bytes?
┌─────────────┐ ┌─────────────┐
│ buffer[64] │ ───overflow───────► │ Return Addr │ CORRUPTED!
└─────────────┘ └─────────────┘
Safe Alternatives
| Dangerous Function | Safe Alternative | Notes |
|---|---|---|
gets(buf) |
fgets(buf, size, stdin) |
Always specify size |
strcpy(dst, src) |
strncpy(dst, src, n) |
NUL-termination issue! |
strcat(dst, src) |
strncat(dst, src, n) |
n = remaining space |
sprintf(buf, fmt) |
snprintf(buf, n, fmt) |
Returns chars needed |
scanf("%s", buf) |
scanf("%63s", buf) |
Width specifier |
The strncpy Trap
// WRONG: strncpy doesn't guarantee NUL termination!
char dest[10];
strncpy(dest, "This is a very long string", 10);
// dest is NOT NUL-terminated!
// CORRECT: Always ensure NUL termination
strncpy(dest, src, sizeof(dest) - 1);
dest[sizeof(dest) - 1] = '\0';
// BETTER: Use strlcpy (BSD) or write your own
size_t strlcpy(char *dst, const char *src, size_t size);
2. Integer Overflow Defense
When arithmetic wraps around, bad things happen.
The Problem
// Unsigned overflow wraps around
unsigned int a = UINT_MAX; // 4294967295
unsigned int b = a + 1; // 0 (wrapped!)
// Size calculation overflow
size_t count = 1000000000;
size_t size = 4;
size_t total = count * size; // Overflow! Allocates tiny buffer
void *buf = malloc(total); // Allocates wrong amount
memcpy(buf, data, count * 4); // Massive overflow!
Safe Arithmetic Patterns
┌─────────────────────────────────────────────────────────────┐
│ SAFE MULTIPLICATION │
├─────────────────────────────────────────────────────────────┤
│ if (a != 0 && b > SIZE_MAX / a) { │
│ // Overflow would occur │
│ return ERROR; │
│ } │
│ result = a * b; // Safe now │
├─────────────────────────────────────────────────────────────┤
│ SAFE ADDITION │
├─────────────────────────────────────────────────────────────┤
│ if (a > SIZE_MAX - b) { │
│ // Overflow would occur │
│ return ERROR; │
│ } │
│ result = a + b; // Safe now │
└─────────────────────────────────────────────────────────────┘
3. Safe APIs vs Legacy APIs
The Hall of Shame (Never Use)
// NEVER USE THESE
gets(buf); // No bounds check AT ALL - removed in C11!
sprintf(buf, fmt, ...); // No bounds check
strcpy(dst, src); // No bounds check
strcat(dst, src); // No bounds check
scanf("%s", buf); // No bounds check
vsprintf(); // No bounds check
The Safe Replacements
// ALWAYS USE THESE
fgets(buf, sizeof(buf), stdin); // Bounds-checked input
snprintf(buf, sizeof(buf), fmt, ...); // Bounds-checked format
strncpy(dst, src, sizeof(dst)); // Size-limited (careful!)
strlcpy(dst, src, sizeof(dst)); // BSD safe copy
strlcat(dst, src, sizeof(dst)); // BSD safe concat
scanf("%63s", buf); // Width-limited
4. Stack Smashing (Buffer Overflow on Stack)
Memory Layout During Overflow
Normal Stack Frame:
┌──────────────────────────────────────────┐
│ Return Address │ ← Saved by CALL
├──────────────────────────────────────────┤
│ Saved RBP │ ← Function prologue
├──────────────────────────────────────────┤
│ Canary Value │ ← Stack protector
├──────────────────────────────────────────┤
│ Local Variables │
├──────────────────────────────────────────┤
│ Buffer[64] │ ← User input here
└──────────────────────────────────────────┘
After Overflow:
┌──────────────────────────────────────────┐
│ AAAA (attacker address) │ ← Hijacked!
├──────────────────────────────────────────┤
│ AAAA │ ← Overwritten
├──────────────────────────────────────────┤
│ AAAA │ ← Canary corrupted!
├──────────────────────────────────────────┤
│ AAAA │ ← Overwritten
├──────────────────────────────────────────┤
│ AAAAAAAAAA... │ ← Overflow starts
└──────────────────────────────────────────┘
Modern Defenses
| Defense | What It Does | How Attackers Bypass |
|---|---|---|
| Stack Canary | Random value before return addr; checked on return | Info leak to discover value |
| ASLR | Randomize memory layout | Info leak + partial overwrite |
| NX/DEP | Stack not executable | ROP (return-oriented programming) |
| PIE | Randomize code addresses | Info leak |
5. Heap Exploitation
The heap is more complex than the stack—and so are its vulnerabilities.
Heap Memory Layout (glibc malloc)
┌─────────────────────────────────────────────────────────────┐
│ ALLOCATED CHUNK │
├────────────────────────────┬────────────────────────────────┤
│ prev_size (8 bytes) │ size (8 bytes) │
│ (if prev is free) │ includes flags (P,M,A) │
├────────────────────────────┴────────────────────────────────┤
│ │
│ USER DATA │
│ │
└─────────────────────────────────────────────────────────────┘
┌─────────────────────────────────────────────────────────────┐
│ FREE CHUNK │
├────────────────────────────┬────────────────────────────────┤
│ prev_size (8 bytes) │ size (8 bytes) │
├────────────────────────────┴────────────────────────────────┤
│ fd (forward ptr) │
├─────────────────────────────────────────────────────────────┤
│ bk (backward ptr) │
├─────────────────────────────────────────────────────────────┤
│ (unused space) │
└─────────────────────────────────────────────────────────────┘
Common Heap Vulnerabilities
1. USE-AFTER-FREE
free(ptr);
// ... ptr used again without reassignment ...
ptr->data = value; // Writing to freed memory!
2. DOUBLE FREE
free(ptr);
free(ptr); // Corrupts heap metadata!
3. HEAP OVERFLOW
char *buf = malloc(64);
strcpy(buf, long_string); // Overwrites next chunk's metadata!
4. NULL POINTER DEREFERENCE
char *ptr = malloc(size);
// Forgot to check if ptr == NULL
*ptr = 'A'; // Crash or exploit on some systems
6. Format String Vulnerabilities
One of the most dangerous and underappreciated vulnerability classes.
The Problem
char *user_input = "%x %x %x %x %n";
printf(user_input); // NEVER DO THIS!
// What happens:
// %x - reads values from stack (info leak)
// %n - WRITES number of chars printed to address on stack!
Format String Powers
┌─────────────────────────────────────────────────────────────┐
│ Format Specifier │ What It Does │
├────────────────────┼────────────────────────────────────────┤
│ %x │ Read 4 bytes from stack (hex) │
│ %lx │ Read 8 bytes from stack (hex) │
│ %s │ Read string from address on stack │
│ %n │ WRITE count of chars to address! │
│ %hn │ Write 2 bytes (short) │
│ %hhn │ Write 1 byte │
│ %7$x │ Direct parameter access (7th arg) │
└─────────────────────────────────────────────────────────────┘
Attack Example:
printf(user_input);
If user_input = "%x.%x.%x.%x"
Output: deadbeef.cafebabe.12345678.87654321
^^^^^^^^ ^^^^^^^^ ^^^^^^^^ ^^^^^^^^
Stack values leaked!
If user_input = "AAAA%7$n"
Writes 4 (length of "AAAA") to address 0x41414141!
Project List
The following 15 projects will teach you secure C programming from defensive techniques to understanding attacker methodologies.
Project 1: Safe String Library
- File: LEARN_SECURE_C_AND_EXPLOIT_AWARENESS.md
- Main Programming Language: C
- Alternative Programming Languages: Rust, Go
- Coolness Level: Level 3: Genuinely Clever
- Business Potential: 4. The “Open Core” Infrastructure
- Difficulty: Level 2: Intermediate
- Knowledge Area: Secure Coding / String Handling
- Software or Tool: GCC, Valgrind, AddressSanitizer
- Main Book: “Effective C, 2nd Edition” by Robert C. Seacord
What you’ll build: A complete safe string library implementing strlcpy(), strlcat(), safe snprintf() wrappers, and bounded string operations that prevent buffer overflows.
Why it teaches secure coding: Strings are the #1 source of buffer overflows in C. Building your own safe library forces you to understand exactly why strcpy() is dangerous and how to prevent overflows.
Core challenges you’ll face:
- Handling edge cases → maps to empty strings, NULL pointers, zero-length buffers
- Ensuring NUL termination → maps to strncpy doesn’t guarantee it
- Calculating remaining space → maps to preventing off-by-one errors
- Return value design → maps to detecting truncation
Resources for key challenges:
- “Effective C, 2nd Edition” Chapter 5 - Strings and arrays
- OpenBSD strlcpy/strlcat man pages - The reference implementation
- CERT C Secure Coding Standard - STR07-C through STR38-C
Key Concepts:
- String Representation in C: “C Programming: A Modern Approach” Ch. 13 - K.N. King
- Buffer Overflow Prevention: “Effective C, 2nd Edition” Ch. 5 - Seacord
- Safe String Functions: OpenBSD strlcpy(3) man page
Difficulty: Intermediate Time estimate: 1 week Prerequisites: Understanding of C pointers, arrays, and memory layout
Real world outcome:
$ ./test_safestring
Testing safe_strlcpy...
✓ Normal copy: "Hello" -> "Hello"
✓ Truncation: "Very long string" -> "Very lon" (truncated, returns 16)
✓ Zero-length buffer: Returns strlen(src), no write
✓ NULL source: Returns 0, dest unchanged
Testing safe_strlcat...
✓ Normal concat: "Hello" + " World" -> "Hello World"
✓ Truncation: Properly truncated, returns total needed
✓ Full buffer: No overflow, returns needed length
Testing safe_snprintf wrapper...
✓ Format string attacks blocked
✓ Truncation detected: snprintf_safe returned -1
✓ Always NUL-terminated
All 15 tests passed!
Implementation Hints:
Key design decisions for safe_strlcpy():
-
Return value: Return the total length of the string that WOULD have been created (like OpenBSD strlcpy). This lets callers detect truncation:
if (strlcpy(dst, src, size) >= size) { /* truncated */ } - Edge cases to handle:
- What if
sizeis 0? (Don’t write anything, return strlen(src)) - What if
srcis NULL? (Define behavior: return 0? crash? assert?) - What if
dstis NULL? (Only valid if size is 0)
- What if
- NUL termination guarantee: Unlike
strncpy(), ALWAYS NUL-terminate (when size > 0)
Questions to guide implementation:
- How does
strncpy()fail to be safe? (Hint: no NUL termination when src >= n) - Why is the return value important for security?
- What’s the difference between
strlcpy()andsnprintf("%s", ...)? - How would you handle multi-byte (UTF-8) strings?
Learning milestones:
- Implement strlcpy/strlcat → Understand size-limited string operations
- Handle all edge cases → Build robust code
- Create snprintf wrapper → Detect truncation automatically
- Write comprehensive tests → Prove correctness
Project 2: Integer Overflow Detection Library
- File: LEARN_SECURE_C_AND_EXPLOIT_AWARENESS.md
- Main Programming Language: C
- Alternative Programming Languages: Rust (has built-in!), C++
- Coolness Level: Level 3: Genuinely Clever
- Business Potential: 3. The “Service & Support” Model
- Difficulty: Level 2: Intermediate
- Knowledge Area: Secure Coding / Arithmetic Safety
- Software or Tool: GCC builtins, UBSan
- Main Book: “Secure Coding in C and C++” by Robert C. Seacord
What you’ll build: A library of safe arithmetic functions that detect and prevent integer overflow for addition, subtraction, multiplication, and division across all integer types.
Why it teaches secure coding: Integer overflows cause malloc() to allocate tiny buffers, loop counters to wrap around, and size calculations to become negative—all leading to exploitable vulnerabilities.
Core challenges you’ll face:
- Detecting overflow before it happens → maps to pre-check patterns
- Handling signed vs unsigned → maps to different overflow behavior
- Type width differences → maps to int, long, size_t variations
- Performance considerations → maps to compiler builtins vs manual checks
Resources for key challenges:
- “Secure Coding in C and C++” Chapter 5 - Integer Security
- GCC __builtin_add_overflow documentation
- CERT C: INT30-C through INT36-C
Key Concepts:
- Two’s Complement Arithmetic: “Computer Systems: A Programmer’s Perspective” Ch. 2
- Integer Overflow Patterns: CERT C INT32-C
- Safe Integer Libraries: SafeInt library documentation
Difficulty: Intermediate Time estimate: 1 week Prerequisites: Understanding of integer representation, two’s complement
Real world outcome:
$ ./test_safe_math
Testing safe_add_size_t...
✓ 1000 + 2000 = 3000 (no overflow)
✓ SIZE_MAX + 1 = OVERFLOW DETECTED
✓ SIZE_MAX/2 + SIZE_MAX/2 = SIZE_MAX-1 (no overflow)
Testing safe_mul_size_t...
✓ 1000 * 1000 = 1000000 (no overflow)
✓ SIZE_MAX * 2 = OVERFLOW DETECTED
✓ 1000000000 * 5 = OVERFLOW DETECTED (on 32-bit size_t)
Testing safe_alloc_array...
✓ alloc_array(1000000, 4) detected overflow, returned NULL
✓ alloc_array(1000, 4) = 4000 bytes allocated
Testing CVE simulation...
Simulating CVE-2021-XXXX (count * element_size overflow)
✓ Vulnerable code: Allocates 64 bytes, copies 4GB!
✓ Safe code: Detects overflow, returns error
All tests passed!
Implementation Hints:
Two approaches:
Approach 1: Manual Pre-Checks
// For unsigned addition: check if result would wrap
// if a + b would overflow, then b > MAX - a
bool safe_add_size_t(size_t a, size_t b, size_t *result) {
if (b > SIZE_MAX - a) {
return false; // Would overflow
}
*result = a + b;
return true;
}
// For unsigned multiplication: check if result would wrap
// if a * b would overflow, then b > MAX / a (when a != 0)
bool safe_mul_size_t(size_t a, size_t b, size_t *result) {
if (a != 0 && b > SIZE_MAX / a) {
return false; // Would overflow
}
*result = a * b;
return true;
}
Approach 2: GCC/Clang Builtins (preferred)
bool safe_add_size_t(size_t a, size_t b, size_t *result) {
return !__builtin_add_overflow(a, b, result);
}
Key questions:
- Why is signed overflow undefined behavior in C?
- How does two’s complement representation affect overflow detection?
- Why is
a * b / a == bnot a reliable overflow check?
Learning milestones:
- Implement unsigned overflow checks → Addition and multiplication
- Handle signed integers → Understand undefined behavior
- Use compiler builtins → Learn modern approach
- Create safe_calloc wrapper → Practical application
Project 3: Vulnerable Program Laboratory
- File: LEARN_SECURE_C_AND_EXPLOIT_AWARENESS.md
- Main Programming Language: C
- Alternative Programming Languages: N/A (must be C for realistic vulns)
- Coolness Level: Level 4: Hardcore Tech Flex
- Business Potential: 1. The “Resume Gold”
- Difficulty: Level 2: Intermediate
- Knowledge Area: Exploit Awareness / Vulnerability Classes
- Software or Tool: GCC with security flags disabled, GDB
- Main Book: “Hacking: The Art of Exploitation” by Jon Erickson
What you’ll build: A collection of intentionally vulnerable programs demonstrating each vulnerability class (stack overflow, heap overflow, format string, integer overflow), with documentation explaining how each can be exploited.
Why it teaches exploit awareness: You can’t defend against what you don’t understand. Building vulnerable programs and understanding exactly how they fail teaches you to recognize these patterns in real code.
Core challenges you’ll face:
- Disabling security features → maps to understanding what each protects
- Creating exploitable conditions → maps to understanding attacker requirements
- Documenting exploitation → maps to clear threat model
- Varying difficulty levels → maps to progressive learning
Resources for key challenges:
- “Hacking: The Art of Exploitation” Chapters 2-5
- picoCTF binary exploitation challenges - See real examples
- Protostar/Nebula exercises - Classic vulnerable VMs
Key Concepts:
- Stack Buffer Overflow: “Hacking: Art of Exploitation” Ch. 2
- Format String Exploitation: “Hacking: Art of Exploitation” Ch. 5
- Heap Exploitation Basics: “The Shellcoder’s Handbook” Ch. 6
Difficulty: Intermediate Time estimate: 2 weeks Prerequisites: Basic C, understanding of memory layout
Real world outcome:
vuln-lab/
├── stack/
│ ├── 01_basic_overflow.c # gets() buffer overflow
│ ├── 02_fixed_offset.c # Known offset to return address
│ ├── 03_with_canary.c # Stack canary to bypass
│ ├── 04_aslr_enabled.c # Need info leak
│ └── README.md # Exploitation walkthrough
├── format_string/
│ ├── 01_info_leak.c # Read stack with %x
│ ├── 02_arbitrary_read.c # Read any address with %s
│ ├── 03_arbitrary_write.c # Write with %n
│ └── README.md
├── integer/
│ ├── 01_size_overflow.c # malloc(n * size) overflow
│ ├── 02_signed_comparison.c # Negative length bypass
│ └── README.md
├── heap/
│ ├── 01_use_after_free.c # Dangling pointer
│ ├── 02_double_free.c # Heap corruption
│ ├── 03_heap_overflow.c # Overwrite chunk metadata
│ └── README.md
├── Makefile # Compile with/without protections
└── solutions/ # Exploit scripts (for learning)
Implementation Hints:
Each vulnerable program should:
- Have a clear, simple vulnerability
- Print helpful messages for learners
- Compile with a specific set of disabled protections
- Have an accompanying exploit script
Example Makefile:
# Disable ALL protections (educational only!)
VULN_FLAGS = -fno-stack-protector -z execstack -no-pie -Wno-format-security
# Enable ALL protections (for comparison)
SAFE_FLAGS = -fstack-protector-strong -D_FORTIFY_SOURCE=2 -pie -Wformat-security
Basic stack overflow template questions:
- How many bytes from buffer start to saved return address?
- What address should the return pointer be overwritten with?
- If NX is enabled, what technique bypasses it?
Learning milestones:
- Create basic overflow → Understand memory layout
- Create format string vuln → Understand printf internals
- Create integer overflow → Understand arithmetic attacks
- Create heap corruption → Understand allocator metadata
Project 4: Stack Canary Implementation
- File: LEARN_SECURE_C_AND_EXPLOIT_AWARENESS.md
- Main Programming Language: C (with inline assembly)
- Alternative Programming Languages: Assembly
- Coolness Level: Level 4: Hardcore Tech Flex
- Business Potential: 1. The “Resume Gold”
- Difficulty: Level 3: Advanced
- Knowledge Area: Exploit Defense / Compiler Security Features
- Software or Tool: GCC, objdump, GDB
- Main Book: “Computer Systems: A Programmer’s Perspective” by Bryant & O’Hallaron
What you’ll build: Your own stack canary protection mechanism—generating random canaries, inserting them in function prologues, and checking them in epilogues.
Why it teaches secure coding: Understanding how stack canaries work (and their limitations) helps you understand both the defense mechanism and how sophisticated attacks bypass them.
Core challenges you’ll face:
- Generating random canaries → maps to entropy sources, /dev/urandom
- Storing the master canary → maps to TLS or global storage
- Inserting checks → maps to function prologue/epilogue modification
- Terminating on failure → maps to safe crash behavior
Resources for key challenges:
- GCC stack-protector source code - See real implementation
- “Smashing the Stack for Fun and Profit” - Aleph One (to understand the attack)
- ProPolice paper - Original stack protector design
Key Concepts:
- Stack Frame Layout: “CSAPP” Ch. 3.7
- Function Prologue/Epilogue: “The Art of Assembly” Ch. 5
- Thread-Local Storage: “The Linux Programming Interface” Ch. 31
Difficulty: Advanced Time estimate: 2 weeks Prerequisites: Project 3, assembly language basics
Real world outcome:
$ ./canary_demo
Initializing stack canary protection...
Canary value: 0x00a83f9b7c4d2e1f (randomized)
Running protected function...
Function prologue: Canary placed on stack
Function body: Simulating buffer overflow...
Function epilogue: Checking canary...
*** STACK SMASHING DETECTED ***
Canary corrupted: expected 0x00a83f9b7c4d2e1f, got 0x4141414141414141
Aborting.
$ ./compare_with_gcc
GCC -fstack-protector-strong generates:
mov rax, QWORD PTR fs:0x28 ; Load canary from TLS
mov QWORD PTR [rbp-0x8], rax ; Store on stack
...
mov rax, QWORD PTR [rbp-0x8] ; Load from stack
xor rax, QWORD PTR fs:0x28 ; Compare with TLS
jne __stack_chk_fail ; Abort if different
Our implementation generates similar code!
Implementation Hints:
Stack canary design decisions:
-
Canary value: Should contain a NUL byte (0x00) as the first or last byte. This stops string operations (
strcpy,strcat) from copying past the canary. - Placement: Goes between local variables and saved frame pointer:
[Return Address] [Saved RBP] [CANARY] ← Attacker must overwrite this to reach return addr [Local Variables] [Buffer] ← Overflow starts here - Per-thread storage: Use thread-local storage (TLS) so each thread has its own reference canary. On x86-64 Linux, GCC uses
fs:0x28.
Implementation approach:
- At program start: Generate random canary using
/dev/urandom - Store in TLS or global (simpler for learning)
- In each protected function:
- Prologue: Copy canary to stack
- Epilogue: Compare stack canary with stored value
- If mismatch: Call abort() with message
Questions to consider:
- Why include a NUL byte in the canary?
- How can attackers bypass canaries? (Info leak)
- What’s the difference between
-fstack-protectorand-fstack-protector-strong?
Learning milestones:
- Generate random canary → Use /dev/urandom
- Insert canary in function → Modify prologue/epilogue
- Detect overflow → Compare and abort
- Compare with GCC → Analyze real implementation
Project 5: Format String Vulnerability Demonstrator
- File: LEARN_SECURE_C_AND_EXPLOIT_AWARENESS.md
- Main Programming Language: C
- Alternative Programming Languages: Python (for exploits)
- Coolness Level: Level 4: Hardcore Tech Flex
- Business Potential: 1. The “Resume Gold”
- Difficulty: Level 3: Advanced
- Knowledge Area: Exploit Awareness / Format Strings
- Software or Tool: GDB, pwntools, printf source
- Main Book: “Hacking: The Art of Exploitation” by Jon Erickson
What you’ll build: A comprehensive demonstration of format string attacks—reading stack values, reading arbitrary memory, writing to arbitrary memory, and achieving code execution.
Why it teaches exploit awareness: Format string bugs are underestimated but devastating. Understanding exactly how %n writes memory is crucial for both offense and defense.
Core challenges you’ll face:
- Understanding printf internals → maps to variadic functions, va_list
- Leaking stack values → maps to %x and %p usage
- Arbitrary read with %s → maps to controlling address on stack
- Arbitrary write with %n → maps to byte-by-byte writing
Resources for key challenges:
- “Hacking: Art of Exploitation” Chapter 5 - Format Strings
- Exploiting Format String Vulnerabilities (USENIX paper)
- scut/team teso formatstring paper
Key Concepts:
- Variadic Functions in C: “C Programming: A Modern Approach” Ch. 26
- Format String Exploitation: “Hacking: Art of Exploitation” Ch. 5
- RELRO and GOT Protection: Hardening ELF binaries article
Difficulty: Advanced Time estimate: 2 weeks Prerequisites: Project 3, understanding of stack layout
Real world outcome:
$ ./format_demo
Format String Vulnerability Demonstrator
-----------------------------------------
[Level 1: Stack Leak]
Enter format string: %x.%x.%x.%x.%x
Output: deadbeef.cafebabe.12345678.ffff0000.41414141
→ Leaked 5 values from stack!
[Level 2: Arbitrary Read]
Target address: 0x404040 (contains "SECRET_KEY")
Enter format string: [crafted payload with address]
Output: SECRET_KEY
→ Read arbitrary memory!
[Level 3: Arbitrary Write]
Target variable at 0x404060 = 0
Enter format string: [crafted payload with %n]
Target variable now = 1337
→ Wrote arbitrary value!
[Level 4: Code Execution]
GOT entry for exit() at 0x404018
win() function at 0x401337
Enter format string: [GOT overwrite payload]
→ Calling exit()... but it's been hijacked!
You win! Format string exploitation complete.
Implementation Hints:
Understanding the attack:
-
Why it works:
printf(user_input)treats user input AS the format string. If user provides%x, printf reads from stack looking for corresponding argument. - Reading with %x:
printf("%x"); // Reads 4 bytes from where arg would be printf("%lx"); // Reads 8 bytes printf("%7$x"); // Direct parameter access - 7th "argument" - Reading arbitrary memory with %s:
If you can control an address on the stack (often through the format string itself),
%swill read from that address:Input: AAAA%7$s Stack: [AAAA][...][...][...][...][...][fmt string ptr] %7$s reads string from address 0x41414141 - Writing with %n:
%nwrites the count of characters printed so far to an address on the stack.int count; printf("AAAA%n", &count); // count = 4Combined with direct parameter access, attacker controls the address.
Questions to explore:
- What happens if you write to a read-only address?
- How does FORTIFY_SOURCE protect against format strings?
- Why is
%noften disabled in production systems?
Learning milestones:
- Leak stack values → Understand printf argument handling
- Read arbitrary memory → Master %s exploitation
- Write single byte → Use %hhn for precision
- Overwrite GOT → Achieve code execution
Project 6: Safe Memory Allocator Wrapper
- File: LEARN_SECURE_C_AND_EXPLOIT_AWARENESS.md
- Main Programming Language: C
- Alternative Programming Languages: C++, Rust
- Coolness Level: Level 3: Genuinely Clever
- Business Potential: 3. The “Service & Support” Model
- Difficulty: Level 2: Intermediate
- Knowledge Area: Secure Coding / Memory Safety
- Software or Tool: Valgrind, AddressSanitizer
- Main Book: “Effective C, 2nd Edition” by Robert C. Seacord
What you’ll build: A secure memory allocation wrapper that prevents integer overflow in size calculations, zeroes memory on free (preventing info leaks), detects double-frees, and tracks allocations for debugging.
Why it teaches secure coding: Most heap vulnerabilities stem from allocation mistakes. Building your own secure allocator teaches you what can go wrong and how to prevent it.
Core challenges you’ll face:
- Safe size calculation → maps to integer overflow before malloc
- Preventing use-after-free → maps to poison freed memory
- Detecting double-free → maps to allocation tracking
- Memory zeroing on free → maps to preventing info leaks
Resources for key challenges:
- “Effective C, 2nd Edition” Chapter 7 - Dynamic Memory
- CERT C: MEM00-C through MEM11-C
- OpenBSD malloc implementation - secure_malloc
Key Concepts:
- calloc vs malloc: CERT C MEM04-C
- Clearing Sensitive Data: CERT C MEM03-C
- Allocation Tracking: Valgrind internals
Difficulty: Intermediate Time estimate: 1-2 weeks Prerequisites: Project 2 (integer overflow), basic dynamic memory
Real world outcome:
$ ./test_safe_alloc
Testing safe_malloc...
✓ Normal allocation: 1024 bytes allocated
✓ Zero size: Returns NULL
✓ Overflow protection: safe_calloc(SIZE_MAX, 2) returns NULL
Testing safe_free...
✓ Memory zeroed on free (sensitive data cleared)
✓ Double-free detected: FATAL: double free at 0x12340000
✓ Use-after-free detected: Memory poisoned with 0xDEADBEEF
Testing safe_realloc...
✓ Normal realloc: 1024 -> 2048 bytes
✓ Shrink realloc: Zeroes extra bytes
✓ Overflow protection: safe_realloc(ptr, SIZE_MAX) returns NULL
Memory stats:
Allocations: 15
Frees: 14
Current allocated: 1024 bytes
LEAK DETECTED: 1 allocation not freed!
Implementation Hints:
Key features to implement:
- Safe size calculation (use Project 2):
void *safe_calloc(size_t count, size_t size) { size_t total; if (!safe_mul_size_t(count, size, &total)) { errno = ENOMEM; return NULL; // Overflow detected } void *ptr = calloc(1, total); // Track allocation... return ptr; } - Allocation tracking (for double-free detection):
typedef struct allocation { void *ptr; size_t size; bool freed; struct allocation *next; } allocation_t; - Memory poisoning (detect use-after-free):
void safe_free(void *ptr) { if (ptr == NULL) return; allocation_t *alloc = find_allocation(ptr); if (alloc->freed) { abort_with_message("Double free detected!"); } memset(ptr, 0xDE, alloc->size); // Poison with recognizable pattern alloc->freed = true; free(ptr); }
Questions to consider:
- Why zero memory on free? (Heartbleed-style leaks)
- How does AddressSanitizer implement similar checks?
- What’s the performance overhead of tracking?
Learning milestones:
- Implement overflow protection → Safe calloc
- Track allocations → Build allocation list
- Detect double-free → Check freed flag
- Zero on free → Prevent info leaks
Project 7: Bounds-Checking Array Library
- File: LEARN_SECURE_C_AND_EXPLOIT_AWARENESS.md
- Main Programming Language: C
- Alternative Programming Languages: C++ (has std::vector), Rust (has slice bounds)
- Coolness Level: Level 3: Genuinely Clever
- Business Potential: 2. The “Micro-SaaS / Pro Tool”
- Difficulty: Level 2: Intermediate
- Knowledge Area: Secure Coding / Memory Safety
- Software or Tool: AddressSanitizer, Valgrind
- Main Book: “C Interfaces and Implementations” by David R. Hanson
What you’ll build: A bounds-checked dynamic array type for C that prevents buffer overflows, includes length tracking, and provides safe accessor functions.
Why it teaches secure coding: C arrays don’t know their own length—this is the root cause of most buffer overflows. Building a “fat pointer” array teaches you what safe languages do automatically.
Core challenges you’ll face:
- Storing length with data → maps to struct design, “fat pointers”
- Bounds checking overhead → maps to performance vs safety tradeoffs
- API design → maps to making safe defaults easy
- Memory management → maps to preventing leaks, ownership
Resources for key challenges:
- “C Interfaces and Implementations” Chapter 14 - Sequence
- “21st Century C” Chapter 6 - Data structures
- Rust slice implementation - Inspiration for design
Key Concepts:
- Fat Pointers: “The Rust Programming Language” Ch. 4
- Defensive Programming: “Code Complete” Ch. 8
- API Design: “C Interfaces and Implementations” Introduction
Difficulty: Intermediate Time estimate: 1-2 weeks Prerequisites: Project 1 (safe strings), dynamic memory
Real world outcome:
$ ./test_safe_array
Creating array...
✓ array_new(10, sizeof(int)) created with capacity 10
Testing bounds checking...
✓ array_get(arr, 5) returns element at index 5
✓ array_get(arr, 15) returns NULL (out of bounds!)
✓ array_set(arr, 100, &val) returns false (out of bounds!)
Testing dynamic growth...
✓ array_push grows array automatically
✓ Capacity doubled: 10 -> 20 -> 40
Testing iteration...
✓ array_foreach provides safe iteration
Testing security...
✓ Cannot access beyond length, even within capacity
✓ Integer overflow in size calculation prevented
Memory test with Valgrind:
No memory leaks detected
No invalid reads/writes
Implementation Hints:
Structure design:
typedef struct {
void *data; // The actual array data
size_t length; // Current number of elements
size_t capacity; // Allocated space
size_t element_size; // Size of each element
} safe_array_t;
Key operations:
// Create array with initial capacity
safe_array_t *array_new(size_t capacity, size_t element_size);
// Get element with bounds check
void *array_get(safe_array_t *arr, size_t index);
// Set element with bounds check
bool array_set(safe_array_t *arr, size_t index, const void *value);
// Push element, growing if needed
bool array_push(safe_array_t *arr, const void *value);
// Safe iteration
#define array_foreach(arr, type, var) \
for (size_t _i = 0; _i < (arr)->length && \
((var) = *(type*)array_get(arr, _i), 1); _i++)
Questions to explore:
- How does this compare to
std::vectorin C++? - What’s the performance cost of bounds checking?
- How would you make this thread-safe?
Learning milestones:
- Implement basic structure → Length-aware arrays
- Add bounds checking → Safe accessors
- Implement growth → Dynamic resizing
- Create iteration → Safe enumeration
Project 8: Heap Overflow Detector
- File: LEARN_SECURE_C_AND_EXPLOIT_AWARENESS.md
- Main Programming Language: C
- Alternative Programming Languages: Python (for analysis)
- Coolness Level: Level 4: Hardcore Tech Flex
- Business Potential: 3. The “Service & Support” Model
- Difficulty: Level 3: Advanced
- Knowledge Area: Exploit Defense / Heap Security
- Software or Tool: glibc malloc, GDB, custom allocator
- Main Book: “The Shellcoder’s Handbook”
What you’ll build: A custom allocator wrapper that adds “red zones” (guard bytes) around allocations to detect heap buffer overflows at runtime.
Why it teaches exploit awareness: Heap overflows corrupt allocator metadata, leading to arbitrary writes. Adding canaries around heap chunks teaches you how tools like AddressSanitizer work.
Core challenges you’ll face:
- Adding metadata to allocations → maps to header/footer design
- Checking red zones → maps to when to verify
- Handling realloc → maps to preserving guards during resize
- Performance impact → maps to memory overhead
Resources for key challenges:
- AddressSanitizer paper - How the pros do it
- Electric Fence documentation - Classic debugging malloc
- “Understanding the Linux Kernel” - glibc malloc internals
Key Concepts:
- Heap Chunk Layout: “The Shellcoder’s Handbook” Ch. 6
- Memory Debugging: Electric Fence, DUMA documentation
- Guard Pages/Bytes: AddressSanitizer design
Difficulty: Advanced Time estimate: 2 weeks Prerequisites: Project 6 (safe allocator), heap internals
Real world outcome:
$ ./test_heap_overflow
Testing red zone detection...
Allocating 64 bytes with guard zones...
Memory layout:
[REDZONE 16 bytes][USER DATA 64 bytes][REDZONE 16 bytes]
0xDEADBEEF x 4 | your data here | 0xDEADBEEF x 4
Writing one byte past buffer...
Calling checked_free()...
*** HEAP OVERFLOW DETECTED ***
Corruption in trailing red zone at offset 0
Expected: 0xDEADBEEF, Found: 0xDEADBE41
Allocation backtrace:
#0 checked_malloc() at heap_guard.c:42
#1 main() at test.c:15
Testing underflow...
Writing before buffer...
*** HEAP UNDERFLOW DETECTED ***
Corruption in leading red zone at offset 12
Implementation Hints:
Memory layout with guards:
┌─────────────────────────────────────────┐
│ Allocation Header │
│ - Original size │
│ - Magic number │
├─────────────────────────────────────────┤
│ LEADING RED ZONE │
ptr - REDZONE_SIZE │ Filled with GUARD_PATTERN │
├─────────────────────────────────────────┤
│ │
ptr (returned) ───►│ USER DATA │
│ │
├─────────────────────────────────────────┤
│ TRAILING RED ZONE │
│ Filled with GUARD_PATTERN │
└─────────────────────────────────────────┘
Implementation approach:
#define REDZONE_SIZE 16
#define GUARD_PATTERN 0xDEADBEEF
void *checked_malloc(size_t size) {
// Allocate: header + leading_redzone + user_data + trailing_redzone
size_t total = sizeof(header_t) + REDZONE_SIZE + size + REDZONE_SIZE;
void *raw = malloc(total);
// Fill header with metadata
header_t *header = (header_t *)raw;
header->size = size;
header->magic = MAGIC_NUMBER;
// Fill red zones with guard pattern
fill_guard(raw + sizeof(header_t), REDZONE_SIZE);
fill_guard(raw + sizeof(header_t) + REDZONE_SIZE + size, REDZONE_SIZE);
// Return pointer to user data
return raw + sizeof(header_t) + REDZONE_SIZE;
}
void checked_free(void *ptr) {
// Calculate back to header
header_t *header = ptr - REDZONE_SIZE - sizeof(header_t);
// Verify red zones
if (!check_guard(ptr - REDZONE_SIZE, REDZONE_SIZE)) {
report_underflow(header);
}
if (!check_guard(ptr + header->size, REDZONE_SIZE)) {
report_overflow(header);
}
free(header);
}
Learning milestones:
- Design memory layout → Header + guards + data
- Implement allocation wrapper → Add guard bytes
- Implement free wrapper → Check guards
- Add diagnostics → Detailed error reporting
Project 9: Input Validation Framework
- File: LEARN_SECURE_C_AND_EXPLOIT_AWARENESS.md
- Main Programming Language: C
- Alternative Programming Languages: Python, Go
- Coolness Level: Level 3: Genuinely Clever
- Business Potential: 3. The “Service & Support” Model
- Difficulty: Level 2: Intermediate
- Knowledge Area: Secure Coding / Input Handling
- Software or Tool: Regular expressions, fuzzing
- Main Book: “Effective C, 2nd Edition” by Robert C. Seacord
What you’ll build: A comprehensive input validation library with validators for integers, strings, paths, emails, and custom patterns—returning safe, validated values or clear error codes.
Why it teaches secure coding: Invalid input is the root cause of most vulnerabilities. Building a validation framework teaches you to think about all the ways input can be malicious.
Core challenges you’ll face:
- Integer range validation → maps to MIN/MAX checks, overflow
- String sanitization → maps to length limits, character whitelist
- Path traversal prevention → maps to rejecting ../, symlinks
- Canonical forms → maps to normalization before validation
Resources for key challenges:
- OWASP Input Validation Cheat Sheet
- CERT C: STR00-C through STR38-C (string validation)
- CWE-20: Improper Input Validation
Key Concepts:
- Whitelisting vs Blacklisting: OWASP guidelines
- Canonicalization: CERT C FIO02-C
- Defense in Depth: Multiple validation layers
Difficulty: Intermediate Time estimate: 1-2 weeks Prerequisites: Project 1 (safe strings), regex basics
Real world outcome:
$ ./test_input_validator
Testing integer validation...
✓ validate_int("123", 0, 1000) = 123
✓ validate_int("-5", 0, 1000) = ERROR_RANGE
✓ validate_int("99999999999999", ...) = ERROR_OVERFLOW
✓ validate_int("12abc", ...) = ERROR_FORMAT
Testing string validation...
✓ validate_string("hello", 1, 10, ALPHA) = "hello"
✓ validate_string("hello123", ..., ALPHA) = ERROR_INVALID_CHARS
✓ validate_string("", 1, 10, ...) = ERROR_TOO_SHORT
✓ validate_string(NULL, ...) = ERROR_NULL
Testing path validation...
✓ validate_path("/home/user/file.txt") = validated path
✓ validate_path("../../../etc/passwd") = ERROR_TRAVERSAL
✓ validate_path("/etc/passwd") = ERROR_OUTSIDE_ROOT
✓ validate_path("file\x00.txt") = ERROR_NULL_BYTE
Testing email validation...
✓ validate_email("user@example.com") = valid
✓ validate_email("user@") = ERROR_FORMAT
✓ validate_email("user+tag@sub.example.com") = valid
All 24 tests passed!
Implementation Hints:
Validation design principles:
- Fail secure: Invalid input should fail, not be silently accepted
- Whitelist over blacklist: Accept known-good, not reject known-bad
- Canonicalize first: Convert to standard form before validating
- Check all properties: Length, characters, format, range
Example validators:
typedef enum {
VALIDATE_OK = 0,
VALIDATE_ERROR_NULL,
VALIDATE_ERROR_LENGTH,
VALIDATE_ERROR_CHARS,
VALIDATE_ERROR_RANGE,
VALIDATE_ERROR_FORMAT,
VALIDATE_ERROR_TRAVERSAL
} validate_result_t;
// Integer validation
validate_result_t validate_int(
const char *str,
long min,
long max,
long *result
);
// String validation
typedef enum {
CHARSET_ALPHA,
CHARSET_ALNUM,
CHARSET_ASCII_PRINTABLE,
CHARSET_CUSTOM
} charset_t;
validate_result_t validate_string(
const char *str,
size_t min_len,
size_t max_len,
charset_t allowed,
char *output,
size_t output_size
);
// Path validation
validate_result_t validate_path(
const char *path,
const char *allowed_root,
char *canonical_path,
size_t path_size
);
Questions to consider:
- What’s the difference between
realpath()and manual validation? - How do you handle Unicode/UTF-8 in C?
- What about timing attacks in string comparison?
Learning milestones:
- Implement integer validator → Range, format, overflow checks
- Implement string validator → Length, charset, sanitization
- Implement path validator → Prevent traversal attacks
- Build test suite → Comprehensive edge cases
Project 10: Return-to-libc Attack Lab
- File: LEARN_SECURE_C_AND_EXPLOIT_AWARENESS.md
- Main Programming Language: C (targets), Python (exploits)
- Alternative Programming Languages: Assembly
- Coolness Level: Level 5: Pure Magic (Super Cool)
- Business Potential: 1. The “Resume Gold”
- Difficulty: Level 3: Advanced
- Knowledge Area: Exploit Awareness / NX Bypass
- Software or Tool: GDB, pwntools, checksec
- Main Book: “Hacking: The Art of Exploitation” by Jon Erickson
What you’ll build: A series of progressively difficult return-to-libc exploits that bypass NX/DEP protection by reusing existing library code instead of injecting shellcode.
Why it teaches exploit awareness: When NX prevents code execution on the stack, attackers call existing functions like system(). Understanding this teaches you why code-reuse attacks exist and what defenses remain.
Core challenges you’ll face:
- Finding libc addresses → maps to ASLR considerations
- Setting up function arguments → maps to calling conventions
- Chaining multiple calls → maps to return chaining
- Finding “/bin/sh” string → maps to string gadgets in libc
Resources for key challenges:
- “Hacking: Art of Exploitation” Chapter 5
- ROP Emporium - ret2win challenge
- LiveOverflow YouTube - ret2libc walkthrough
Key Concepts:
- Calling Conventions: System V ABI for x64
- GOT/PLT: “Practical Binary Analysis” Ch. 2
- ASLR: Address Space Layout Randomization
Difficulty: Advanced Time estimate: 2-3 weeks Prerequisites: Projects 3-5, buffer overflow basics
Real world outcome:
$ cd ret2libc-lab
$ ./level1
Simple ret2libc: NX enabled, no ASLR, no canary
Enter name: [overflow payload with system() address]
$ whoami
pwned
$ ./level2
ret2libc with argument: Must pass correct argument to system()
[payload with gadget: pop rdi; ret + "/bin/sh" addr + system()]
$ id
uid=1000(user) gid=1000(user) groups=1000(user)
$ ./level3
ret2libc with ASLR: Must leak libc address first
[leak puts@got, calculate libc base, call system("/bin/sh")]
$ cat /flag
FLAG{ret2libc_master}
$ ./level4
Full chain: leak + ROP + shell
[complete exploit with info leak and chained calls]
Implementation Hints:
ret2libc concept:
Normal return: ret2libc:
┌─────────────┐ ┌─────────────┐
│ Return Addr │ → caller │ system() │ → libc function
├─────────────┤ ├─────────────┤
│ Saved RBP │ │ pop rdi;ret │ → gadget
├─────────────┤ ├─────────────┤
│ │ │ "/bin/sh" │ → argument
│ Buffer │ │ addr │
│ │ ├─────────────┤
│ │ │ AAAA... │ → overflow
└─────────────┘ └─────────────┘
For x64, arguments are in registers (RDI, RSI, RDX…), so you need “gadgets”:
- Find
pop rdi; retgadget in binary or libc - Stack: [gadget addr][“/bin/sh” addr][system addr]
- Gadget pops “/bin/sh” into RDI, returns to system()
Finding gadgets:
$ ROPgadget --binary ./vuln | grep "pop rdi"
0x0000000000401234 : pop rdi ; ret
$ strings -t x /lib/x86_64-linux-gnu/libc.so.6 | grep "/bin/sh"
18ce57 /bin/sh
Questions to explore:
- Why can’t you just call
system()directly (without gadget)? - How does ASLR affect ret2libc attacks?
- What’s the difference between ret2libc and ROP?
Learning milestones:
- Basic ret2libc → Call system() with static address
- With gadgets → Set up argument in register
- With ASLR → Leak address first
- Chain calls → Multiple function calls
Project 11: Static Analysis Tool (Basic)
- File: LEARN_SECURE_C_AND_EXPLOIT_AWARENESS.md
- Main Programming Language: Python
- Alternative Programming Languages: C, Go
- Coolness Level: Level 4: Hardcore Tech Flex
- Business Potential: 4. The “Open Core” Infrastructure
- Difficulty: Level 3: Advanced
- Knowledge Area: Security Tools / Code Analysis
- Software or Tool: pycparser, Clang AST
- Main Book: “Secure Coding in C and C++” by Robert C. Seacord
What you’ll build: A static analysis tool that scans C source code for common vulnerability patterns—dangerous functions, format string bugs, potential integer overflows, and missing bounds checks.
Why it teaches secure coding: Building an analyzer forces you to formally define what “dangerous” code looks like and creates a tool you can use on real projects.
Core challenges you’ll face:
- Parsing C code → maps to using pycparser or Clang
- Pattern matching → maps to AST traversal
- Reducing false positives → maps to context-aware analysis
- Reporting findings → maps to useful output format
Resources for key challenges:
- pycparser documentation
- Clang Static Analyzer documentation
- Semgrep rule writing guide
Key Concepts:
- Abstract Syntax Trees: Compiler textbooks
- Taint Analysis: “Secure Programming with Static Analysis” Ch. 4
- Pattern-Based Detection: Semgrep approach
Difficulty: Advanced Time estimate: 3-4 weeks Prerequisites: All previous projects, parsing basics
Real world outcome:
$ ./cscan vulnerable.c
═══════════════════════════════════════════════════════════════════
C Security Scanner Report
═══════════════════════════════════════════════════════════════════
File: vulnerable.c
[HIGH] Dangerous Function: gets()
Line 15: gets(buffer);
Issue: gets() has no bounds checking. Use fgets() instead.
CWE: CWE-120 (Buffer Copy without Checking Size)
[HIGH] Format String Vulnerability
Line 23: printf(user_input);
Issue: User-controlled format string. Use printf("%s", ...).
CWE: CWE-134 (Use of Externally-Controlled Format String)
[MEDIUM] Potential Integer Overflow
Line 31: size_t total = count * element_size;
Issue: Multiplication may overflow. Use safe_mul().
CWE: CWE-190 (Integer Overflow)
[MEDIUM] Unbounded String Copy
Line 45: strcpy(dest, source);
Issue: strcpy() has no bounds check. Use strncpy() or strlcpy().
CWE: CWE-120
[LOW] Unchecked Return Value
Line 52: malloc(size);
Issue: malloc() return value not checked for NULL.
CWE: CWE-252 (Unchecked Return Value)
═══════════════════════════════════════════════════════════════════
Summary: 2 HIGH, 2 MEDIUM, 1 LOW findings
═══════════════════════════════════════════════════════════════════
Implementation Hints:
Two approaches:
Approach 1: Regex-based (simpler, more false positives)
import re
dangerous_functions = [
(r'\bgets\s*\(', 'gets() has no bounds check', 'HIGH'),
(r'\bstrcpy\s*\(', 'strcpy() has no bounds check', 'MEDIUM'),
(r'\bprintf\s*\(\s*[a-zA-Z_]\w*\s*\)', 'Possible format string', 'HIGH'),
(r'\bscanf\s*\(\s*"%s"', 'scanf %s has no bounds', 'HIGH'),
]
Approach 2: AST-based (more accurate)
import pycparser
class VulnChecker(pycparser.c_ast.NodeVisitor):
def visit_FuncCall(self, node):
func_name = node.name.name if hasattr(node.name, 'name') else None
if func_name == 'gets':
self.report('HIGH', 'Dangerous function: gets()', node.coord)
elif func_name == 'printf':
if len(node.args.exprs) == 1:
arg = node.args.exprs[0]
if isinstance(arg, pycparser.c_ast.ID):
self.report('HIGH', 'Format string vulnerability', node.coord)
self.generic_visit(node)
Detection rules to implement:
- Dangerous functions (gets, strcpy, sprintf, etc.)
- Format string bugs (printf with variable format)
- Integer overflow (multiplication before allocation)
- Unchecked return values (malloc, fopen)
- Buffer size mismatches
Learning milestones:
- Parse C code → Use pycparser or Clang
- Detect dangerous functions → Pattern matching
- Detect format strings → Argument analysis
- Report findings → Useful output format
Project 12: Use-After-Free Demonstrator
- File: LEARN_SECURE_C_AND_EXPLOIT_AWARENESS.md
- Main Programming Language: C
- Alternative Programming Languages: Python (for exploit)
- Coolness Level: Level 5: Pure Magic (Super Cool)
- Business Potential: 1. The “Resume Gold”
- Difficulty: Level 4: Expert
- Knowledge Area: Exploit Awareness / Heap Exploitation
- Software or Tool: GDB, heap visualization tools
- Main Book: “The Shellcoder’s Handbook”
What you’ll build: A demonstration of use-after-free vulnerabilities and exploitation—showing how freed memory can be reallocated and abused to achieve code execution.
Why it teaches exploit awareness: UAF is one of the most common vulnerability classes in browsers and operating systems. Understanding it is essential for modern security work.
Core challenges you’ll face:
- Understanding heap reuse → maps to how freed chunks are recycled
- Controlling freed data → maps to timing and allocation order
- Hijacking control flow → maps to overwriting function pointers
- Heap grooming → maps to manipulating heap state
Resources for key challenges:
- “The Shellcoder’s Handbook” Chapter 6
- Azeria Labs - Heap Exploitation
- how2heap repository (Shellphish)
Key Concepts:
- Heap Chunk Lifecycle: glibc malloc internals
- Fastbin/Tcache: Modern allocator optimizations
- Type Confusion: Reusing memory as different type
Difficulty: Expert Time estimate: 3-4 weeks Prerequisites: Projects 3, 6, 8, heap internals
Real world outcome:
$ ./uaf_demo
=== Use-After-Free Demonstration ===
Step 1: Allocate object with function pointer
Object at 0x55a0001234a0
object->callback = 0x55a000001234 (good_function)
Step 2: Free the object
Freed object (but pointer still exists!)
Dangling pointer: 0x55a0001234a0
Step 3: Allocate new data of same size
New allocation at: 0x55a0001234a0 (same address!)
Writing "AAAA" + evil_function address...
Step 4: Call the dangling pointer's callback
Calling object->callback()...
*** HIJACKED ***
evil_function() called!
This would be shellcode in a real exploit.
=== Exploitation Successful ===
Understanding what happened:
1. Original object freed, but pointer kept
2. New allocation reused same memory
3. Attacker data overwrote function pointer
4. Calling original pointer's method = calling attacker's code
Implementation Hints:
UAF exploitation concept:
typedef struct {
char name[32];
void (*callback)(void); // Function pointer
} user_t;
user_t *user = malloc(sizeof(user_t));
user->callback = good_function;
free(user); // Freed, but pointer still valid
// Attacker causes new allocation of same size
char *evil = malloc(sizeof(user_t));
memcpy(evil + 32, &evil_addr, 8); // Overwrite callback
// Victim uses dangling pointer
user->callback(); // Calls evil_addr!
Heap allocation strategy:
- glibc reuses recently freed chunks (fastbin LIFO)
- Same-size allocation likely gets same address
- Attacker controls content of new allocation
- Original pointer dereferences attacker data
Questions to explore:
- What triggers the same-address reuse?
- How do tcache and fastbins affect UAF?
- How does AddressSanitizer detect UAF?
- What defenses exist (MTE, heap randomization)?
Learning milestones:
- Demonstrate dangling pointer → Show the bug
- Achieve memory reuse → Same address allocation
- Overwrite function pointer → Hijack control
- Achieve code execution → Complete exploit
Project 13: FORTIFY_SOURCE Implementation
- File: LEARN_SECURE_C_AND_EXPLOIT_AWARENESS.md
- Main Programming Language: C
- Alternative Programming Languages: N/A
- Coolness Level: Level 4: Hardcore Tech Flex
- Business Potential: 1. The “Resume Gold”
- Difficulty: Level 4: Expert
- Knowledge Area: Secure Coding / Compiler Defenses
- Software or Tool: GCC, glibc headers
- Main Book: “Effective C, 2nd Edition” by Robert C. Seacord
What you’ll build: Your own implementation of FORTIFY_SOURCE-style compile-time and runtime bounds checking for string functions.
Why it teaches secure coding: FORTIFY_SOURCE is glibc’s defense layer that prevents many buffer overflows. Understanding how it works teaches you both the technique and its limitations.
Core challenges you’ll face:
- Compile-time size detection → maps to __builtin_object_size
- Function wrappers → maps to replacing libc functions
- Runtime checks → maps to abort on overflow
- Performance balance → maps to when to check
Resources for key challenges:
- glibc FORTIFY_SOURCE source code
- GCC __builtin_object_size documentation
- Red Hat FORTIFY_SOURCE blog post
Key Concepts:
- Object Size Builtin: GCC documentation
- Macro Replacement: glibc bits/string_fortified.h
- Compile-Time vs Runtime: FORTIFY_SOURCE levels
Difficulty: Expert Time estimate: 2-3 weeks Prerequisites: Projects 1, 7, macro programming
Real world outcome:
$ cat test.c
char buf[10];
strcpy(buf, argv[1]); // Potentially dangerous!
$ gcc -D_FORTIFY_SOURCE=2 test.c -o test
(Our fortified headers intercept strcpy)
$ ./test "short"
OK
$ ./test "this is a very long string"
*** buffer overflow detected ***: terminated
Aborted
$ gcc -DFORTIFY_EXPLAIN -D_FORTIFY_SOURCE=2 test.c -o test
$ ./test "this is a very long string"
FORTIFY: strcpy overflow detected
Destination size: 10 bytes (known at compile time)
Source size: 28 bytes
Call site: test.c:3
*** buffer overflow detected ***: terminated
Implementation Hints:
How FORTIFY_SOURCE works:
- Compile-time size detection:
char buf[10]; __builtin_object_size(buf, 0); // Returns 10 char *ptr = malloc(20); __builtin_object_size(ptr, 0); // Returns 20 (if optimizer knows) - Function replacement via macros:
// In fortified header: #define strcpy(dest, src) \ __fortified_strcpy(dest, src, __builtin_object_size(dest, 0)) static inline char *__fortified_strcpy(char *dest, const char *src, size_t dest_size) { size_t src_len = strlen(src) + 1; if (src_len > dest_size) { __fortify_fail("buffer overflow"); } return __builtin_strcpy(dest, src); } - Different FORTIFY levels:
- Level 1: Check when size is known at compile time
- Level 2: Also check with stricter object size tracking
Questions to consider:
- When does
__builtin_object_sizereturn -1 (unknown)? - Why can’t FORTIFY_SOURCE catch all overflows?
- What’s the performance overhead?
Learning milestones:
- Understand __builtin_object_size → Compile-time size
- Create wrapper functions → Intercept libc calls
- Implement runtime checks → Abort on overflow
- Compare with glibc → Study real implementation
Project 14: Secure Configuration Parser
- File: LEARN_SECURE_C_AND_EXPLOIT_AWARENESS.md
- Main Programming Language: C
- Alternative Programming Languages: Python, Rust
- Coolness Level: Level 3: Genuinely Clever
- Business Potential: 2. The “Micro-SaaS / Pro Tool”
- Difficulty: Level 2: Intermediate
- Knowledge Area: Secure Coding / Input Parsing
- Software or Tool: Fuzzer (AFL++), Valgrind
- Main Book: “C Interfaces and Implementations” by David R. Hanson
What you’ll build: A secure configuration file parser that handles INI/TOML-like syntax with proper bounds checking, integer validation, and path safety—demonstrating secure parsing techniques.
Why it teaches secure coding: Parsers are a common source of vulnerabilities. Building a secure parser teaches you to handle malformed input, prevent injection, and validate all data.
Core challenges you’ll face:
- Handling malformed input → maps to graceful error handling
- Line length limits → maps to bounded buffers
- String escaping → maps to preventing injection
- Type conversion safety → maps to integer overflow in values
Resources for key challenges:
- “C Interfaces and Implementations” - Parsing chapter
- AFL++ fuzzing tutorial - Find your parser bugs
- OWASP - Configuration file injection
Key Concepts:
- Parser State Machine: Compiler textbooks
- Defensive Parsing: “Secure Coding in C” Ch. 8
- Fuzz Testing: AFL++ documentation
Difficulty: Intermediate Time estimate: 2 weeks Prerequisites: Projects 1, 2, 9
Real world outcome:
$ cat config.ini
[database]
host = localhost
port = 5432
max_connections = 100
[paths]
log_file = /var/log/app.log
data_dir = ../../../etc/passwd # Attack attempt!
[security]
timeout = 999999999999999999 # Overflow attempt!
$ ./secure_parser config.ini
Parsing config.ini...
[database]
host = "localhost" (string, 9 chars)
port = 5432 (integer, range: 1-65535 ✓)
max_connections = 100 (integer, range: 1-10000 ✓)
[paths]
log_file = "/var/log/app.log" (path, validated ✓)
data_dir = ERROR: Path traversal detected ("../../../etc/passwd")
[security]
timeout = ERROR: Integer overflow (value exceeds INT_MAX)
Parse complete: 4 values loaded, 2 errors
Implementation Hints:
Secure parsing principles:
- Bounded reads:
char line[MAX_LINE_LENGTH + 1]; if (fgets(line, sizeof(line), file) == NULL) break; // Check if line was truncated size_t len = strlen(line); if (len == MAX_LINE_LENGTH && line[len-1] != '\n') { // Line too long - error or skip to newline } - Type-safe value parsing:
typedef enum { CONFIG_STRING, CONFIG_INTEGER, CONFIG_PATH, CONFIG_BOOLEAN } config_type_t; typedef struct { const char *key; config_type_t type; long min_value; // For integers long max_value; size_t max_length; // For strings const char *base_path; // For path validation } config_schema_t; - Schema-based validation:
config_schema_t schema[] = { {"port", CONFIG_INTEGER, 1, 65535, 0, NULL}, {"host", CONFIG_STRING, 0, 0, 255, NULL}, {"log_file", CONFIG_PATH, 0, 0, PATH_MAX, "/var/log"}, {NULL} };
Questions to consider:
- How do you handle UTF-8 in config files?
- What about comments and whitespace?
- How do you prevent shell injection in path values?
Learning milestones:
- Parse basic INI format → Section and key=value
- Add type validation → Integer bounds, string length
- Add path validation → Prevent traversal
- Fuzz the parser → Find edge cases
Project 15: Complete Secure Coding Toolkit
- File: LEARN_SECURE_C_AND_EXPLOIT_AWARENESS.md
- Main Programming Language: C
- Alternative Programming Languages: Companion tools in Python
- Coolness Level: Level 5: Pure Magic (Super Cool)
- Business Potential: 4. The “Open Core” Infrastructure
- Difficulty: Level 4: Expert
- Knowledge Area: Complete Security Library
- Software or Tool: All previous projects
- Main Book: All previous books
What you’ll build: A unified secure coding library combining all previous projects—safe strings, safe integers, safe memory, input validation, and security utilities—with comprehensive documentation.
Why it teaches secure coding: Integration forces you to think about API consistency, documentation, and real-world usability of security code.
Core challenges you’ll face:
- API consistency → maps to naming, error handling uniformity
- Documentation → maps to making it usable by others
- Testing → maps to comprehensive test coverage
- Performance → maps to overhead measurement
Time estimate: 4-6 weeks Prerequisites: All previous projects
Real world outcome:
libsecurec/
├── include/
│ ├── securec.h # Master header
│ ├── securec/string.h # Safe string functions
│ ├── securec/memory.h # Safe allocation
│ ├── securec/integer.h # Safe arithmetic
│ ├── securec/validate.h # Input validation
│ └── securec/crypto.h # Secure memory clearing
├── src/
│ ├── string.c
│ ├── memory.c
│ ├── integer.c
│ ├── validate.c
│ └── crypto.c
├── tests/
│ ├── test_string.c
│ ├── test_memory.c
│ └── ...
├── docs/
│ ├── API.md
│ ├── SECURITY.md
│ └── EXAMPLES.md
├── examples/
│ ├── secure_server.c
│ └── config_parser.c
├── Makefile
└── README.md
Usage example:
#include <securec.h>
int main(int argc, char **argv) {
// Safe string handling
char buffer[64];
if (sc_strlcpy(buffer, argv[1], sizeof(buffer)) >= sizeof(buffer)) {
fprintf(stderr, "Input truncated\n");
return 1;
}
// Safe integer parsing
long port;
if (sc_strtol_safe(argv[2], 1, 65535, &port) != SC_OK) {
fprintf(stderr, "Invalid port\n");
return 1;
}
// Safe memory allocation
size_t count = 1000, size = 400;
void *data = sc_calloc_safe(count, size); // Overflow-checked
if (!data) {
fprintf(stderr, "Allocation failed\n");
return 1;
}
// Secure memory clearing
sc_memzero(password, sizeof(password)); // Can't be optimized away
sc_free(data);
return 0;
}
Implementation Hints:
API design principles:
- Consistent naming:
sc_prefix for all functions - Consistent error handling: Return codes or NULL
- Compile-time checks where possible: Static assertions
- Runtime checks for dynamic values: Bounds, overflow
Library structure:
// securec.h - Master header
#ifndef SECUREC_H
#define SECUREC_H
#include <securec/config.h> // Library configuration
#include <securec/types.h> // Common types and error codes
#include <securec/string.h> // Safe string functions
#include <securec/memory.h> // Safe allocation
#include <securec/integer.h> // Safe arithmetic
#include <securec/validate.h> // Input validation
// Error codes
typedef enum {
SC_OK = 0,
SC_ERROR_NULL,
SC_ERROR_OVERFLOW,
SC_ERROR_BOUNDS,
SC_ERROR_FORMAT,
SC_ERROR_MEMORY
} sc_result_t;
#endif
Learning milestones:
- Integrate all components → Unified API
- Write documentation → API reference
- Create test suite → Full coverage
- Benchmark performance → Measure overhead
Project Comparison Table
| # | Project | Difficulty | Time | Key Skill | Fun |
|---|---|---|---|---|---|
| 1 | Safe String Library | ⭐⭐ | 1 week | Bounds Checking | ⭐⭐⭐ |
| 2 | Integer Overflow Library | ⭐⭐ | 1 week | Safe Arithmetic | ⭐⭐⭐ |
| 3 | Vulnerable Program Lab | ⭐⭐ | 2 weeks | Vulnerability Classes | ⭐⭐⭐⭐⭐ |
| 4 | Stack Canary Implementation | ⭐⭐⭐ | 2 weeks | Exploit Defense | ⭐⭐⭐⭐ |
| 5 | Format String Demonstrator | ⭐⭐⭐ | 2 weeks | Format Strings | ⭐⭐⭐⭐⭐ |
| 6 | Safe Memory Allocator | ⭐⭐ | 1-2 weeks | Memory Safety | ⭐⭐⭐ |
| 7 | Bounds-Checking Arrays | ⭐⭐ | 1-2 weeks | Array Safety | ⭐⭐⭐ |
| 8 | Heap Overflow Detector | ⭐⭐⭐ | 2 weeks | Heap Security | ⭐⭐⭐⭐ |
| 9 | Input Validation Framework | ⭐⭐ | 1-2 weeks | Input Handling | ⭐⭐⭐ |
| 10 | Return-to-libc Lab | ⭐⭐⭐ | 2-3 weeks | NX Bypass | ⭐⭐⭐⭐⭐ |
| 11 | Static Analysis Tool | ⭐⭐⭐ | 3-4 weeks | Code Analysis | ⭐⭐⭐⭐ |
| 12 | Use-After-Free Demo | ⭐⭐⭐⭐ | 3-4 weeks | Heap Exploitation | ⭐⭐⭐⭐⭐ |
| 13 | FORTIFY_SOURCE Impl | ⭐⭐⭐⭐ | 2-3 weeks | Compiler Defenses | ⭐⭐⭐⭐ |
| 14 | Secure Config Parser | ⭐⭐ | 2 weeks | Secure Parsing | ⭐⭐⭐ |
| 15 | Complete Toolkit | ⭐⭐⭐⭐ | 4-6 weeks | Integration | ⭐⭐⭐⭐ |
Recommended Learning Path
Phase 1: Foundations (3-4 weeks)
Build secure coding fundamentals:
- Project 1: Safe String Library - Master bounded string operations
- Project 2: Integer Overflow Library - Prevent arithmetic bugs
- Project 6: Safe Memory Allocator - Secure dynamic memory
- Project 7: Bounds-Checking Arrays - Safe array access
Phase 2: Understanding Attacks (4-5 weeks)
Learn what you’re defending against:
- Project 3: Vulnerable Program Lab - See all vulnerability types
- Project 5: Format String Demonstrator - Deep format string understanding
- Project 10: Return-to-libc Lab - NX bypass techniques
Phase 3: Defense Mechanisms (4-5 weeks)
Implement real defenses:
- Project 4: Stack Canary Implementation - Compiler protection
- Project 8: Heap Overflow Detector - Runtime detection
- Project 13: FORTIFY_SOURCE Implementation - Compile-time protection
Phase 4: Practical Security (4-5 weeks)
Build usable tools:
- Project 9: Input Validation Framework - Secure input handling
- Project 14: Secure Config Parser - Real-world parsing
- Project 11: Static Analysis Tool - Find vulnerabilities
Phase 5: Advanced & Integration (6-8 weeks)
Master-level work:
- Project 12: Use-After-Free Demo - Advanced heap exploitation
- Project 15: Complete Toolkit - Professional-grade library
Summary
| # | Project | Main Language |
|---|---|---|
| 1 | Safe String Library | C |
| 2 | Integer Overflow Detection Library | C |
| 3 | Vulnerable Program Laboratory | C |
| 4 | Stack Canary Implementation | C/Assembly |
| 5 | Format String Vulnerability Demonstrator | C |
| 6 | Safe Memory Allocator Wrapper | C |
| 7 | Bounds-Checking Array Library | C |
| 8 | Heap Overflow Detector | C |
| 9 | Input Validation Framework | C |
| 10 | Return-to-libc Attack Lab | C/Python |
| 11 | Static Analysis Tool | Python |
| 12 | Use-After-Free Demonstrator | C |
| 13 | FORTIFY_SOURCE Implementation | C |
| 14 | Secure Configuration Parser | C |
| 15 | Complete Secure Coding Toolkit | C |
Resources
Essential Books
- “Effective C, 2nd Edition” by Robert C. Seacord - Modern secure C
- “Secure Coding in C and C++” by Robert C. Seacord - Comprehensive security reference
- “Hacking: The Art of Exploitation” by Jon Erickson - Attacker perspective
- “The Shellcoder’s Handbook” - Advanced exploitation
- “C Programming: A Modern Approach” by K.N. King - C fundamentals
Standards & Guidelines
- CERT C Coding Standard: https://wiki.sei.cmu.edu/confluence/display/c
- CWE Top 25: https://cwe.mitre.org/top25/
- OWASP Secure Coding Practices: https://owasp.org/
Tools
- GCC Security Flags:
-fstack-protector,-D_FORTIFY_SOURCE=2,-pie - AddressSanitizer: Compile with
-fsanitize=address - Valgrind: Memory error detection
- AFL++: Fuzz testing
- pwntools: Exploit development
Practice Platforms
- picoCTF: https://picoctf.org/ - Beginner CTF
- pwnable.kr: https://pwnable.kr/ - Binary exploitation
- Exploit Education: https://exploit.education/ - Phoenix, Protostar
Total Estimated Time: 6-8 months of dedicated study
After completion: You’ll be able to write C code that’s resistant to buffer overflows, integer overflows, format string attacks, and use-after-free vulnerabilities. More importantly, you’ll understand why these attacks work, enabling you to audit code, find vulnerabilities, and design secure systems from the ground up.