949 lines
30 KiB
Markdown
949 lines
30 KiB
Markdown
# 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**:
|
|
|
|
```c
|
|
// 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**:
|
|
1. `get-symbols` includeExternal=true → Find strcpy, strcat, gets, scanf, sprintf imports
|
|
2. `find-cross-references` to unsafe functions → Locate call sites
|
|
3. `get-decompilation` with includeContext=true → Analyze buffer size vs. input size
|
|
4. Calculate: input_max_size > buffer_size? → Buffer overflow exists
|
|
5. `set-bookmark` type="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**:
|
|
|
|
```c
|
|
// 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**:
|
|
1. `search-decompilation` pattern="printf|fprintf|sprintf|snprintf|syslog"
|
|
2. `get-decompilation` at each match with includeContext=true
|
|
3. Check format string argument: Is it a constant string or variable?
|
|
4. If variable, trace source: Does it come from user input?
|
|
5. `set-bookmark` type="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**:
|
|
|
|
```c
|
|
// 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**:
|
|
1. `get-decompilation` → Identify buffer size from local variable declaration
|
|
2. Find operations on buffer (read, memcpy, strcpy, loops)
|
|
3. Compare buffer size to operation size
|
|
4. `rename-variables` → buffer, buffer_size, read_size for clarity
|
|
5. `set-decompilation-comment` → "Buffer overflow: reads 128 into 64-byte buffer"
|
|
6. `set-bookmark` type="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**:
|
|
|
|
```c
|
|
// 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**:
|
|
1. `search-decompilation` pattern="malloc|calloc|realloc"
|
|
2. Trace size parameter back to source
|
|
3. Check for multiplication/addition in size calculation
|
|
4. `change-variable-datatypes` to proper types (uint32_t, size_t, ssize_t)
|
|
5. Look for overflow checks (or lack thereof)
|
|
6. `set-decompilation-comment` → "Integer overflow: count * size may wrap"
|
|
7. `set-bookmark` type="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**:
|
|
|
|
```c
|
|
// 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**:
|
|
1. `search-decompilation` pattern="free"
|
|
2. For each free(), trace pointer usage after free
|
|
3. `find-cross-references` to pointer variable → See all uses
|
|
4. Check if pointer is nulled after free (ptr = NULL)
|
|
5. Check if pointer is checked before use (if (ptr != NULL))
|
|
6. `rename-variables` → freed_ptr, dangling_ptr for clarity
|
|
7. `set-decompilation-comment` at use site → "Use-after-free"
|
|
8. `set-bookmark` type="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**:
|
|
|
|
```c
|
|
// 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**:
|
|
1. `search-decompilation` pattern="malloc"
|
|
2. Trace allocated buffer through code
|
|
3. Find write operations on buffer (strcpy, memcpy, read, loops)
|
|
4. Compare allocation size to write size
|
|
5. Check for adjacent allocations (exploitation targets)
|
|
6. `set-decompilation-comment` → "Heap overflow: writes 256 into 64-byte allocation"
|
|
7. `set-bookmark` type="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**:
|
|
1. Identify vulnerability (format string, overflow, use-after-free)
|
|
2. Analyze what can be overwritten
|
|
3. Trace pointer dereferencing after corruption
|
|
4. `set-bookmark` type="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**:
|
|
1. Find format string vulnerabilities (user-controlled format)
|
|
2. Find uninitialized variables sent to output
|
|
3. Find string operations missing null termination
|
|
4. `set-bookmark` type="Analysis" category="Info Leak" → Document primitive
|
|
5. 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**:
|
|
1. Identify overflow vulnerability
|
|
2. Determine what's adjacent in memory (return address, function pointer, vtable)
|
|
3. Calculate offset from buffer to target
|
|
4. `get-data` at GOT/PLT addresses → Get function pointer locations
|
|
5. `set-bookmark` type="Analysis" category="Control Flow Hijack"
|
|
6. 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**:
|
|
1. Identify leak primitive (format string, uninitialized data, over-read)
|
|
2. Determine what's leaked (stack, code, heap, libc addresses)
|
|
3. Calculate offsets to useful addresses
|
|
4. `set-bookmark` type="Note" category="Address Leak" → Document leak
|
|
5. `set-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**:
|
|
1. `get-decompilation` of vulnerable function → Find buffer overflow
|
|
2. `rename-variables` → buffer, user_input, size
|
|
3. Calculate offset: buffer to return address (usually buffer_size + 8)
|
|
4. `search-strings-regex` pattern="/bin/sh" → Find shell string
|
|
5. `get-symbols` includeExternal=true → Find system() import
|
|
6. `set-bookmark` type="Analysis" category="Exploit Plan"
|
|
7. 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**:
|
|
1. `search-decompilation` pattern="printf|sprintf" → Find format string calls
|
|
2. `get-decompilation` with includeContext → Verify format string from user
|
|
3. `get-data` at GOT addresses → Identify targets for overwrite
|
|
4. Calculate stack offset to controlled buffer
|
|
5. `set-bookmark` type="Warning" category="Format String"
|
|
6. 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**:
|
|
1. `search-decompilation` pattern="malloc|free"
|
|
2. Trace allocation and free patterns
|
|
3. Identify vulnerability (use-after-free, overflow, double-free)
|
|
4. `rename-variables` → chunk1, chunk2, freed_ptr, size
|
|
5. Analyze adjacent allocations (overflow targets)
|
|
6. `set-bookmark` type="Warning" category="Heap Vulnerability"
|
|
7. 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**:
|
|
1. `get-decompilation` → Find buffer overflow
|
|
2. `search-strings-regex` pattern="/bin/sh" → Get shell string address
|
|
3. `get-symbols` includeExternal=true → Find system import
|
|
4. Check calling convention (x86: stack args, x64: RDI register)
|
|
5. Calculate ROP gadgets if needed: pop rdi; ret
|
|
6. `set-bookmark` type="Note" category="Ret2libc Plan"
|
|
7. 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**:
|
|
1. Identify required gadgets (pop rdi; ret, pop rsi; ret, syscall; ret, etc.)
|
|
2. Use external tool (ROPgadget) to find gadgets in binary/libc
|
|
3. `set-bookmark` type="Note" category="ROP Gadget" at each gadget address
|
|
4. `set-comment` at gadget address → "pop rdi; ret"
|
|
5. 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
|
|
6. `set-bookmark` type="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**:
|
|
1. `search-decompilation` pattern="__stack_chk_fail" → Detect canary presence
|
|
2. `get-decompilation` → See canary check in code
|
|
3. Identify canary position on stack
|
|
4. `set-bookmark` type="Note" category="Stack Canary" → Document location
|
|
5. 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**:
|
|
1. `get-memory-blocks` → Check stack/heap permissions (look for 'x' flag)
|
|
2. If NX enabled, plan ROP or ret2libc
|
|
3. `get-symbols` includeExternal=true → Find usable functions
|
|
4. `set-bookmark` type="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**:
|
|
1. Identify leak primitive (format string, over-read, uninitialized)
|
|
2. Calculate what's leaked (code, stack, heap, libc)
|
|
3. Determine offsets: leaked_addr to target_addr
|
|
4. `set-comment` → "Leak libc: system = libc_base + 0x4f4e0"
|
|
5. `set-bookmark` type="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**:
|
|
1. Identify if PIE enabled (check binary properties)
|
|
2. Find code address leak (stack return address)
|
|
3. Calculate offsets from code base to targets
|
|
4. `set-bookmark` type="Analysis" category="PIE Bypass"
|
|
|
|
---
|
|
|
|
## Using This Reference
|
|
|
|
### Pattern Recognition Workflow
|
|
|
|
1. **Identify vulnerability class** → Match decompiled code to vulnerability patterns
|
|
2. **Determine exploitation primitive** → What capability does vulnerability provide?
|
|
3. **Check protections** → What bypass techniques are needed?
|
|
4. **Plan exploitation workflow** → Chain primitives to achieve goal
|
|
5. **Document in Ghidra** → Bookmarks, comments, renamed variables
|
|
|
|
### Investigation Priority
|
|
|
|
**Start with:**
|
|
1. Unsafe API recognition (strcpy, printf, etc.)
|
|
2. Buffer size vs. operation size comparison
|
|
3. 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.
|