正如我们所知道的,/system/bin/linker负责动态链接机制,但是libdl对于在动态链接器( dynamic,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那么劫机是什么时候发生的?如果你能给我看一下android开源的代码,我会非常感激的。
发布于 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中找到。
// 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二进制文件中有关解释条目的声明:
$ readelf --string-dump=.interp system/bin/vold
String dump of section '.interp':
[ 0] /system/bin/linker64linker和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) {这个__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]是要执行的二进制文件:
soinfo* si = soinfo_alloc(args.argv[0], nullptr, 0, RTLD_GLOBAL);对于这个soinfo,它将调用:
if (!si->prelink_image()) {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);可以使用以下命令行检查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]
...当指向dynamic表的指针初始化后,for_each_dt_needed助手函数变得可用,它为dynamic表中的每个NEEDED条目运行一个指定的操作。在__linker_init_post_relocation中,这个帮助函数用于收集我们必须加载的共享库的名称:
for_each_dt_needed(si, [&](const char* name) {
needed_library_name_list.push_back(name);
++needed_libraries_count;
});接下来,将所需库的列表传递给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));
}对于每个这样的加载任务,它将加载二进制文件,并将加载库的依赖项附加到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())) {对于每个这样的加载任务,它调用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)) {这正是最初用被劫持的libdl.so条目填充的libdl.so,指向dlopen等的非存根实现。因此,每当二进制文件在dynamic部分的NEEDED列表中有libdl.so时,加载过程总是会发现libdl.so已经加载并返回被劫持的soinfo。
如果在solist中找不到库,则find_library_internal函数调用load_library来读取实际的库文件。它为这个库创建一个新的soinfo条目,并使用soinfo_alloc将其附加到全局solist的末尾(使用sonext全局变量,它总是指向solist启动的列表的末尾)。
https://stackoverflow.com/questions/60916039
复制相似问题