9.9 KiB
ネイティブライブラリのリバース
{{#include ../../banners/hacktricks-training.md}}
詳細は次を参照してください: https://maddiestone.github.io/AndroidAppRE/reversing_native_libs.html
Androidアプリは、通常CやC++で書かれたネイティブライブラリをパフォーマンスが重要な処理に使います。マルウェア作成者もこれらのライブラリを悪用します。なぜなら、ELF shared objectsはDEX/OATバイトコードよりもまだデコンパイルが難しいからです。
本ページは、Androidの.so
ファイルのリバースを容易にする、実践的なワークフローと最近のツール改善(2023–2025)に焦点を当てています。
新たに取得した libfoo.so
のクイックトリアージワークフロー
- ライブラリを抽出する
# From an installed application
adb shell "run-as <pkg> cat lib/arm64-v8a/libfoo.so" > libfoo.so
# Or from the APK (zip)
unzip -j target.apk "lib/*/libfoo.so" -d extracted_libs/
- アーキテクチャと保護を特定する
file libfoo.so # arm64 or arm32 / x86
readelf -h libfoo.so # OS ABI, PIE, NX, RELRO, etc.
checksec --file libfoo.so # (peda/pwntools)
- エクスポートされたシンボルとJNIバインディングを列挙する
readelf -s libfoo.so | grep ' Java_' # dynamic-linked JNI
strings libfoo.so | grep -i "RegisterNatives" -n # static-registered JNI
- デコンパイラにロードする (Ghidra ≥ 11.0, IDA Pro, Binary Ninja, Hopper or Cutter/Rizin) そして自動解析を実行する。 新しいGhidraはAArch64のデコンパイラを導入し、PAC/BTIスタブやMTEタグを認識するようになったため、Android 14 NDKでビルドされたライブラリの解析が大幅に改善されました。
- 静的解析か動的解析かを決める: ストリップ済みや難読化されたコードはしばしばinstrumentation(Frida、ptrace/gdbserver、LLDB)が必要になります。
Dynamic Instrumentation (Frida ≥ 16)
Fridaの16シリーズは、ターゲットが最新のClang/LLD最適化を使っている場合に役立ついくつかのAndroid固有の改善をもたらしました:
thumb-relocator
は、LLDの積極的なアラインメント(--icf=all
)で生成される小さなARM/Thumb関数に対してフックできるようになりました。- ELFのimportスロットを列挙してリバインドする機能がAndroid上で動作するようになり、inline hooksが拒否された場合でもモジュール単位での
dlopen()
/dlsym()
パッチが可能になりました。 - Javaフッキングは、Android 14で
--enable-optimizations
付きでコンパイルされたアプリが使う新しいART quick-entrypointに対して修正されました。
Example: enumerating all functions registered through RegisterNatives
and dumping their addresses at runtime:
Java.perform(function () {
var Runtime = Java.use('java.lang.Runtime');
var register = Module.findExportByName(null, 'RegisterNatives');
Interceptor.attach(register, {
onEnter(args) {
var envPtr = args[0];
var clazz = Java.cast(args[1], Java.use('java.lang.Class'));
var methods = args[2];
var count = args[3].toInt32();
console.log('[+] RegisterNatives on ' + clazz.getName() + ' -> ' + count + ' methods');
// iterate & dump (JNI nativeMethod struct: name, sig, fnPtr)
}
});
});
Frida will work out of the box on PAC/BTI-enabled devices (Pixel 8/Android 14+) as long as you use frida-server 16.2 or later – earlier versions failed to locate padding for inline hooks.
プロセスローカルなJNIテレメトリ via preloaded .so (SoTap)
フル機能のインストゥルメンテーションが過剰だったりブロックされている場合でも、ターゲットプロセス内に小さなロガーを事前ロードすることでネイティブレベルの可視性を得られます。SoTapは、同じアプリプロセス内の他のJNI (.so)ライブラリの実行時挙動をログする軽量のAndroidネイティブ(.so)ライブラリです(root不要)。
Key properties:
- 早期に初期化され、それをロードしたプロセス内のJNI/nativeのやり取りを監視します。
- 複数の書き込み可能パスにログを保存し、ストレージが制限されている場合はLogcatにフォールバックします。
- Source-customizable: edit sotap.c to extend/adjust what gets logged and rebuild per ABI.
Setup (repack the APK):
- Drop the proper ABI build into the APK so the loader can resolve libsotap.so:
- lib/arm64-v8a/libsotap.so (for arm64)
- lib/armeabi-v7a/libsotap.so (for arm32)
- Ensure SoTap loads before other JNI libs. Inject a call early (e.g., Application subclass static initializer or onCreate) so the logger is initialized first. Smali snippet example:
const-string v0, "sotap"
invoke-static {v0}, Ljava/lang/System;->loadLibrary(Ljava/lang/String;)V
- Rebuild/sign/install, run the app, then collect logs.
Log paths (checked in order):
/data/user/0/%s/files/sotap.log
/data/data/%s/files/sotap.log
/sdcard/Android/data/%s/files/sotap.log
/sdcard/Download/sotap-%s.log
# If all fail: fallback to Logcat only
Notes and troubleshooting:
- ABI の整合は必須です。不一致があると UnsatisfiedLinkError が発生し、ロガーはロードされません。
- ストレージ制約は近年の Android では一般的です。ファイル書き込みが失敗しても SoTap は Logcat 経由で出力します。
- 動作や冗長度はカスタマイズを意図しています。sotap.c を編集したらソースから再ビルドしてください。
この手法は、プロセス開始時からのネイティブ呼び出しフローを観察することが重要で、root やシステム全体のフックが使えない場合のマルウェアトリアージや JNI デバッグに有用です。
参照: in‑memory native code execution via JNI
一般的な攻撃パターンとして、ランタイムで生の shellcode blob をダウンロードし、JNI ブリッジ経由でメモリから直接実行する(ディスク上に ELF を置かない)という手法があります。詳細と使える JNI スニペットはこちら:
{{#ref}} in-memory-jni-shellcode-execution.md {{#endref}}
APK 内で探すべき最近の脆弱性
年 | CVE | 影響ライブラリ | 備考 |
---|---|---|---|
2023 | CVE-2023-4863 | libwebp ≤ 1.3.1 |
WebP 画像をデコードするネイティブコードから到達可能な heap buffer overflow。複数の Android アプリが脆弱なバージョンをバンドルしています。APK 内に libwebp.so を見つけたら、そのバージョンを確認し、エクスプロイトやパッチ適用を試みてください. |
2024 | Multiple | OpenSSL 3.x series | 複数のメモリ安全性やパディングオラクルの問題。多くの Flutter & ReactNative バンドルは独自の libcrypto.so を同梱しています。 |
APK 内で third-party の .so
ファイルを見つけたら、常に upstream のアドバイザリとハッシュを突き合わせてください。SCA (Software Composition Analysis) はモバイルではあまり行われていないため、古い脆弱なビルドが横行しています。
Anti-Reversing & Hardening trends (Android 13-15)
- Pointer Authentication (PAC) & Branch Target Identification (BTI): Android 14 はサポートされる ARMv8.3+ シリコン上でシステムライブラリに PAC/BTI を有効にします。Decompiler は PAC 関連の疑似命令を表示するようになりました。動的解析では Frida が PAC を剥がした後にトランポリンを注入しますが、カスタムトランポリンは必要に応じて
pacda
/autibsp
を呼ぶべきです。 - MTE & Scudo hardened allocator: memory-tagging はオプトインですが、多くの Play-Integrity 対応アプリは
-fsanitize=memtag
でビルドしています。タグフォールトをキャプチャするにはsetprop arm64.memtag.dump 1
とadb shell am start ...
を併用してください。 - LLVM Obfuscator (opaque predicates, control-flow flattening): 商用パッカー(例:Bangcle、SecNeo)は Java のみならず native コードを保護することが増えています。
.rodata
内に偽の制御フローや暗号化された文字列ブロブが存在することを想定してください。
リソース
- Learning ARM Assembly: Azeria Labs – ARM Assembly Basics
- JNI & NDK Documentation: Oracle JNI Spec · Android JNI Tips · NDK Guides
- Debugging Native Libraries: Debug Android Native Libraries Using JEB Decompiler
参考
- Frida 16.x change-log (Android hooking, tiny-function relocation) – frida.re/news
- NVD advisory for
libwebp
overflow CVE-2023-4863 – nvd.nist.gov - SoTap: Lightweight in-app JNI (.so) behavior logger – github.com/RezaArbabBot/SoTap
- SoTap Releases – github.com/RezaArbabBot/SoTap/releases
- How to work with SoTap? – t.me/ForYouTillEnd/13
- CoRPhone — JNI memory-only execution pattern and packaging
{{#include ../../banners/hacktricks-training.md}}