Подтвердить что ты не робот

Может ли gdb указать точку указателя на функцию в другом месте?

Я объясню:

Скажем, меня интересует замена функции rand(), используемой определенным приложением.

Итак, я подключаю gdb к этому процессу и загружаю свою собственную общую библиотеку (которая имеет настраиваемую функцию rand()):

call (int) dlopen("path_to_library/asdf.so")

Это помещает настроенную rand() функцию внутри памяти процесса. Однако в этот момент символ rand будет по-прежнему указывать на функцию по умолчанию rand(). Есть ли способ сделать gdb указывать символ на новую функцию rand(), заставляя процесс использовать мою версию?

Я должен сказать, что мне также запрещено использовать методы LD_PRELOAD (linux) и DYLD_INSERT_LIBRARIES (mac os x) для этого, потому что они позволяют вводить код только в начале выполнения программы.

Приложение, которое я хотел бы заменить rand(), запускает несколько потоков, а некоторые из них запускают новые процессы, и мне интересно вводить код в один из этих новых процессов. Как я уже упоминал выше, GDB отлично подходит для этой цели, поскольку он позволяет вводить код в определенный процесс.

4b9b3361

Ответ 1

Я последовал за этот пост и эту презентацию и пришел со следующим набором команд gdb для OSX с исполняемым файлом x86-64, который может быть загружен с опцией -x при подключении к процессу:

set $s = dyld_stub_rand
set $p = ($s+6+*(int*)($s+2))
call (void*)dlsym((void*)dlopen("myrand.dylib"), "my_rand")
set *(void**)$p = my_rand
c

Магия находится в команде set $p = .... dyld_stub_rand - 6-байтная команда перехода. Сдвиг смещения составляет dyld_stub_rand+2 (4 байта). Это скачок $rip -relative, поэтому добавьте смещение к тому, что $rip будет в этой точке (сразу после инструкции, dyld_stub_rand+6).

Это указывает на запись таблицы символов, которая должна быть либо реальной rand, либо динамической компоновкой компоновщика для ее загрузки (если она никогда не вызывалась). Затем он заменяется на my_rand.

Иногда gdb выберет dyld_stub_rand из libSystem или другой общей библиотеки, если это произойдет, сначала выгрузите их с помощью remove-symbol-file перед запуском других команд.

Ответ 2

Этот вопрос заинтриговал меня, поэтому я сделал небольшое исследование. То, что вы ищете, это dll injection. Вы пишете функцию для замены некоторой библиотечной функции, поместите ее в .so и сообщите ld, чтобы предварительно загрузить вашу dll. Я просто попробовал, и все получилось отлично! Я понимаю, что это не отвечает на ваш вопрос по отношению к gdb, но я думаю, что он предлагает жизнеспособное обходное решение.

Для решения gdb-only см. мое другое решение.


// -*- compile-command: "gcc -Wall -ggdb -o test test.c"; -*-
// test.c

#include "stdio.h"
#include "stdlib.h"

int main(int argc, char** argv)
{
    //should print a fairly random number...
    printf("Super random number: %d\n", rand());

    return 0;
}

/ -*- compile-command: "gcc -Wall -fPIC -shared my_rand.c -o my_rand.so"; -*-
//my_rand.c

int rand(void)
{
    return 42;
}

скомпилируйте оба файла, затем запустите: LD_PRELOAD="./my_rand.so" ./test

Super random number: 42

Ответ 3

У меня есть новое решение, основанное на исходных ограничениях new. (Я не удаляю свой первый ответ, так как другие могут найти его полезным.)

