NDK中so的加載分析
//java層加載
#參數(shù)為庫(kù)文件名,不包含庫(kù)文件的擴(kuò)展名,必須是在JVM屬性Java.library.path所指向的路徑中
System.loadLibrary("libname");
#參數(shù)必須為庫(kù)文件的絕對(duì)路徑,可以是任意路徑;
System.load("lib_path");
libcore/ojluni/src/main/java/java/lang/System.java
public static void load(String filename) {
Runtime.getRuntime().load0(VMStack.getStackClass1(), filename);
}
public static void loadLibrary(String libname) {
Runtime.getRuntime().loadLibrary0(VMStack.getCallingClassLoader(), libname);
}
libcore/ojluni/src/main/java/java/lang/Runtime.java
synchronized void load0(Class<?> fromClass, String filename) {
if (!(new File(filename).isAbsolute())) {
throw new UnsatisfiedLinkError("Expecting an absolute path of the library: " + filename);
}
if (filename == null) {
throw new NullPointerException("filename == null");
}
String error = nativeLoad(filename, fromClass.getClassLoader());
if (error != null) {
throw new UnsatisfiedLinkError(error);
}
}
synchronized void loadLibrary0(ClassLoader loader, String libname) {
if (libname.indexOf((int)File.separatorChar) != -1) {
throw new UnsatisfiedLinkError("Directory separator should not appear in library name: " + libname);
}
String libraryName = libname;
if (loader != null) {
String filename = loader.findLibrary(libraryName);
if (filename == null) {
throw new UnsatisfiedLinkError(loader + " couldn't find \"" +System.mapLibraryName(libraryName) + "\"");
}
String error = nativeLoad(filename, loader);
if (error != null) {
throw new UnsatisfiedLinkError(error);
}
return;
}
String filename = System.mapLibraryName(libraryName);
List<String> candidates = new ArrayList<String>();
String lastError = null;
for (String directory : getLibPaths()) {
String candidate = directory + filename;
candidates.add(candidate)
if (IoUtils.canOpenReadOnly(candidate)) {
String error = nativeLoad(candidate, loader);
if (error == null) {
return; // We successfully loaded the library. Job done.
}
lastError = error;
}
}
if (lastError != null) {
throw new UnsatisfiedLinkError(lastError);
}
throw new UnsatisfiedLinkError("Library " + libraryName + " not found; tried " + candidates);
}
libcore/ojluni/src/main/native/Runtime.c
JNIEXPORT jstring JNICALL
Runtime_nativeLoad(JNIEnv* env, jclass ignored, jstring javaFilename,jobject javaLoader)
{
return JVM_NativeLoad(env, javaFilename, javaLoader);
}
art/openjdkjvm/OpenjdkJvm.cc
JNIEXPORT jstring JVM_NativeLoad(JNIEnv* env,
jstring javaFilename,
jobject javaLoader) {
ScopedUtfChars filename(env, javaFilename);
if (filename.c_str() == NULL) {
return NULL;
}
std::string error_msg;
{
art::JavaVMExt* vm = art::Runtime::Current()->GetJavaVM();
bool success = vm->LoadNativeLibrary(env,
filename.c_str(),
javaLoader,
&error_msg);
if (success) {
return nullptr;
}
}
env->ExceptionClear();
return env->NewStringUTF(error_msg.c_str());
}
art/runtime/java_vm_ext.cc
java_vm_ext.cc ---> native_loader.cpp ---> OpenNativeLibrary()
java_vm_ext.cc ---> FindSymbol()
bool JavaVMExt::LoadNativeLibrary(JNIEnv* env,
const std::string& path,
jobject class_loader,
std::string* error_msg) {
...
OpenNativeLibrary(){
...
android_dlopen_ext()
...
}
FindSymbol(){
...
FindSymbolWithNativeBridge()/FindSymbolWithoutNativeBridge(){
...
dlsym()
...
}
...
}
...
}
簡(jiǎn)單歸納一下調(diào)用流程:
System.loadLibrary()
Runtime.loadLibrary()
Runtime.loadLibrary0()
Runtime.java ---> nativeload()
Runtime.c ---> Runtime_nativeLoad()
OpenjdkJvm.cc ---> JVM_NativeLoad()
java_vm_ext.cc ---> LoadNativeLibrary()
LoadNativeLibrary() 分為兩步
1.dlfcn.cpp ---> dlopen() //加載init()/init_array()
2.libdl.cpp ---> dlsym() //加載jni_onload()

