Yerel Kütüphanelerin Tersine Mühendisliği
Reading time: 11 minutes
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ı genellikle performans-kritik görevler için C veya C++ ile yazılmış native kütüphaneler kullanır. Malware yazarları da bu kütüphaneleri suistimal eder çünkü ELF shared objects hala DEX/OAT byte-code'tan daha zor decompile edilebilir.
Bu sayfa, Android .so dosyalarını tersine çevirmeyi kolaylaştıran pratik iş akışlarına ve (2023-2025) dönemine ait son araç iyileştirmelerine odaklanır.
Yeni çekilmiş libfoo.so için hızlı triyaj iş akışı
- Kütüphaneyi çıkar
# 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ı belirle
file libfoo.so # arm64 or arm32 / x86
readelf -h libfoo.so # OS ABI, PIE, NX, RELRO, etc.
checksec --file libfoo.so # (peda/pwntools)
- Dışa aktarılan sembolleri ve JNI bağlarını listele
readelf -s libfoo.so | grep ' Java_' # dynamic-linked JNI
strings libfoo.so | grep -i "RegisterNatives" -n # static-registered JNI
- Bir decompiler içinde yükle (Ghidra ≥ 11.0, IDA Pro, Binary Ninja, Hopper or Cutter/Rizin) ve otomatik analiz çalıştırın. Yeni Ghidra sürümleri, Android 14 NDK ile derlenen kütüphanelerin analizini büyük ölçüde iyileştiren PAC/BTI stub'larını ve MTE etiketlerini tanıyan bir AArch64 decompiler getirdi.
- Statik vs dinamik tersine mühendislik arasında karar ver: stripped, obfuscated kod genellikle instrumentation (Frida, ptrace/gdbserver, LLDB) gerektirir.
Dinamik Enstrümantasyon (Frida ≥ 16)
Frida'nin 16 serisi, hedef modern Clang/LLD optimizasyonları kullandığında yardımcı olan birkaç Android'e özgü iyileştirme getirdi:
thumb-relocatorartık LLD'nin agresif hizalaması (--icf=all) tarafından üretilen küçük ARM/Thumb fonksiyonlarına hook atabilir.- Android'de ELF import slots'u enumerate etmek ve yeniden bağlamak çalışır; bu, inline hook'lar reddedildiğinde modül başına
dlopen()/dlsym()patchleme yapılmasını sağlar. - Java hooking, Android 14'te uygulamalar
--enable-optimizationsile derlendiğinde kullanılan yeni ART quick-entrypoint için düzeltildi.
Örnek: RegisterNatives aracılığıyla kaydedilmiş tüm fonksiyonları enumerate etmek ve çalışma zamanında adreslerini dump etmek:
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.
Preloaded .so ile işlem içi JNI telemetri (SoTap)
Tam özellikli instrumentation gereksiz veya engellenmiş olduğunda, hedef işlem içine küçük bir logger preload ederek yine de native düzeyde görünürlük elde edebilirsiniz. SoTap, aynı uygulama işlemi içindeki diğer JNI (.so) kütüphanelerinin çalışma zamanı davranışını kaydeden hafif bir Android native (.so) kütüphanesidir (root gerekmez).
Ana özellikler:
- Erken başlatılır ve onu yükleyen işlem içindeki JNI/native etkileşimlerini gözlemler.
- Kayıtları birden çok yazılabilir yol kullanarak kalıcı hale getirir ve depolama kısıtlıysa Logcat'e sorunsuz bir geri dönüş sağlar.
- Kaynak özelleştirilebilir: kaydedilenleri genişletmek/ayarlamak için sotap.c'yi düzenleyin ve ABI başına yeniden derleyin.
Kurulum (repack the APK):
- Yükleyicinin libsotap.so'yu çözümleyebilmesi için uygun ABI derlemesini APK'ye ekleyin:
- lib/arm64-v8a/libsotap.so (for arm64)
- lib/armeabi-v7a/libsotap.so (for arm32)
- SoTap'in diğer JNI kütüphanelerinden önce yüklendiğinden emin olun. Logger'ın önce inisyalize edilmesi için erken bir yerde çağrı enjekte edin (ör. Application alt sınıfı static initializer veya onCreate). Smali snippet example:
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 ve ardından 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
Notlar ve sorun giderme:
- ABI hizalaması zorunludur. Uyumsuzluk UnsatisfiedLinkError oluşturur ve logger yüklenmez.
- Depolama kısıtları modern Android'de yaygındır; dosya yazmaları başarısız olursa, SoTap yine Logcat aracılığıyla çıktı verecektir.
- Davranış ve ayrıntı düzeyi özelleştirilebilir; sotap.c düzenlendikten sonra kaynaktan yeniden derleyin.
Bu yaklaşım, süreç başlangıcından itibaren native çağrı akışlarını gözlemlemenin kritik olduğu ancak root veya system-wide hook'ların 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
APK'larda aranması gereken güncel zafiyetler
| Year | CVE | Affected library | Notes |
|---|---|---|---|
| 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. |
Bir APK içinde third-party .so dosyaları gördüğünüzde, hash'lerini her zaman upstream advisories ile karşılaştırın. Mobilde SCA (Software Composition Analysis) nadiren uygulanır, bu nedenle güncelliğini yitirmiş zafiyetli build'ler yaygındır.
Anti-Reversing & Hardening eğilimleri (Android 13-15)
- Pointer Authentication (PAC) & Branch Target Identification (BTI): Android 14, desteklenen ARMv8.3+ yongada sistem kütüphanelerinde PAC/BTI'yi etkinleştirir. Decompiler'lar artık PAC ile ilgili pseudo-instruction'ları gösterir; dinamik analizde Frida PAC'ı çıkardıktan sonra trampolin enjekte eder, ancak özel trampolinleriniz 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 1veadb shell am start ...kullanın. - LLVM Obfuscator (opaque predicates, control-flow flattening): ticari packer'lar (ör. Bangcle, SecNeo) giderek native kodu, yalnızca Java'yı değil, koruyor;
.rodataiçinde sahte control-flow ve şifrelenmiş string blob'ları bekleyin.
Erken native initializer'ları (.init_array) ve JNI_OnLoad'u erken enstrümantasyon için etkisizleştirme (ARM64 ELF)
Yüksek korumalı uygulamalar genellikle root/emulator/debug kontrollerini, JNI_OnLoad'dan ve herhangi bir Java kodunun çalışmasından çok önce, .init_array aracılığıyla çok erken çalışan native constructor'lara yerleştirir. Bu örtük initializer'ları açık hale getirip kontrolü geri kazanmak için şunları yapabilirsiniz:
- Loader'ın
.init_arraygirdilerini otomatik çalıştırmaması için DYNAMIC tablosundanINIT_ARRAY/INIT_ARRAYSZ'yi kaldırın. - Constructor adresini RELATIVE relocations üzerinden çözümleyip bunu normal bir fonksiyon sembolü (örn.
INIT0) olarak export edin. JNI_OnLoad'uJNI_OnLoad0olarak yeniden adlandırarak ART'in bunu örtük olarak çağırmasını engelleyin.
Android/arm64'te bunun neden işe yaradığı
- AArch64'te,
.init_arraygirdleri genellikle yükleme zamanındaR_AARCH64_RELATIVErelocations tarafından doldurulur; bu reloc'ların addend'i.textiçindeki hedef fonksiyon adresidir. .init_array'ın byte'ları statik olarak boş görünmeyebilir; dynamic linker relocation işlemi sırasında çözümlenen adresi yazar.
Constructor hedefini belirleme
- AArch64 üzerinde doğru ELF ayrıştırması için Android NDK toolchain'i kullanın:
# 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
.init_arraysanal adres aralığına düşen relocation'ı bulun; oR_AARCH64_RELATIVE'ınaddendi constructor'dır (örn.0xA34,0x954).- Mantık kontrolü için o adres çevresini disassemble edin:
objdump -D ./libnativestaticinit.so --start-address=0xA34 | head -n 40
Yama planı
INIT_ARRAYveINIT_ARRAYSZDYNAMIC tag'lerini kaldırın. Bölümleri silmeyin.- Constructor adresine GLOBAL DEFAULT FUNC sembolü
INIT0ekleyin, böylece elle çağrılabilir. JNI_OnLoad'uJNI_OnLoad0olarak yeniden adlandırın, böylece ART bunu örtük olarak çağırmaz.
Yama sonrası doğrulama
readelf -W -d libnativestaticinit.so.patched | egrep -i 'init_array|fini_array|flags'
readelf -W -s libnativestaticinit.so.patched | egrep 'INIT0|JNI_OnLoad0'
Patching with LIEF (Python)
Betik: remove INIT_ARRAY/INIT_ARRAYSZ, export INIT0, rename JNI_OnLoad→JNI_OnLoad0
import lief
b = 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')
Notlar ve başarısız yaklaşımlar (taşınabilirlik için)
.init_arraybaytlarını sıfırlamak veya bölüm uzunluğunu 0 yapmak işe yaramıyor: dynamic linker relocations aracılığıyla yeniden dolduruyor.INIT_ARRAY/INIT_ARRAYSZdeğerlerini 0 yapmak inconsistent tags nedeniyle loader'ı bozabilir. Bu DYNAMIC girdilerinin temiz şekilde kaldırılması güvenilir çözümdür..init_arraybölümünü tamamen silmek genellikle loader'ın çökmesine neden olur.- Patch uygulandıktan sonra fonksiyon/yerleşim adresleri kayabilir; patch'i tekrar çalıştırmanız gerekirse patched dosyada
.rela.dynaddends'lerinden constructor'ı her zaman yeniden hesaplayın.
Bootstrapping a minimal ART/JNI to invoke INIT0 and JNI_OnLoad0
- Tek başına bir binary içinde küçük bir ART VM context'i ayağa kaldırmak için JNIInvocation'ı kullanın. Sonra herhangi bir Java kodundan önce
INIT0()veJNI_OnLoad0(vm)'i elle çağırın. - Hedef APK/classes'ı classpath'e dahil edin ki herhangi bir
RegisterNativesJava sınıflarını bulsun.
Minimal harness (CMake ve C) — INIT0 → JNI_OnLoad0 → Java method çağırmak için
# 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, yeniden yerleşim (re-layout) nedeniyle yamalandıktan sonra değişir; son binary üzerinde
.rela.dyn'den her zaman yeniden hesaplayın. -Djava.class.path'inRegisterNativesçağrıları tarafından kullanılan her sınıfı kapsadığından emin olun.- Davranış NDK/loader sürümlerine göre değişebilir; tutarlı şekilde güvenilir adım
INIT_ARRAY/INIT_ARRAYSZDYNAMIC etiketlerini kaldırmaktı.
Referanslar
- ARM Assembly Öğrenme: Azeria Labs – ARM Assembly Basics
- JNI & NDK Dokümantasyonu: Oracle JNI Spec · Android JNI Tips · NDK Guides
- Native Kütüphaneleri Hata Ayıklama: Debug Android Native Libraries Using JEB Decompiler
- Frida 16.x change-log (Android hooking, tiny-function relocation) – frida.re/news
- NVD duyurusu for
libwebpoverflow 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
- 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
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.
HackTricks