在安卓中的app进行so加载过程中,分析一下so的动态静态的so加载过程
在 Android 中,.so
文件是共享库文件,.so
文件可以分为动态链接库(动态.so
文件)和静态链接库(静态.a
文件),但 Android 中一般更常见的是动态.so
文件,静态链接库通常在编译时被集成到最终的应用中,而不直接加载。所以经常看到的so文件的链接大多是都是以动态链接的
-
动态链接会利用对应的打包的生成的APK,按照对应的架构(lib/armeabi-v7a/,lib/arm64-v8a/,lib/x86/,lib/x86_64/)去选择对应的so文件,然后去实现在 Java 层,通过JNI来进行。
Java 代码使用 静态
System.loadLibrary("libsofile")
来加载共享库文件。复制代码 隐藏代码
static { System.loadLibrary("libsofile); // 加载libsofile.so}
或者通过动态加载路径的so文件的过程来实现
复制代码 隐藏代码
StringsoPath="/data/data/com.example.libsofile/libsofile.so";System.load(soPath);
-
在 Android 中,静态链接库(
.a
文件)是被链接到最终的可执行文件中的,而不是在运行时加载。Android NDK 编译时,静态库会被打包到 APK 中的应用代码部分。
我们要去探究SO文件最真实的加载过程就要从System.load(sopath)这里开始,去剖析安卓源码
安卓源码
安卓源码剖析:
System.load(sopath)开始进行解析,查看整个so文件加载过程
System.load(sopath)
复制代码 隐藏代码@CallerSensitivepublicstaticvoidload(String filename) { Runtime.getRuntime().load0(Reflection.getCallerClass(), filename); }
先解释一下这里的情况Reflection.getCallerClass()
通过反射机制获取调用此方法的类的引用。它返回的是调用load0
方法的调用者类。这里加载到了直接去加载了load0函数。
load0(Class<?> fromClass, String filename)
复制代码 隐藏代码//libcore/ojluni/src/main/java/java/lang/Runtime.javasynchronizedvoidload0(Class<?> fromClass, String filename) {Filefile=newFile(filename);if (!(file.isAbsolute())) {thrownewUnsatisfiedLinkError("Expecting an absolute path of the library: " + filename); }if (filename == null) {thrownewNullPointerException("filename == null"); }if (Flags.readOnlyDynamicCodeLoad()) {if (!file.toPath().getFileSystem().isReadOnly() && file.canWrite()) {if (VMRuntime.getSdkVersion() >= VersionCodes.VANILLA_ICE_CREAM) { System.logW("Attempt to load writable file: " + filename + ". This will throw on a future Android version"); } } }Stringerror= nativeLoad(filename, fromClass.getClassLoader(), fromClass);if (error != null) {thrownewUnsatisfiedLinkError(error); } }
在这里去检测了对应加载过程中的sofile。然后就开始往nativeLoad函数走了
nativeLoad(filename, fromClass.getClassLoader(), fromClass);
Runtime_nativeLoad
复制代码 隐藏代码JNIEXPORT jstring JNICALLRuntime_nativeLoad(JNIEnv* env, jclass ignored, jstring javaFilename, jobject javaLoader, jclass caller){return JVM_NativeLoad(env, javaFilename, javaLoader, caller);}
这里是最正常的返回,直接走 JVM_NativeLoad(env, javaFilename, javaLoader, caller)
JVM_NativeLoad(env, javaFilename, javaLoader, caller)
复制代码 隐藏代码JNIEXPORT jstring JVM_NativeLoad(JNIEnv* env, jstring javaFilename, jobject javaLoader, jclass caller) { ScopedUtfChars filename(env, javaFilename);if (filename.c_str() == nullptr) {return nullptr; }std::string error_msg; { art::JavaVMExt* vm = art::Runtime::Current()->GetJavaVM();bool success = vm->LoadNativeLibrary(env, filename.c_str(), javaLoader, caller, &error_msg);if (success) {return nullptr; } }// Don't let a pending exception from JNI_OnLoad cause a CheckJNI issue with NewStringUTF. env->ExceptionClear();return env->NewStringUTF(error_msg.c_str());}
同样得直接向下去分析就好了vm->LoadNativeLibrary函数
vm->LoadNativeLibrary(env, filename.c_str(),javaLoader,caller,&error_msg);
这里的大多数的函数都是对于so加载中的中途函数,也就是一层一层得调用到关键函数的,所以这里直接往下走就是了
在 JavaVMExt::LoadNativeLibrary这个函数中有需要去注意和理解的地方,同时这里也是在进行调用dlopen来进行真正so文件加载的地方。
复制代码 隐藏代码 ClassLinker* class_linker = Runtime::Current()->GetClassLinker();if (class_linker->IsBootClassLoader(loader)) { loader = nullptr; class_loader = nullptr; }if (caller_class != nullptr) { ObjPtr<mirror::Class> caller = soa.Decode<mirror::Class>(caller_class); ObjPtr<mirror::DexCache> dex_cache = caller->GetDexCache();if (dex_cache != nullptr) { caller_location = dex_cache->GetLocation()->ToModifiedUtf8(); } }
首先是这里的Linker的位置,这里去解码了 ClassLoader 和 Caller Class 信息,同时去判断了加载器是否为BootClassLoader
。其实在so加载过程也有借助linker判断so文件结构,链接的位置则是so文件的头部,判断的是so文件结构是否正确。
复制代码 隐藏代码 Locks::mutator_lock_->AssertNotHeld(self);constchar* path_str = path.empty() ? nullptr : path.c_str();bool needs_native_bridge = false;char* nativeloader_error_msg = nullptr;void* handle = android::OpenNativeLibrary( env, runtime_->GetTargetSdkVersion(), path_str, class_loader, (caller_location.empty() ? nullptr : caller_location.c_str()), library_path.get(), &needs_native_bridge, &nativeloader_error_msg); VLOG(jni) << "[Call to dlopen("" << path << "", RTLD_NOW) returned " << handle << "]";if (handle == nullptr) { *error_msg = nativeloader_error_msg; android::NativeLoaderFreeErrorMessage(nativeloader_error_msg); VLOG(jni) << "dlopen("" << path << "", RTLD_NOW) failed: " << *error_msg;returnfalse;
OpenNativeLibrary
复制代码 隐藏代码//art/libnativeloader/native_loader.cppvoid* OpenNativeLibrary(JNIEnv* env,int32_t target_sdk_version,constchar* path, jobject class_loader,constchar* caller_location, jstring library_path_j,bool* needs_native_bridge,char** error_msg) {#if defined(ART_TARGET_ANDROID)if (class_loader == nullptr) {// class_loader is null only for the boot class loader (see// IsBootClassLoader call in JavaVMExt::LoadNativeLibrary), i.e. the caller// is in the boot classpath. *needs_native_bridge = false;if (caller_location != nullptr) {std::optional<NativeLoaderNamespace> ns = FindApexNamespace(caller_location);if (ns.has_value()) {const android_dlextinfo dlextinfo = { .flags = ANDROID_DLEXT_USE_NAMESPACE, .library_namespace = ns.value().ToRawAndroidNamespace(), };void* handle = android_dlopen_ext(path, RTLD_NOW, &dlextinfo);char* dlerror_msg = handle == nullptr ? strdup(dlerror()) : nullptr; ALOGD("Load %s using APEX ns %s for caller %s: %s", path, ns.value().name().c_str(), caller_location, dlerror_msg == nullptr ? "ok" : dlerror_msg);if (dlerror_msg != nullptr) { *error_msg = dlerror_msg; }return handle; } }
在android12中会直接由 android_dlopen_ext直接返回到__loader_android_dlopen_ext函数,而在其他版本可以会到mock->mock_dlopen_ext(这里会走到mock_dlopen_ext
会模拟dlopen
的行为,同时通过flag和宏定义走到不同的函数位置这里我们固定在android12的位置去实现。
__loader_android_dlopen_ext
复制代码 隐藏代码void* __loader_android_dlopen_ext(constchar* filename,int flags,const android_dlextinfo* extinfo,constvoid* caller_addr) {return dlopen_ext(filename, flags, extinfo, caller_addr);}
直接的返回进入下一个函数。
dlopen_ext
复制代码 隐藏代码//bionic/linker/dlfcn.cppstaticvoid* dlopen_ext(constchar* filename,int flags,const android_dlextinfo* extinfo,constvoid* caller_addr) { ScopedPthreadMutexLocker locker(&g_dl_mutex); g_linker_logger.ResetState();void* result = do_dlopen(filename, flags, extinfo, caller_addr);if (result == nullptr) { __bionic_format_dlerror("dlopen failed", linker_get_error_buffer());return nullptr; }return result;}
同样进入do_dlopen(filename, flags, extinfo, caller_addr)do_dlopen(filename, flags, extinfo, caller_addr)在这个函数中附加了很多对于do_dlopen函数参数的检测判断
通过还对于这里的path进行了对应路径的转换和翻译。
复制代码 隐藏代码 ProtectedDataGuard guard; soinfo* si = find_library(ns, translated_name, flags, extinfo, caller); loading_trace.End();
这里是对于do_dlopen最为重要的位置,也就是在这里去实现了对于soinfo的初始化,也就是在这开始调用so的.init_proc函数,接着调用.init_array中的函数,最后才是JNI_OnLoad函数。在很多的so文件的检测点判断中很多人也会利用这里的位置对于检测点是在JNI_OnLoad函数之前还是之后的判断依据。同时我们还需要注意的是:find_library 的作用:该函数负责加载共享库(包括解析依赖),但此时仅完成库的映射和重定位,尚未执行库的初始化函数(如 .init、.init_array 等)。构造函数的调用时机:在代码片段中,si->call_constructors() 被显式调用于 dlopen 返回句柄之前。这意味着:构造函数的执行(.init_xxx)是 dlopen 过程的一部分,发生在 dlopen 返回句柄之前。当 dlopen 返回 handle 时,库的初始化代码(构造函数)已执行完毕。所以其实在dlopen函数返回完成之前,.init和.init_arry已经完成了
继续去往find_library(ns, translated_name, flags, extinfo, caller)
find_library(ns, translated_name, flags, extinfo caller)
复制代码 隐藏代码static soinfo* find_library(android_namespace_t* ns,constchar* name, int rtld_flags,const android_dlextinfo* extinfo, soinfo* needed_by) { soinfo* si = nullptr;if (name == nullptr) { si = solist_get_somain(); } elseif (!find_libraries(ns, needed_by, &name,1, &si, nullptr,0, rtld_flags, extinfo,false/* add_as_children */)) {if (si != nullptr) { soinfo_unload(si); }return nullptr; } si->increment_ref_count();return si;}
这里直接往find_libraries里面就可以了
find_libraries
这个函数就是在so文件加执行流程的最后了
复制代码 隐藏代码boolfind_libraries(android_namespace_t* ns, soinfo* start_with,constchar* const library_names[],size_t library_names_count, soinfo* soinfos[],std::vector<soinfo*>* ld_preloads,size_t ld_preloads_count,int rtld_flags,const android_dlextinfo* extinfo,bool add_as_children,std::vector<android_namespace_t*>* namespaces) {// Step 0: prepare.std::unordered_map<const soinfo*, ElfReader> readers_map; LoadTaskList load_tasks;for (size_t i = 0; i < library_names_count; ++i) {constchar* name = library_names[i]; load_tasks.push_back(LoadTask::create(name, start_with, ns, &readers_map)); }// If soinfos array is null allocate one on stack.// The array is needed in case of failure; for example// when library_names[] = {libone.so, libtwo.so} and libone.so// is loaded correctly but libtwo.so failed for some reason.// In this case libone.so should be unloaded on return.// See also implementation of failure_guard below.if (soinfos == nullptr) {size_t soinfos_size = sizeof(soinfo*)*library_names_count; soinfos = reinterpret_cast<soinfo**>(alloca(soinfos_size));memset(soinfos, 0, soinfos_size); }// list of libraries to link - see step 2.size_t soinfos_count = 0;auto scope_guard = android::base::make_scope_guard([&]() {for (LoadTask* t : load_tasks) { LoadTask::deleter(t); } }); ZipArchiveCache zip_archive_cache;soinfo_list_t new_global_group_members;// Step 1: expand the list of load_tasks to include// all DT_NEEDED libraries (do not load them just yet)for (size_t i = 0; i<load_tasks.size(); ++i) { LoadTask* task = load_tasks[i]; soinfo* needed_by = task->get_needed_by();bool is_dt_needed = needed_by != nullptr && (needed_by != start_with || add_as_children); task->set_extinfo(is_dt_needed ? nullptr : extinfo); task->set_dt_needed(is_dt_needed);// Note: start from the namespace that is stored in the LoadTask. This namespace// is different from the current namespace when the LoadTask is for a transitive// dependency and the lib that created the LoadTask is not found in the// current namespace but in one of the linked namespaces.android_namespace_t* start_ns = const_cast<android_namespace_t*>(task->get_start_from()); LD_LOG(kLogDlopen, "find_library_internal(ns=%s@%p): task=%s, is_dt_needed=%d", start_ns->get_name(), start_ns, task->get_name(), is_dt_needed);if (!find_library_internal(start_ns, task, &zip_archive_cache, &load_tasks, rtld_flags)) {returnfalse; } soinfo* si = task->get_soinfo();if (is_dt_needed) { needed_by->add_child(si); }// When ld_preloads is not null, the first// ld_preloads_count libs are in fact ld_preloads.bool is_ld_preload = false;if (ld_preloads != nullptr && soinfos_count < ld_preloads_count) { ld_preloads->push_back(si); is_ld_preload = true; }if (soinfos_count < library_names_count) { soinfos[soinfos_count++] = si; }// Add the new global group members to all initial namespaces. Do this secondary namespace setup// at the same time that libraries are added to their primary namespace so that the order of// global group members is the same in the every namespace. Only add a library to a namespace// once, even if it appears multiple times in the dependency graph.if (is_ld_preload || (si->get_dt_flags_1() & DF_1_GLOBAL) != 0) {if (!si->is_linked() && namespaces != nullptr && !new_global_group_members.contains(si)) { new_global_group_members.push_back(si);for (auto linked_ns : *namespaces) {if (si->get_primary_namespace() != linked_ns) { linked_ns->add_soinfo(si); si->add_secondary_namespace(linked_ns); } } } } }// Step 2: Load libraries in random order (see b/24047022) LoadTaskList load_list;for (auto&& task : load_tasks) { soinfo* si = task->get_soinfo();auto pred = [&](const LoadTask* t) {return t->get_soinfo() == si; };if (!si->is_linked() &&std::find_if(load_list.begin(), load_list.end(), pred) == load_list.end() ) { load_list.push_back(task); } }bool reserved_address_recursive = false;if (extinfo) { reserved_address_recursive = extinfo->flags & ANDROID_DLEXT_RESERVED_ADDRESS_RECURSIVE; }if (!reserved_address_recursive) {// Shuffle the load order in the normal case, but not if we are loading all// the libraries to a reserved address range. shuffle(&load_list); }// Set up address space parameters. address_space_params extinfo_params, default_params;size_t relro_fd_offset = 0;if (extinfo) {if (extinfo->flags & ANDROID_DLEXT_RESERVED_ADDRESS) { extinfo_params.start_addr = extinfo->reserved_addr; extinfo_params.reserved_size = extinfo->reserved_size; extinfo_params.must_use_address = true; } elseif (extinfo->flags & ANDROID_DLEXT_RESERVED_ADDRESS_HINT) { extinfo_params.start_addr = extinfo->reserved_addr; extinfo_params.reserved_size = extinfo->reserved_size; } }for (auto&& task : load_list) { address_space_params* address_space = (reserved_address_recursive || !task->is_dt_needed()) ? &extinfo_params : &default_params;if (!task->load(address_space)) {returnfalse; } }// The WebView loader uses RELRO sharing in order to promote page sharing of the large RELRO// segment, as it's full of C++ vtables. Because MTE globals, by default, applies random tags to// each global variable, the RELRO segment is polluted and unique for each process. In order to// allow sharing, but still provide some protection, we use deterministic global tagging schemes// for DSOs that are loaded through android_dlopen_ext, such as those loaded by WebView.bool dlext_use_relro = extinfo && extinfo->flags & (ANDROID_DLEXT_WRITE_RELRO | ANDROID_DLEXT_USE_RELRO);// Step 3: pre-link all DT_NEEDED libraries in breadth first order.bool any_memtag_stack = false;for (auto&& task : load_tasks) { soinfo* si = task->get_soinfo();if (!si->is_linked() && !si->prelink_image(dlext_use_relro)) {returnfalse; }// si->memtag_stack() needs to be called after si->prelink_image() which populates// the dynamic section.if (si->memtag_stack()) { any_memtag_stack = true; LD_LOG(kLogDlopen,"... load_library requesting stack MTE for: realpath="%s", soname="%s"", si->get_realpath(), si->get_soname()); } register_soinfo_tls(si); }if (any_memtag_stack) {if (auto* cb = __libc_shared_globals()->memtag_stack_dlopen_callback) { cb(); } else {// find_library is used by the initial linking step, so we communicate that we// want memtag_stack enabled to __libc_init_mte. __libc_shared_globals()->initial_memtag_stack_abi = true; } }// Step 4: Construct the global group. DF_1_GLOBAL bit is force set for LD_PRELOADed libs because// they must be added to the global group. Note: The DF_1_GLOBAL bit for a library is normally set// in step 3.if (ld_preloads != nullptr) {for (auto&& si : *ld_preloads) { si->set_dt_flags_1(si->get_dt_flags_1() | DF_1_GLOBAL); } }// Step 5: Collect roots of local_groups.// Whenever needed_by->si link crosses a namespace boundary it forms its own local_group.// Here we collect new roots to link them separately later on. Note that we need to avoid// collecting duplicates. Also the order is important. They need to be linked in the same// BFS order we link individual libraries.std::vector<soinfo*> local_group_roots;if (start_with != nullptr && add_as_children) { local_group_roots.push_back(start_with); } else { CHECK(soinfos_count == 1); local_group_roots.push_back(soinfos[0]); }for (auto&& task : load_tasks) { soinfo* si = task->get_soinfo(); soinfo* needed_by = task->get_needed_by();bool is_dt_needed = needed_by != nullptr && (needed_by != start_with || add_as_children);android_namespace_t* needed_by_ns = is_dt_needed ? needed_by->get_primary_namespace() : ns;if (!si->is_linked() && si->get_primary_namespace() != needed_by_ns) {auto it = std::find(local_group_roots.begin(), local_group_roots.end(), si); LD_LOG(kLogDlopen,"Crossing namespace boundary (si=%s@%p, si_ns=%s@%p, needed_by=%s@%p, ns=%s@%p, needed_by_ns=%s@%p) adding to local_group_roots: %s", si->get_realpath(), si, si->get_primary_namespace()->get_name(), si->get_primary_namespace(), needed_by == nullptr ? "(nullptr)" : needed_by->get_realpath(), needed_by, ns->get_name(), ns, needed_by_ns->get_name(), needed_by_ns, it == local_group_roots.end() ? "yes" : "no");if (it == local_group_roots.end()) { local_group_roots.push_back(si); } } }// Step 6: Link all local groupsfor (auto root : local_group_roots) {soinfo_list_t local_group;android_namespace_t* local_group_ns = root->get_primary_namespace(); walk_dependencies_tree(root, [&] (soinfo* si) {if (local_group_ns->is_accessible(si)) { local_group.push_back(si);return kWalkContinue; } else {return kWalkSkip; } });soinfo_list_t global_group = local_group_ns->get_global_group(); SymbolLookupList lookup_list(global_group, local_group); soinfo* local_group_root = local_group.front();bool linked = local_group.visit([&](soinfo* si) {// Even though local group may contain accessible soinfos from other namespaces// we should avoid linking them (because if they are not linked -> they// are in the local_group_roots and will be linked later).if (!si->is_linked() && si->get_primary_namespace() == local_group_ns) {const android_dlextinfo* link_extinfo = nullptr;if (si == soinfos[0] || reserved_address_recursive) {// Only forward extinfo for the first library unless the recursive// flag is set. link_extinfo = extinfo; }if (__libc_shared_globals()->load_hook) { __libc_shared_globals()->load_hook(si->load_bias, si->phdr, si->phnum); } lookup_list.set_dt_symbolic_lib(si->has_DT_SYMBOLIC ? si : nullptr);if (!si->link_image(lookup_list, local_group_root, link_extinfo, &relro_fd_offset) || !get_cfi_shadow()->AfterLoad(si, solist_get_head())) {returnfalse; } }returntrue; });if (!linked) {returnfalse; } }// Step 7: Mark all load_tasks as linked and increment refcounts// for references between load_groups (at this point it does not matter if// referenced load_groups were loaded by previous dlopen or as part of this// one on step 6)if (start_with != nullptr && add_as_children) { start_with->set_linked(); }for (auto&& task : load_tasks) { soinfo* si = task->get_soinfo(); si->set_linked(); }for (auto&& task : load_tasks) { soinfo* si = task->get_soinfo(); soinfo* needed_by = task->get_needed_by();if (needed_by != nullptr && needed_by != start_with && needed_by->get_local_group_root() != si->get_local_group_root()) { si->increment_ref_count(); } }returntrue;}
这里很长一部分的代码,在安卓源码中也有对于其进行了批注,一步一步得去加载和解析so文件,去实现so文件的加载
Step 0准备加载任务
比如在Step 0 中
复制代码 隐藏代码for (size_t i = 0; i < library_names_count; ++i) {constchar* name = library_names[i]; load_tasks.push_back(LoadTask::create(name, start_with, ns, &readers_map)); }// If soinfos array is null allocate one on stack.// The array is needed in case of failure; for example// when library_names[] = {libone.so, libtwo.so} and libone.so// is loaded correctly but libtwo.so failed for some reason.// In this case libone.so should be unloaded on return.// See also implementation of failure_guard below.if (soinfos == nullptr) {size_t soinfos_size = sizeof(soinfo*)*library_names_count; soinfos = reinterpret_cast<soinfo**>(alloca(soinfos_size));memset(soinfos, 0, soinfos_size); }
程序去实现了对于这个加载的so文件进行的,存储于数组中,并且去实现了条件判断,假如soinfos为空,还会去实现构造了soinfo* 的结构体指针来申请一段空间来存储
Step 1解析依赖
复制代码 隐藏代码for (size_t i = 0; i<load_tasks.size(); ++i) { LoadTask* task = load_tasks[i]; soinfo* needed_by = task->get_needed_by();bool is_dt_needed = needed_by != nullptr && (needed_by != start_with || add_as_children); task->set_extinfo(is_dt_needed ? nullptr : extinfo); task->set_dt_needed(is_dt_needed);// Note: start from the namespace that is stored in the LoadTask. This namespace// is different from the current namespace when the LoadTask is for a transitive// dependency and the lib that created the LoadTask is not found in the// current namespace but in one of the linked namespaces.android_namespace_t* start_ns = const_cast<android_namespace_t*>(task->get_start_from()); LD_LOG(kLogDlopen, "find_library_internal(ns=%s@%p): task=%s, is_dt_needed=%d", start_ns->get_name(), start_ns, task->get_name(), is_dt_needed);if (!find_library_internal(start_ns, task, &zip_archive_cache, &load_tasks, rtld_flags)) {returnfalse; } soinfo* si = task->get_soinfo();if (is_dt_needed) { needed_by->add_child(si); }
这里 task->get_needed_by() 能够看到其实是在借用上一步得到的so文件的数组去实现检测依赖关系,因为我们知道一个so文件,在ida分析中可以看到的导入表和导出表,全是so与so之间的相互依赖来实现的。通过学习过NDK开发的也知道,在so之间的相互调用中,通过也是通过dlopen来实现。
so文件之间的调用:
复制代码 隐藏代码extern"C" JNIEXPORT jstring JNICALLJava_com_chen_javaandso_MainActivity_stringFromJNI( JNIEnv* env, jobject /* this */, jstring path) {std::string hello = "Hello from C++";constchar* cpath = env->GetStringUTFChars(path, nullptr);//这里是java转c++的转char函数if (cpath == nullptr) {// 处理 JNIEnv::GetStringUTFChars 返回 nullptr 的情况return nullptr; }void* soinfo = dlopen(cpath, RTLD_NOW);//这里去获取对应路径下的so文件的句柄if (soinfo == nullptr) {// 处理 dlopen 失败的情况 __android_log_print(ANDROID_LOG_ERROR, "JNI", "Failed to load library: %s", dlerror()); env->ReleaseStringUTFChars(path, cpath);return nullptr; }//由于我们是通过的对应路径去查找的so文件的函数名,所以这里创建了一个函数指针去得到对应的函数句柄,然后直接调用void (*def)(char*) = reinterpret_cast<void (*)(char*)>(dlsym(soinfo, "_Z7seconedv"));//直接去利用句柄去实现查找对应的函数,这里的函数名由于没有加上extern "C",所以会在执行过程中改变if (def == nullptr) {// 处理 dlsym 找不到符号的情况 __android_log_print(ANDROID_LOG_ERROR, "JNI", "Failed to find symbol: %s", dlerror()); dlclose(soinfo); env->ReleaseStringUTFChars(path, cpath);return nullptr; } def(nullptr); dlclose(soinfo); // 关闭动态库句柄 env->ReleaseStringUTFChars(path, cpath); // 释放 JNI 字符串return env->NewStringUTF(hello.c_str());}
Step 2 随机顺序
Step2 中,安卓动态链接器采用随机顺序加载 SO 文件(避免固定顺序带来的漏洞)。如果extinfo
需要ANDROID_DLEXT_RESERVED_ADDRESS_RECURSIVE
,则不会进行随机化。设定地址空间参数,比如reserved_addr
(预留地址)和reserved_size
。
Step3 预链接
复制代码 隐藏代码bool any_memtag_stack = false;for (auto&& task : load_tasks) { soinfo* si = task->get_soinfo();if (!si->is_linked() && !si->prelink_image(dlext_use_relro)) {returnfalse; }// si->memtag_stack() needs to be called after si->prelink_image() which populates// the dynamic section.if (si->memtag_stack()) { any_memtag_stack = true; LD_LOG(kLogDlopen,"... load_library requesting stack MTE for: realpath="%s", soname="%s"", si->get_realpath(), si->get_soname()); } register_soinfo_tls(si); }
这里是实现预链接的位置,也是在文章之前提到实现Linker来解析ELF文件结构的地方si>prelink_image(dlext_use_relro))
复制代码 隐藏代码1.检查 ELF 头的合法性,确定是有效的 ELF 文件。2.读取动态段,提取符号表、字符串表等信息。3.初始化 .got.plt 和 .rel.dyn 等重定位表,用于后续符号解析。4.解析 DT_NEEDED(依赖的库),准备后续的 link_image() 进行符号解析。
这也是对于ELF文件结构处理的细节位置,只有对应的so文件是完整的才能进行加载链接
复制代码 隐藏代码register_soinfo_tls(si);
同时也将soinfo转入到了TLS中去。
Step 4 处理全局符号解析
这里去实现把在Step3中解析的符号表等等的信息来进行处理了DF_1_GLOBAL
标志的库会被添加到全局符号解析列表。如果是LD_PRELOAD
方式加载的库,强制DF_1_GLOBAL
以确保它能影响所有加载的库。
Step5 --- Step7
从Step5到Step7之间的这些处理就很细节了,比如有对于so文件的跨越了匿名空间的特殊处理,以及对于之前的so文件之间的依赖库的递归处理,来实现依赖so之间的函数使用。总的来说全是遍历load_tasks,之前准备的so文件,来实现的各种属性和信息的处理。
就此,整个SO文件被全部解析处理。
总结
这里我们去重新梳理一下整个so文件加载过程,现在不管是frida检测,还是更多的防护都在so文件里面,这在APP防护和加固很重要,同样破防也是。
我们首先是通过System.load()
进入
复制代码 隐藏代码@CallerSensitivepublicstaticvoidload(String filename) { Runtime.getRuntime().load0(Reflection.getCallerClass(), filename);}
此方法最终调用Runtime.load0()
,然后进入nativeLoad()
函数。
Runtime_nativeLoad
→vm->LoadNativeLibrary
进入JavaVMExt::LoadNativeLibrary
方法后,最终会调用dlopen
进行真正的 SO 文件加载。
复制代码 隐藏代码vm->LoadNativeLibrary(env, filename.c_str(), javaLoader, caller, &error_msg);
在 Android 12 及以上版本,会调用android_dlopen_ext
返回__loader_android_dlopen_ext
。
复制代码 隐藏代码void* __loader_android_dlopen_ext(constchar* filename,int flags,const android_dlextinfo* extinfo,constvoid* caller_addr) {return dlopen_ext(filename, flags, extinfo, caller_addr);}
该方法最终调用dlopen_ext()
。
复制代码 隐藏代码staticvoid* dlopen_ext(constchar* filename,int flags,const android_dlextinfo* extinfo,constvoid* caller_addr) { ScopedPthreadMutexLocker locker(&g_dl_mutex);void* result = do_dlopen(filename, flags, extinfo, caller_addr);return result;}
do_dlopen(filename, flags, extinfo, caller_addr)
在do_dlopen()
中,会调用find_library()
进行 SO 文件的真正加载。
复制代码 隐藏代码soinfo* si = find_library(ns, translated_name, flags, extinfo, caller);
这里是对于soinfo的赋值,同时在这里开始调用so的.init_proc函数,接着调用.init_array中的函数,最后才是JNI_OnLoad函数。最后到达find_libraries执行最后的处理。
这里补上一张来自于评论区的包含模拟器执行程的执行流程图:
· 今 日 推 荐 ·
本文内容来自吾爱破解,如有侵权请联系删除
原文始发于微信公众号(逆向有你):安卓逆向 -- Android SO文件加载过程探究
- 左青龙
- 微信扫一扫
-
- 右白虎
- 微信扫一扫
-
评论