नेटिव लाइब्रेरीज़ का रिवर्स इंजीनियरिंग
Reading time: 13 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 गिटहब रिपोजिटरी में PRs सबमिट करें।
अधिक जानकारी के लिए देखें: https://maddiestone.github.io/AndroidAppRE/reversing_native_libs.html
Android apps performance-critical tasks के लिए अक्सर native libraries का उपयोग करते हैं, जो सामान्यतः C या C++ में लिखी होती हैं। मैलवेयर बनाने वाले भी इन लाइब्रेरीज़ का दुरुपयोग करते हैं क्योंकि ELF shared objects अभी भी DEX/OAT byte-code की तुलना में decompile करना कठिन होते हैं।
यह पृष्ठ व्यावहारिक वर्कफ़्लो और उन हालिया tooling सुधारों (2023-2025) पर केंद्रित है जो Android .so फाइलों का रिवर्सिंग आसान बनाते हैं।
ताज़ा निकाली गई 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)
- एक्सपोर्टेड symbols और JNI bindings सूचीबद्ध करें
readelf -s libfoo.so | grep ' Java_' # dynamic-linked JNI
strings libfoo.so | grep -i "RegisterNatives" -n # static-registered JNI
- Decompiler में लोड करें (Ghidra ≥ 11.0, IDA Pro, Binary Ninja, Hopper or Cutter/Rizin) और auto-analysis चलाएँ।
नए Ghidra वर्शन में एक AArch64 decompiler शामिल हुआ है जो PAC/BTI stubs और MTE tags को पहचानता है, जिससे Android 14 NDK से बनी लाइब्रेरीज़ के विश्लेषण में भारी सुधार होता है। - स्टेटिक बनाम डायनामिक रिवर्सिंग का निर्णय लें: stripped, obfuscated code अक्सर instrumentation (Frida, ptrace/gdbserver, LLDB) की जरूरत होती है।
डायनेमिक instrumentation (Frida ≥ 16)
Frida की 16-सीरीज़ ने कई Android-विशिष्ट सुधार लाए हैं जो तब मदद करते हैं जब लक्ष्य आधुनिक Clang/LLD optimisations का उपयोग करता है:
thumb-relocatorअब LLD की aggressive alignment (--icf=all) द्वारा जेनरेट की गई छोटी ARM/Thumb functions को hook कर सकता है।- Android पर ELF import slots का enumerate और rebind करना काम करता है, जिससे inline hooks अस्वीकृत होने पर per-module
dlopen()/dlsym()patching संभव होता है। - Java hooking को नए ART quick-entrypoint के लिए फिक्स किया गया है, जो तब उपयोग होता है जब ऐप्स Android 14 पर
--enable-optimizationsके साथ compiled होते हैं।
उदाहरण: RegisterNatives के माध्यम से रजिस्टर की गई सभी functions को enumerate करना और runtime पर उनके addresses dump करना:
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 PAC/BTI-सक्षम डिवाइसों (Pixel 8/Android 14+) पर आउट-ऑफ़-द-बॉक्स काम करेगा जब तक आप frida-server 16.2 या नए संस्करण का उपयोग करते हैं – पुराने संस्करण inline hooks के लिए padding locate करने में असफल थे।
Process-local JNI telemetry via preloaded .so (SoTap)
जब full-featured instrumentation ज़रूरत से ज्यादा हो या blocked हो, तब भी आप target process के अंदर एक छोटा logger प्रीलोड करके native-लेवल visibility प्राप्त कर सकते हैं। SoTap एक हल्का Android native (.so) लाइब्रेरी है जो उसी app process के भीतर अन्य JNI (.so) लाइब्रेरीज़ के runtime व्यवहार को लॉग करता है (no root required)।
Key properties:
- जल्दी initialize होता है और उस process के अंदर JNI/native interactions को observe करता है जो इसे लोड करता है।
- कई writable paths का उपयोग करके logs को persist करता है और जब storage restricted हो तो graceful fallback के तौर पर Logcat का उपयोग करता है।
- Source-customizable: sotap.c को edit करके यह निर्धारित/विस्तार किया जा सकता है कि क्या log होगा और फिर ABI के अनुसार rebuild करें।
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 alignment अनिवार्य है। mismatch होने पर UnsatisfiedLinkError होगा और logger लोड नहीं होगा।
- आधुनिक Android पर storage constraints सामान्य हैं; अगर file writes असफल होते हैं, तो SoTap फिर भी Logcat के माध्यम से emit करेगा।
- Behavior/verbosity को कस्टमाइज़ करने के लिए बनाया गया है; sotap.c में बदलाव करने के बाद source से rebuild करें।
This approach malware triage और JNI debugging के लिए उपयोगी है जहाँ process start से native call flows को देखने की आवश्यकता होती है लेकिन root/system-wide hooks उपलब्ध नहीं होते।
See also: in‑memory native code execution via JNI
A common attack pattern runtime पर raw shellcode blob डाउनलोड करके उसे सीधे memory से JNI bridge के माध्यम से execute करने का है (कोई on‑disk ELF नहीं)। विवरण और ready‑to‑use JNI snippet यहाँ:
In Memory Jni Shellcode Execution
Recent vulnerabilities worth hunting for in APKs
| Year | CVE | Affected library | Notes |
|---|---|---|---|
| 2023 | CVE-2023-4863 | libwebp ≤ 1.3.1 | Heap buffer overflow native code से reachable है जो WebP images को decode करता है। कई Android apps vulnerable versions bundle करते हैं। जब आप किसी APK के अंदर libwebp.so देखते हैं, तो उसकी version चेक करें और exploitation या patching का प्रयास करें. |
| 2024 | Multiple | OpenSSL 3.x series | कई memory-safety और padding-oracle issues मौजूद हैं। कई Flutter & ReactNative bundles अपना libcrypto.so ship करते हैं। |
जब आप किसी APK के अंदर third-party .so files देखें, तो हमेशा उनके hash को upstream advisories के साथ cross-check करें। SCA (Software Composition Analysis) मोबाइल पर uncommon है, इसलिए outdated vulnerable builds आम हैं।
Anti-Reversing & Hardening trends (Android 13-15)
- Pointer Authentication (PAC) & Branch Target Identification (BTI): Android 14 supported ARMv8.3+ silicon पर system libraries में PAC/BTI सक्षम करता है। Decompilers अब PAC‑related pseudo-instructions दिखाते हैं; dynamic analysis के लिए Frida PAC strip करने के बाद trampolines inject करता है, लेकिन आपके custom trampolines जहाँ आवश्यक हों
pacda/autibspको कॉल करना चाहिए। - MTE & Scudo hardened allocator: memory-tagging opt-in है पर कई Play-Integrity aware apps
-fsanitize=memtagके साथ build होते हैं; tag faults capture करने के लिएsetprop arm64.memtag.dump 1के साथadb shell am start ...चलाएँ। - LLVM Obfuscator (opaque predicates, control-flow flattening): commercial packers (जैसे Bangcle, SecNeo) native code की भी सुरक्षा करते हैं, सिर्फ Java नहीं;
.rodataमें bogus control-flow और encrypted string blobs की उम्मीद रखें।
Neutralizing early native initializers (.init_array) and JNI_OnLoad for early instrumentation (ARM64 ELF)
Highly protected apps अक्सर native constructors में root/emulator/debug checks रखते हैं जो .init_array के जरिये बहुत जल्दी चल जाते हैं, JNI_OnLoad से पहले और Java code के चलने से बहुत पहले। आप उन implicit initializers को explicit बनाकर नियंत्रण वापस पा सकते हैं:
- DYNAMIC table से
INIT_ARRAY/INIT_ARRAYSZहटाएँ ताकि loader.init_arrayentries को auto-execute न करे। - RELATIVE relocations से constructor address resolve करके उसे एक regular function symbol के रूप में export करें (उदा.,
INIT0)। - ART को implicit रूप से कॉल करने से रोकने के लिए
JNI_OnLoadका नामJNI_OnLoad0बदल दें।
Why this works on Android/arm64
- AArch64 पर,
.init_arrayentries अक्सर load time परR_AARCH64_RELATIVErelocations द्वारा populate होते हैं जिनका addend target function address होता है जो.textके अंदर होता है। .init_arrayके bytes statically खाली दिख सकते हैं; dynamic linker relocation processing के दौरान resolved address लिख देता है।
Identify the constructor target
- AArch64 पर सटीक ELF parsing के लिए Android NDK toolchain का उपयोग करें:
# 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
- उस relocation को खोजें जो
.init_arrayके virtual address range के अंदर land करता है; उसR_AARCH64_RELATIVEकाaddendconstructor होता है (उदा.,0xA34,0x954)। - sanity check के लिए उस address के आसपास disassemble करें:
objdump -D ./libnativestaticinit.so --start-address=0xA34 | head -n 40
Patch plan
- DYNAMIC tags से
INIT_ARRAYऔरINIT_ARRAYSZहटाएँ। sections को delete न करें। - constructor address पर एक GLOBAL DEFAULT FUNC symbol
INIT0जोड़ें ताकि इसे मैन्युअली कॉल किया जा सके। JNI_OnLoad→JNI_OnLoad0rename करें ताकि ART उसे implicitly invoke न करे।
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'
Patching with LIEF (Python)
स्क्रिप्ट: INIT_ARRAY/INIT_ARRAYSZ हटाएँ, INIT0 निर्यात करें, 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')
Notes and failed approaches (for portability)
- Zeroing
.init_arraybytes or setting the section length to 0 does not help: the dynamic linker repopulates it via relocations. - Setting
INIT_ARRAY/INIT_ARRAYSZto 0 can break the loader due to inconsistent tags. Clean removal of those DYNAMIC entries is the reliable lever. - Deleting the
.init_arraysection entirely tends to crash the loader. - After patching, function/layout addresses might shift; always recompute the constructor from
.rela.dynaddends on the patched file if you need to re-run the patch.
Bootstrapping a minimal ART/JNI to invoke INIT0 and JNI_OnLoad0
- JNIInvocation का उपयोग करके standalone binary में एक छोटा ART VM context लॉन्च करें। फिर किसी भी Java कोड से पहले मैन्युअली
INIT0()औरJNI_OnLoad0(vm)को कॉल करें। - target APK/classes को classpath में शामिल करें ताकि कोई भी
RegisterNativesउसके Java क्लासेस को ढूँढ सके।
न्यूनतम harness (CMake और C) ताकि INIT0 → JNI_OnLoad0 → Java method को कॉल किया जा सके
# 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
सामान्य समस्याएँ:
- Constructor addresses patching के बाद re-layout के कारण बदल सकते हैं; अंतिम binary पर हमेशा
.rela.dynसे पुनः गणना करें। - सुनिश्चित करें कि
-Djava.class.pathउन सभी क्लासों को कवर करे जोRegisterNativesकॉल्स द्वारा उपयोग होती हैं। - व्यवहार NDK/loader वर्शन के साथ भिन्न हो सकता है; लगातार भरोसेमंद कदम
INIT_ARRAY/INIT_ARRAYSZDYNAMIC टैग्स को हटाना था।
संदर्भ
- 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
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
- How to work with SoTap? – 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 हैकिंग सीखें और अभ्यास करें:
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 गिटहब रिपोजिटरी में PRs सबमिट करें।
HackTricks