尝试使用ptrace调用用户函数时遇到问题-nanosleep导致崩溃

时间:2019-03-18 15:12:04

标签: c ptrace

我正在一个项目中,我需要使一个正在运行的程序按需执行功能。为此,我正在使用ptrace。我知道这是可能的,因为GDB做到了。

现在,我正在使用https://github.com/eklitzke/ptrace-call-userspace上的代码的改编版本 该程序显示了如何在目标程序中调用fprintf。

当被调用的函数使用nanosleep()时,将出现我要面对的程序。如果在跟踪程序调用的函数内部调用nanosleep(),则跟踪程序将以SIGSEGV崩溃,但仅在睡眠结束后才会崩溃。如果该函数由跟踪本身正常调用,则一切正常。

我得出的结论是,问题与函数的调用方式有关,这可能与Tracee的堆栈或其寄存器值有关。例如,在输入函数时,我已经检查了堆栈是否对齐了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 ()

转储内核的寄存器值:

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 :(得分:0)

问题如下:

您的call-hello程序会编写两条指令

syscall
call %rax

指向%rip寄存器(指令指针)的当前值所指向的内存。由于目标程序在其主循环中有一个(隐式)调用nanosleep(),因此%rip几乎总是指向syscall的返回地址(在libc中的某个地方)。此时,系统调用将执行mmap(),然后跳转到返回值(刚映射的空间)。

但是稍后,在您的hello()函数中,您再次 调用nanosleep()。在寄信人地址的上方,有 still 是上面插入的代码!执行了一些随机的系统调用(取决于%rax的内容),该调用失败,并显示错误代码-9(EBADFD),它现在在%rax中为0xfffffffffffffff7。然后,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;
}

但是,稍后,您必须短暂地重新安装syscall代码以进行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

现在它应该可以按预期工作。