Kurejesha Maktaba za Native

Tip

Jifunze na fanya mazoezi ya AWS Hacking:HackTricks Training AWS Red Team Expert (ARTE)
Jifunze na fanya mazoezi ya GCP Hacking: HackTricks Training GCP Red Team Expert (GRTE) Jifunze na fanya mazoezi ya Azure Hacking: HackTricks Training Azure Red Team Expert (AzRTE)

Support HackTricks

Kwa taarifa zaidi angalia: https://maddiestone.github.io/AndroidAppRE/reversing_native_libs.html

Programu za Android zinaweza kutumia maktaba za native, kawaida zilizoandikwa kwa C au C++, kwa kazi zinazohitaji utendaji wa juu. Mitengenezaji wa malware pia hutumia vibaya maktaba hizi kwa sababu ELF shared objects bado ni ngumu zaidi ku-decompile kuliko DEX/OAT byte-code. Ukurasa huu unalenga workflows za vitendo na maboresho ya zana za hivi karibuni (2023-2025) yanayofanya kurejesha faili za Android .so kuwa rahisi.


Mtiririko wa uchunguzi wa haraka kwa libfoo.so iliyovutwa hivi karibuni

  1. Toa maktaba
# 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/
  1. Tambua usanifu & kinga
file libfoo.so        # arm64 or arm32 / x86
readelf -h libfoo.so  # OS ABI, PIE, NX, RELRO, etc.
checksec --file libfoo.so  # (peda/pwntools)
  1. Orodhesha alama zilizotumwa & viunganishi vya JNI
readelf -s libfoo.so | grep ' Java_'     # dynamic-linked JNI
strings libfoo.so   | grep -i "RegisterNatives" -n   # static-registered JNI
  1. Pakia kwenye decompiler (Ghidra ≥ 11.0, IDA Pro, Binary Ninja, Hopper or Cutter/Rizin) na endesha auto-analysis. Matoleo mapya ya Ghidra yalileta decompiler ya AArch64 inayotambua PAC/BTI stubs na MTE tags, ikiboresha sana uchambuzi wa maktaba zilizojengwa na Android 14 NDK.
  2. Amua kati ya static vs dynamic reversing: nambari iliyokatwa au iliyofichwa mara nyingi inahitaji uingiliaji wa runtime (instrumentation) (Frida, ptrace/gdbserver, LLDB).

Uingiliaji wa Dynamic (Frida ≥ 16)

Mfululizo wa Frida 16 ulethe maboresho kadhaa maalum kwa Android ambayo husaidia wakati lengo linapotumia optimizations za kisasa za Clang/LLD:

  • thumb-relocator sasa inaweza hook tiny ARM/Thumb functions zilizozalishwa na alignment kali ya LLD (--icf=all).
  • Kuorodhesha na kurebind ELF import slots kunafanya kazi kwenye Android, kuruhusu patching kwa kila-moduli kupitia dlopen()/dlsym() wakati inline hooks zinakataa.
  • Java hooking imerekebishwa kwa ART quick-entrypoint mpya inayotumika wakati apps zinapojengwa na --enable-optimizations kwenye Android 14.

Mfano: kuorodhesha functions zote zilizosajiliwa kupitia RegisterNatives na kutoa anwani zao wakati wa 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 itafanya kazi mara moja kwenye vifaa vilivyowezeshwa PAC/BTI (Pixel 8/Android 14+) mradi tu utumie frida-server 16.2 au baadaye – matoleo ya awali yalishindwa kupata padding kwa inline hooks.

Telemetri ya JNI ya mchakato kupitia .so iliyopreloaded (SoTap)

Wakati instrumentation yenye sifa kamili ni overkill au imezuiwa, bado unaweza kupata muonekano wa ngazi ya native kwa kupakia logger ndogo ndani ya mchakato lengwa. SoTap ni maktaba nyepesi ya Android native (.so) inayorekodi tabia ya runtime ya maktaba nyingine za JNI (.so) ndani ya mchakato moja la app (hakuna root inahitajika).

