30 KiB
CTF Binary Exploitation Patterns
This document contains patterns for recognizing common vulnerability classes and exploitation primitives in CTF challenges. Focus on conceptual understanding rather than specific exploits.
Vulnerability Recognition Patterns
Unsafe String Operation Patterns
Conceptual characteristics:
- Functions that don't check destination buffer size
- Unbounded copying from source to destination
- Reliance on null terminator without size validation
- No length parameter or ignored length parameter
Dangerous API patterns:
// Unbounded copy (no size checking)
strcpy(dest, user_input); // Copies until null byte
strcat(dest, user_input); // Appends until null byte
sprintf(dest, "%s", user_input); // Formats without bounds
gets(buffer); // Reads unlimited from stdin
// Underspecified bounds
strncpy(dest, src, sizeof(dest)); // Doesn't guarantee null termination
scanf("%s", buffer); // No size limit specified
read(fd, buffer, 1024); // May exceed buffer size if buffer < 1024
recv(sock, buffer, MAX, 0); // May exceed buffer capacity
What to look for in decompiled code:
Buffer declaration:
char buffer[64]; // Fixed-size local array
Unsafe operation on same buffer:
strcpy(buffer, user_input); // No size check
read(fd, buffer, 256); // Reads more than buffer holds
Distance to critical data:
buffer[64] // Local variable at stack offset
saved_rbp // Usually at buffer + buffer_size
return_address // Usually at buffer + buffer_size + 8
Investigation strategy:
get-symbolsincludeExternal=true → Find strcpy, strcat, gets, scanf, sprintf importsfind-cross-referencesto unsafe functions → Locate call sitesget-decompilationwith includeContext=true → Analyze buffer size vs. input size- Calculate: input_max_size > buffer_size? → Buffer overflow exists
set-bookmarktype="Warning" category="Buffer Overflow" at vulnerability
Telltale signs:
- Local char arrays with small sizes (64, 128, 256 bytes)
- Unbounded string functions called on those arrays
- User input directly passed to unsafe function
- No explicit size checking before copy operation
Format String Vulnerability Patterns
Conceptual characteristics:
- User controls the format string parameter
- Format specifiers allow memory read (%x, %s, %p) and write (%n)
- Stack-based exploitation (format string reads stack arguments)
- Arbitrary read/write primitive when exploited
Vulnerable patterns:
// VULNERABLE: User input as format string
printf(user_input);
fprintf(fp, user_input);
sprintf(buffer, user_input);
snprintf(buffer, size, user_input);
syslog(priority, user_input);
// SAFE: Format string is literal
printf("%s", user_input);
fprintf(fp, "Input: %s\n", user_input);
sprintf(buffer, "Data: %s", user_input);
What to look for in decompiled code:
Direct user input to format function:
read(0, buffer, 256);
printf(buffer); // VULNERABLE
Variable format string:
char* fmt = get_format_string(); // Source from user
printf(fmt, args); // VULNERABLE if fmt user-controlled
Missing format string:
fprintf(stderr, error_msg); // VULNERABLE if error_msg from user
Exploitation primitives:
%x or %p → Leak stack values (addresses, canaries, pointers)
%s → Arbitrary read (if pointer on stack)
%n → Arbitrary write (writes byte count to pointer)
%N$x → Direct parameter access (Nth argument)
%N$n → Write to Nth argument pointer
Example attack:
printf("AAAA%10$x"); → Leak 10th stack parameter
printf("AAAA%7$n"); → Write to pointer at 7th stack position
Investigation strategy:
search-decompilationpattern="printf|fprintf|sprintf|snprintf|syslog"get-decompilationat each match with includeContext=true- Check format string argument: Is it a constant string or variable?
- If variable, trace source: Does it come from user input?
set-bookmarktype="Warning" category="Format String" at vulnerability
Telltale signs:
- printf/fprintf with single argument (no format string literal)
- Format string stored in writable buffer
- User input copied into format string variable
- Error message formatted with user-supplied data
Buffer Size vs. Operation Mismatch Patterns
Conceptual characteristics:
- Buffer allocated with one size
- Operation assumes different (larger) size
- Off-by-one errors
- Mismatched size calculations
Common mismatch patterns:
// Wrong size constant
char buffer[64];
read(fd, buffer, 128); // Reads 128 into 64-byte buffer
// Off-by-one
char buffer[64];
for (i = 0; i <= 64; i++) // Loop goes to 64 (65 iterations)
buffer[i] = input[i]; // Writes one byte past end
// Null terminator forgotten
char buffer[64];
strncpy(buffer, input, 64); // May not null-terminate
printf("%s", buffer); // Reads past end if not terminated
// Size calculation error
char buffer[64];
memcpy(buffer, src, strlen(src)); // strlen doesn't include null byte
// But may overflow if strlen(src) >= 64
What to look for in decompiled code:
Size declaration:
local_48 = buffer (char array, size 64)
Operation size:
read(0, local_48, 0x80); // 0x80 = 128 > 64
Offset calculation:
local_48[iVar1] = input[iVar1]; // Check iVar1 bounds
Loop bounds:
for (i = 0; i < size; i++) // Is size validated?
buffer[i] = input[i]; // Does size match buffer capacity?
Investigation strategy:
get-decompilation→ Identify buffer size from local variable declaration- Find operations on buffer (read, memcpy, strcpy, loops)
- Compare buffer size to operation size
rename-variables→ buffer, buffer_size, read_size for clarityset-decompilation-comment→ "Buffer overflow: reads 128 into 64-byte buffer"set-bookmarktype="Warning" category="Size Mismatch"
Telltale signs:
- Magic constants in read/copy operations that don't match buffer size
- sizeof() used incorrectly (sizeof(pointer) vs. sizeof(array))
- Off-by-one in loop bounds (<= instead of <)
- Missing null terminator checks
Integer Overflow Leading to Memory Corruption
Conceptual characteristics:
- Integer arithmetic wraps around at type bounds
- Overflow in size calculation leads to small allocation
- Small allocation leads to buffer overflow
- Underflow in bounds check bypasses security
Vulnerable patterns:
// Allocation size overflow
uint32_t count = user_input; // User controls this
uint32_t size = count * sizeof(element); // May overflow
buffer = malloc(size); // Allocates small buffer due to overflow
for (i = 0; i < count; i++) // Loop uses original count
buffer[i] = data[i]; // Heap overflow
// Bounds check underflow
size_t len = user_input;
if (len - 1 < MAX_SIZE) { // Underflows if len == 0 (unsigned)
memcpy(buffer, src, len); // Large len bypasses check
}
// Sign confusion
int size = user_size; // User controls, may be negative
if (size < MAX_SIZE) { // Passes check if negative
memcpy(buffer, src, size); // Casted to size_t (huge number)
}
What to look for in decompiled code:
Size calculation:
size = user_count * 16; // Multiplication may overflow
Wraparound check missing:
if (user_count < 1000) { // Doesn't check for overflow
size = user_count * 16;
buf = malloc(size);
}
Unsigned underflow:
if (len - 1 < 1024) { // What if len == 0?
Sign conversion:
int signed_size = user_input; // Signed integer
malloc(signed_size); // Casted to size_t (unsigned)
// Negative becomes huge positive
Investigation strategy:
search-decompilationpattern="malloc|calloc|realloc"- Trace size parameter back to source
- Check for multiplication/addition in size calculation
change-variable-datatypesto proper types (uint32_t, size_t, ssize_t)- Look for overflow checks (or lack thereof)
set-decompilation-comment→ "Integer overflow: count * size may wrap"set-bookmarktype="Warning" category="Integer Overflow"
Telltale signs:
- Multiplication in allocation size without overflow check
- Unsigned subtraction in bounds check
- Signed/unsigned type confusion
- Missing validation for very large user-supplied sizes
Use-After-Free Patterns
Conceptual characteristics:
- Memory freed but pointer still accessible (dangling pointer)
- Dangling pointer dereferenced (use after free)
- Heap allocator may reuse freed memory for new allocation
- Type confusion when old pointer accesses new object
Vulnerable patterns:
// Classic use-after-free
object* ptr = malloc(sizeof(object));
use_object(ptr);
free(ptr);
// ... later in code ...
use_object(ptr); // Use after free!
// Double-free (special case)
free(ptr);
free(ptr); // Corrupts heap metadata
// Use-after-free via aliasing
object* ptr1 = malloc(sizeof(object));
object* ptr2 = ptr1; // Aliased pointer
free(ptr1);
use_object(ptr2); // Use after free via alias
What to look for in decompiled code:
Allocation and free:
heap_ptr = malloc(0x40);
// ... use heap_ptr ...
free(heap_ptr);
Later usage (use-after-free):
// ... some code ...
*heap_ptr = value; // Write to freed memory
function(heap_ptr); // Pass freed pointer
Conditional free (double-free risk):
if (condition1) free(ptr);
if (condition2) free(ptr); // May free twice if both true
No pointer nulling:
free(ptr);
// ptr not set to NULL, can be reused
Investigation strategy:
search-decompilationpattern="free"- For each free(), trace pointer usage after free
find-cross-referencesto pointer variable → See all uses- Check if pointer is nulled after free (ptr = NULL)
- Check if pointer is checked before use (if (ptr != NULL))
rename-variables→ freed_ptr, dangling_ptr for clarityset-decompilation-commentat use site → "Use-after-free"set-bookmarktype="Warning" category="Use-After-Free"
Telltale signs:
- free() call without setting pointer to NULL
- Pointer dereferenced after free() in any code path
- Multiple free() calls on same pointer
- Pointer used in different contexts (freed as type A, used as type B)
Heap Overflow Patterns
Conceptual characteristics:
- Allocation with one size
- Write operation exceeds allocated size
- Overflows into adjacent heap chunk
- Can corrupt heap metadata or adjacent object data
Vulnerable patterns:
// Allocation too small
buffer = malloc(64);
read(fd, buffer, 128); // Heap overflow
// Calculation error
buffer = malloc(count * sizeof(element));
for (i = 0; i <= count; i++) // Off-by-one (should be <, not <=)
buffer[i] = data[i]; // Overflows by one element
// Unchecked string operation on heap
buffer = malloc(64);
strcpy(buffer, user_input); // Overflow if user_input > 63 bytes
What to look for in decompiled code:
Heap allocation:
heap_buf = malloc(0x40); // Allocates 64 bytes
Write operation:
read(0, heap_buf, 0x100); // Reads 256 bytes → overflow
Adjacent allocations:
buf1 = malloc(0x40);
buf2 = malloc(0x40); // buf2 likely adjacent to buf1
strcpy(buf1, user_input); // May overflow into buf2
Metadata corruption risk:
chunk = malloc(size);
overflow_write(chunk, large_size); // May corrupt next chunk's metadata
Investigation strategy:
search-decompilationpattern="malloc"- Trace allocated buffer through code
- Find write operations on buffer (strcpy, memcpy, read, loops)
- Compare allocation size to write size
- Check for adjacent allocations (exploitation targets)
set-decompilation-comment→ "Heap overflow: writes 256 into 64-byte allocation"set-bookmarktype="Warning" category="Heap Overflow"
Telltale signs:
- Small malloc() followed by large read/write
- String operations on heap buffers without bounds
- Loop writing to heap array without bounds check
- Multiple sequential allocations (heap layout predictable)
Exploitation Primitive Patterns
Arbitrary Memory Write Primitives
Conceptual characteristics:
- Ability to write controlled data to chosen address
- Achieved through various vulnerability classes
- Foundation for control flow hijacking and data corruption
Primitive construction patterns:
Format string arbitrary write:
// Concept: %n writes byte count to pointer argument
printf("AAAA%7$n");
// If stack[7] is controlled pointer, writes to *stack[7]
Technique:
1. Place target address on stack
2. Position format string to access it (%N$n)
3. Adjust byte count with padding to write desired value
4. Use width specifiers: %200c%7$n → writes 200+4=204
Buffer overflow arbitrary write:
// Concept: Overflow to overwrite pointer, then use pointer
Step 1: Overflow to corrupt pointer
[buffer overflow] → [overwrite ptr variable]
Step 2: Trigger write through pointer
*ptr = value; // Writes to attacker-controlled address
Heap overflow arbitrary write:
// Concept: Overflow heap chunk to corrupt adjacent chunk's pointers
Chunk layout:
[chunk1 metadata][chunk1 data][chunk2 metadata][chunk2 data]
Overflow chunk1 data → overwrite chunk2 metadata → corrupt pointers
When chunk2 used, writes to attacker-controlled addresses
Investigation strategy:
- Identify vulnerability (format string, overflow, use-after-free)
- Analyze what can be overwritten
- Trace pointer dereferencing after corruption
set-bookmarktype="Analysis" category="Arbitrary Write" → Document primitive
What enables arbitrary write:
- Controlled pointer value (overflow, format string)
- Dereference of controlled pointer (assignment, function call)
- Heap metadata corruption (unlink exploitation)
Arbitrary Memory Read Primitives
Conceptual characteristics:
- Ability to read from chosen memory address
- Used to leak addresses, canaries, code/data
- Critical for defeating ASLR and other protections
Primitive construction patterns:
Format string arbitrary read:
// Concept: %s reads string from pointer argument
printf("AAAA%10$s");
// If stack[10] is controlled pointer, prints string at *stack[10]
Technique:
1. Place target address on stack
2. Position format string to access it (%N$s)
3. Read output to obtain memory contents
Uninitialized data read:
// Concept: Uninitialized variables contain previous stack/heap data
Pattern in decompiled code:
char buffer[64];
// No initialization
send(socket, buffer, 64, 0); // Leaks stack contents
Investigation:
Look for send/write without initialization
Check if data used before written
Buffer over-read:
// Concept: Read past end of buffer into adjacent memory
Pattern:
char buffer[64];
strncpy(buffer, input, 64); // No null termination
printf("%s", buffer); // Reads past end until null byte
Result: Leaks adjacent stack data
Investigation strategy:
- Find format string vulnerabilities (user-controlled format)
- Find uninitialized variables sent to output
- Find string operations missing null termination
set-bookmarktype="Analysis" category="Info Leak" → Document primitive- Calculate what can be leaked (addresses, canaries, pointers)
What enables arbitrary read:
- Format string with %s and controlled pointer
- Uninitialized buffer sent to network/file
- Missing null terminator allows over-read
- Heap use-after-free with read operations
Control Flow Hijack Primitives
Conceptual characteristics:
- Redirect program execution to attacker-controlled location
- Achieved by overwriting function pointers or return addresses
- Goal: Execute shellcode, ROP chain, or existing functions
Hijack target patterns:
Return address overwrite (stack overflow):
Stack layout:
[buffer][saved rbp][return address]
Overflow buffer → overwrite return address → redirect on function return
What to look for:
Local buffer vulnerable to overflow
Return address at predictable offset (buffer_size + 8 on x64)
Calculate offset: buffer start to return address location
Function pointer overwrite:
// Global or heap-allocated function pointer
void (*callback)(void) = default_handler;
// Overflow to overwrite callback
buffer_overflow → overwrite callback pointer
// Trigger hijack
callback(); // Calls attacker-controlled address
GOT/PLT overwrite:
// Global Offset Table contains addresses of library functions
// Overwrite GOT entry to redirect library call
Example:
Overwrite GOT[puts] with system address
Next call to puts() actually calls system()
Requirement: Arbitrary write primitive to GOT address
Virtual table (vtable) overwrite:
// C++ objects have vtable pointers
// Overwrite vtable pointer to fake vtable
Object layout:
[vtable ptr][member1][member2]...
Overflow → overwrite vtable ptr → point to attacker-controlled memory
Virtual function call → uses fake vtable → hijacks control flow
Investigation strategy:
- Identify overflow vulnerability
- Determine what's adjacent in memory (return address, function pointer, vtable)
- Calculate offset from buffer to target
get-dataat GOT/PLT addresses → Get function pointer locationsset-bookmarktype="Analysis" category="Control Flow Hijack"- Document target address and offset
Telltale signs:
- Function pointers in global variables or structures
- Indirect calls through function pointers
- Virtual function calls (C++ code)
- GOT/PLT entries for library functions
Information Leak Primitives (Defeating ASLR)
Conceptual characteristics:
- Leak address from memory to defeat address randomization
- Calculate base addresses from leaked pointers
- Use leaked addresses in subsequent exploitation
Leak source patterns:
Stack address leak:
// Stack addresses often present on stack itself
Format string: printf("%p %p %p %p") // Leak stack pointers
Uninitialized: Stack variable contains previous stack frame address
Use: Calculate stack layout, predict buffer addresses
Code address leak (PIE bypass):
// Return addresses on stack point to code section
Format string leak of return address → code address
Calculate code base: leaked_addr & ~0xFFF (page alignment)
Use: Calculate gadget addresses, function addresses
Libc address leak (ASLR bypass):
// GOT contains resolved libc function addresses
Arbitrary read of GOT entry → libc function address
Calculate libc base: leaked_addr - function_offset
Use: Calculate system(), one_gadget, useful function addresses
Heap address leak:
// Heap pointers often in freed chunks or stack
Use-after-free leak: Read freed chunk (contains fwd/bck pointers)
Format string: Leak heap pointer from stack
Use: Predict heap layout, target heap objects
Investigation strategy:
- Identify leak primitive (format string, uninitialized data, over-read)
- Determine what's leaked (stack, code, heap, libc addresses)
- Calculate offsets to useful addresses
set-bookmarktype="Note" category="Address Leak" → Document leakset-comment→ "Leaks libc address, calculate system() as libc_base + 0x4f4e0"
Telltale signs:
- printf with user-controlled format string
- Send/write with uninitialized buffer
- String operations without null termination
- Heap metadata visible to program (freed chunks)
Common Exploitation Workflows
Stack Overflow to Shell
Attack flow:
1. Find buffer overflow on stack
2. Calculate offset to return address
3. Identify target for hijack:
a. Shellcode address (if NX disabled)
b. system() address (if no ASLR)
c. ROP chain address (if protections enabled)
4. Construct payload: [padding][return address][arguments/ROP]
5. Trigger overflow, return redirects to attacker code
6. Execute shellcode/system("/bin/sh") to get shell
Investigation steps:
get-decompilationof vulnerable function → Find buffer overflowrename-variables→ buffer, user_input, size- Calculate offset: buffer to return address (usually buffer_size + 8)
search-strings-regexpattern="/bin/sh" → Find shell stringget-symbolsincludeExternal=true → Find system() importset-bookmarktype="Analysis" category="Exploit Plan"- Document payload structure in comment
Format String to Arbitrary Write
Attack flow:
1. Find printf(user_input) vulnerability
2. Test format string: Send "%x %x %x" → leak stack values
3. Find offset to controlled data on stack
4. Construct format string to write to arbitrary address:
- Place target address on stack
- Use %N$n to write to address at stack[N]
5. Target: Overwrite GOT entry, return address, or function pointer
6. Redirect execution to attacker code
Investigation steps:
search-decompilationpattern="printf|sprintf" → Find format string callsget-decompilationwith includeContext → Verify format string from userget-dataat GOT addresses → Identify targets for overwrite- Calculate stack offset to controlled buffer
set-bookmarktype="Warning" category="Format String"- Document exploitation: "%7$n writes to address at stack[7]"
Heap Exploitation to Code Execution
Attack flow:
1. Find heap vulnerability (use-after-free, heap overflow, double-free)
2. Understand heap layout (chunk sizes, allocation order)
3. Exploit heap corruption:
a. Use-after-free: Free object, allocate new, use old pointer (type confusion)
b. Heap overflow: Overflow chunk to corrupt adjacent chunk metadata
c. Double-free: Corrupt freelist to allocate arbitrary address
4. Gain arbitrary write or control flow hijack primitive
5. Overwrite function pointer, GOT entry, or return address
6. Execute attacker code
Investigation steps:
search-decompilationpattern="malloc|free"- Trace allocation and free patterns
- Identify vulnerability (use-after-free, overflow, double-free)
rename-variables→ chunk1, chunk2, freed_ptr, size- Analyze adjacent allocations (overflow targets)
set-bookmarktype="Warning" category="Heap Vulnerability"- Document exploitation primitive achieved
Ret2libc (Return-to-libc)
Attack flow:
1. Find stack overflow vulnerability
2. Cannot use shellcode (NX enabled)
3. Redirect to existing libc function: system()
4. Set up arguments: First arg points to "/bin/sh"
5. Payload structure:
- Overflow to return address
- Overwrite return address → system() address
- Set first argument → pointer to "/bin/sh" string
6. Function returns, calls system("/bin/sh"), spawns shell
Investigation steps:
get-decompilation→ Find buffer overflowsearch-strings-regexpattern="/bin/sh" → Get shell string addressget-symbolsincludeExternal=true → Find system import- Check calling convention (x86: stack args, x64: RDI register)
- Calculate ROP gadgets if needed: pop rdi; ret
set-bookmarktype="Note" category="Ret2libc Plan"- Document payload: [padding][system_addr][ret_addr]["/bin/sh"_ptr]
ROP Chain Construction
Attack flow:
1. Find code execution vulnerability (overflow, etc.)
2. Protections prevent direct shellcode/ret2libc
3. Build ROP chain: Sequence of gadget addresses
4. Each gadget: Small code fragment ending in 'ret'
5. Chain gadgets to build desired operation (e.g., execve syscall)
6. Place chain on stack, trigger vulnerability
7. Execution flows through gadgets, performs desired operation
Investigation steps:
- Identify required gadgets (pop rdi; ret, pop rsi; ret, syscall; ret, etc.)
- Use external tool (ROPgadget) to find gadgets in binary/libc
set-bookmarktype="Note" category="ROP Gadget" at each gadget addressset-commentat gadget address → "pop rdi; ret"- Document ROP chain structure:
- [gadget1_addr] → pop rdi; ret
- ["/bin/sh"_ptr] → argument for rdi
- [gadget2_addr] → pop rsi; ret
- [NULL] → argument for rsi
- [syscall_addr] → execve syscall
set-bookmarktype="Analysis" category="ROP Chain Plan"
Protection Mechanism Bypass Patterns
Stack Canary Bypass
Canary mechanism:
Stack layout with canary:
[buffer][stack canary][saved rbp][return address]
On function return:
if (canary != expected_canary)
__stack_chk_fail(); // Abort on corruption
Bypass techniques:
1. Leak canary value (format string, uninitialized data):
printf(user_input); // Format string leak
Send "%7$p" → leak canary from stack position 7
Include leaked canary in overflow payload to preserve it
2. Brute-force canary (fork server):
If server forks instead of exiting:
Canary same across fork
Brute-force one byte at a time
256 attempts per byte, 1024 total for 32-bit canary
3. Overwrite without corrupting canary:
Partial overwrite: Overflow only up to return address
Don't touch canary if it's not in the way
Or overwrite saved rbp and return address precisely
Investigation:
search-decompilationpattern="__stack_chk_fail" → Detect canary presenceget-decompilation→ See canary check in code- Identify canary position on stack
set-bookmarktype="Note" category="Stack Canary" → Document location- Plan bypass: leak, brute-force, or avoid
NX/DEP Bypass (No Execute)
Protection mechanism:
Stack/heap marked non-executable
Shellcode injection doesn't work (causes segfault)
Bypass techniques:
1. Return-to-libc (ret2libc):
Don't inject code, reuse existing code
Redirect to system(), execve(), etc.
Set up arguments properly
2. Return-Oriented Programming (ROP):
Chain existing code fragments (gadgets)
Build complex operations from simple gadgets
No new code introduced
3. mprotect/VirtualProtect ROP:
Use ROP to call mprotect(shellcode_addr, RWX)
Change shellcode memory to executable
Jump to now-executable shellcode
Investigation:
get-memory-blocks→ Check stack/heap permissions (look for 'x' flag)- If NX enabled, plan ROP or ret2libc
get-symbolsincludeExternal=true → Find usable functionsset-bookmarktype="Analysis" category="NX Bypass"
ASLR Bypass (Address Space Layout Randomization)
Protection mechanism:
Addresses randomized each execution
Code base, libc base, stack base, heap base all randomized
Exploit addresses must be dynamically calculated
Bypass techniques:
1. Information leak:
Leak address from memory (format string, uninitialized data)
Calculate base address from leaked pointer
Use base + offset to find desired functions
2. Partial overwrite:
Only lowest 12 bits (page offset) are not randomized
Overwrite only last byte of address
Reduces entropy, enables brute-force or partial redirect
3. Heap spraying (rarely applicable in CTF):
Fill heap with controlled data
Increase probability of hitting controlled memory
Investigation:
- Identify leak primitive (format string, over-read, uninitialized)
- Calculate what's leaked (code, stack, heap, libc)
- Determine offsets: leaked_addr to target_addr
set-comment→ "Leak libc: system = libc_base + 0x4f4e0"set-bookmarktype="Analysis" category="ASLR Bypass"
PIE Bypass (Position Independent Executable)
Protection mechanism:
Code section randomized (in addition to ASLR)
Function addresses, gadget addresses randomized
Cannot hardcode code addresses
Bypass techniques:
1. Leak code address:
Leak return address from stack → points to code
Calculate code base: leaked_addr & ~0xFFF
Calculate function/gadget addresses: code_base + offset
2. Partial overwrite:
Overwrite only last byte of return address
Redirect within same function or nearby functions
Useful for redirecting to existing win() function
Investigation:
- Identify if PIE enabled (check binary properties)
- Find code address leak (stack return address)
- Calculate offsets from code base to targets
set-bookmarktype="Analysis" category="PIE Bypass"
Using This Reference
Pattern Recognition Workflow
- Identify vulnerability class → Match decompiled code to vulnerability patterns
- Determine exploitation primitive → What capability does vulnerability provide?
- Check protections → What bypass techniques are needed?
- Plan exploitation workflow → Chain primitives to achieve goal
- Document in Ghidra → Bookmarks, comments, renamed variables
Investigation Priority
Start with:
- Unsafe API recognition (strcpy, printf, etc.)
- Buffer size vs. operation size comparison
- Input flow tracing (where does user data go?)
Then analyze: 4. Memory layout (what's adjacent to vulnerable buffer?) 5. Available exploitation targets (return address, function pointers, GOT) 6. Protection mechanisms (canary, NX, ASLR, PIE)
Finally plan: 7. Exploitation primitive construction 8. Protection bypass strategy 9. Payload structure 10. Exploit execution plan
Progressive Understanding
First pass: "Unsafe strcpy in main() on buffer[64]" Second pass: "Overflow of 64 bytes to reach return address at offset +72" Third pass: "Can redirect to system@plt, need '/bin/sh' string address" Fourth pass: "Full ret2libc: overflow → system('/bin/sh') → shell"
Each iteration refines the exploitation plan.
Evidence-Based Exploitation
Every claim needs evidence:
- "Buffer overflow exists" → Show buffer size < input size
- "Return address at offset 72" → Show stack layout calculation
- "Can call system()" → Show system@plt address or import
- "ASLR bypass possible" → Show leak primitive and calculation
Document all evidence with bookmarks and comments in Ghidra.