我想将自己附加到一个进程并拦截该进程中的所有printf
个调用。
的main.c
int main()
{
int i;
for(i = 0; i < 10; i++)
{
printf("HelloWorld\n");
sleep(5);
}
return 0;
}
然后要附加我有这个代码,我想做一个无限循环或直到main.c完成 - 无限循环将起作用这只是带有ptrace的Hello World进行测试,没什么特别的。
#include <sys/ptrace.h>
#include <sys/types.h>
#include <sys/wait.h>
#include <unistd.h>
#include <sys/user.h> // For user_regs_struct
int main(int argc, char *argv[])
{
struct user_regs_struct regs;
pid_t traced_process = atoi(argv[1]);
long t = ptrace(PTRACE_ATTACH, traced_process, NULL, NULL);
wait(NULL);
ptrace(PTRACE_GETREGS, traced_process, NULL, ®s);
long ins = ptrace(PTRACE_PEEKTEXT, traced_process, regs.eip, NULL);
printf("EIP: %lx Instruction executed: %lx\n", regs.eip, ins);
char *c = &ins;
printf("%c\n",c);
ptrace(PTRACE_DETACH, traced_process, NULL, NULL);
return 0;
}
我尝试在附加后放置while(1)
但实际上只是循环在main.c中执行的第一个printf。
我真的很挣扎于此,我遇到的每一个例子都是另一个带有大量代码的复制粘贴,这些代码甚至与我正在尝试做的事情无关。我确实知道printf是内核中的write(),所以这就是我应该寻找的。 p>
所以我想再次引用printf尝试打印到另一个终端屏幕的字符串。我该怎么做呢?
答案 0 :(得分:1)
您需要重现一半调试器才能执行此操作。
简而言之:
在正在运行的过程中解析printf的地址。
在printf设置断点。
找出您所在架构的ABI,并从堆栈或相关寄存器中读取printf的第一个参数。
让我们更详细地介绍这些步骤。
第1步 - 在正在运行的过程中查找printf的地址。
对于第一步,您的跟踪程序需要知道正在跟踪的二进制文件以打开它并解析该二进制文件的符号。您可能希望为此阅读ELF规范或阅读一些类似的代码。
首次尝试时,我强烈建议您静态链接您的跟踪程序。因为您需要做的下一件事是弄清楚动态链接器专门为调试器提供的钩子。这通常是操作系统没有记录的。您可能需要在您正在使用的操作系统上读取调试器的代码并执行相同的操作以挂钩到动态链接器以确定哪些库被加载到哪里并使用该信息从中提取符号那些库并找到printf。
通过让您的跟踪程序打印printf的地址可以绕过第一步。这可能更有意义,因为所有这些步骤合在一起会让所有人同时工作有点痛苦。我建议将第1步留到最后,因为它是最难做到的,而第2步和第3步将教你一些从跟踪过程中实现读符号所需的工具。
第2步 - 设置断点。
现在您拥有printf的地址,让我们进入第二步。断点。如果您很幸运,您的操作系统会提供ptrace操作,以便在正在运行的进程中插入断点。只是使用它,你就是金色的。阅读ptrace文档,了解如何向您发出断点信号(通常只是wait
)。
如果您的ptrace实现没有断点功能,请找出您的体系结构的断点指令,使用ptrace提供的任何机制来覆盖跟踪进程中printf的开头。然后当你收到断点时,用断点指令写回你覆盖的代码的原始内容,用断点指令覆盖下一条指令(注意像x86这样的可变长度指令体系结构,你可能需要有一个好的这里的指令解析器),根据需要调整指令指针寄存器(有些架构不会重启断点指令,所以你必须自己动手),重启程序直到它命中下一条指令,恢复你覆盖的指令的先前内容时间,(在可变长度指令体系结构上,您可能需要重复,直到您可以将断点指令放入printf函数的第一条指令)并将断点再次放回第一条指令。大多数现代系统在ptrace中都具有断点功能,所以祈祷你不需要这样做,这对屁股来说真的很痛苦。
第3步 - 阅读字符串。
这很简单。只需找到ABI文档,或读取其他人编写的代码,或者只是将一个简单的函数编译成汇编程序,然后查看汇编程序如何访问函数的第一个参数。使用该信息将printf的第一个参数拉出来。您需要在ptrace中使用GETREGS(或等效的)功能来获取寄存器,然后使用PEEKDATA(或等效的)来读取字符串的数据。