尝试使用 ptrace 调用用户函数时出现问题 - nanosleep 导致崩溃
Problem trying to call user function using ptrace - nanosleep causes crash
我正在做一个项目,我需要让一个 运行 程序按需执行一个函数。为此,我正在使用 ptrace。我知道这是可能的,因为 GDB 做到了。
现在我正在使用在以下位置找到的代码的改编版本:https://github.com/eklitzke/ptrace-call-userspace
此程序显示如何在目标程序中调用 fprintf。
当调用的函数使用nanosleep()时出现我面对的程序。如果在跟踪器调用的函数内调用 nanosleep(),则跟踪器会崩溃并发出 SIGSEGV,但只有在睡眠结束后才会发生。如果该函数被 tracee 本身正常调用,则一切正常。
我断定问题与函数的调用方式有关,可能与被跟踪者的堆栈或寄存器值有关。例如,在进入函数时,我已经检查过堆栈是 16 字节对齐的。
跟踪器的代码出现在上面的 github 中(不同之处在于被调用的函数,我也删除了参数)
tracee 的代码是一个简单的虚拟进程,每秒打印它的 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 版上进行测试。
问题如下:
你的 call-hello 程序写了两条指令
syscall
call %rax
%rip 寄存器(指令指针)的当前值指向的内存。由于您的目标程序在其主循环中有对 nanosleep()
的(隐式)调用,因此 %rip 几乎总是指向系统调用的 return 地址(libc 中的某处)。此时,系统调用执行mmap()
,然后跳转到return值(新映射的space)。
但稍后,在您的 hello()
函数中,您再次 调用 nanosleep()
。在return地址,仍然是上面的注入代码!执行了一些随机系统调用(取决于 %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;
}
不过,稍后您必须重新安装系统调用代码才能使 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
现在它应该可以正常工作了。
我正在做一个项目,我需要让一个 运行 程序按需执行一个函数。为此,我正在使用 ptrace。我知道这是可能的,因为 GDB 做到了。
现在我正在使用在以下位置找到的代码的改编版本:https://github.com/eklitzke/ptrace-call-userspace 此程序显示如何在目标程序中调用 fprintf。
当调用的函数使用nanosleep()时出现我面对的程序。如果在跟踪器调用的函数内调用 nanosleep(),则跟踪器会崩溃并发出 SIGSEGV,但只有在睡眠结束后才会发生。如果该函数被 tracee 本身正常调用,则一切正常。
我断定问题与函数的调用方式有关,可能与被跟踪者的堆栈或寄存器值有关。例如,在进入函数时,我已经检查过堆栈是 16 字节对齐的。
跟踪器的代码出现在上面的 github 中(不同之处在于被调用的函数,我也删除了参数)
tracee 的代码是一个简单的虚拟进程,每秒打印它的 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 版上进行测试。
问题如下:
你的 call-hello 程序写了两条指令
syscall
call %rax
%rip 寄存器(指令指针)的当前值指向的内存。由于您的目标程序在其主循环中有对 nanosleep()
的(隐式)调用,因此 %rip 几乎总是指向系统调用的 return 地址(libc 中的某处)。此时,系统调用执行mmap()
,然后跳转到return值(新映射的space)。
但稍后,在您的 hello()
函数中,您再次 调用 nanosleep()
。在return地址,仍然是上面的注入代码!执行了一些随机系统调用(取决于 %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;
}
不过,稍后您必须重新安装系统调用代码才能使 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
现在它应该可以正常工作了。