找不到如何正确使用 ptrace()
Can't find how to use ptrace() properly
目前,对于一个项目,我需要使用 ptrace() 编写某种调试器。最后,它应该显示要跟踪的程序中的每个 function/syscall entered/exited。
现在,我很困惑。我制作了一个小程序,它应该尝试跟踪给定的程序,并根据操作码(通过寄存器检索)找到调用或系统调用时打印。这是:
#include <sys/ptrace.h>
#include <sys/types.h>
#include <sys/wait.h>
#include <unistd.h>
#include <sys/reg.h>
#include <sys/syscall.h>
#include <sys/user.h>
#include <stdio.h>
#include <unistd.h>
#include <stdlib.h>
int main()
{
pid_t child;
const int long_size = sizeof(long);
child = fork();
if(child == 0) {
ptrace(PTRACE_TRACEME, 0, NULL, NULL);
execl("./bin", "bin", NULL);
} else {
int status;
unsigned ins;
struct user_regs_struct regs;
unsigned char prim, sec;
while (1) {
wait(&status);
if (WIFEXITED(status))
break;
ptrace(PTRACE_GETREGS, child, NULL, ®s);
ins = ptrace(PTRACE_PEEKTEXT, child, regs.rip, NULL);
prim = (unsigned)0xFF & ins;
sec = ((unsigned)0xFF00 & ins) >> 8;
if (prim == 0xE8 && sec == 0xCD)
printf("call found!\n");
if (prim == 0x80 && sec == 0xCD)
printf("syscall found!\n");
ptrace(PTRACE_SINGLESTEP, child, NULL, NULL);
}
}
return 0;
}
这里是 "bin" 二进制文件的代码:
#include <unistd.h>
void toto()
{
write(1, "hello\n", 6);
}
int main()
{
toto();
toto();
return (1);
}
当我查看我的迷你调试器的输出时,它似乎只找到一个系统调用和一个调用...我试着弄乱寄存器和偏移量,但我在互联网上找到的每个教程似乎都是对于 32 位机器,这在我的情况下不起作用:/
有人可以给我一个小提示来帮助我继续吗?
谢谢,祝你有愉快的一天!
你快到了,但你的掩码(正如最初怀疑的那样)没有捕捉到 callq
操作码。使用 PTRACE_SINGLESTEP
也会捕获 大量 的额外 callq
代码,我不确定你是否意识到这一点。
我静态编译了你的bin
程序,所以你可以获得main
和toto
的一致地址
gcc bin.c -o bin -g -Wall -static
在 64 位机器上。
然后在主脚本中,我更改了 ins
变量的屏蔽操作:
#include <sys/ptrace.h>
#include <sys/types.h>
#include <sys/wait.h>
#include <unistd.h>
#include <sys/reg.h>
#include <sys/syscall.h>
#include <sys/user.h>
#include <stdio.h>
#include <unistd.h>
#include <stdlib.h>
int main()
{
pid_t child;
child = fork();
if(child == 0) {
ptrace(PTRACE_TRACEME, 0, NULL, NULL);
execl("./bin", "bin", NULL);
} else {
int status;
unsigned ins;
struct user_regs_struct regs;
unsigned char prim;
while (1) {
ptrace(PTRACE_SINGLESTEP, child, NULL, NULL);
wait(&status);
if (WIFEXITED(status))
break;
ptrace(PTRACE_GETREGS, child, NULL, ®s);
ins = ptrace(PTRACE_PEEKTEXT, child, regs.rip, NULL);
prim = (unsigned)0xFF & ins;
// Here in prim just mask for the first byte
if (prim == 0xe8) {
// Print the addresses to check out too
printf("RIP: %#x --> %#x\n", regs.rip, ins);
printf("call found!\n");
}
}
}
return 0;
}
你只需要掩码检查第一个字节是否匹配call
操作码。我添加了一些带有指令指针地址的额外打印语句,因此您可以检查静态源代码以确保您正在捕捉正确的调用。
您可以从主程序重定向您的输出(我称之为 stepper
):
./stepper > calls.txt
如果您随后执行 objdump -S bin > dump.txt
,您可以看到来自 toto
和 main
的地址,其中 callq
指令也将在 calls.txt
中文件
您最终会从 crt 函数、链接器和库调用中进行所有额外调用。
目前,对于一个项目,我需要使用 ptrace() 编写某种调试器。最后,它应该显示要跟踪的程序中的每个 function/syscall entered/exited。
现在,我很困惑。我制作了一个小程序,它应该尝试跟踪给定的程序,并根据操作码(通过寄存器检索)找到调用或系统调用时打印。这是:
#include <sys/ptrace.h>
#include <sys/types.h>
#include <sys/wait.h>
#include <unistd.h>
#include <sys/reg.h>
#include <sys/syscall.h>
#include <sys/user.h>
#include <stdio.h>
#include <unistd.h>
#include <stdlib.h>
int main()
{
pid_t child;
const int long_size = sizeof(long);
child = fork();
if(child == 0) {
ptrace(PTRACE_TRACEME, 0, NULL, NULL);
execl("./bin", "bin", NULL);
} else {
int status;
unsigned ins;
struct user_regs_struct regs;
unsigned char prim, sec;
while (1) {
wait(&status);
if (WIFEXITED(status))
break;
ptrace(PTRACE_GETREGS, child, NULL, ®s);
ins = ptrace(PTRACE_PEEKTEXT, child, regs.rip, NULL);
prim = (unsigned)0xFF & ins;
sec = ((unsigned)0xFF00 & ins) >> 8;
if (prim == 0xE8 && sec == 0xCD)
printf("call found!\n");
if (prim == 0x80 && sec == 0xCD)
printf("syscall found!\n");
ptrace(PTRACE_SINGLESTEP, child, NULL, NULL);
}
}
return 0;
}
这里是 "bin" 二进制文件的代码:
#include <unistd.h>
void toto()
{
write(1, "hello\n", 6);
}
int main()
{
toto();
toto();
return (1);
}
当我查看我的迷你调试器的输出时,它似乎只找到一个系统调用和一个调用...我试着弄乱寄存器和偏移量,但我在互联网上找到的每个教程似乎都是对于 32 位机器,这在我的情况下不起作用:/
有人可以给我一个小提示来帮助我继续吗?
谢谢,祝你有愉快的一天!
你快到了,但你的掩码(正如最初怀疑的那样)没有捕捉到 callq
操作码。使用 PTRACE_SINGLESTEP
也会捕获 大量 的额外 callq
代码,我不确定你是否意识到这一点。
我静态编译了你的bin
程序,所以你可以获得main
和toto
gcc bin.c -o bin -g -Wall -static
在 64 位机器上。
然后在主脚本中,我更改了 ins
变量的屏蔽操作:
#include <sys/ptrace.h>
#include <sys/types.h>
#include <sys/wait.h>
#include <unistd.h>
#include <sys/reg.h>
#include <sys/syscall.h>
#include <sys/user.h>
#include <stdio.h>
#include <unistd.h>
#include <stdlib.h>
int main()
{
pid_t child;
child = fork();
if(child == 0) {
ptrace(PTRACE_TRACEME, 0, NULL, NULL);
execl("./bin", "bin", NULL);
} else {
int status;
unsigned ins;
struct user_regs_struct regs;
unsigned char prim;
while (1) {
ptrace(PTRACE_SINGLESTEP, child, NULL, NULL);
wait(&status);
if (WIFEXITED(status))
break;
ptrace(PTRACE_GETREGS, child, NULL, ®s);
ins = ptrace(PTRACE_PEEKTEXT, child, regs.rip, NULL);
prim = (unsigned)0xFF & ins;
// Here in prim just mask for the first byte
if (prim == 0xe8) {
// Print the addresses to check out too
printf("RIP: %#x --> %#x\n", regs.rip, ins);
printf("call found!\n");
}
}
}
return 0;
}
你只需要掩码检查第一个字节是否匹配call
操作码。我添加了一些带有指令指针地址的额外打印语句,因此您可以检查静态源代码以确保您正在捕捉正确的调用。
您可以从主程序重定向您的输出(我称之为 stepper
):
./stepper > calls.txt
如果您随后执行 objdump -S bin > dump.txt
,您可以看到来自 toto
和 main
的地址,其中 callq
指令也将在 calls.txt
中文件
您最终会从 crt 函数、链接器和库调用中进行所有额外调用。