← Back to all projects

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():

  1. 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 */ }

  2. Edge cases to handle:
    • What if size is 0? (Don’t write anything, return strlen(src))
    • What if src is NULL? (Define behavior: return 0? crash? assert?)
    • What if dst is NULL? (Only valid if size is 0)
  3. 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() and snprintf("%s", ...)?
  • How would you handle multi-byte (UTF-8) strings?

Learning milestones:

  1. Implement strlcpy/strlcat → Understand size-limited string operations
  2. Handle all edge cases → Build robust code
  3. Create snprintf wrapper → Detect truncation automatically
  4. 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 == b not a reliable overflow check?

Learning milestones:

  1. Implement unsigned overflow checks → Addition and multiplication
  2. Handle signed integers → Understand undefined behavior
  3. Use compiler builtins → Learn modern approach
  4. 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:

  1. Have a clear, simple vulnerability
  2. Print helpful messages for learners
  3. Compile with a specific set of disabled protections
  4. 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:

  1. Create basic overflow → Understand memory layout
  2. Create format string vuln → Understand printf internals
  3. Create integer overflow → Understand arithmetic attacks
  4. 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:

  1. 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.

  2. 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
    
  3. 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:

  1. At program start: Generate random canary using /dev/urandom
  2. Store in TLS or global (simpler for learning)
  3. 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-protector and -fstack-protector-strong?

Learning milestones:

  1. Generate random canary → Use /dev/urandom
  2. Insert canary in function → Modify prologue/epilogue
  3. Detect overflow → Compare and abort
  4. 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:

  1. 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.

  2. 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"
    
  3. Reading arbitrary memory with %s: If you can control an address on the stack (often through the format string itself), %s will read from that address:
    Input: AAAA%7$s
    Stack: [AAAA][...][...][...][...][...][fmt string ptr]
    %7$s reads string from address 0x41414141
    
  4. Writing with %n: %n writes the count of characters printed so far to an address on the stack.
    int count;
    printf("AAAA%n", &count);  // count = 4
    

    Combined 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 %n often disabled in production systems?

Learning milestones:

  1. Leak stack values → Understand printf argument handling
  2. Read arbitrary memory → Master %s exploitation
  3. Write single byte → Use %hhn for precision
  4. 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:

  1. 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;
    }
    
  2. Allocation tracking (for double-free detection):
    typedef struct allocation {
        void *ptr;
        size_t size;
        bool freed;
        struct allocation *next;
    } allocation_t;
    
  3. 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:

  1. Implement overflow protection → Safe calloc
  2. Track allocations → Build allocation list
  3. Detect double-free → Check freed flag
  4. 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::vector in C++?
  • What’s the performance cost of bounds checking?
  • How would you make this thread-safe?

Learning milestones:

  1. Implement basic structure → Length-aware arrays
  2. Add bounds checking → Safe accessors
  3. Implement growth → Dynamic resizing
  4. 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:

  1. Design memory layout → Header + guards + data
  2. Implement allocation wrapper → Add guard bytes
  3. Implement free wrapper → Check guards
  4. 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:

  1. Fail secure: Invalid input should fail, not be silently accepted
  2. Whitelist over blacklist: Accept known-good, not reject known-bad
  3. Canonicalize first: Convert to standard form before validating
  4. 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:

  1. Implement integer validator → Range, format, overflow checks
  2. Implement string validator → Length, charset, sanitization
  3. Implement path validator → Prevent traversal attacks
  4. 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”:

  1. Find pop rdi; ret gadget in binary or libc
  2. Stack: [gadget addr][“/bin/sh” addr][system addr]
  3. 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:

  1. Basic ret2libc → Call system() with static address
  2. With gadgets → Set up argument in register
  3. With ASLR → Leak address first
  4. 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:

  1. Dangerous functions (gets, strcpy, sprintf, etc.)
  2. Format string bugs (printf with variable format)
  3. Integer overflow (multiplication before allocation)
  4. Unchecked return values (malloc, fopen)
  5. Buffer size mismatches

Learning milestones:

  1. Parse C code → Use pycparser or Clang
  2. Detect dangerous functions → Pattern matching
  3. Detect format strings → Argument analysis
  4. 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:

  1. glibc reuses recently freed chunks (fastbin LIFO)
  2. Same-size allocation likely gets same address
  3. Attacker controls content of new allocation
  4. 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:

  1. Demonstrate dangling pointer → Show the bug
  2. Achieve memory reuse → Same address allocation
  3. Overwrite function pointer → Hijack control
  4. 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:

  1. 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)
    
  2. 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);
    }
    
  3. 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_size return -1 (unknown)?
  • Why can’t FORTIFY_SOURCE catch all overflows?
  • What’s the performance overhead?

Learning milestones:

  1. Understand __builtin_object_size → Compile-time size
  2. Create wrapper functions → Intercept libc calls
  3. Implement runtime checks → Abort on overflow
  4. 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:

  1. 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
    }
    
  2. 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;
    
  3. 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:

  1. Parse basic INI format → Section and key=value
  2. Add type validation → Integer bounds, string length
  3. Add path validation → Prevent traversal
  4. 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:

  1. Consistent naming: sc_ prefix for all functions
  2. Consistent error handling: Return codes or NULL
  3. Compile-time checks where possible: Static assertions
  4. 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:

  1. Integrate all components → Unified API
  2. Write documentation → API reference
  3. Create test suite → Full coverage
  4. 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 ⭐⭐⭐⭐

Phase 1: Foundations (3-4 weeks)

Build secure coding fundamentals:

  1. Project 1: Safe String Library - Master bounded string operations
  2. Project 2: Integer Overflow Library - Prevent arithmetic bugs
  3. Project 6: Safe Memory Allocator - Secure dynamic memory
  4. Project 7: Bounds-Checking Arrays - Safe array access

Phase 2: Understanding Attacks (4-5 weeks)

Learn what you’re defending against:

  1. Project 3: Vulnerable Program Lab - See all vulnerability types
  2. Project 5: Format String Demonstrator - Deep format string understanding
  3. Project 10: Return-to-libc Lab - NX bypass techniques

Phase 3: Defense Mechanisms (4-5 weeks)

Implement real defenses:

  1. Project 4: Stack Canary Implementation - Compiler protection
  2. Project 8: Heap Overflow Detector - Runtime detection
  3. Project 13: FORTIFY_SOURCE Implementation - Compile-time protection

Phase 4: Practical Security (4-5 weeks)

Build usable tools:

  1. Project 9: Input Validation Framework - Secure input handling
  2. Project 14: Secure Config Parser - Real-world parsing
  3. Project 11: Static Analysis Tool - Find vulnerabilities

Phase 5: Advanced & Integration (6-8 weeks)

Master-level work:

  1. Project 12: Use-After-Free Demo - Advanced heap exploitation
  2. 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.