Я занимаюсь кучей исследований, и я думаю, что это будет работать с немного более вопиющим.

  • В вашем .so переименуйте вашу заменяющую функцию rand, например my_rand
  • Скомпилируйте все и загрузите gdb
  • Используйте info functions, чтобы найти адрес rand в таблице символов
  • Используйте dlopen, затем dlsym, чтобы загрузить функцию в память и получить ее адрес

    call (int) dlopen("my_rand.so", 1) → -val -

    call (unsigned int) dlsym(-val-, "my_rand") → my_rand_addr

  • - сложная часть. Найдите шестнадцатеричный код инструкции jumpq 0x*my_rand_addr*
  • Используйте set {int}*rand_addr* = *my_rand_addr* для изменения команды таблицы символов
  • Continue Выполнение: теперь, когда вызывается rand, он будет вместо my_rand переходить

Это немного сложно и очень круто, но я уверен, что это сработает. Единственное, чего я еще не достиг, это создать код команды jumpq. Все до этого момента отлично работает.

Ответ 4

Я не уверен, как это сделать в запущенной программе, но, возможно, LD_PRELOAD будет работать на вас. Если вы установите эту переменную среды в список общих объектов, загрузчик времени выполнения загрузит общий объект в начале процесса и позволит функциям в нем иметь приоритет над другими.

LD_PRELOAD=path_to_library/asdf.so path/to/prog 

Вам нужно сделать это до начала процесса, но вам не нужно перестраивать программу.

Ответ 5

Несколько ответов здесь и статья для инъекций кода, с которой вы связали в своем ответе куски блоков, что я считаю оптимальным gdb ориентированное решение, но ни один из них не сдержит все это или не покрывает все точки. Кодовое выражение решения немного длиннее, поэтому здесь приводится краткое описание важных шагов:

  • Загрузите код для ввода. Большинство ответов, размещенных здесь, используют то, что я считаю лучшим, - вызовите dlopen() в нижнем процессе для связи в общей библиотеке, содержащей введенный код. В статье, которую вы связали с автором, вместо этого загрузили перемещаемый объектный файл и связали его с нижним. Это совершенно откровенно безумный - перемещаемые объекты не "готовы к запуску" и включают перестановки даже для внутренних ссылок. И ручная работа утомительна и подвержена ошибкам - намного проще позволить реальному динамическому компоновщику выполнения работать. Это означает получение libdl в процессе в первую очередь, но есть много вариантов для этого.
  • Создать обход. Большинство ответов, размещенных здесь до сих пор, включали в себя поиск записи PLT для интересующей функции, используя это для поиска соответствующей записи GOT, а затем изменения записи GOT, чтобы указать на вашу введенную функцию. Это до некоторой степени, но некоторые функции компоновщика - например, использование dlsym - могут обойти GOT и обеспечить прямой доступ к интересующей функции. Единственный способ быть уверенным в перехвате всех вызовов определенной функции - это перезаписать начальные инструкции этого кода функции в памяти, чтобы создать "перенастройку" перенаправления выполнения вашей введенной функции.
  • Создать батут (необязательно). Часто при выполнении такого рода инъекций вы хотите вызвать исходную функцию, вызов которой вы перехватываете. Способ сделать это с помощью функции обхода - создать небольшой код "батут", который включает в себя перезаписанные инструкции исходной функции, а затем переход к оставшейся части оригинала. Это может быть сложным, поскольку любые инструкции, относящиеся к IP-адресам в скопированном наборе, должны быть изменены для учета их новых адресов.
  • Автоматизировать все. Эти шаги могут быть утомительными, даже если они выполняют некоторые более простые решения, размещенные в других ответах. Лучший способ убедиться, что шаги выполняются правильно каждый раз с переменными параметрами (ввод различных функций и т.д.), - автоматизировать их выполнение. Начиная с серии 7.0, gdb включил возможность писать новые команды в Python. Эта поддержка может быть использована для реализации решения "под ключ" для инъекций и обхода кода в/в более низкий процесс.

Вот пример. У меня есть те же a и b исполняемые файлы, как и раньше, и inject2.so, созданные из следующего кода:

#include <unistd.h>
#include <stdio.h>

int (*rand__)(void) = NULL;

int
rand(void)
{
    int result = rand__();
    printf("rand invoked! result = %d\n", result);
    return result % 47;
}

