Загрузка Динамической Библиотеки Из Памяти В Linux

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

Ни один из вариантов создания файла не является безопасным, так как возможна замена файла между проверкой подписи и его загрузкой, а также невозможно проверить подпись после загрузки, так как статические конструкторы уже выполнены.

Поэтому необходимо загружать плагин без создания файла.

Перехват open, mmap и других функций невозможен, так как ld.so статически связан с библиотекой, исполняемые файлы, загружаемые собственным загрузчиком, являются «неполными» (даже при перехвате функций в libdl): они не прописаны в список загруженных библиотек и/или их символы не видны через dlsym. Поэтому остается только перехватывать системные вызовы.

Точка входа загрузчика:

  
   

void *dlopen_memory(void *base, size_t size, void *(*custom_dlopen)(const char *filename, void *arg), void *arg);

Параметры:
  • база — базовый адрес, по которому было загружено изображение.

    После завершения эта функция больше не нужна и ее можно отключить.

  • размер — размер изображения в байтах.

  • custom_dlopen - кастомная оболочка для dlopen. Может использоваться, если вы хотите загрузить библиотеку из памяти, используя какую-либо библиотечную функцию, вызывающую dlopen. Если не требуется, передайте NULL.
  • аргумент — дополнительный параметр для custom_dlopen.
Возвращаемое значение такое же, как и dlopen: дескриптор библиотеки или NULL и описание ошибки с помощью dlerror().

Это работает следующим образом:

  1. Генерируется псевдослучайное имя библиотеки.

    Мне нужно было именно это, если нужно что-то читабельное, передайте пожалуйста.

    параметр имени файла.

  2. Устанавливается пустой обработчик сигнала SIGQUIT, а старый сохраняется.

    Этот сигнал используется позже, когда загрузка будет завершена.

  3. Создается дочерний процесс, который фактически выполнит загрузку.

  4. Ожидание установки флага готовности дочернего процесса.

  5. Библиотека загружается через dlopen или custom_dlopen.
  6. SIGQUIT отправляется собственному процессу.

  7. Ожидание завершения дочернего процесса.

  8. Обработчик SIGQUIT восстановлен.

Дочерний процесс выполняет следующие действия:
  1. Начинает отслеживать своего родителя.

    Это приводит к некоторым интересным последствиям, например, к тому, что он становится родителем своего родителя.

  2. Устанавливает флаг готовности в родительском процессе.

  3. Пока родительский процесс запущен и не было запроса на остановку:
    1. Родительский процесс запускается до входа в системный вызов.

      Если системный вызов не является псевдофайлом mmap, то процесс все равно выполняется до завершения вызова.

    2. Регистры дочернего процесса извлекаются.

    3. Если этот вызов отправляет вызов SIGQUIT родительскому процессу, трассировка останавливается.

    4. Если этот вызов предназначен для открытия псевдофайла библиотеки, то выходной дескриптор заменяется специальным дескриптором, который используется для идентификации операций над псевдофайлом.

    5. Если этот вызов является чтением из псевдофайла, то образ библиотеки считывается в буфер родительского процесса, и вызов завершается успешно.

    6. Если этот вызов предназначен для получения атрибутов файла, то информация о псевдофайле генерируется и помещается в буфер родительского процесса (в нем имеет значение только размер).

    7. Если этот вызов предназначен для закрытия файла, то вызов завершается успешно.

    8. Если этот вызов предназначен для отображения псевдофайла в памяти, то выполняются следующие действия:
      1. Установлен флаг MAP_ANONYMOUS (запрашивается область памяти, не сопоставленная с файлом).

      2. Вызов mmap выполнен.

      3. Запрошенная часть псевдофайла копируется в созданный регион родительского процесса.

  4. Процесс отделяется от своего родителя и завершается.

Этот код не является переносимым и будет работать только в Linux и только на процессорах архитектуры IA-32 в 32-битном режиме.

