diff --git a/src/binary-exploitation/common-exploiting-problems.md b/src/binary-exploitation/common-exploiting-problems.md index 925dc3c534c..a844491acad 100644 --- a/src/binary-exploitation/common-exploiting-problems.md +++ b/src/binary-exploitation/common-exploiting-problems.md @@ -35,7 +35,139 @@ In order to bypass this the **escape character `\x16` must be prepended to any ` **Here you can** [**find an example of this behaviour**](https://ir0nstone.gitbook.io/hackthebox/challenges/pwn/dream-diary-chapter-1/unlink-exploit)**.** -{{#include ../banners/hacktricks-training.md}} +## 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: + +1. **Locate callable entry points.** `objdump -T libvalidate.so | grep -E "validate"` quickly lists exported functions. Decompilers (Ghidra, IDA, BN) reveal the real signature, e.g. `int validate(const uint8_t *buf, uint64_t len)`. +2. **Write a standalone harness.** Load a file, keep the buffer alive, and call the exported symbol exactly as the app would. Cross-compile with the NDK (e.g. `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; +} +``` + +
+ +3. **Reconstruct the expected structure.** Error strings and comparisons in Ghidra showed the function parsed strict JSON with constant keys (`magic`, `version`, nested `root.children.*`) and arithmetic checks (e.g., `value * 2 == 84` ⇒ `value` must be `42`). Feeding syntactically valid JSON that progressively satisfies each branch lets you map the schema without instrumentation. +4. **Bypass anti-debug to leak secrets.** Because the `.so` imports `snprintf`, override it with `LD_PRELOAD` to dump sensitive format strings even when breakpoints are blocked: + +
+Minimal snprintf leak hook + +```c +#define _GNU_SOURCE +#include +#include +#include +#include + +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 the internal flag and confirms the crash oracle without patching the binary. +5. **Shrink the fuzz space.** Disassembly exposed an XOR key reused across the flag comparison, meaning the first seven bytes of `flag` were known. Only fuzz the nine unknown bytes. +6. **Embed fuzz bytes inside a valid JSON envelope.** The AFL harness reads exactly nine bytes from `stdin`, copies them into the flag suffix, and hard-codes every other field (constants, tree depths, arithmetic preimage). Any malformed read simply exits, so AFL spends cycles on meaningful testcases. + +
+AFL-friendly harness for structured JSON + +```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; +} +``` +
+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. + +### Related pages + +{{#ref}} +../mobile-pentesting/android-app-pentesting/reversing-native-libraries.md +{{#endref}} + +{{#ref}} +../reversing/reversing-tools-basic-methods/README.md +{{#endref}} + +## References + +- [FD duplication exploit example](https://ir0nstone.gitbook.io/notes/types/stack/exploiting-over-sockets/exploit) +- [Socat delete-character behaviour](https://ir0nstone.gitbook.io/hackthebox/challenges/pwn/dream-diary-chapter-1/unlink-exploit) +- [FuzzMe – Reverse Engineering and Fuzzing an Android Shared Library](https://hackmd.io/@sal/fuzzme-mobilehackinglab-ctf-writeup) + +{{#include ../banners/hacktricks-training.md}}