Проблема при попытке вызвать пользовательскую функцию с помощью ptrace - nanosleep вызывает сбой - PullRequest
2 голосов
/ 18 марта 2019

Я работаю над проектом, в котором мне нужно, чтобы работающая программа выполняла функцию по требованию.Для этого я использую ptrace.Я знаю, что это возможно, потому что GDB делает это.

Сейчас я использую адаптированную версию кода, найденного на: https://github.com/eklitzke/ptrace-call-userspace Эта программа показывает, как вызвать fprintf в целевой программе.

Программа, с которой я сталкиваюсьпоявляется, когда вызываемая функция использует nanosleep ().Если nanosleep () вызывается в то время как внутри функции, вызываемой трассировщиком, трассировка падает с SIGSEGV, но только после завершения сна.Если функция вызывается нормально самой трассировкой, все работает правильно.

Я пришел к выводу, что проблема связана с тем, как вызывается функция, возможно, что-то связанное со стеком трассировки или ее значениями регистров.Я уже проверил, что при входе в функцию, например, стек выравнивается по 16 байтам.

Код трассировщика присутствует в github выше (разница - вызываемая функция, и я также удалил аргументы)

Код для трассировки - простой фиктивный процесс, который печатает его PID каждую секунду.

Код вызываемой функции:

#include <stdio.h>
#include <time.h>

void hello()
{
    struct timespec tim1;
    tim1.tv_sec = 1;
    tim1.tv_nsec = 0;
    struct timespec tim2;
    nanosleep(&tim1, &tim2);    
    puts("Hello World!!!");
}

В случае сбоя отслеживаемой программы обратная трассировка выглядит следующим образом:

#0  0xfffffffffffffff7 in ?? ()
#1  0x00007effb0e6e6e0 in hello () at hello.c:10
#2  0x00007effb195c005 in ?? ()
#3  0x00007effb1435cc4 in __sleep (seconds=0) at ../sysdeps/unix/sysv/linux/sleep.c:137
#4  0x00000000004005de in main ()

Значения регистра для сброшенногоcore:

rax            0xfffffffffffffff7       -9
rbx            0x7ffc858a0e40   140722548903488
rcx            0x7effb1435e12   139636655742482
rdx            0x7ffc858a0df8   140722548903416
rsi            0x7ffc858a0df8   140722548903416
rdi            0x7ffc858a0e08   140722548903432
rbp            0x7ffc858a0e18   0x7ffc858a0e18
rsp            0x7ffc858a0df0   0x7ffc858a0df0
r8             0xffffffffffffffff       -1
r9             0x0      0
r10            0x7ffc858a0860   140722548901984
r11            0x246    582
r12            0x7ffc858a0ec0   140722548903616
r13            0x7ffc858a1100   140722548904192
r14            0x0      0
r15            0x0      0
rip            0xfffffffffffffff7       0xfffffffffffffff7
eflags         0x10246  [ PF ZF IF RF ]
cs             0x33     51
ss             0x2b     43
ds             0x0      0
es             0x0      0
fs             0x0      0
gs             0x0      0

Вывод трассировщика:

./call_hello -p 17611
their %rip           0x7effb1435e10
allocated memory at  0x7effb195c000
executing jump to mmap region
successfully jumped to mmap area
their lib            0x7effb0e6e000
their func           0x7effb0e6e000
Adding rel32 to new_text[0]Adding func_delta to new_text[1-4]Adding TRAP to new_text[5]inserting code/data into the mmap area at 0x7effb195c000
setting the registers of the remote process
continuing execution
PTRACE_CONT unexpectedly got status Unknown signal 2943

Если я уберу вызов nanosleep, все будет работать как положено - «Hello World !!!»печатается.Как я уже говорил ранее, ошибка сегментации возникает только после запрошенного спящего режима в течение 1 секунды.Я не знаю, как nanosleep заставляет указатель инструкции держать 0xfffffffffffffff7.Любые предложения или идеи о том, что я должен изучить, чтобы решить эту проблему?Заранее спасибо!

Я тестирую это на CentOS Linux версии 7.6.1810.

1 Ответ

0 голосов
/ 19 марта 2019

Проблема заключается в следующем:

Ваша программа call-hello записывает две инструкции

syscall
call %rax

в память, на которую указывает текущее значение регистра% rip (указатель инструкции). Поскольку ваша основная программа имеет (неявный) вызов nanosleep() в своем основном цикле,% rip почти всегда указывает на адрес возврата системного вызова (где-то в libc). В этот момент системный вызов выполняет mmap(), а затем переходит к возвращаемому значению (только что отображенное пространство).

Но позже, в вашей функции hello(), вы снова вызовете nanosleep(). На обратном адресе все еще - введенный код выше! Выполняется некоторый случайный системный вызов (в зависимости от содержимого% rax), который завершается ошибкой с кодом ошибки -9 (EBADFD), который сейчас равен 0xfffffffffffffff7 в% rax. Затем call %rax прыгает прямо здесь, убивая ваш процесс.

Итак, лучшее решение - найти место, где вы можете внедрить и выполнить 4 байта кода, не перезаписывая другой код. В качестве альтернативы, вы можете восстановить исходный код, прежде чем продолжить выполнение hello(), и вставить его снова после завершения hello() (после прерывания), например, так:

// update the mmap area
printf("inserting code/data into the mmap area at %p\n", mmap_memory);
if (poke_text(pid, mmap_memory, new_text, NULL, sizeof(new_text))) {
  goto fail;
}

- if (poke_text(pid, rip, new_word, NULL, sizeof(new_word))) {
+ if (poke_text(pid, rip, old_word, NULL, sizeof(old_word))) {
  goto fail;
}

Однако позже вам придется ненадолго переустановить код системного вызова, чтобы сделать вызов munmap(), например, здесь:

if (ptrace(PTRACE_SETREGS, pid, NULL, &newregs)) {
  perror("PTRACE_SETREGS");
  goto fail;
}

+ if (poke_text(pid, rip, new_word, NULL, sizeof(new_word))) {
+   goto fail;
+ }

new_word[0] = 0xff; // JMP %rax
new_word[1] = 0xe0; // JMP %rax

Теперь все должно работать так, как вы ожидаете.

...