首页
学习
活动
专区
圈层
工具
发布
社区首页 >问答首页 >libdl是如何在android中使用链接器的?

libdl是如何在android中使用链接器的?
EN

Stack Overflow用户
提问于 2020-03-29 14:56:48
回答 1查看 2.7K关注 0票数 3

正如我们所知道的,/system/bin/linker负责动态链接机制,但是libdl对于在动态链接器( dynamic,dlfcn.c)中实际定义并在运行时被劫持的函数有存根,如下所示:

代码语言:javascript
复制
#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

那么劫机是什么时候发生的?如果你能给我看一下android开源的代码,我会非常感激的。

EN

回答 1

Stack Overflow用户

发布于 2020-04-15 00:30:18

从您的问题中,您不清楚您对哪个Android版本感兴趣,但似乎您正在研究一个较旧的Android版本(考虑到它使用的是dlfcn.c而不是dlfcn.cpp)。我将讨论基于Android 6的劫持过程。

对于较新的Android版本,这个过程基本上是一样的,但是有些方法名和文件名已经改变了。

bionic/README.md中,可以找到以下描述:

libdl/ -- libdl.so动态链接器接口库。实际上,这只是一堆存根,动态链接器在运行时用指向自己实现的指针来替换这些存根。像dlopen(3)这样的东西就住在这里。 链接器/- /system/bin/linker和/system/bin/ linker 64动态链接器。当您运行一个动态链接的可执行文件时,它的ELF文件有一个DT_INTERP条目,上面写着“使用下面的程序启动我”。在Android上,这要么是linker,要么是linker64 (取决于它是32位还是64位可执行文件)。它负责将ELF可执行文件加载到内存中,并解析对符号的引用(这样,当您的代码试图跳转到fopen(3)时,它就落在了正确的位置)。

您发布的代码可以在bionic/libdl/libdl.c中找到。

代码语言:javascript
复制
// 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; }

我们可以验证ELF二进制文件中有关解释条目的声明:

代码语言:javascript
复制
$ readelf --string-dump=.interp system/bin/vold 

String dump of section '.interp':
  [     0]  /system/bin/linker64

linkerlinker64的高级入口点可以在bionic/linker/linker.cpp中找到(对于程序集级入口点,您必须挖掘特定于体系结构的文件,例如bionic/linker/arch/x86_64/begin.S):

代码语言:javascript
复制
/*
 * 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) {

这个__linker_init函数除其他外,用solist = get_libdl_info();初始化全局变量static soinfo* solist;

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_relocation的地址返回给调用程序集代码,该程序集代码接着跳到此方法(例如,在bionic/linker/arch/x86_64/begin.S中)。

__linker_init_post_relocation中,初始化一个soinfo结构,argv[0]是要执行的二进制文件:

代码语言:javascript
复制
  soinfo* si = soinfo_alloc(args.argv[0], nullptr, 0, RTLD_GLOBAL);

对于这个soinfo,它将调用:

代码语言:javascript
复制
  if (!si->prelink_image()) {

prelink_image函数除其他外提取指向二进制文件的dynamic表的指针:

代码语言:javascript
复制
bool soinfo::prelink_image() {
  /* Extract dynamic section */
  ElfW(Word) dynamic_flags = 0;
  phdr_table_get_dynamic_section(phdr, phnum, load_bias, &dynamic, &dynamic_flags);

可以使用以下命令行检查dynamic表:

代码语言:javascript
复制
$ 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]
...

当指向dynamic表的指针初始化后,for_each_dt_needed助手函数变得可用,它为dynamic表中的每个NEEDED条目运行一个指定的操作。在__linker_init_post_relocation中,这个帮助函数用于收集我们必须加载的共享库的名称:

代码语言:javascript
复制
  for_each_dt_needed(si, [&](const char* name) {
    needed_library_name_list.push_back(name);
    ++needed_libraries_count;
  });

接下来,将所需库的列表传递给find_libraries。在那里,为每个库创建了一个LoadTask

代码语言:javascript
复制
  // 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));
  }

对于每个这样的加载任务,它将加载二进制文件,并将加载库的依赖项附加到load_tasks列表的末尾。换句话说,它执行依赖关系图的宽度优先遍历

代码语言:javascript
复制
  // 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())) {

对于每个这样的加载任务,它调用find_library_internal来执行实际加载。此函数首先调用find_loaded_library_by_soname,通过遍历全局solist来检查库是否已经加载。

代码语言:javascript
复制
  for (soinfo* si = solist; si != nullptr; si = si->next) {
    const char* soname = si->get_soname();
    if (soname != nullptr && (strcmp(name, soname) == 0)) {

这正是最初用被劫持的libdl.so条目填充的libdl.so,指向dlopen等的非存根实现。因此,每当二进制文件在dynamic部分的NEEDED列表中有libdl.so时,加载过程总是会发现libdl.so已经加载并返回被劫持的soinfo

如果在solist中找不到库,则find_library_internal函数调用load_library来读取实际的库文件。它为这个库创建一个新的soinfo条目,并使用soinfo_alloc将其附加到全局solist的末尾(使用sonext全局变量,它总是指向solist启动的列表的末尾)。

票数 8
EN
页面原文内容由Stack Overflow提供。腾讯云小微IT领域专用引擎提供翻译支持
原文链接:

https://stackoverflow.com/questions/60916039

复制
相关文章

相似问题

领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档