Sifa kuu:

  • Inaanzishwa mapema na inachunguza mwingiliano wa JNI/native ndani ya mchakato unaoipakia.
  • Huhifadhi logu kwa kutumia njia nyingi zinazoweza kuandikwa na kurejea kwa upole kwenye Logcat wakati uhifadhi unaporuhusiwa kwa vikwazo.
  • Inayoweza kubadilishwa kwenye chanzo: hariri sotap.c ili kupanua/kubadilisha kile kinachorekodiwa na ujenge tena kwa kila ABI.

Usanidi (repack the APK):

  1. Weka build sahihi ya ABI ndani ya APK ili loader iweze kutatua libsotap.so:
  • lib/arm64-v8a/libsotap.so (kwa arm64)
  • lib/armeabi-v7a/libsotap.so (kwa arm32)
  1. Hakikisha SoTap inapakia kabla ya maktaba nyingine za JNI. Ingiza wito mapema (kwa mfano, Application subclass static initializer au onCreate) ili logger ianzishwe kwanza. Smali snippet example:
const-string v0, "sotap"
invoke-static {v0}, Ljava/lang/System;->loadLibrary(Ljava/lang/String;)V
  1. Jenga tena/saini/sakinisha, endesha app, kisha ukusanye logu.

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

Vidokezo na utatuzi wa matatizo:

  • 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.

Njia hii ni muhimu kwa malware triage na JNI debugging ambapo kuangalia mtiririko wa simu za native tangu kuanza kwa mchakato ni muhimu lakini root/system-wide hooks aren’t available.


Angalia pia: in‑memory native code execution via JNI

Mfumo wa kawaida wa shambulio ni kupakua shellcode blob ghafi wakati wa utekelezaji na kuitekeleza moja kwa moja kutoka kwa kumbukumbu kupitia daraja la JNI (hakuna on‑disk ELF). Maelezo na snippet ya JNI tayari kutumika hapa:

In Memory Jni Shellcode Execution


Udhaifu za hivi karibuni zinazostahili kutafutwa katika APKs

MwakaCVEMaktaba iliyoathirikaMaelezo
2023CVE-2023-4863libwebp ≤ 1.3.1Heap 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.
2024MultipleOpenSSL 3.x seriesSeveral memory-safety and padding-oracle issues. Many Flutter & ReactNative bundles ship their own libcrypto.so.

Unapoona wa tatu .so files ndani ya APK, daima linganisha hash zao dhidi ya advisories za upstream. SCA (Software Composition Analysis) haijatumiwa mara kwa mara kwenye mobile, hivyo builds zilizozee na zilizo hatarifu ni nyingi.


Mwelekeo ya Anti-Reversing & Hardening (Android 13-15)

  • Pointer Authentication (PAC) & Branch Target Identification (BTI): Android 14 inawasha PAC/BTI katika maktaba za mfumo kwenye silicon zinazounga mkono ARMv8.3+. Decompilers sasa zinaonyesha PAC‐related pseudo-instructions; kwa uchambuzi wa kimaisha Frida inaingiza trampolines after stripping PAC, lakini trampolines zako za kawaida zinapaswa kuita pacda/autibsp pale inapohitajika.
  • MTE & Scudo hardened allocator: memory-tagging ni ya kuchagua lakini programu nyingi zinazotambua Play-Integrity zinajenga na -fsanitize=memtag; tumia setprop arm64.memtag.dump 1 plus adb shell am start ... ili kunasa tag faults.
  • LLVM Obfuscator (opaque predicates, control-flow flattening): commercial packers (e.g., Bangcle, SecNeo) increasingly protect native code, not only Java; tarajia bogus control-flow na encrypted string blobs katika .rodata.

Kuzuia initializers za native za mapema (.init_array) na JNI_OnLoad kwa instrumentation ya mapema (ARM64 ELF)

Programu zilizo na ulinzi mkubwa mara nyingi zinaweka ukaguzi wa root/emulator/debug katika constructors za native ambazo zinafanya kazi mapema sana kupitia .init_array, kabla ya JNI_OnLoad na kwa muda mrefu kabla ya Java yoyote kufanyika. Unaweza kufanya initializers hizo zilizofichwa ziwe wazi na kurejesha udhibiti kwa:

  • Removing INIT_ARRAY/INIT_ARRAYSZ from the DYNAMIC table so the loader does not auto-execute .init_array entries.
  • Resolving the constructor address from RELATIVE relocations and exporting it as a regular function symbol (e.g., INIT0).
  • Renaming JNI_OnLoad to JNI_OnLoad0 to prevent ART from calling it implicitly.

