Как libdl работает с компоновщиком в android? - PullRequest
1 голос
/ 29 марта 2020

Как мы знаем, /system/bin/linker отвечает за механизм связывания Dynami c, но у libdl есть заглушки для функций, которые на самом деле определены в компоновщике Dynami c (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 с открытым исходным кодом.

1 Ответ

1 голос
/ 15 апреля 2020

Из вашего вопроса не ясно, какая версия Android вас интересует, но кажется, что вы смотрите на более старую версию Android (учитывая, что она использует dlfcn.c вместо dlfcn.cpp). Я буду обсуждать процесс угона на основе Android 6.

Для более новых версий Android процесс в основном такой же, но некоторые имена методов и имена файлов изменились.

В bionic/README.md, можно найти следующее описание:

libdl / --- libdl.so

Библиотека интерфейса динамического компоновщика c. На самом деле это просто набор заглушек, которые динамический компоновщик c заменяет указателями на собственную реализацию во время выполнения. Здесь живут такие вещи, как dlopen(3).

компоновщик / --- / system / bin / linker и / system / bin / linker64

динамический линкер c. Когда вы запускаете динамически связанный исполняемый файл, его 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/linker64

High точку входа уровня для linker и linker64 можно найти в bionic/linker/linker.cpp (для точки входа на уровне сборки вам придется копаться в файлах c, определяемых архитектурой, например, 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 функция инициализирует, среди прочего, глобальную переменную 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 et c.

Итак, мы нашли точку, в которой происходит угон: компоновщик инициализирует список информации об общем объекте, содержащий в качестве первого элемента запись для libdl.so, с таблицей символов, указывающей на реальную реализацию dlopen et c. вместо заглушек. В оставшейся части этого раздела рассказывается о том, как этот связанный список используется, чтобы понять, почему этот угон эффективен.

__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, которая запускает указанное действие для каждой записи NEEDED в таблице dynamic. В __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)) {

Это точно solist, который был изначально заполнен угнанной записью для libdl.so, указывая на реализацию без заглушки для dlopen et c. Таким образом, всякий раз, когда двоичный файл имеет libdl.so в списке NEEDED своего раздела dynamic, процесс загрузки всегда обнаружит, что libdl.so уже загружен, и вернет угнанный soinfo.

Если библиотека не найдена в solist, функция find_library_internal вызывает load_library для чтения фактического файла библиотеки. Он создает новую запись soinfo для этой библиотеки и добавляет ее в конец глобальной solist, используя soinfo_alloc (используя глобальную переменную sonext, которая всегда указывает на конец списка

). *1168*
...