Initial commit
This commit is contained in:
532
skills/ctf-pwn/SKILL.md
Normal file
532
skills/ctf-pwn/SKILL.md
Normal file
@@ -0,0 +1,532 @@
|
||||
---
|
||||
name: ctf-pwn
|
||||
description: Solve CTF binary exploitation challenges by discovering and exploiting memory corruption vulnerabilities to read flags. Use for buffer overflows, format strings, heap exploits, ROP challenges, or any pwn/exploitation task.
|
||||
---
|
||||
|
||||
# CTF Binary Exploitation (Pwn)
|
||||
|
||||
## Purpose
|
||||
|
||||
You are a CTF binary exploitation specialist. Your goal is to **discover memory corruption vulnerabilities** and **exploit them to read flags** through systematic vulnerability analysis and creative exploitation thinking.
|
||||
|
||||
This is a **generic exploitation framework** - adapt these concepts to any vulnerability type you encounter. Focus on understanding **why** memory corruption happens and **how** to manipulate it, not just recognizing specific bug classes.
|
||||
|
||||
## Conceptual Framework
|
||||
|
||||
### The Exploitation Mindset
|
||||
|
||||
**Think in three layers:**
|
||||
|
||||
1. **Data Flow Layer**: Where does attacker-controlled data go?
|
||||
- Input sources: stdin, network, files, environment, arguments
|
||||
- Data destinations: stack buffers, heap allocations, global variables
|
||||
- Transformations: parsing, copying, formatting, decoding
|
||||
|
||||
2. **Memory Safety Layer**: What assumptions does the program make?
|
||||
- Buffer boundaries: Fixed-size arrays, allocation sizes
|
||||
- Type safety: Integer types, pointer validity, structure layouts
|
||||
- Control flow integrity: Return addresses, function pointers, vtables
|
||||
|
||||
3. **Exploitation Layer**: How can we violate trust boundaries?
|
||||
- Memory writes: Overwrite critical data (return addresses, function pointers, flags)
|
||||
- Memory reads: Leak information (addresses, canaries, pointer values)
|
||||
- Control flow hijacking: Redirect execution to attacker-controlled locations
|
||||
- Logic manipulation: Change program state to skip checks or trigger unintended paths
|
||||
|
||||
### Core Question Sequence
|
||||
|
||||
For every CTF pwn challenge, ask these questions **in order**:
|
||||
|
||||
1. **What data do I control?**
|
||||
- Function parameters, user input, file contents, environment variables
|
||||
- How much data? What format? Any restrictions (printable chars, null bytes)?
|
||||
|
||||
2. **Where does my data go in memory?**
|
||||
- Stack buffers? Heap allocations? Global variables?
|
||||
- What's the size of the destination? Is it checked?
|
||||
|
||||
3. **What interesting data is nearby in memory?**
|
||||
- Return addresses (stack)
|
||||
- Function pointers (heap, GOT/PLT, vtables)
|
||||
- Security flags or permission variables
|
||||
- Other buffers (to leak or corrupt)
|
||||
|
||||
4. **What happens if I send more data than expected?**
|
||||
- Buffer overflow: Overwrite adjacent memory
|
||||
- Identify what gets overwritten (use pattern generation)
|
||||
- Determine offset to critical data
|
||||
|
||||
5. **What can I overwrite to change program behavior?**
|
||||
- Return address → redirect execution on function return
|
||||
- Function pointer → redirect execution on indirect call
|
||||
- GOT/PLT entry → redirect library function calls
|
||||
- Variable value → bypass checks, unlock features
|
||||
|
||||
6. **Where can I redirect execution?**
|
||||
- Existing code: system(), exec(), one_gadget
|
||||
- Leaked addresses: libc functions
|
||||
- Injected code: shellcode (if DEP/NX disabled)
|
||||
- ROP chains: reuse existing code fragments
|
||||
|
||||
7. **How do I read the flag?**
|
||||
- Direct: Call system("/bin/cat flag.txt") or open()/read()/write()
|
||||
- Shell: Call system("/bin/sh") and interact
|
||||
- Leak: Read flag into buffer, leak buffer contents
|
||||
|
||||
## Core Methodologies
|
||||
|
||||
### Vulnerability Discovery
|
||||
|
||||
**Unsafe API Pattern Recognition:**
|
||||
|
||||
Identify dangerous functions that don't enforce bounds:
|
||||
- **Unbounded copies**: strcpy, strcat, sprintf, gets
|
||||
- **Underspecified bounds**: read(), recv(), scanf("%s"), strncpy (no null termination)
|
||||
- **Format string bugs**: printf(user_input), fprintf(fp, user_input)
|
||||
- **Integer overflows**: malloc(user_size), buffer[user_index], length calculations
|
||||
|
||||
**Investigation strategy:**
|
||||
1. `get-symbols` includeExternal=true → Find unsafe API imports
|
||||
2. `find-cross-references` to unsafe functions → Locate usage points
|
||||
3. `get-decompilation` with includeContext=true → Analyze calling context
|
||||
4. Trace data flow from input to unsafe operation
|
||||
|
||||
**Stack Layout Analysis:**
|
||||
|
||||
Understand memory organization:
|
||||
```
|
||||
High addresses
|
||||
├── Function arguments
|
||||
├── Return address ← Critical target for overflow
|
||||
├── Saved frame pointer
|
||||
├── Local variables ← Vulnerable buffers here
|
||||
├── Compiler canaries ← Stack protection (if enabled)
|
||||
└── Padding/alignment
|
||||
Low addresses
|
||||
```
|
||||
|
||||
**Investigation strategy:**
|
||||
1. `get-decompilation` of vulnerable function → See local variable layout
|
||||
2. Estimate offsets: buffer → saved registers → return address
|
||||
3. `set-bookmark` type="Analysis" category="Vulnerability" at overflow site
|
||||
4. `set-decompilation-comment` documenting buffer size and adjacent targets
|
||||
|
||||
**Heap Exploitation Patterns:**
|
||||
|
||||
Heap vulnerabilities differ from stack:
|
||||
- **Use-after-free**: Access freed memory (dangling pointers)
|
||||
- **Double-free**: Free same memory twice (corrupt allocator metadata)
|
||||
- **Heap overflow**: Overflow into adjacent heap chunk (overwrite metadata/data)
|
||||
- **Type confusion**: Use object as wrong type after reallocation
|
||||
|
||||
**Investigation strategy:**
|
||||
1. `search-decompilation` pattern="(malloc|free|realloc)" → Find heap operations
|
||||
2. Trace pointer lifecycle: allocation → use → free
|
||||
3. Look for dangling pointer usage after free
|
||||
4. Identify adjacent allocations (overflow targets)
|
||||
|
||||
### Memory Layout Understanding
|
||||
|
||||
**Address Space Discovery:**
|
||||
|
||||
Map the binary's memory:
|
||||
1. `get-memory-blocks` → See sections (.text, .data, .bss, heap, stack)
|
||||
2. Note executable sections (shellcode candidates if NX disabled)
|
||||
3. Note writable sections (data corruption targets)
|
||||
4. Identify ASLR status (addresses randomized each run?)
|
||||
|
||||
**Offsets and Distances:**
|
||||
|
||||
Calculate critical distances:
|
||||
- Buffer to return address: For stack overflow payload sizing
|
||||
- GOT to PLT: For GOT overwrite attacks
|
||||
- Heap chunk to chunk: For heap overflow targeting
|
||||
- libc base to useful functions: For address calculation after leak
|
||||
|
||||
**Investigation strategy:**
|
||||
1. `get-data` or `read-memory` at known addresses → Sample memory layout
|
||||
2. `find-cross-references` direction="both" → Map relationships
|
||||
3. Calculate offsets manually from decompilation
|
||||
4. `set-comment` at key offsets documenting distances
|
||||
|
||||
### Exploitation Planning
|
||||
|
||||
**Constraint Analysis:**
|
||||
|
||||
Identify exploitation constraints:
|
||||
- **Bad bytes**: Null bytes (\x00) terminate C strings → avoid in address/payload
|
||||
- **Input size limits**: Truncation, buffering, network MTU
|
||||
- **Character restrictions**: Printable-only, alphanumeric, no special chars
|
||||
- **Protection mechanisms**: Detect via `search-decompilation` pattern="(canary|__stack_chk)"
|
||||
|
||||
**Bypass Strategies:**
|
||||
|
||||
Common protections and bypass techniques:
|
||||
- **Stack canaries**: Leak canary value, brute-force (fork servers), overwrite without corrupting
|
||||
- **ASLR**: Leak addresses (format strings, uninitialized data), partial overwrite (last byte randomization)
|
||||
- **NX/DEP**: ROP (Return-Oriented Programming), ret2libc, JOP (Jump-Oriented Programming)
|
||||
- **PIE**: Leak code addresses, relative offsets within binary, partial overwrites
|
||||
|
||||
**Exploitation Primitives:**
|
||||
|
||||
Build these fundamental capabilities:
|
||||
- **Arbitrary write**: Write controlled data to chosen address (format string, heap overflow)
|
||||
- **Arbitrary read**: Read from chosen address (format string, uninitialized data, overflow into pointer)
|
||||
- **Control flow hijack**: Redirect execution (overwrite return address, function pointer, GOT entry)
|
||||
- **Information leak**: Obtain addresses, canaries, pointers (uninitialized variables, format strings)
|
||||
|
||||
**Chain multiple primitives when needed:**
|
||||
- Leak → Calculate addresses → Overwrite function pointer → Exploit
|
||||
- Partial overwrite → Leak full address → Calculate libc base → ret2libc
|
||||
- Heap overflow → Overwrite function pointer → Arbitrary write → GOT overwrite → Shell
|
||||
|
||||
## Flexible Workflow
|
||||
|
||||
This is a **thinking framework**, not a rigid checklist. Adapt to the challenge:
|
||||
|
||||
### Phase 1: Binary Reconnaissance (5-10 tool calls)
|
||||
|
||||
**Understand the challenge:**
|
||||
|
||||
1. `get-current-program` or `list-project-files` → Identify target binary
|
||||
2. `get-memory-blocks` → Map sections, identify protections
|
||||
3. `get-functions` filterDefaultNames=false → Count functions (stripped vs. symbolic)
|
||||
4. `search-strings-regex` pattern="flag" → Find flag-related strings
|
||||
5. `get-symbols` includeExternal=true → List imported functions
|
||||
|
||||
**Identify entry points and input vectors:**
|
||||
|
||||
6. `get-decompilation` functionNameOrAddress="main" limit=50 → See program flow
|
||||
7. Look for input functions: read(), recv(), gets(), scanf(), fgets()
|
||||
8. `find-cross-references` to input functions → Map input flow
|
||||
9. `set-bookmark` type="TODO" category="Input Vector" at each input point
|
||||
|
||||
**Flag suspicious patterns:**
|
||||
- Unsafe functions (strcpy, sprintf, gets)
|
||||
- Large stack buffers with small read operations
|
||||
- Format string vulnerabilities (user-controlled format)
|
||||
- Unbounded loops or recursion
|
||||
|
||||
### Phase 2: Vulnerability Analysis (10-15 tool calls)
|
||||
|
||||
**Trace data flow from input to vulnerability:**
|
||||
|
||||
1. `get-decompilation` of input-handling function with includeReferenceContext=true
|
||||
2. Identify buffer sizes: char buf[64], malloc(size), etc.
|
||||
3. Identify write operations: strcpy(dest, src), read(fd, buf, 1024)
|
||||
4. **Calculate vulnerability**: Write size > buffer size?
|
||||
|
||||
**Analyze vulnerable function context:**
|
||||
|
||||
5. `rename-variables` → Clarify data flow (user_input, buffer, size, etc.)
|
||||
6. `change-variable-datatypes` → Fix types for clarity
|
||||
7. `set-decompilation-comment` → Document vulnerability location and type
|
||||
|
||||
**Map memory layout around vulnerability:**
|
||||
|
||||
8. Identify local variables and their stack positions
|
||||
9. Calculate offset from buffer start to return address
|
||||
10. `read-memory` at nearby addresses → Sample stack layout (if debugging available)
|
||||
11. `set-bookmark` type="Warning" category="Overflow" → Mark vulnerability
|
||||
|
||||
**Cross-reference analysis:**
|
||||
|
||||
12. `find-cross-references` to vulnerable function → How is it called?
|
||||
13. Check for exploitation helpers: system(), exec(), "/bin/sh" string
|
||||
14. `search-strings-regex` pattern="/bin/(sh|bash)" → Find shell strings
|
||||
15. `search-decompilation` pattern="system|exec" → Find execution functions
|
||||
|
||||
### Phase 3: Exploitation Strategy (5-10 tool calls)
|
||||
|
||||
**Determine exploitation approach:**
|
||||
|
||||
Based on protections and available primitives:
|
||||
|
||||
**If no protections (NX disabled, no canary, no ASLR):**
|
||||
- Stack overflow → overwrite return address → jump to shellcode
|
||||
- Inject shellcode in buffer, jump to buffer address
|
||||
|
||||
**If NX enabled but no ASLR:**
|
||||
- ret2libc: Overwrite return address → chain to system() with "/bin/sh"
|
||||
- ROP chain: Chain gadgets to build system("/bin/sh") call
|
||||
- GOT overwrite: Overwrite GOT entry to redirect library call
|
||||
|
||||
**If ASLR enabled:**
|
||||
- Leak addresses first (format string, uninitialized data)
|
||||
- Calculate libc base from leaked address
|
||||
- Use leak to build ROP chain or ret2libc with correct addresses
|
||||
|
||||
**If stack canary present:**
|
||||
- Leak canary value (format string, sequential overflow)
|
||||
- Preserve canary in overflow payload
|
||||
- Or use heap exploitation instead
|
||||
|
||||
**Investigation for each strategy:**
|
||||
|
||||
1. `search-strings-regex` pattern="(\\x2f|/)bin/(sh|bash)" → Find shell strings
|
||||
2. `find-cross-references` to "/bin/sh" → Get string address
|
||||
3. `get-symbols` includeExternal=true → Find system/exec imports
|
||||
4. `get-decompilation` of system → Get address (if not PIE)
|
||||
|
||||
**For ROP:**
|
||||
5. `search-decompilation` pattern="(pop|ret)" → Find gadget candidates
|
||||
6. Manual ROP gadget discovery (use external tools like ROPgadget)
|
||||
7. Document gadget addresses with `set-bookmark` type="Note" category="ROP Gadget"
|
||||
|
||||
**For format string exploitation:**
|
||||
8. `get-decompilation` of printf call → Analyze format string control
|
||||
9. Test format string primitives: %x (leak), %n (write), %s (arbitrary read)
|
||||
10. `set-comment` documenting exploitation primitive
|
||||
|
||||
### Phase 4: Payload Construction (Conceptual)
|
||||
|
||||
**Build the exploit payload:**
|
||||
|
||||
This happens **outside Ghidra** using Python/pwntools, but plan it here:
|
||||
|
||||
1. **Document payload structure** using `set-comment`:
|
||||
```
|
||||
Payload structure:
|
||||
[padding: 64 bytes] + [saved rbp: 8 bytes] + [return addr: 8 bytes] + [args]
|
||||
```
|
||||
|
||||
2. **Record critical addresses** with `set-bookmark`:
|
||||
- Buffer address: 0x7fffffffdd00
|
||||
- Return address location: 0x7fffffffdd40 (offset +64)
|
||||
- system() address: 0x7ffff7e14410
|
||||
- "/bin/sh" string: 0x00404030
|
||||
|
||||
3. **Document exploitation steps** with `set-bookmark` type="Analysis" category="Exploit Plan":
|
||||
```
|
||||
Step 1: Send 64 bytes padding
|
||||
Step 2: Overwrite return address with system() address
|
||||
Step 3: Inject "/bin/sh" pointer as argument
|
||||
Step 4: Trigger return to execute system("/bin/sh")
|
||||
```
|
||||
|
||||
4. **Track assumptions** with `set-bookmark` type="Warning" category="Assumption":
|
||||
- "Assuming stack addresses are stable (no ASLR)"
|
||||
- "Assuming no canary based on decompilation (verify runtime)"
|
||||
|
||||
### Phase 5: Exploitation Validation (Iterative)
|
||||
|
||||
**This phase happens outside Ghidra**, but document findings:
|
||||
|
||||
1. Test exploit against local binary
|
||||
2. Adjust offsets based on crash analysis
|
||||
3. Handle bad bytes or character restrictions
|
||||
4. Refine payload until successful
|
||||
|
||||
**Update Ghidra database with findings:**
|
||||
- `set-comment` with actual working offsets
|
||||
- `set-bookmark` documenting successful exploitation
|
||||
- `checkin-program` message="Documented successful exploitation of buffer overflow in function_X"
|
||||
|
||||
## Pattern Recognition
|
||||
|
||||
See `patterns.md` for detailed vulnerability patterns:
|
||||
- Unsafe API usage patterns
|
||||
- Buffer overflow indicators
|
||||
- Format string vulnerability signatures
|
||||
- Heap exploitation patterns
|
||||
- Integer overflow scenarios
|
||||
- Control flow hijacking opportunities
|
||||
|
||||
## Exploitation Techniques Reference
|
||||
|
||||
### Stack Buffer Overflow
|
||||
|
||||
**Concept**: Write beyond buffer bounds to overwrite return address or function pointers on stack.
|
||||
|
||||
**Discovery**:
|
||||
1. Find unsafe copy: strcpy, gets, scanf("%s"), read with large size
|
||||
2. Identify buffer size from decompilation
|
||||
3. Compare buffer size to maximum input size
|
||||
4. Calculate offset to return address (buffer size + saved registers)
|
||||
|
||||
**Exploitation**:
|
||||
- Payload: [padding to return address] + [new return address] + [optional arguments/ROP chain]
|
||||
- Target: Overwrite return address to redirect execution
|
||||
|
||||
### Format String Vulnerability
|
||||
|
||||
**Concept**: User-controlled format string allows arbitrary memory read/write.
|
||||
|
||||
**Discovery**:
|
||||
1. `search-decompilation` pattern="printf|fprintf|sprintf"
|
||||
2. Check if format string comes from user input: printf(user_buffer)
|
||||
3. Vulnerable pattern: printf(input) instead of printf("%s", input)
|
||||
|
||||
**Exploitation**:
|
||||
- Read: %x, %p (leak stack values), %s (arbitrary read via pointer on stack)
|
||||
- Write: %n (write number of bytes printed to pointer on stack)
|
||||
- Position: %N$x (access Nth argument directly)
|
||||
|
||||
**Investigation**:
|
||||
4. `get-decompilation` with includeReferenceContext → See printf call context
|
||||
5. `set-decompilation-comment` documenting format string control
|
||||
6. `set-bookmark` type="Warning" category="Format String"
|
||||
|
||||
### Return-Oriented Programming (ROP)
|
||||
|
||||
**Concept**: Chain existing code fragments (gadgets) ending in 'ret' to build arbitrary computation without injecting code.
|
||||
|
||||
**Discovery**:
|
||||
1. Find gadgets: `pop reg; ret`, `mov [addr], reg; ret`, `syscall; ret`
|
||||
2. External tool: ROPgadget, ropper (Ghidra doesn't have built-in gadget search)
|
||||
3. Document gadgets in Ghidra with `set-bookmark` type="Note" category="ROP Gadget"
|
||||
|
||||
**Exploitation**:
|
||||
- Chain gadgets by placing addresses on stack
|
||||
- Each gadget executes, then 'ret' pops next gadget address
|
||||
- Build syscall with proper registers: execve("/bin/sh", NULL, NULL)
|
||||
|
||||
**Workflow**:
|
||||
4. Identify required gadgets for goal (e.g., execve syscall)
|
||||
5. `set-comment` at gadget addresses documenting purpose
|
||||
6. Plan ROP chain structure with `set-bookmark` type="Analysis" category="ROP Chain"
|
||||
|
||||
### ret2libc
|
||||
|
||||
**Concept**: Redirect execution to libc functions (system, exec, one_gadget) instead of shellcode.
|
||||
|
||||
**Discovery**:
|
||||
1. `get-symbols` includeExternal=true → Find libc imports
|
||||
2. `find-cross-references` to system, execve → Get addresses
|
||||
3. `search-strings-regex` pattern="/bin/sh" → Find shell string
|
||||
|
||||
**Exploitation** (no ASLR):
|
||||
- Overwrite return address → system function address
|
||||
- Set first argument → pointer to "/bin/sh" string
|
||||
- Calling convention: x86-64 uses RDI for first arg, x86 uses stack
|
||||
|
||||
**Exploitation** (with ASLR):
|
||||
- Leak libc address (format string, uninitialized pointer)
|
||||
- Calculate system/exec address = libc_base + offset
|
||||
- Build ROP chain with calculated addresses
|
||||
|
||||
**Investigation**:
|
||||
4. `get-data` at GOT entries → See libc function addresses
|
||||
5. Calculate libc base from known offset
|
||||
6. `set-bookmark` documenting calculated addresses
|
||||
|
||||
### Heap Exploitation
|
||||
|
||||
**Concept**: Corrupt heap metadata or overflow between heap chunks to achieve arbitrary write or control flow hijack.
|
||||
|
||||
**Discovery**:
|
||||
1. `search-decompilation` pattern="malloc|free|realloc"
|
||||
2. Trace allocation and free patterns
|
||||
3. Look for use-after-free: pointer used after free()
|
||||
4. Look for heap overflow: write beyond allocated size
|
||||
|
||||
**Exploitation techniques**:
|
||||
- **Use-after-free**: Free object, allocate new object in same slot, use old pointer to access new object (type confusion)
|
||||
- **Double-free**: Free same pointer twice, corrupt allocator metadata
|
||||
- **Heap overflow**: Overflow into next chunk, overwrite metadata (size, pointers) or data (function pointers)
|
||||
- **Fastbin/tcache poisoning**: Corrupt freelist pointers to allocate arbitrary memory
|
||||
|
||||
**Investigation**:
|
||||
5. `rename-variables` for heap pointers (heap_ptr, freed_ptr, chunk1, chunk2)
|
||||
6. `set-decompilation-comment` at allocation/free sites
|
||||
7. `set-bookmark` type="Warning" category="Use-After-Free"
|
||||
|
||||
### Integer Overflow
|
||||
|
||||
**Concept**: Integer overflow/underflow leads to incorrect buffer size calculation or bounds check bypass.
|
||||
|
||||
**Discovery**:
|
||||
1. Find size calculations: size = user_input * sizeof(element)
|
||||
2. Check for overflow: What if user_input is very large?
|
||||
3. Find bounds checks: if (index < size) → What if index is large unsigned?
|
||||
|
||||
**Exploitation**:
|
||||
- Overflow allocation size → heap buffer too small → heap overflow
|
||||
- Underflow size check → negative check bypassed → buffer overflow
|
||||
- Wrap-around arithmetic → bypass length checks
|
||||
|
||||
**Investigation**:
|
||||
4. `change-variable-datatypes` to proper integer types (uint32_t, size_t)
|
||||
5. Identify overflow scenarios in comments
|
||||
6. `set-bookmark` type="Warning" category="Integer Overflow"
|
||||
|
||||
## Tool Integration
|
||||
|
||||
**Use ReVa tools systematically:**
|
||||
|
||||
### Discovery Tools
|
||||
- `get-symbols` → Find unsafe API imports
|
||||
- `search-strings-regex` → Find interesting strings (flag, shell, paths)
|
||||
- `search-decompilation` → Find vulnerability patterns (unsafe functions)
|
||||
- `get-functions-by-similarity` → Find functions similar to known vulnerable pattern
|
||||
|
||||
### Analysis Tools
|
||||
- `get-decompilation` with `includeIncomingReferences=true` and `includeReferenceContext=true`
|
||||
- `find-cross-references` with `includeContext=true` → Trace data flow
|
||||
- `get-data` → Examine global variables, GOT entries, constant data
|
||||
- `read-memory` → Sample memory layout
|
||||
|
||||
### Database Improvement Tools
|
||||
- `rename-variables` → Clarify exploitation-relevant variables (buffer, user_input, return_addr)
|
||||
- `change-variable-datatypes` → Fix types for proper understanding
|
||||
- `set-decompilation-comment` → Document vulnerabilities inline
|
||||
- `set-comment` → Document exploitation strategy at key addresses
|
||||
- `set-bookmark` → Track vulnerabilities, gadgets, exploit plan
|
||||
|
||||
### Organization Tools
|
||||
- `set-bookmark` type="Warning" category="Vulnerability" → Mark vulnerabilities
|
||||
- `set-bookmark` type="Note" category="ROP Gadget" → Track gadgets
|
||||
- `set-bookmark` type="Analysis" category="Exploit Plan" → Document strategy
|
||||
- `set-bookmark` type="TODO" category="Verify" → Track assumptions to verify
|
||||
- `checkin-program` → Save progress
|
||||
|
||||
## Success Criteria
|
||||
|
||||
You've successfully completed the challenge when:
|
||||
|
||||
1. **Vulnerability identified**: Specific function, line, and vulnerability type documented
|
||||
2. **Memory layout understood**: Buffer sizes, offsets, adjacent data mapped
|
||||
3. **Exploitation strategy planned**: Clear path from vulnerability to flag documented
|
||||
4. **Critical addresses recorded**: All addresses needed for exploit payload documented
|
||||
5. **Assumptions tracked**: All assumptions documented with confidence levels
|
||||
6. **Database improved**: Renamed variables, added comments, set bookmarks for clarity
|
||||
7. **Exploit plan ready**: Sufficient information to write exploit code outside Ghidra
|
||||
|
||||
**Return to user:**
|
||||
- Vulnerability description with evidence
|
||||
- Exploitation approach explanation
|
||||
- Critical addresses and offsets
|
||||
- Payload structure plan
|
||||
- Assumptions and verification needs
|
||||
- Follow-up tasks if needed (e.g., "Test exploit against binary")
|
||||
|
||||
## Anti-Patterns
|
||||
|
||||
**Don't**:
|
||||
- Assume vulnerability without evidence (check buffer sizes!)
|
||||
- Forget about protections (canaries, NX, ASLR, PIE)
|
||||
- Overlook input restrictions (bad bytes, size limits)
|
||||
- Get stuck on one approach (try different exploitation techniques)
|
||||
- Ignore calling conventions (x86 vs x64 argument passing)
|
||||
- Forget null byte termination (C string functions)
|
||||
|
||||
**Do**:
|
||||
- Verify buffer sizes from decompilation
|
||||
- Check for stack canaries: `__stack_chk_fail` references
|
||||
- Calculate offsets precisely (buffer to return address)
|
||||
- Document all assumptions with `set-bookmark` type="Warning"
|
||||
- Adapt exploitation technique to protections present
|
||||
- Think creatively (chain primitives, use unconventional targets)
|
||||
|
||||
## Remember
|
||||
|
||||
Binary exploitation is **creative problem-solving**:
|
||||
- Understand **why** vulnerabilities exist (unsafe assumptions)
|
||||
- Think **how** to manipulate memory (data flow analysis)
|
||||
- Plan **what** to overwrite (control flow, data, pointers)
|
||||
- Determine **where** to redirect (existing code, injected code, ROP)
|
||||
- Execute **step-by-step** (leak, calculate, overwrite, trigger)
|
||||
|
||||
Every CTF challenge is different. Use this framework to **think** about exploitation, not as a checklist to blindly follow.
|
||||
|
||||
**Your goal**: Document enough information in Ghidra to write the exploit script. The actual exploitation happens outside, but the analysis happens here.
|
||||
948
skills/ctf-pwn/patterns.md
Normal file
948
skills/ctf-pwn/patterns.md
Normal file
@@ -0,0 +1,948 @@
|
||||
# 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.
|
||||
Reference in New Issue
Block a user