Затем я могу поместить мою команду Python detour в detour.py и иметь следующий сеанс gdb:

(gdb) source detour.py
(gdb) exec-file a
(gdb) set follow-fork-mode child
(gdb) catch exec
Catchpoint 1 (exec)
(gdb) run
Starting program: /home/llasram/ws/detour/a 
a: 1933263113
a: 831502921
[New process 8500]
b: 918844931
process 8500 is executing new program: /home/llasram/ws/detour/b
[Switching to process 8500]

Catchpoint 1 (exec'd /home/llasram/ws/detour/b), 0x00007ffff7ddfaf0 in _start ()
   from /lib64/ld-linux-x86-64.so.2
(gdb) break main
Breakpoint 2 at 0x4005d0: file b.c, line 7.
(gdb) cont
Continuing.

Breakpoint 2, main (argc=1, argv=0x7fffffffdd68) at b.c:7
7       {
(gdb) detour libc.so.6:rand inject2.so:rand inject2.so:rand__
(gdb) cont
Continuing.
rand invoked! result = 392103444
b: 22

Program exited normally.

В дочернем процессе я создаю объезд из функции rand() в libc.so.6 в функцию rand() в inject2.so и сохраняю указатель на батут для исходного rand() в rand__ переменная inject2.so. И, как и ожидалось, введенный код вызывает оригинал, отображает полный результат и возвращает результат по модулю 47.

Из-за длины я просто привязываюсь к пастике, содержащей код для моей команды detour. Это довольно поверхностная реализация (особенно с точки зрения генерации батута), но она должна хорошо работать в большом проценте случаев. Я тестировал его с помощью gdb 7.2 (последняя версия) для Linux с 32-разрядными и 64-разрядными исполняемыми файлами. Я не тестировал его на OS X, но любые различия должны быть относительно незначительными.

Ответ 6

Для исполняемых файлов вы можете легко найти адрес, где указатель функции хранится с помощью objdump. Например:

objdump -R /bin/bash | grep write
00000000006db558 R_X86_64_JUMP_SLOT  fwrite
00000000006db5a0 R_X86_64_JUMP_SLOT  write

Следовательно, 0x6db5a0 является адресом указателя для write. Если вы измените его, вызовы для записи будут перенаправлены на выбранную вами функцию. Загрузка новых библиотек в gdb и получение указателей функций были рассмотрены в более ранних сообщениях. Исполняемый файл и каждая библиотека имеют свои собственные указатели. Замена влияет только на модуль, указатель которого был изменен.

Для библиотек вам необходимо найти базовый адрес библиотеки и добавить его к адресу, указанному objdump. В Linux /proc/<pid>/maps выдает это. Я не знаю, будут ли работать автономные исполняемые файлы с рандомизацией адреса. maps -информация может быть недоступна в таких случаях.

Ответ 7

Пока функция, которую вы хотите заменить, находится в общей библиотеке, вы можете перенаправлять вызовы этой функции во время выполнения (во время отладки), выталкивая PLT. Вот статья, которая может быть полезна:

Перенаправление общего вызова библиотеки с использованием ELF PLT-инфекции

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

Googling для "PLT" вместе с такими терминами, как "ELF", "shared library", "dynamic linking", "PIC" и т.д., может найти более подробную информацию по этому вопросу.

Ответ 8

Вы все еще можете LD_PRELOAD, если вы заставите функцию preloaded понять ситуации, в которых она будет использоваться. Вот пример, который будет использовать rand() как обычно, за исключением внутри разветвленного процесса, когда он всегда будет возвращать 42. Я использую подпрограммы dl для загрузки стандартной библиотеки rand() в указатель функции для использования захваченным rand().

// -*- compile-command: "gcc -Wall -fPIC -shared my_rand.c -o my_rand.so -ldl"; -*-
//my_rand.c
#include <sys/types.h>
#include <unistd.h>

#include <dlfcn.h>


int pid = 0;
int (*real_rand)(void) = NULL;

void f(void) __attribute__ ((constructor));

void f(void) {
    pid = getpid();
    void* dl = dlopen("libc.so.6", RTLD_LAZY);
    if(dl) {
        real_rand = dlsym(dl, "rand");
    }
}

int rand(void) 
{
    if(pid == getpid() && real_rand)
        return real_rand();
    else
        return 42;
}

//test.c
#include <dlfcn.h>
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
int main(int argc, char** argv)
{

    printf("Super random number: %d\n", rand());
    if(fork()) {
        printf("original process rand: %d\n", rand());

    } else {
        printf("forked process rand: %d\n", rand());
    }

    return 0;
}

[email protected]:~$ ./test
Super random number: 1804289383
original process rand: 846930886
forked process rand: 846930886

[email protected]:~$ LD_PRELOAD="/lib/ld-linux.so.2 ./my_rand.so" ./test
Super random number: 1804289383
original process rand: 846930886
forked process rand: 42

Ответ 9

Я нашел этот учебник невероятно полезным, и до сих пор это единственный способ, которым мне удалось добиться того, что я искал с помощью GDB: Вставка кода в приложение для Linux: http://www.codeproject.com/KB/DLL/code_injection.aspx

Существует также хороший Q & A для ввода кода для Mac здесь: http://www.mikeash.com/pyblog/friday-qa-2009-01-30-code-injection.html p >

Ответ 10

Я часто использую инъекцию кода как метод издевательств для автоматического тестирования кода C. Если это та ситуация, в которой вы находитесь, - если ваше использование GDB просто потому, что вас не интересуют родительские процессы, а не потому, что вы хотите интерактивно выбирать интересующие вас процессы, то вы все равно можете используйте LD_PRELOAD для достижения вашего решения. Ваш введенный код просто должен определить, находится ли он в родительском или дочернем процессах. Есть несколько способов сделать это, но в Linux, поскольку ваш дочерний процесс обрабатывает exec(), самым простым, возможно, является просмотр активного исполняемого изображения.

Я создал два исполняемых файла, один из которых назван a, а другой b. Исполняемый a дважды печатает результат вызова rand(), затем fork() и exec() b дважды. Исполняемый b печатает результат вызова rand() один раз. Я использую LD_PRELOAD для ввода результата компиляции следующего кода в исполняемые файлы:

// -*- compile-command: "gcc -D_GNU_SOURCE=1 -Wall -std=gnu99 -O2 -pipe -fPIC -shared -o inject.so inject.c"; -*-
#include <sys/types.h>
#include <unistd.h>
#include <limits.h>
#include <stdio.h>
#include <dlfcn.h>

#define constructor __attribute__((__constructor__))

typedef int (*rand_t)(void);

typedef enum {
    UNKNOWN,
    PARENT,
    CHILD
} state_t;

state_t state = UNKNOWN;
rand_t rand__ = NULL;

state_t
determine_state(void)
{
    pid_t pid = getpid();
    char linkpath[PATH_MAX] = { 0, };
    char exepath[PATH_MAX] = { 0, };
    ssize_t exesz = 0;

    snprintf(linkpath, PATH_MAX, "/proc/%d/exe", pid);
    exesz = readlink(linkpath, exepath, PATH_MAX);
    if (exesz < 0)
        return UNKNOWN;

    switch (exepath[exesz - 1]) {
    case 'a':
        return PARENT;
    case 'b':
        return CHILD;
    }

    return UNKNOWN;
}

int
rand(void)
{
    if (state == CHILD)
        return 47;
    return rand__();
}

constructor static void
inject_init(void) 
{
    rand__ = dlsym(RTLD_NEXT, "rand");
    state = determine_state();
}

Результат запуска a с инъекцией и без нее:

$ ./a
a: 644034683
a: 2011954203
b: 375870504
b: 1222326746
$ LD_PRELOAD=$PWD/inject.so ./a
a: 1023059566
a: 986551064
b: 47
b: 47

Я отправлю решение, ориентированное на gdb, позже.