看了下安卓11的linker源码,但是没有写注释(逐行写太多了,作者白天要工作没那么多时间),从上而下如何加载so的代码浏览了下,大致知道了其加载过程,并贴了出来,特此记录下,当然这一切为了在ida中找到init_array,本篇文章不建议观看,因为阅读感太差,如果手上有一份安卓11源码去看可能去自己理解比较好一些。
首先经过java层的实现.
1. libcore/ojluni/src/main/java/java/lang/System.java
javaloadLibray函数被调用
public static void loadLibrary(String libname) {
//参数1传输调用者的类,参数2传入将要加载的soname
Runtime.getRuntime().loadLibrary0(Reflection.getCallerClass(), libname);
}
# 1.1 libcore/ojluni/src/main/java/java/lang/Runtime.java
//这个函数存在重载
void loadLibrary0(Class<?> fromClass, String libname) {
ClassLoader classLoader = ClassLoader.getClassLoader(fromClass);
loadLibrary0(classLoader, fromClass, libname);
}
private synchronized void loadLibrary0(ClassLoader loader, Class<?> callerClass, String libname) {
//so库的名字不含有分割字符
if (libname.indexOf((int)File.separatorChar) != -1) {
throw new UnsatisfiedLinkError(
"Directory separator should not appear in library name: " + libname);
}
String libraryName = libname;
//前方调用的类的classloader不为空,并且不是 BootClassLoade
//这个实现的也就是应用程序的so加载
if (loader != null && !(loader instanceof BootClassLoader)) {
//1.1.1使用用ClassLoader的findLibrary()方法获取so库的path
String filename = loader.findLibrary(libraryName);
//这个地方还不知道什么意思
if (filename == null &&
(loader.getClass() == PathClassLoader.class ||
loader.getClass() == DelegateLastClassLoader.class)) {
filename = System.mapLibraryName(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;
}
getLibPaths();
String filename = System.mapLibraryName(libraryName);
String error = nativeLoad(filename, loader, callerClass);
if (error != null) {
throw new UnsatisfiedLinkError(error);
}
}
1.1.1 为什么可以 loader.findLibrary找到
libcore/dalvik/src/main/java/dalvik/system/BaseDexClassLoader.java
public String findLibrary(String name) {
return pathList.findLibrary(name);
}
libcore/dalvik/src/main/java/dalvik/system/DexPathList.java
public String findLibrary(String libraryName) {
String fileName = System.mapLibraryName(libraryName);
for (NativeLibraryElement element : nativeLibraryPathElements) {
String path = element.findNativeLibrary(fileName);
if (path != null) {
return path;
}
}
return null;
}
想要知道nativeLibraryPathElements从何而来?
//获得nativelibrary
this.nativeLibraryPathElements = makePathElements(getAllNativeLibraryDirectories());
private List<File> getAllNativeLibraryDirectories() {
List<File> allNativeLibraryDirectories = new ArrayList<>(nativeLibraryDirectories);
allNativeLibraryDirectories.addAll(systemNativeLibraryDirectories);
return allNativeLibraryDirectories;
}
//可以知道通过 systemNativeLibraryDirectories
this.systemNativeLibraryDirectories =splitPaths(System.getProperty("java.library.path"), true);
java.library.path 又是从何而来呢?
(System.getProperty("java.library.path"),
在system我们可以找到其定义
public static String getProperty(String key) {
checkKey(key);
SecurityManager sm = getSecurityManager();
if (sm != null) {
sm.checkPropertyAccess(key);
}
//相当于从props.getProperty()里面拿到的值
return props.getProperty(key);
}
可以找到
props = initProperties();
private static Properties initProperties() {
Properties p = new PropertiesWithNonOverrideableDefaults(unchangeableProps);
setDefaultChangeableProperties(p);
return p;
}
但是在这个类中没有找到其赋值
我们点击specialProperties 可以跳到specialProperties 的定义位置
private static native String[] specialProperties();
那么根据java和native之间的映射关系可以去System_specialProperties,找到其实现过程。
static jobjectArray System_specialProperties(JNIEnv* env, jclass ignored) {
****代码省略
//从"LD_LIBRARY_PATH获取
const char* library_path = getenv("LD_LIBRARY_PATH");
//如果为空就调用android_get_LD_LIBRARY_PATH
if (library_path == NULL) {
android_get_LD_LIBRARY_PATH(path, sizeof(path));
library_path = path;
}
if (library_path == NULL) {
library_path = "";
}
char* java_path = malloc(strlen("java.library.path=") + strlen(library_path) + 1);
strcpy(java_path, "java.library.path=");
strcat(java_path, library_path);
jstring java_path_str = (*env)->NewStringUTF(env, java_path);
free((void*)java_path);
if ((*env)->ExceptionCheck(env)) {
return NULL;
}
(*env)->SetObjectArrayElement(env, result, 3, java_path_str);
if ((*env)->ExceptionCheck(env)) {
return NULL;
}
return result;
}
那么我们android_get_LD_LIBRARY_PATH(path, sizeof(path));需要知道他的来源
bionic/linker/linker.cpp
void do_android_get_LD_LIBRARY_PATH(char* buffer, size_t buffer_size) {
//从get_default_library_paths()来的
const auto& default_ld_paths = g_default_namespace.get_default_library_paths();
size_t required_size = 0;
for (const auto& path : default_ld_paths) {
required_size += path.size() + 1;
}
if (buffer_size < required_size) {
async_safe_fatal("android_get_LD_LIBRARY_PATH failed, buffer too small: "
"buffer len %zu, required len %zu", buffer_size, required_size);
}
char* end = buffer;
//从这个地方给buff赋值
for (size_t i = 0; i < default_ld_paths.size(); ++i) {
if (i > 0) *end++ = ':';
end = stpcpy(end, default_ld_paths[i].c_str());
}
}
接下来我们追get_default_library_paths()是如何来的
//AddressSanitizer (ASan) 是一种基于编译器的快速检测工具,用于检测原生代码中的内存错误。
auto default_ld_paths = is_asan ? kAsanDefaultLdPaths : kDefaultLdPaths;
static const char* const kDefaultLdPaths[] = {
kSystemLibDir,
kOdmLibDir,
kVendorLibDir,
nullptr
};
static const char* const kAsanDefaultLdPaths[] = {
kAsanSystemLibDir,
kSystemLibDir,
kAsanOdmLibDir,
kOdmLibDir,
kAsanVendorLibDir,
kVendorLibDir,
nullptr
};
```
static const char* const kSystemLibDir = "/system/lib64";
static const char* const kOdmLibDir = "/odm/lib64";
static const char* const kVendorLibDir = "/vendor/lib64";
static const char* const kAsanSystemLibDir = "/data/asan/system/lib64";
static const char* const kAsanOdmLibDir = "/data/asan/odm/lib64";
static const char* const kAsanVendorLibDir = "/data/asan/vendor/lib64";
static const char* const kSystemLibDir = "/system/lib";
static const char* const kOdmLibDir = "/odm/lib";
static const char* const kVendorLibDir = "/vendor/lib";
static const char* const kAsanSystemLibDir = "/data/asan/system/lib";
static const char* const kAsanOdmLibDir = "/data/asan/odm/lib";
static const char* const kAsanVendorLibDir = "/data/asan/vendor/lib";
```
从上面的代码,我们能得出一个结论,在arm32位模式下和arm64位下加载的文件目录不同。
接下来,我们再回到,只加载了着几个目录中的so文件,难道对于app本身的so文件没有找路径吗
并不是的
private List<File> getAllNativeLibraryDirectories() {
List<File> allNativeLibraryDirectories = new ArrayList<>(nativeLibraryDirectories);
allNativeLibraryDirectories.addAll(systemNativeLibraryDirectories);
return allNativeLibraryDirectories;
}
我们看看nativeLibraryDirectories ,这个是app自己的so库,在/data/data/app包名/lib,可以说就是app自己的so库。
经过上边的代码阅读,我们知道this.nativeLibraryPathElements 其实也就是系统库,和appso库的集合,也就知道 findLibrary找的是那个地方的so了。
如何加载的init_arry
然后接下来分析 private static native String nativeLoad(String f
那我们就可以找到对应的c代码所在的位置
/dalvik/vm/native/java_lang_Runtime
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();
//从java虚拟机去加载so
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());
}
static void* dlopen_ext(const char* filename,
int flags,
const android_dlextinfo* extinfo,
const void* 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;
}
void* do_dlopen(const char* name, int flags,
const android_dlextinfo* extinfo,
const void* caller_addr) {
std::string trace_prefix = std::string("dlopen: ") + (name == nullptr ? "(nullptr)" : name);
ScopedTrace trace(trace_prefix.c_str());
ScopedTrace loading_trace((trace_prefix + " - loading and linking").c_str());
soinfo* const caller = find_containing_library(caller_addr);
android_namespace_t* ns = get_caller_namespace(caller);
LD_LOG(kLogDlopen,
"dlopen(name="%s", flags=0x%x, extinfo=%s, caller="%s", caller_ns=%s@%p, targetSdkVersion=%i) ...",
name,
flags,
android_dlextinfo_to_string(extinfo).c_str(),
caller == nullptr ? "(null)" : caller->get_realpath(),
ns == nullptr ? "(null)" : ns->get_name(),
ns,
get_application_target_sdk_version());
auto purge_guard = android::base::make_scope_guard([&]() { purge_unused_memory(); });
auto failure_guard = android::base::make_scope_guard(
[&]() { LD_LOG(kLogDlopen, "... dlopen failed: %s", linker_get_error_buffer()); });
if ((flags & ~(RTLD_NOW|RTLD_LAZY|RTLD_LOCAL|RTLD_GLOBAL|RTLD_NODELETE|RTLD_NOLOAD)) != 0) {
DL_OPEN_ERR("invalid flags to dlopen: %x", flags);
return nullptr;
}
if (extinfo != nullptr) {
if ((extinfo->flags & ~(ANDROID_DLEXT_VALID_FLAG_BITS)) != 0) {
DL_OPEN_ERR("invalid extended flags to android_dlopen_ext: 0x%" PRIx64, extinfo->flags);
return nullptr;
}
if ((extinfo->flags & ANDROID_DLEXT_USE_LIBRARY_FD) == 0 &&
(extinfo->flags & ANDROID_DLEXT_USE_LIBRARY_FD_OFFSET) != 0) {
DL_OPEN_ERR("invalid extended flag combination (ANDROID_DLEXT_USE_LIBRARY_FD_OFFSET without "
"ANDROID_DLEXT_USE_LIBRARY_FD): 0x%" PRIx64, extinfo->flags);
return nullptr;
}
if ((extinfo->flags & ANDROID_DLEXT_USE_NAMESPACE) != 0) {
if (extinfo->library_namespace == nullptr) {
DL_OPEN_ERR("ANDROID_DLEXT_USE_NAMESPACE is set but extinfo->library_namespace is null");
return nullptr;
}
ns = extinfo->library_namespace;
}
}
// Workaround for dlopen(/system/lib/<soname>) when .so is in /apex. http://b/121248172
// The workaround works only when targetSdkVersion < Q.
std::string name_to_apex;
if (translateSystemPathToApexPath(name, &name_to_apex)) {
const char* new_name = name_to_apex.c_str();
LD_LOG(kLogDlopen, "dlopen considering translation from %s to APEX path %s",
name,
new_name);
// Some APEXs could be optionally disabled. Only translate the path
// when the old file is absent and the new file exists.
// TODO(b/124218500): Re-enable it once app compat issue is resolved
/*
if (file_exists(name)) {
LD_LOG(kLogDlopen, "dlopen %s exists, not translating", name);
} else
*/
if (!file_exists(new_name)) {
LD_LOG(kLogDlopen, "dlopen %s does not exist, not translating",
new_name);
} else {
LD_LOG(kLogDlopen, "dlopen translation accepted: using %s", new_name);
name = new_name;
}
}
// End Workaround for dlopen(/system/lib/<soname>) when .so is in /apex.
std::string asan_name_holder;
const char* translated_name = name;
if (g_is_asan && translated_name != nullptr && translated_name[0] == '/') {
char original_path[PATH_MAX];
if (realpath(name, original_path) != nullptr) {
asan_name_holder = std::string(kAsanLibDirPrefix) + original_path;
if (file_exists(asan_name_holder.c_str())) {
soinfo* si = nullptr;
if (find_loaded_library_by_realpath(ns, original_path, true, &si)) {
PRINT("linker_asan dlopen NOT translating "%s" -> "%s": library already loaded", name,
asan_name_holder.c_str());
} else {
PRINT("linker_asan dlopen translating "%s" -> "%s"", name, translated_name);
translated_name = asan_name_holder.c_str();
}
}
}
}
ProtectedDataGuard guard;
soinfo* si = find_library(ns, translated_name, flags, extinfo, caller);
loading_trace.End();
if (si != nullptr) {
void* handle = si->to_handle();
LD_LOG(kLogDlopen,
"... dlopen calling constructors: realpath="%s", soname="%s", handle=%p",
si->get_realpath(), si->get_soname(), handle);
si->call_constructors();
failure_guard.Disable();
LD_LOG(kLogDlopen,
"... dlopen successful: realpath="%s", soname="%s", handle=%p",
si->get_realpath(), si->get_soname(), handle);
return handle;
}
return nullptr;
}
int do_dladdr(const void* addr, Dl_info* info) {
// Determine if this address can be found in any library currently mapped.
soinfo* si = find_containing_library(addr);
if (si == nullptr) {
return 0;
}
memset(info, 0, sizeof(Dl_info));
info->dli_fname = si->get_realpath();
// Address at which the shared object is loaded.
info->dli_fbase = reinterpret_cast<void*>(si->base);
// Determine if any symbol in the library contains the specified address.
ElfW(Sym)* sym = si->find_symbol_by_address(addr);
if (sym != nullptr) {
info->dli_sname = si->get_string(sym->st_name);
info->dli_saddr = reinterpret_cast<void*>(si->resolve_symbol_address(sym));
}
return 1;
}
static soinfo* soinfo_from_handle(void* handle) {
if ((reinterpret_cast<uintptr_t>(handle) & 1) != 0) {
auto it = g_soinfo_handles_map.find(reinterpret_cast<uintptr_t>(handle));
if (it == g_soinfo_handles_map.end()) {
return nullptr;
} else {
return it->second;
}
}
return static_cast<soinfo*>(handle);
}
void soinfo::call_constructors() {
if (constructors_called || g_is_ldd) {
return;
}
if (!is_main_executable() && preinit_array_ != nullptr) {
// The GNU dynamic linker silently ignores these, but we warn the developer.
PRINT(""%s": ignoring DT_PREINIT_ARRAY in shared library!", get_realpath());
}
get_children().for_each([] (soinfo* si) {
si->call_constructors();
});
if (!is_linker()) {
bionic_trace_begin((std::string("calling constructors: ") + get_realpath()).c_str());
}
// DT_INIT should be called before DT_INIT_ARRAY if both are present.
call_function("DT_INIT", init_func_, get_realpath());
call_array("DT_INIT_ARRAY", init_array_, init_array_count_, false, get_realpath());
if (!is_linker()) {
bionic_trace_end();
}
}
static void call_array(const char* array_name __unused,
F* functions,
size_t count,
bool reverse,
const char* realpath) {
if (functions == nullptr) {
return;
}
TRACE("[ Calling %s (size %zd) @ %p for '%s' ]", array_name, count, functions, realpath);
int begin = reverse ? (count - 1) : 0;
int end = reverse ? -1 : count;
int step = reverse ? -1 : 1;
for (int i = begin; i != end; i += step) {
TRACE("[ %s[%d] == %p ]", array_name, i, functions[i]);
call_function("function", functions[i], realpath);
}
TRACE("[ Done calling %s for '%s' ]", array_name, realpath);
}
好了可以专心搞反调试去了 嘻嘻。
我是BestToYou,分享工作或日常学习中关于二进制逆向和分析的一些思路和一些自己闲暇时刻调试的一些程序,文中若有错误的地方,恳请大家联系我批评指正。
原文始发于微信公众号(二进制科学):安卓11 linker加载so
- 左青龙
- 微信扫一扫
- 右白虎
- 微信扫一扫
评论