Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How does libdl work with linker in android?

As we know, the /system/bin/linker is responsible for the dynamic linking mechanism, but libdl have stubs for functions that are actually defined in the dynamic linker (dlfcn.c), and hijacked at runtime, like below:

#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

So when and how the hijack happen? It would be very appreciated if you can show me the code in android open source.

like image 857
cong Avatar asked Oct 20 '25 15:10

cong


1 Answers

From your question it isn't clear which Android version you are interested in, but it seems you are looking at an older Android version (given that it uses dlfcn.c instead of dlfcn.cpp). I will discuss the hijacking process based on Android 6.

For newer Android versions, the process is fundamentally the same, but some method names and file names have changed.

In bionic/README.md, the following description can be found:

libdl/ --- libdl.so

The dynamic linker interface library. This is actually just a bunch of stubs that the dynamic linker replaces with pointers to its own implementation at runtime. This is where stuff like dlopen(3) lives.

linker/ --- /system/bin/linker and /system/bin/linker64

The dynamic linker. When you run a dynamically-linked executable, its ELF file has a DT_INTERP entry that says "use the following program to start me". On Android, that's either linker or linker64 (depending on whether it's a 32-bit or 64-bit executable). It's responsible for loading the ELF executable into memory and resolving references to symbols (so that when your code tries to jump to fopen(3), say, it lands in the right place).

The code you posted can be found in 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; }

We can verify the statement about the interpretation entry in the ELF binary:

$ readelf --string-dump=.interp system/bin/vold 

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

The high-level entry point for linker and linker64 can be found in bionic/linker/linker.cpp (for the assembly-level entry point you would have to dig through the architecture-specific files, e.g. 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) {

This __linker_init function initializes, among other things, the global variable static soinfo* solist; with solist = get_libdl_info();.

The struct soinfo is defined in bionic/linker/linker.h and represents a node in a linked list (by having a member soinfo* next;). Each node in such a linked list holds information about a shared object, including a symbol table, through the member symtab_.

The get_libdl_info returns a linked list with a single entry, representing the libdl.so shared object. However, the symbol table of this node is not initialized with pointers to the stub functions from libdl.so, but with the real implementations: The symtab_ member is initialized with __libdl_info->symtab_ = g_libdl_symtab;. The g_libdl_symtab table is initialized here with pointers to the real dlopen etc.

So we found the point where the hijacking happens: The linker initializes a list of shared object information, containing as the first element an entry for libdl.so, with the symbol table directing to the real implementation of dlopen etc., instead of the stubs. The remainder of this section is about how this linked list is used to understand why this hijacking is effective.

The __linker_init returns the address of __linker_init_post_relocation to the calling assembly code, which jumps to this method next (e.g. in bionic/linker/arch/x86_64/begin.S).

In __linker_init_post_relocation, an soinfo struct is initialized, with argv[0] being the binary to be executed:

  soinfo* si = soinfo_alloc(args.argv[0], nullptr, 0, RTLD_GLOBAL);

For this soinfo, it will then call:

  if (!si->prelink_image()) {

The prelink_image function extracts, among other things, the pointer to the dynamic table of the binary:

bool soinfo::prelink_image() {
  /* Extract dynamic section */
  ElfW(Word) dynamic_flags = 0;
  phdr_table_get_dynamic_section(phdr, phnum, load_bias, &dynamic, &dynamic_flags);

The dynamic table can be inspected from the command line with:

$ 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]
...

With the pointer to the dynamic table initialized, the for_each_dt_needed helper function becomes usable, which runs a specified action for each NEEDED entry in the dynamic table. Back in __linker_init_post_relocation, this helper function is used to collect the names of the shared libraries we have to load:

  for_each_dt_needed(si, [&](const char* name) {
    needed_library_name_list.push_back(name);
    ++needed_libraries_count;
  });

Next, the list of needed libraries is passed to find_libraries. There, a LoadTask is created for each library:

  // 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));
  }

For each such load task, it will then load the binary, and append the dependencies of the loaded library to the end of the load_tasks list. In other words, it does a Breadth-first traversal of the dependency graph.

  // 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())) {

For each such load task, it calls find_library_internal to do the actual loading. This function first calls find_loaded_library_by_soname, to check if the library has already been loaded, by traversing the global solist:

  for (soinfo* si = solist; si != nullptr; si = si->next) {
    const char* soname = si->get_soname();
    if (soname != nullptr && (strcmp(name, soname) == 0)) {

This is exactly the solist that was filled initially with the hijacked entry for libdl.so, pointing the non-stub implementation for dlopen etc. Thus, whenever a binary has libdl.so in the NEEDED list of its dynamic section, the loading process will always find that libdl.so has already been loaded and return the hijacked soinfo.

If a library is not found in the solist, the find_library_internal function calls load_library for reading the actual library file. It creates a new soinfo entry for this library and appends it to the end of the global solist using soinfo_alloc (using the sonext global variable which always points to the end of the list started by solist).

like image 83
f9c69e9781fa194211448473495534 Avatar answered Oct 22 '25 05:10

f9c69e9781fa194211448473495534



Donate For Us

If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!