繼續(xù)往后走進(jìn)入到init/init_array
extern "C"
void _init(void) { }
-------> 編譯生成后在.init段
__attribute__((constructor))
void _init(void) { }
-------》編譯生成后在.init_array段
__attribute__((destructor))
void final(void) { }
-------》編譯生成后在.final_array段
# RegisterNative效率高于直接調(diào)用JNI本地函數(shù)(靜態(tài)注冊(cè))
- 1.調(diào)用 System. loadlibrarye()方法,將包含本地方法具體實(shí)現(xiàn)的C++運(yùn)行庫(kù)加載![JB7_XF%YY6S1]I367G3)TST.png](https://upload-images.jianshu.io/upload_images/9548468-3c52ae3c0609d2ae.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240)
到內(nèi)存中
- 2.Java虛擬機(jī)檢索加載進(jìn)來(lái)的庫(kù)函數(shù)符號(hào),在其中査找與Java本地方法擁有相同簽名的JNI本地函數(shù)符號(hào)。若找到一致的,則將本地方法映射到具體的JNI本地函數(shù)
- 3.Android Framework這類復(fù)雜的系統(tǒng)下,擁有大量的包含本地方法的Java類,Java虛機(jī)加載相應(yīng)運(yùn)行庫(kù),再逐一檢索,將各個(gè)本地方法與相應(yīng)的函數(shù)映射起來(lái),這顯然會(huì)增加運(yùn)行時(shí)間,降低運(yùn)行的效率,所以有了RegisterNative()
# 調(diào)用順序
linker
# \bionic\linker\dlfcn.cpp
---> do_dlopen() ---> find_library()
## \bionic\linker\linker.cpp
---> CallConstructors()
# 調(diào)用init
---> CallFunction("DT_INIT", init_func)
# 調(diào)用init_array
---> CallArray("DT_INIT_ARRAY", init_array, init_array_count, false);
CallConstructors
// so庫(kù)文件加載完畢以后調(diào)用構(gòu)造函數(shù)
void soinfo::CallConstructors() {
if (constructors_called) {
return;
}
// We set constructors_called before actually calling the constructors, otherwise it doesn't
// protect against recursive constructor calls. One simple example of constructor recursion
// is the libc debug malloc, which is implemented in libc_malloc_debug_leak.so:
// 1. The program depends on libc, so libc's constructor is called here.
// 2. The libc constructor calls dlopen() to load libc_malloc_debug_leak.so.
// 3. dlopen() calls the constructors on the newly created
// soinfo for libc_malloc_debug_leak.so.
// 4. The debug .so depends on libc, so CallConstructors is
// called again with the libc soinfo. If it doesn't trigger the early-
// out above, the libc constructor will be called again (recursively!).
constructors_called = true;
if ((flags & FLAG_EXE) == 0 && preinit_array != NULL) {
// The GNU dynamic linker silently ignores these, but we warn the developer.
PRINT("\"%s\": ignoring %d-entry DT_PREINIT_ARRAY in shared library!",
name, preinit_array_count);
}
// 調(diào)用DT_NEEDED類型段的構(gòu)造函數(shù)
if (dynamic != NULL) {
for (Elf32_Dyn* d = dynamic; d->d_tag != DT_NULL; ++d) {
if (d->d_tag == DT_NEEDED) {
const char* library_name = strtab + d->d_un.d_val;
TRACE("\"%s\": calling constructors in DT_NEEDED \"%s\"", name, library_name);
find_loaded_library(library_name)->CallConstructors();
}
}
}
TRACE("\"%s\": calling constructors", name);
// DT_INIT should be called before DT_INIT_ARRAY if both are present.
// 先調(diào)用.init段的構(gòu)造函數(shù)
CallFunction("DT_INIT", init_func);
// 再調(diào)用.init_array段的構(gòu)造函數(shù)
CallArray("DT_INIT_ARRAY", init_array, init_array_count, false);
}
CallFunction
// 構(gòu)造函數(shù)調(diào)用的實(shí)現(xiàn)
void soinfo::CallFunction(const char* function_name UNUSED, linker_function_t function) {
// 判斷構(gòu)造函數(shù)的調(diào)用地址是否符合要求
if (function == NULL || reinterpret_cast<uintptr_t>(function) == static_cast<uintptr_t>(-1)) {
return;
}
// function_name被調(diào)用的函數(shù)名稱,function為函數(shù)的調(diào)用地址
// [ Calling %s @ %p for '%s' ] 字符串為在 /system/bin/linker 中查找.init和.init_array段調(diào)用函數(shù)的關(guān)鍵
TRACE("[ Calling %s @ %p for '%s' ]", function_name, function, name);
// 調(diào)用function函數(shù)
function();
TRACE("[ Done calling %s @ %p for '%s' ]", function_name, function, name);
// The function may have called dlopen(3) or dlclose(3), so we need to ensure our data structures
set_soinfo_pool_protection(PROT_READ | PROT_WRITE);
}
CallArray
void soinfo::CallArray(const char* array_name UNUSED, linker_function_t* functions, size_t count, bool reverse) {
if (functions == NULL) {
return;
}
TRACE("[ Calling %s (size %d) @ %p for '%s' ]", array_name, count, functions, name);
int begin = reverse ? (count - 1) : 0;
int end = reverse ? -1 : count;
int step = reverse ? -1 : 1;
// 循環(huán)遍歷調(diào)用.init_arrayt段中每個(gè)函數(shù)
for (int i = begin; i != end; i += step) {
TRACE("[ %s[%d] == %p ]", array_name, i, functions[i]);
// .init_arrayt段中,每個(gè)函數(shù)指針的調(diào)用和上面的.init段的構(gòu)造函數(shù)的實(shí)現(xiàn)是一樣的
CallFunction("function", functions[i]);
}
TRACE("[ Done calling %s for '%s' ]", array_name, name);
}
以上為安卓4.4.4.rc的源碼
以下為安卓9.0.0_r8的源碼
第一個(gè)dlfcn.cpp 中的申明就不看了,直接看到linker.cpp,代碼量確實(shí)比4.4.4多多了,但是邏輯都差不多,可以找到 call_constructors




- 和以前一樣 call_array() 就是遍歷一個(gè)數(shù)組重復(fù)去調(diào)用 call_function()
- 守住 call_function() 就可以守住 init() 和 init_array()

下面歸納一下從源碼的角度考慮脫殼方向
5.0以下的脫殼時(shí)機(jī)
dvmDexFileOpenPartial(addr, len, &pDvmDex)
dexFileParse(const u1* data, size_t length, int flags)
5.0~8.0以下的脫殼時(shí)機(jī)
DexFile::
OpenMemory
(const byte* base ,size_t size, const std::string& location, uint32_t location_checksum, MemMap* mem_map)
8.0及8.0以上的脫殼時(shí)機(jī)
DexFile::
OpenCommon
(const uint8_t* base, size_t size, const std::string& location, uint32_t location_checksum, const OatDexFile* oat_dex_file, bool verify, bool verify_checksum, std::string* error_msg, VerifyResult* verify_result)
OpenCommon
- 匯編位于libdexfile.so (/system/lib/libdexfile.so)
- 源碼位于dex_file_loader.cc (源碼)

代表脫殼項(xiàng)目:frida-unpack


說(shuō)到脫殼除了系統(tǒng)關(guān)鍵函數(shù)的攔截,還有另一個(gè)思路來(lái)自hluwa的FRIDA-DEXDump
思路是基于frida的內(nèi)存dex035魔數(shù)的搜索

除了使用frida的動(dòng)態(tài)注入也可以選擇使用ida調(diào)試斷點(diǎn)關(guān)鍵函數(shù),使用idc腳本dump內(nèi)存,或者是使用xposed插件來(lái)實(shí)現(xiàn)脫殼,xposed插件:比如WrBug的 dumpDex 以及集成了 dumpDex 的 易開發(fā) ,或者是 ApkShelling 都可以用來(lái)脫殼,以上針對(duì)一二代殼,至于三代殼可以考慮使用DexHunter
或者是使用frida_dump
dump_dex使用到的關(guān)鍵點(diǎn)在libart.so中的DefineClass

- 基于內(nèi)存關(guān)鍵字搜索(dex035)
- 基于openCommen函數(shù)斷點(diǎn)
- 基于DefineClass函數(shù)斷點(diǎn)
- Fart 函數(shù)方法體抽取
Android ClassLoader
對(duì)于ClassLoader的基礎(chǔ)理解可以讓我在插件化,或者是存在動(dòng)態(tài)加載dex的時(shí)候找到dex,frida枚舉classloader并嘗試加載heap中存在class即可找到對(duì)應(yīng)dex的classloader,自然找到了classloader的pathList,一般情況下加殼的apk使用的ClassLoader也是自定義繼承自PathClassLoader,所以在使用frida腳本的時(shí)候也需要我們手動(dòng)的去切換一下ClassLoader,使用默認(rèn)的PathClassLoader可能會(huì)找不到類

BootClassLoader
位于:libcore/ojluni/src/main/java/java/lang/ClassLoader.java
SecureClassLoader
位于:libcore/ojluni/src/main/java/java/security/SecureClassLoader.java
(URLClassLoader extends SercureClassLoader)
URLClassLoader
位于:libcore/ojluni/src/main/java/java/net/URLClassLoader.java
BootstrapClassLoader
位于:external/icu/icu4j/main/classes/core/src/com/ibm/icu/impl/*ClassLoaderUtil.java
BaseDexClassLoader
位于:libcore/dalvik/src/main/java/dalvik/system/BaseDexClassLoader.java
(和java虛擬機(jī)中不同的是BootClassLoader是ClassLoader內(nèi)部類,由java代碼實(shí)現(xiàn)而不是c++實(shí)現(xiàn),是Android平臺(tái)上所有ClassLoader的最終parent,這個(gè)內(nèi)部類是包內(nèi)可見)
PathClassLoader
位于:libcore/dalvik/src/main/java/dalvik/system/PathClassLoader.java
DexClassLoader
位于:libcore/dalvik/src/main/java/dalvik/system/DexClassLoader.java
以下著重分析最常見,常用的 BaseDexClassLoader
BaseDexClassLoader的構(gòu)造函數(shù)包含四個(gè)參數(shù),分別為:
- dexPath,指目標(biāo)類所在的APK或jar文件的路徑,類裝載器將從該路徑中尋找指定的目標(biāo)類
- File optimizedDirectory,由于dex文件被包含在APK或者Jar文件中,因此在裝載目標(biāo)類之前需要先從APK或Jar文件中解壓出dex文件,該參數(shù)就是制定解壓出的dex,PathClassLoader默認(rèn)文件存放的路徑/data/dalvik-cache
- libPath,指目標(biāo)類中所使用的C/C++庫(kù)存放的路徑
- ClassLoader,是指該裝載器的父裝載器,一般為當(dāng)前執(zhí)行類的裝載器
源碼中可以發(fā)現(xiàn)BaseDexClassLoader維護(hù)了一個(gè)
DexPathList pathList
DexPathList中維護(hù)一個(gè)dex列表
Element[] dexElements
DexFile用來(lái)解析dex
DexFile dexFile;
public Enumeration<String> entries() {
return new DFEnum(this);
}
/*
* Helper class.
*/
private static class DFEnum implements Enumeration<String> {
private int mIndex;
@UnsupportedAppUsage
private String[] mNameList;
DFEnum(DexFile df) {
mIndex = 0;
mNameList = getClassNameList(df.mCookie);
}
public boolean hasMoreElements() {
return (mIndex < mNameList.length);
}
public String nextElement() {
return mNameList[mIndex++];
}
}
從Dex中獲取所有類
function HookDexMethod(){
Java.perform(function(){
var DexFileclass = Java.use("dalvik.system.DexFile");
var DexPathListclass = Java.use("dalvik.system.DexPathList");
var BaseDexClassLoaderclass = Java.use("dalvik.system.BaseDexClassLoader");
Java.enumerateClassLoaders({
onMatch: function (loader) {
try{
console.log("\n"+loader)
var BaseDexClassLoader_obj = Java.cast(loader, BaseDexClassLoaderclass);
var pathList = BaseDexClassLoader_obj.pathList.value;
console.warn("\npathList\t---->\t",pathList);
var dexElements = Java.cast(pathList, DexPathListclass).dexElements.value;
console.warn("\ndexElements\t---->\t",dexElements);
dexElements.forEach(function(element,index,arrays){
try {
try{var dexfile = element.dexFile.value;}catch(e){}
var dexfileobj = Java.cast(dexfile, DexFileclass);
const DFEnumName = dexfileobj.entries();
while(DFEnumName.hasMoreElements()){
var className = DFEnumName.nextElement().toString()
if(className.indexOf("lzy")!=-1){
console.log(className)
}
}
}catch(e){
console.log(e)
}
})
}catch(e){
}
},
onComplete:function(){}
})
})