Из вашего вопроса не ясно, какая версия 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*