Может ли GDB сделать указатель на функцию, указывающую на другое место? - PullRequest
26 голосов
/ 17 июля 2010

Я объясню:

Допустим, я заинтересован в замене функции 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 отлично подходит для этой цели, потому что он позволяет вводить код в конкретный процесс.

Ответы [ 10 ]

12 голосов
/ 15 сентября 2010

Я следовал за этот пост и за эту презентацию и придумал следующий набор команд 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 -относительный переход, поэтому добавьте смещение к тому, что $rip будет в этой точке (сразу после инструкции dyld_stub_rand+6).

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

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

9 голосов
/ 17 июля 2010

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

Для решения только для gdb см. Мое другое решение.


// -*- 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

5 голосов
/ 18 июля 2010

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

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

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

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

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

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

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

2 голосов
/ 17 сентября 2010

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

  1. Загрузка кода для внедрения .Большинство ответов, опубликованных здесь, используют то, что я считаю лучшим подходом - вызовите dlopen() в подчиненном процессе, чтобы создать ссылку в общей библиотеке, содержащей введенный код.В этой статье вы связались с автором, вместо этого загрузили перемещаемый объектный файл и связали его вручную с подчиненным.Это, откровенно говоря, безумие - перемещаемые объекты не являются «готовыми к работе» и включают перемещения даже для внутренних ссылок.А ручная компоновка утомительна и подвержена ошибкам - гораздо проще позволить реальной работе динамического компоновщика во время выполнения.Это, в первую очередь, означает получение libdl в процессе, но для этого есть много вариантов.
  2. Создание объезда .Большинство ответов, опубликованных здесь до сих пор, включали в себя поиск записи PLT для интересующей функции, использование которой для нахождения соответствующей записи GOT, а затем изменение записи GOT для указания на введенную функцию.Это хорошо до некоторой степени, но некоторые функции компоновщика - например, использование dlsym - могут обойти GOT и обеспечить прямой доступ к интересующей функции.Единственный способ быть уверенным в перехвате всех вызовов определенной функции - это переписать начальные инструкции кода этой функции в памяти, чтобы создать «обходной» путь, перенаправляющий выполнение на введенную функцию.
  3. Созданиебатут (опционально).Часто при выполнении такого рода инъекций вы захотите вызвать исходную функцию, вызов которой вы перехватываете.Способ сделать это с помощью обхода функции заключается в создании небольшого кода «батут», который включает перезаписанные инструкции исходной функции, а затем переход к оставшейся части оригинала.Это может быть сложным, поскольку любые IP-относительные инструкции в скопированном наборе необходимо изменить, чтобы учесть их новые адреса.
  4. Автоматизируйте все это .Эти шаги могут быть утомительными, даже если вы делаете некоторые из простых решений, опубликованных в других ответах.Лучший способ убедиться, что шаги выполняются правильно каждый раз с переменными параметрами (внедрение различных функций и т. Д.), - это автоматизировать их выполнение.Начиная с серии 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, но любые различия должны быть относительно незначительными.

2 голосов
/ 17 июля 2010

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

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

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

1 голос
/ 18 сентября 2010

Вы можете использовать нас LD_PRELOAD, если вы заставите предварительно загруженную функцию понимать ситуации, в которых она используется. Вот пример, в котором 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;
}

jdizzle@pudding:~$ ./test
Super random number: 1804289383
original process rand: 846930886
forked process rand: 846930886

jdizzle@pudding:~$ LD_PRELOAD="/lib/ld-linux.so.2 ./my_rand.so" ./test
Super random number: 1804289383
original process rand: 846930886
forked process rand: 42
1 голос
/ 16 сентября 2010

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

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

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

Поиск в Google для PLT вместе с такими терминами, как "ELF", "shared"библиотека "," динамическое связывание "," PIC "и т. д. может найти вас более подробную информацию по этому вопросу.

1 голос
/ 15 сентября 2010

Для исполняемых файлов вы можете легко найти адрес, где хранится указатель функции, используя 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 -информация может быть недоступна в таких случаях.

0 голосов
/ 14 сентября 2010

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

Я создал два исполняемых файла, один с именем a, а другой b. Исполняемый файл a печатает результат вызова rand() дважды, затем fork() s и exec() s 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.

0 голосов
/ 18 июля 2010

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

Здесь также есть хорошие вопросы и ответы по внедрению кода для Mac здесь: http://www.mikeash.com/pyblog/friday-qa-2009-01-30-code-injection.html

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...