Yerel (Native) Kütüphanelerin Tersine Mühendisliği
Tip
AWS Hacking’i öğrenin ve pratik yapın:
HackTricks Training AWS Red Team Expert (ARTE)
GCP Hacking’i öğrenin ve pratik yapın:HackTricks Training GCP Red Team Expert (GRTE)
Azure Hacking’i öğrenin ve pratik yapın:
HackTricks Training Azure Red Team Expert (AzRTE)
HackTricks'i Destekleyin
- abonelik planlarını kontrol edin!
- 💬 Discord grubuna veya telegram grubuna katılın ya da Twitter’da bizi takip edin 🐦 @hacktricks_live.**
- Hacking ipuçlarını paylaşmak için HackTricks ve HackTricks Cloud github reposuna PR gönderin.
Daha fazla bilgi için bakınız: https://maddiestone.github.io/AndroidAppRE/reversing_native_libs.html
Android uygulamaları performans-kritik görevler için tipik olarak C veya C++ ile yazılmış native kütüphaneler kullanabilir. Malware yazarları da bu kütüphaneleri kötüye kullanır; çünkü ELF shared objects, DEX/OAT byte-code’a göre decompile etmek halen daha zordur.
Bu sayfa, Android .so dosyalarının tersine mühendisliğini kolaylaştıran pratik iş akışları ve son (2023-2025) araç geliştirmelerine odaklanır.
Yeni çekilmiş libfoo.so için hızlı triage-iş akışı
- Kütüphaneyi çıkarın
# 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/
- Mimari ve korumaları tespit edin
file libfoo.so # arm64 or arm32 / x86
readelf -h libfoo.so # OS ABI, PIE, NX, RELRO, etc.
checksec --file libfoo.so # (peda/pwntools)
- Export edilen sembolleri ve JNI bağlarını listeleyin
readelf -s libfoo.so | grep ' Java_' # dynamic-linked JNI
strings libfoo.so | grep -i "RegisterNatives" -n # static-registered JNI
- Bir decompiler’a yükleyin (Ghidra ≥ 11.0, IDA Pro, Binary Ninja, Hopper or Cutter/Rizin) ve otomatik analiz çalıştırın. Yeni Ghidra sürümleri, AArch64 decompiler’ı ile PAC/BTI stub’larını ve MTE tag’lerini tanıyarak Android 14 NDK ile derlenmiş kütüphanelerin analizini büyük ölçüde iyileştirdi.
- Statik vs dinamik tersine mühendislik arasında karar verin: stripped, obfuscated kod genellikle instrumentation (Frida, ptrace/gdbserver, LLDB) gerektirir.
Dinamik Enstrümantasyon (Frida ≥ 16)
Frida’nın 16 serisi, hedef modern Clang/LLD optimizasyonları kullandığında yardımcı olan birkaç Android-özgü iyileştirme getirdi:
thumb-relocatorartık LLD’nin agresif hizalaması (--icf=all) tarafından üretilen küçük ARM/Thumb fonksiyonlarını hooklayabilir.- Android’de ELF import slots’ları enumerate etmek ve yeniden bağlamak (rebind) çalışır; inline hook’lar reddedildiğinde modül başına
dlopen()/dlsym()ile patch yapılmasını sağlar. - Android 14’te uygulamalar
--enable-optimizationsile derlendiğinde kullanılan yeni ART quick-entrypoint için Java hooking düzeltildi.
Örnek: RegisterNatives aracılığıyla kayıtlı tüm fonksiyonları sıralama ve çalışma zamanında adreslerini dökme:
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.
Bellekten çalışma-zamanında deşifre edilen native kütüphanelerin dökümü (Frida soSaver)
Korunan bir APK native kodu şifreli tutuyorsa veya yalnızca çalışma zamanında haritalıyorsa (packers, downloaded payloads, generated libs), Frida’yı bağlayın ve haritalanmış ELF’i doğrudan process belleğinden dökün.
soSaver iş akışı (Python host + TS/JS Frida agent):
dlopenveandroid_dlopen_exthook’lar ile load-time library mapping’i tespit eder ve zaten yüklenmiş modüllerin ilk taramasını yapar.- Süreç bellek eşlemelerini periyodik olarak ELF başlıkları için tarar; böylece non-standard mappers aracılığıyla yüklenen ve loader APIs’e hiç uğramayan modülleri yakalar.
- Her modülü bloklar halinde bellekten okur ve byte’ları Frida mesajlarıyla host’a stream eder; bir bölge okunamıyorsa, varsa on-disk path’ten okuma yoluna geri döner.
- Yeniden yapılandırılmış
.sodosyalarını kaydeder ve modül başına extraction stats yazdırır, static RE için artifacts sağlar.
Çalıştır (root + frida-server, Python ≥3.8, uv):
git clone https://github.com/TheQmaks/sosaver.git
cd sosaver && uv sync
source .venv/bin/activate # .venv\Scripts\activate on Windows
# target by package or PID; choose output/verbosity
sosaver com.example.app
sosaver 1234 -o /tmp/so-dumps --debug
Bu yaklaşım, canlı eşlenmiş imajı kurtararak “only decrypted in RAM” korumalarını atlar; böylece dosya sistemi kopyası obfuskeli veya yok olsa bile IDA/Ghidra’da çevrimdışı analiz yapılabilir.
Process-local JNI telemetry via preloaded .so (SoTap)
When full-featured instrumentation is overkill or blocked, you can still gain native-level visibility by preloading a small logger inside the target process. SoTap is a lightweight Android native (.so) library that logs the runtime behavior of other JNI (.so) libraries within the same app process (no root required).
Ana özellikler:
- Erken başlatılır ve yükleyen proses içindeki JNI/native etkileşimlerini gözlemler.
- Yazılabilir birden fazla yol kullanarak logları kalıcılaştırır; depolama kısıtlıysa zarifçe Logcat’e geri döner.
- Kaynağı özelleştirilebilir: neyin loglanacağını genişletmek/ayarlamak için sotap.c’yi düzenleyin ve her ABI için yeniden derleyin.
Kurulum (APK’yi yeniden paketleyin):
- Doğru ABI derlemesini APK’ye yerleştirin, böylece loader libsotap.so’yu çözebilir:
- lib/arm64-v8a/libsotap.so (for arm64)
- lib/armeabi-v7a/libsotap.so (for arm32)
- SoTap’ın diğer JNI kütüphanelerinden önce yüklendiğinden emin olun. Logger’ın önce başlatılması için erken bir yerde bir çağrı enjekte edin (ör. Application subclass static initializer veya onCreate). Smali snippet örneği:
const-string v0, "sotap"
invoke-static {v0}, Ljava/lang/System;->loadLibrary(Ljava/lang/String;)V
- Yeniden derleyin/imzalayın/kurun, uygulamayı çalıştırın, sonra logları toplayın.
Log yolları (sırasıyla kontrol edilir):
/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 alignment is mandatory. A mismatch will raise UnsatisfiedLinkError and the logger won’t load.
- Storage constraints are common on modern Android; if file writes fail, SoTap will still emit via Logcat.
- Behavior/verbosity is intended to be customized; rebuild from source after editing sotap.c.
Bu yaklaşım, işlemin başından itibaren native çağrı akışlarını gözlemlemenin kritik olduğu ancak root/system-wide hooks’in mevcut olmadığı durumlarda malware triage ve JNI debugging için faydalıdır.
See also: in‑memory native code execution via JNI
A common attack pattern is to download a raw shellcode blob at runtime and execute it directly from memory through a JNI bridge (no on‑disk ELF). Details and ready‑to‑use JNI snippet here:
In Memory Jni Shellcode Execution
Recent vulnerabilities worth hunting for in APKs
| Yıl | CVE | Etkilenen kütüphane | Notlar |
|---|---|---|---|
| 2023 | CVE-2023-4863 | libwebp ≤ 1.3.1 | Heap buffer overflow reachable from native code that decodes WebP images. Several Android apps bundle vulnerable versions. When you see a libwebp.so inside an APK, check its version and attempt exploitation or patching. |
| 2024 | Multiple | OpenSSL 3.x series | Several memory-safety and padding-oracle issues. Many Flutter & ReactNative bundles ship their own libcrypto.so. |
APK içinde third-party .so dosyaları fark ettiğinizde, bunların hash’lerini upstream advisories ile mutlaka karşılaştırın. SCA (Software Composition Analysis) mobilde nadirdir; bu yüzden eski ve savunmasız build’ler yaygındır.
Anti-Reversing & Hardening trends (Android 13-15)
- Pointer Authentication (PAC) & Branch Target Identification (BTI): Android 14, desteklenen ARMv8.3+ silikonlarda sistem kütüphanelerinde PAC/BTI’yi etkinleştirir. Decompilers artık PAC‐ile ilişkili pseudo-instructions gösteriyor; dinamik analiz için Frida, PAC’i kaldırdıktan sonra trampolines inject eder, ancak özel trampolines’iniz gerektiğinde
pacda/autibspçağırmalıdır. - MTE & Scudo hardened allocator: memory-tagging isteğe bağlıdır ancak birçok Play-Integrity uyumlu uygulama
-fsanitize=memtagile derlenir; tag hatalarını yakalamak içinsetprop arm64.memtag.dump 1ve ardındanadb shell am start ...kullanın. - LLVM Obfuscator (opaque predicates, control-flow flattening): ticari packer’lar (ör. Bangcle, SecNeo) giderek native kodu, sadece Java’yı değil, koruyor;
.rodataiçinde sahte control-flow ve şifrelenmiş string blob’ları bekleyin.
Neutralizing early native initializers (.init_array) and JNI_OnLoad for early instrumentation (ARM64 ELF)
Highly protected apps often place root/emulator/debug checks in native constructors that run extremely early via .init_array, before JNI_OnLoad and long before any Java code executes. Bu örtük initializers’ı açık hale getirerek ve kontrolü geri alarak çözebilirsiniz:
- Removing
INIT_ARRAY/INIT_ARRAYSZfrom the DYNAMIC table so the loader does not auto-execute.init_arrayentries. - Resolving the constructor address from RELATIVE relocations and exporting it as a regular function symbol (e.g.,
INIT0). - Renaming
JNI_OnLoadtoJNI_OnLoad0to prevent ART from calling it implicitly.
Why this works on Android/arm64
- On AArch64,
.init_arrayentries are often populated at load time byR_AARCH64_RELATIVErelocations whose addend is the target function address inside.text. - The bytes of
.init_arraymay look empty statically; the dynamic linker writes the resolved address during relocation processing.
Identify the constructor target
- Use the Android NDK toolchain for accurate ELF parsing on AArch64:
# Adjust paths to your NDK; use the aarch64-linux-android-* variants
readelf -W -a ./libnativestaticinit.so | grep -n "INIT_ARRAY" -C 4
readelf -W --relocs ./libnativestaticinit.so
- Find the relocation that lands inside the
.init_arrayvirtual address range; theaddendof thatR_AARCH64_RELATIVEis the constructor (e.g.,0xA34,0x954). - Disassemble around that address to sanity check:
objdump -D ./libnativestaticinit.so --start-address=0xA34 | head -n 40
Patch plan
- Remove
INIT_ARRAYandINIT_ARRAYSZDYNAMIC tags. Do not delete sections. - Add a GLOBAL DEFAULT FUNC symbol
INIT0at the constructor address so it can be called manually. - Rename
JNI_OnLoad→JNI_OnLoad0to stop ART from invoking it implicitly.
Validation after patch
readelf -W -d libnativestaticinit.so.patched | egrep -i 'init_array|fini_array|flags'
readelf -W -s libnativestaticinit.so.patched | egrep 'INIT0|JNI_OnLoad0'
LIEF ile yama (Python)
Betik: INIT_ARRAY/INIT_ARRAYSZ kaldır, INIT0'ı dışa aktar, JNI_OnLoad→JNI_OnLoad0 olarak yeniden adlandır
```python import liefb = lief.parse(“libnativestaticinit.so”)
Locate .init_array VA range
init = b.get_section(‘.init_array’) va, sz = init.virtual_address, init.size
Compute constructor address from RELATIVE relocation landing in .init_array
ctor = None for r in b.dynamic_relocations: if va <= r.address < va + sz: ctor = r.addend break if ctor is None: raise RuntimeError(“No R_*_RELATIVE relocation found inside .init_array”)
Remove auto-run tags so loader skips .init_array
for tag in (lief.ELF.DYNAMIC_TAGS.INIT_ARRAYSZ, lief.ELF.DYNAMIC_TAGS.INIT_ARRAY): try: b.remove(b[tag]) except Exception: pass
Add exported FUNC symbol INIT0 at constructor address
sym = lief.ELF.Symbol() sym.name = ‘INIT0’ sym.value = ctor sym.size = 0 sym.binding = lief.ELF.SYMBOL_BINDINGS.GLOBAL sym.type = lief.ELF.SYMBOL_TYPES.FUNC sym.visibility = lief.ELF.SYMBOL_VISIBILITY.DEFAULT
Place symbol in .text index
text = b.get_section(‘.text’) for idx, sec in enumerate(b.sections): if sec == text: sym.shndx = idx break b.add_dynamic_symbol(sym)
Rename JNI_OnLoad -> JNI_OnLoad0 to block implicit ART init
j = b.get_symbol(‘JNI_OnLoad’) if j: j.name = ‘JNI_OnLoad0’
b.write(‘libnativestaticinit.so.patched’)
</details>
Notlar ve başarısız yaklaşımlar (taşınabilirlik açısından)
- `.init_array` baytlarını sıfırlamak veya bölüm uzunluğunu 0 yapmak işe yaramaz: dynamic linker bunu relocations yoluyla yeniden doldurur.
- `INIT_ARRAY`/`INIT_ARRAYSZ`'yi 0 yapmak uyumsuz tag'lar nedeniyle loader'ı bozabilir. Bu DYNAMIC entries'lerin temizce kaldırılması güvenilir çözümdür.
- `.init_array` bölümünü tamamen silmek genellikle loader'ın çökmesine yol açar.
- Patch uygulandıktan sonra fonksiyon/yerleşim adresleri kayabilir; patch'i yeniden çalıştırmanız gerekirse constructor'ı her zaman patch'lenmiş dosyadaki `.rela.dyn` addends'ten yeniden hesaplayın.
Bootstrapping a minimal ART/JNI to invoke INIT0 and JNI_OnLoad0
- Küçük bir ART VM bağlamı oluşturmak için bağımsız bir binary içinde JNIInvocation kullanın. Ardından herhangi bir Java kodundan önce `INIT0()` ve `JNI_OnLoad0(vm)`'i manuel olarak çağırın.
- Hedef APK/classes'i classpath'e dahil edin, böylece herhangi bir `RegisterNatives` Java sınıflarını bulur.
<details>
<summary>INIT0 → JNI_OnLoad0 → Java metodunu çağırmak için minimal harness (CMake and C)</summary>
```cmake
# CMakeLists.txt
project(caller)
cmake_minimum_required(VERSION 3.8)
include_directories(AFTER ${CMAKE_SOURCE_DIR}/include)
link_directories(${CMAKE_SOURCE_DIR}/lib)
find_library(log-lib log REQUIRED)
add_executable(caller "caller.c")
add_library(jenv SHARED "jnihelper.c")
target_link_libraries(caller jenv nativestaticinit)
// caller.c
#include <jni.h>
#include "jenv.h"
JavaCTX ctx;
void INIT0();
void JNI_OnLoad0(JavaVM* vm);
int main(){
char *jvmopt = "-Djava.class.path=/data/local/tmp/base.apk"; // include app classes
if (initialize_java_environment(&ctx,&jvmopt,1)!=0) return -1;
INIT0(); // manual constructor
JNI_OnLoad0(ctx.vm); // manual JNI init
jclass c = (*ctx.env)->FindClass(ctx.env, "eu/nviso/nativestaticinit/MainActivity");
jmethodID m = (*ctx.env)->GetStaticMethodID(ctx.env,c,"stringFromJNI","()Ljava/lang/String;");
jstring s = (jstring)(*ctx.env)->CallStaticObjectMethod(ctx.env,c,m);
const char* p = (*ctx.env)->GetStringUTFChars(ctx.env,s,NULL);
printf("Native string: %s\n", p);
cleanup_java_env(&ctx);
}
# Build (adjust NDK/ABI)
cmake -DANDROID_PLATFORM=31 \
-DCMAKE_TOOLCHAIN_FILE=$HOME/Android/Sdk/ndk/26.1.10909125/build/cmake/android.toolchain.cmake \
-DANDROID_ABI=arm64-v8a ..
make
Yaygın Tuzaklar:
- Constructor adresleri re-layout nedeniyle yama sonrası değişir; son binary üzerinde her zaman
.rela.dyn’den yeniden hesaplayın. -Djava.class.path’in RegisterNatives çağrılarında kullanılan her sınıfı kapsadığından emin olun.- Davranış NDK/loader sürümlerine göre değişebilir; tutarlı olarak güvenilir adım
INIT_ARRAY/INIT_ARRAYSZDYNAMIC etiketlerinin kaldırılmasıydı.
References
- ARM Assembly Öğrenimi: Azeria Labs – ARM Assembly Basics
- JNI & NDK Dokümantasyonu: Oracle JNI Spec · Android JNI Tips · NDK Guides
- Native Kütüphanelerin Hata Ayıklanması: Debug Android Native Libraries Using JEB Decompiler
- Frida 16.x değişiklik günlüğü (Android hooking, tiny-function relocation) – frida.re/news
- NVD duyurusu:
libwebpoverflow CVE-2023-4863 – nvd.nist.gov - SoTap: Hafif in-app JNI (.so) davranış kaydedici – github.com/RezaArbabBot/SoTap
- SoTap Sürümleri – github.com/RezaArbabBot/SoTap/releases
- SoTap ile nasıl çalışılır? – t.me/ForYouTillEnd/13
- CoRPhone — JNI memory-only execution pattern and packaging
- Patching Android ARM64 library initializers for easy Frida instrumentation and debugging
- LIEF Project
- JNIInvocation
- soSaver — Frida tabanlı Android
.sokütüphaneleri için canlı bellek dökücüsü – github.com/TheQmaks/sosaver - soSaver Frida agent (TypeScript/JS) – github.com/TheQmaks/soSaver-frida
Tip
AWS Hacking’i öğrenin ve pratik yapın:
HackTricks Training AWS Red Team Expert (ARTE)
GCP Hacking’i öğrenin ve pratik yapın:HackTricks Training GCP Red Team Expert (GRTE)
Azure Hacking’i öğrenin ve pratik yapın:
HackTricks Training Azure Red Team Expert (AzRTE)
HackTricks'i Destekleyin
- abonelik planlarını kontrol edin!
- 💬 Discord grubuna veya telegram grubuna katılın ya da Twitter’da bizi takip edin 🐦 @hacktricks_live.**
- Hacking ipuçlarını paylaşmak için HackTricks ve HackTricks Cloud github reposuna PR gönderin.