Для систем другой архитектуры необходимо (завернуто в #if/#end) реализовать эмуляцию системных вызовов; для систем с другой длиной слова также необходимо изменить процедуру поиска строки.

Если вызов ptrace или waitpid завершится неудачно, как родительский, так и дочерний процессы завершатся.

Если вам нужно другое поведение, перепишите обработчик метки сбоя.

Код доступен для бесплатного использования без каких-либо ограничений.



#include <sys/mman.h> #include <sys/ptrace.h> #include <string.h> #include <stdlib.h> #include <assert.h> #include <time.h> #include <stdio.h> #include <sched.h> #include <unistd.h> #include <signal.h> #include <sys/wait.h> #include <sys/user.h> #include <sys/syscall.h> #include <dlfcn.h> #include <errno.h> #include <limits.h> #include <fcntl.h> #include <sys/stat.h> #define min(a, b) ((a) < (b) ? (a) : (b)) static void generate_name(char *name, size_t length) { assert(length > 5); strcpy(name + length - 4, ".

so"); for(unsigned int i = 1; i < length - 4; i++) name[i] = rand() % ('Z' - 'A' + 1) + 'A'; name[0] = '/'; } static void quit_handler(int sig) { (void) sig; } static int peekstring(pid_t pid, void *base, char *dest, size_t length) { unsigned int word; unsigned int offset = 0; do { word = ptrace(PTRACE_PEEKDATA, pid, base + offset, NULL); memcpy(dest + offset, &word, sizeof(unsigned int)); offset += sizeof(unsigned int); } while((word & 0xFF) && (word & 0xFF00) && (word & 0xFF0000) && (word & 0xFF000000) && offset < length); dest[length - 1] = 0; return 0; } static int pokedata(pid_t pid, void *address, const void *data, size_t length) { length = (length + sizeof(unsigned int) - 1) & ~(sizeof(unsigned int) - 1); const unsigned int *src = data; for(unsigned int offset = 0; offset < length; offset += sizeof(unsigned int)) { if(ptrace(PTRACE_POKEDATA, pid, address + offset, (void *) *src++) == -1) return -1; } return 0; } void *dlopen_memory(void *base, size_t size, void *(*custom_dlopen)(const char *filename, void *arg), void *arg) { char fakename[16]; int ready = 0; unsigned int offset = 0; generate_name(fakename, sizeof(fakename)); struct sigaction old_handler, new_handler = { .

sa_handler = quit_handler, .

sa_flags = 0, .

sa_restorer = NULL }; sigfillset(&new_handler.sa_mask); if(sigaction(SIGQUIT, &new_handler, &old_handler) == -1) return NULL; pid_t child = fork(); if(child == -1) { sigaction(SIGQUIT, &old_handler, NULL); return NULL; } else if(child == 0) { pid_t parent = getppid(); if(ptrace(PTRACE_ATTACH, parent, NULL, NULL) == -1) { kill(parent, SIGKILL); _exit(1); } ready = 1; if(ptrace(PTRACE_POKEDATA, parent, &ready, (void *)ready) == -1) goto fail; int status; char path[PATH_MAX]; int handle = getdtablesize(); do { struct user_regs_struct regs; for(int i = 0; i < 2; i++) { if(ptrace(PTRACE_SYSCALL, parent, NULL, NULL) == -1) goto fail; if(waitpid(parent, &status, 0) == -1) goto fail; if(!WIFSTOPPED(status)) goto outer_break; if(ptrace(PTRACE_GETREGS, parent, NULL, &regs) == -1) goto fail; if(regs.orig_eax == SYS_mmap2 && regs.edi == handle) break; } if(regs.orig_eax == SYS_kill && regs.ebx == parent && regs.ecx == SIGQUIT) { break; } else if(regs.orig_eax == SYS_open) { if(peekstring(parent, (void *) regs.ebx, path, PATH_MAX) == -1) goto fail; if(strcmp(path, fakename) != 0) continue; regs.eax = handle; offset = 0; } else if(regs.orig_eax == SYS_read && regs.ebx == handle) { unsigned int bytes = min((unsigned int) regs.edx, size - offset); if(pokedata(parent, (void *) regs.ecx, base + offset, bytes) == -1) goto fail; offset += bytes; regs.eax = bytes; } else if(regs.orig_eax == SYS_close && regs.ebx == handle) { regs.eax = 0; } else if(regs.orig_eax == SYS_fstat64 && regs.ebx == handle) { struct stat statbuf = { .

st_dev = 0, .

st_ino = 1, .

st_mode = 0444, .

st_nlink = 1, .

st_uid = 0, .

st_gid = 0, .

st_rdev = 0, .

st_size = size, .

st_blksize = 512, .

st_blocks = (size + 511) & ~511, .

st_atim = { 0, 0 }, .

st_mtim = { 0, 0 }, .

st_ctim = { 0, 0 } }; if(pokedata(parent, (void *) regs.ecx, &statbuf, sizeof(struct stat)) == -1) goto fail; regs.eax = 0; } else if(regs.orig_eax == SYS_mmap2 && regs.edi == handle) { regs.esi |= MAP_ANONYMOUS; if(ptrace(PTRACE_SETREGS, parent, NULL, &regs) == -1) goto fail; if(ptrace(PTRACE_SYSCALL, parent, NULL, NULL) == -1) goto fail; if(waitpid(parent, &status, 0) == -1) goto fail; if(!WIFSTOPPED(status)) break; if(ptrace(PTRACE_GETREGS, parent, NULL, &regs) == -1) goto fail; unsigned int offset = regs.ebp * 4096; unsigned int bytes = min(size - offset, (unsigned int) regs.ecx); if(pokedata(parent, (void *) regs.eax, base + offset, bytes) < 0) { regs.eax = -errno; if(ptrace(PTRACE_SETREGS, parent, NULL, &regs) == -1) goto fail; } continue; } else if(regs.orig_eax == -1) break; if(ptrace(PTRACE_SETREGS, parent, NULL, &regs) == -1) goto fail; } while(1); outer_break: ptrace(PTRACE_DETACH, parent, NULL, NULL); _exit(0); fail: ptrace(PTRACE_KILL, parent, NULL, NULL); _exit(1); } while(ready == 0) sched_yield(); void *handle; if(custom_dlopen) handle = custom_dlopen(fakename, arg); else handle = dlopen(fakename, RTLD_NOW); kill(getpid(), SIGQUIT); waitpid(child, NULL, 0); sigaction(SIGQUIT, &old_handler, NULL); return handle; }

Теги: #dlopen #linux #извращения #Ненормальное программирование

Вместе с данным постом часто просматривают: