con*_*ong 4 linker android shared-libraries loader android-source
众所周知,/system/bin/linker负责动态链接机制,但libdl具有实际在动态链接器 ( ) 中定义的函数存根dlfcn.c,并在运行时被劫持,如下所示:
#include <dlfcn.h>
/* These are stubs for functions that are actually defined
* in the dynamic linker (dlfcn.c), and hijacked at runtime.
*/
void *dlopen(const char *filename, int flag) { return 0; }
const char *dlerror(void) { return 0; }
void *dlsym(void *handle, const char *symbol) { return 0; }
int dladdr(const void *addr, Dl_info *info) { return 0; }
int dlclose(void *handle) { return 0; }
void android_update_LD_LIBRARY_PATH(const char* ld_library_path) { }
#if defined(__arm__)
void *dl_unwind_find_exidx(void *pc, int *pcount) { return 0; }
#elif defined(__i386__) || defined(__mips__)
/* we munge the cb definition so we don't have to include any headers here.
* It won't affect anything since these are just symbols anyway */
int dl_iterate_phdr(int (*cb)(void *info, void *size, void *data), void *data) { return 0; }
#else
#error Unsupported architecture. Only mips, arm and x86 are supported.
#endif
Run Code Online (Sandbox Code Playgroud)
那么劫持何时以及如何发生?如果您能向我展示 android 开源代码,我将不胜感激。
从您的问题来看,尚不清楚您对哪个 Android 版本感兴趣,但您似乎正在查看较旧的 Android 版本(假设它使用dlfcn.c而不是dlfcn.cpp)。我将讨论基于Android 6的劫持过程。
对于较新的 Android 版本,该过程基本相同,但某些方法名称和文件名已更改。
在 中bionic/README.md,可以找到以下描述:
libdl/ --- libdl.so动态链接器接口库。这实际上只是动态链接器在运行时用指向其自身实现的指针替换的一堆存根。这就是
链接器/ --- /system/bin/linker 和 /system/bin/linker64dlopen(3)生命之类的东西的所在。动态链接器。当您运行动态链接的可执行文件时,其 ELF 文件有一个
DT_INTERP条目显示“使用以下程序来启动我”。在 Android 上,可以是linker或linker64(取决于它是 32 位还是 64 位可执行文件)。它负责将 ELF 可执行文件加载到内存中并解析对符号的引用(这样,当您的代码尝试跳转到 时fopen(3),它就会到达正确的位置)。
您发布的代码可以在以下位置找到bionic/libdl/libdl.c:
// These are stubs for functions that are actually defined
// in the dynamic linker and hijacked at runtime.
void* dlopen(const char* filename __unused, int flag __unused) { return 0; }
Run Code Online (Sandbox Code Playgroud)
我们可以验证一下ELF二进制文件中关于解释条目的说法:
$ readelf --string-dump=.interp system/bin/vold
String dump of section '.interp':
[ 0] /system/bin/linker64
Run Code Online (Sandbox Code Playgroud)
linker和的高级入口点linker64可以在以下位置找到bionic/linker/linker.cpp(对于程序集级入口点,您必须深入挖掘特定于体系结构的文件,例如bionic/linker/arch/x86_64/begin.S):
/*
* This is the entry point for the linker, called from begin.S. This
* method is responsible for fixing the linker's own relocations, and
* then calling __linker_init_post_relocation().
*/
extern "C" ElfW(Addr) __linker_init(void* raw_args) {
Run Code Online (Sandbox Code Playgroud)
除其他事项外,该函数还使用__linker_init初始化全局变量。static soinfo* solist;solist = get_libdl_info();
定义struct soinfo并bionic/linker/linker.h表示链表中的节点(通过具有成员soinfo* next;)。这样的链表中的每个节点通过成员保存有关共享对象的信息,包括符号表symtab_。
返回get_libdl_info一个包含单个条目的链表,代表libdl.so共享对象。然而,该节点的符号表不是用 中指向存根函数的指针初始化的libdl.so,而是用实际实现来初始化的:该symtab_成员是用 初始化的__libdl_info->symtab_ = g_libdl_symtab;。该g_libdl_symtab表在这里用指向实数dlopen等的指针进行初始化。
所以我们找到了劫持发生的地方:链接器初始化一个共享对象信息列表,其中第一个元素包含 的条目libdl.so,符号表指向dlopen等的实际实现,而不是存根。本节的其余部分将介绍如何使用此链表来理解此劫持为何有效。
返回调用汇编代码__linker_init的地址,该汇编代码接下来跳转到该方法(例如在 中)。__linker_init_post_relocationbionic/linker/arch/x86_64/begin.S
在 中,初始化了__linker_init_post_relocation一个结构体,并将其作为要执行的二进制文件:soinfoargv[0]
soinfo* si = soinfo_alloc(args.argv[0], nullptr, 0, RTLD_GLOBAL);
Run Code Online (Sandbox Code Playgroud)
为此soinfo,它将调用:
if (!si->prelink_image()) {
Run Code Online (Sandbox Code Playgroud)
除其他外,该函数提取指向二进制文件表的prelink_image指针:dynamic
bool soinfo::prelink_image() {
/* Extract dynamic section */
ElfW(Word) dynamic_flags = 0;
phdr_table_get_dynamic_section(phdr, phnum, load_bias, &dynamic, &dynamic_flags);
Run Code Online (Sandbox Code Playgroud)
dynamic可以使用以下命令从命令行检查该表:
$ readelf -d system/bin/vold
Dynamic section at offset 0x781a0 contains 46 entries:
Tag Type Name/Value
0x0000000000000001 (NEEDED) Shared library: [libbase.so]
0x0000000000000001 (NEEDED) Shared library: [libc++.so]
0x0000000000000001 (NEEDED) Shared library: [libdl.so]
0x0000000000000001 (NEEDED) Shared library: [libc.so]
0x0000000000000001 (NEEDED) Shared library: [libm.so]
...
Run Code Online (Sandbox Code Playgroud)
当指向表的指针被dynamic初始化后,辅助函数就变得可用,它为表中的每个条目for_each_dt_needed运行指定的操作。回到,这个辅助函数用于收集我们必须加载的共享库的名称:NEEDEDdynamic__linker_init_post_relocation
for_each_dt_needed(si, [&](const char* name) {
needed_library_name_list.push_back(name);
++needed_libraries_count;
});
Run Code Online (Sandbox Code Playgroud)
接下来,所需库的列表被传递到find_libraries. 在那里,LoadTask为每个库创建一个:
// Step 0: prepare.
LoadTaskList load_tasks;
for (size_t i = 0; i < library_names_count; ++i) {
const char* name = library_names[i];
load_tasks.push_back(LoadTask::create(name, start_with));
}
Run Code Online (Sandbox Code Playgroud)
对于每个这样的加载任务,它将加载二进制文件,并将加载的库的依赖项附加到列表的末尾load_tasks。换句话说,它对依赖图进行广度优先遍历。
// Step 1: load and pre-link all DT_NEEDED libraries in breadth first order.
for (LoadTask::unique_ptr task(load_tasks.pop_front());
task.get() != nullptr; task.reset(load_tasks.pop_front())) {
Run Code Online (Sandbox Code Playgroud)
对于每个这样的加载任务,它都会调用find_library_internal来执行实际加载。该函数首先调用find_loaded_library_by_soname, 通过遍历全局来检查库是否已经加载solist:
for (soinfo* si = solist; si != nullptr; si = si->next) {
const char* soname = si->get_soname();
if (soname != nullptr && (strcmp(name, soname) == 0)) {
Run Code Online (Sandbox Code Playgroud)
这正是solist最初填充的被劫持的条目libdl.so,指向非存根实现dlopen等。因此,每当二进制文件libdl.so在NEEDED其dynamic部分列表中时,加载过程总是会发现libdl.so已经加载并返回被劫持了soinfo。
如果在 中找不到库solist,则该find_library_internal函数调用load_library读取实际的库文件。它为此库创建一个新soinfo条目,并将其附加到全局solistusing的末尾soinfo_alloc(使用sonext始终指向以 开头的列表末尾的全局变量solist)。