Kwa nini hii inafanya kazi kwenye Android/arm64

  • On AArch64, .init_array entries are often populated at load time by R_AARCH64_RELATIVE relocations whose addend is the target function address inside .text.
  • The bytes of .init_array may look empty statically; the dynamic linker writes the resolved address during relocation processing.

Tambua lengwa la constructor

  • 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_array virtual address range; the addend of that R_AARCH64_RELATIVE is the constructor (e.g., 0xA34, 0x954).
  • Disassemble around that address to sanity check:
objdump -D ./libnativestaticinit.so --start-address=0xA34 | head -n 40

Mpango wa patch

  1. Ondoa INIT_ARRAY na INIT_ARRAYSZ DYNAMIC tags. Usifute sections.
  2. Ongeza alama ya GLOBAL DEFAULT FUNC INIT0 kwenye anwani ya constructor ili iweze kuitwa kwa mikono.
  3. Badilisha jina JNI_OnLoadJNI_OnLoad0 ili kumzuia ART kuituma kiotomatiki.

Uthibitisho baada ya patch

readelf -W -d libnativestaticinit.so.patched | egrep -i 'init_array|fini_array|flags'
readelf -W -s libnativestaticinit.so.patched | egrep 'INIT0|JNI_OnLoad0'

Kurekebisha kwa LIEF (Python)

Skripti: ondoa INIT_ARRAY/INIT_ARRAYSZ, export INIT0, badilisha jina JNI_OnLoad→JNI_OnLoad0 ```python 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’)

</details>

Maelezo na mbinu zilizoshindikana (kwa uhamaji)
- Kuweka byte za `.init_array` kuwa zero au kuweka urefu wa section kuwa 0 hakusaidii: linker ya dynamic anazijaza tena kupitia relocations.
- Kuweka `INIT_ARRAY`/`INIT_ARRAYSZ` kuwa 0 kunaweza kuvunja loader kutokana na tags zisizoendana. Kuondoa kabisa kwa usafi vingo vya DYNAMIC ndio njia inayofaa.
- Kufuta kabisa sehemu ya `.init_array` kawaida husababisha loader crash.
- Baada ya ku-patch, anwani za function/layout zinaweza kubadilika; hakikisha unahesabu upya constructor kutoka kwa `.rela.dyn` addends kwenye faili iliyopatch ikiwa utahitaji kuendesha tena patch.

Kuanzisha ART/JNI ndogo ili kuita INIT0 na JNI_OnLoad0
- Tumia JNIInvocation kuanzisha ART VM context ndogo katika binary huru. Kisha piga `INIT0()` na `JNI_OnLoad0(vm)` kwa mkono kabla ya code yoyote ya Java.
- Jumuisha APK/classes lengwa kwenye classpath ili kila `RegisterNatives` ipate Java classes zake.

<details>
<summary>Harness ndogo (CMake and C) ya kuita INIT0 → JNI_OnLoad0 → Java method</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

Makosa ya Kawaida:

  • Anwani za constructor hubadilika baada ya patching kutokana na re-layout; hakikisha upima tena kutoka .rela.dyn kwenye binary ya mwisho.
  • Hakikisha -Djava.class.path inafunika kila class inayotumika na wito za RegisterNatives.
  • Tabia inaweza kutofautiana na matoleo ya NDK/loader; hatua iliyokuwa imedumu kuwa ya kuaminika ni kuondoa tags za DYNAMIC INIT_ARRAY/INIT_ARRAYSZ.

References

Tip

Jifunze na fanya mazoezi ya AWS Hacking:HackTricks Training AWS Red Team Expert (ARTE)
Jifunze na fanya mazoezi ya GCP Hacking: HackTricks Training GCP Red Team Expert (GRTE) Jifunze na fanya mazoezi ya Azure Hacking: HackTricks Training Azure Red Team Expert (AzRTE)

Support HackTricks