Software Vulnerabilities

Topics Buffer Overflows · Heap Overflows · Integer Overflow · Format Strings
01 //

Bugs and Vulnerabilities

Bugs are common in software. Programmers make implementation errors, and finding them becomes harder as codebases grow. Even though testing often consumes ~80% of development budget, bugs still slip through. When a bug can be abused by an attacker, it becomes a security vulnerability.

From Bug to Attack

The path from a coding mistake to an actual attack typically follows:

  • Bug – Unintended behavior in the program
  • Security Flaw – The bug can be abused to violate security
  • Vulnerability – Exploitable weakness; often assigned a CVE ID
  • Exploit – Code or input that takes advantage of the vulnerability
  • Attack – Malicious use of the exploit against a target
CVE Database

Security researchers and vendors disclose vulnerabilities at cve.mitre.org. Developers then issue patches. Public disclosure helps defenders fix systems before attackers exploit them.

Most Common Vulnerability Types
  • Buffer overflows (stack and heap)
  • Format string vulnerabilities
  • Integer overflows (often leading to buffer overflows)
02 //

Buffer Overflows (Stack)

A buffer overflow occurs when a program writes more data into a fixed-size buffer than it can hold. The extra bytes spill into adjacent memory, overwriting whatever is there. On the stack, that includes local variables, saved registers, and crucially—the return address that tells the CPU where to go when the function finishes.

Stack Layout (High to Low Address)

Data is laid out in order: buffer → other locals → saved registers → return address. An overflow writes upward in memory, so excess bytes overwrite variables and eventually the return address.

Attack 1: Variable Tampering

Overwrite passwordok or userid to bypass authentication. A crafted input can flip a "false" to "true" or change a user ID to gain privileged access.

Attack 2: Code Injection

Overwrite the return address to point into the buffer itself, where the attacker has placed machine code (shellcode). When the function returns, the CPU executes the attacker's code instead of the real caller.

Classic Vulnerable Pattern

gets(password) reads input with no length limit into a fixed buffer—extremely dangerous. Safer alternatives: fgets with size limit, bounds checking, or higher-level safe APIs.

03 //

Heap Overflow

A heap overflow happens when a program writes past the end of a heap-allocated buffer (from malloc). Unlike the stack, the heap stores dynamically allocated data. Exploits often target the internal structures of the memory allocator (e.g., dlmalloc used in glibc).

Case 1: Function Pointer Overwrite

If an adjacent buffer holds a function pointer (e.g., callback, vtable), overflowing into it lets the attacker replace the pointer with an address of their choosing. The next call goes to attacker-controlled code.

Case 2: Unlink Exploit (dlmalloc)

When freeing a chunk, the allocator "unlinks" it from a doubly-linked list of free chunks. The unlink macro does FD->bk = BK and BK->fd = FD. By corrupting chunk headers, the attacker controls FD and BK—and thus which memory locations get overwritten with which values.

How the Unlink Exploit Works

Free chunks are in a doubly-linked list with forward (FD) and backward (BK) pointers. The unlink macro updates the neighboring chunks' pointers. If the attacker overflows and fakes a free-chunk header, they choose FD and BK so that the unlink writes a chosen value to a chosen address (e.g., return address or GOT entry). Modern glibc has hardened unlink checks to mitigate this.

04 //

Integer Overflow

Integers have a fixed size (e.g., 32 bits). When a computation exceeds the maximum value, it wraps around instead of raising an error. Examples: 0xffffffff + 1 = 0 (unsigned), 0x80000001 * 2 = 2. Integer overflow by itself does not change control flow, but it often leads to buffer overflows or bypassed security checks.

Leads to Buffer Overflow

If p = malloc(x * 4) and x is attacker-controlled: when x * 4 overflows (e.g., x = 0x40000001), the allocation is tiny (e.g., 4 bytes). A later p[x] = 0 then writes far out-of-bounds.

Bypasses Security Check

Example: if (x + 4 > y) error(); buffer[x] = 0; The check intends to block large x. But if x = 0xffffffff, then x + 4 = 3 (overflow). The check passes (3 < y), yet buffer[x] is a huge out-of-bounds access.

05 //

Format String Vulnerability

Functions like printf take a format string (e.g., "%d %s") and optional arguments. The format string tells printf how many arguments to expect and where to read them from the stack. If the format string is user-controlled (e.g., printf(user_input)), the attacker can make printf read from arbitrary stack locations or even write to memory.

Information Leak

With printf(argv[1]), an attacker passes %d %d %d .... Each %d makes printf treat the next stack slot as an integer and print it. This leaks locals, return addresses, and other sensitive data.

Arbitrary Write via %n

The %n specifier writes the count of bytes printed so far to the address pointed to by the corresponding argument. printf("AAAA%n", &i) sets i = 4. By controlling the format string and stacking arguments, the attacker can write chosen values to return addresses, GOT entries, etc.

Fix

Never pass user input directly as the format string. Use printf("%s", user_input) so the user's input is treated as a plain string, not as format directives.

06 //

Summary

Key Takeaways
  • Stack overflow: Unbounded write overwrites return address → redirect execution to injected shellcode for arbitrary code execution
  • Heap overflow: Corrupt allocator structures (e.g., unlink) or adjacent function pointers to gain control
  • Integer overflow: Wrapping arithmetic leads to undersized allocation or bypassed bounds checks → out-of-bounds access
  • Format string: User-controlled format string lets attacker read stack (%d, %s) or write memory (%n)