仅使用 PTRACE_SINGLESTEP 拦截系统调用
Intercept only syscall with PTRACE_SINGLESTEP
我们有一个学校项目需要重新编码“strace”。
我们只能拦截像写和读这样的系统调用,但我们不能使用PTRACE_SYSCALL
。我正在寻找一种使用 PTRACE_SINGLESTEP
的方法,我已经编写了一种打印系统调用的方法,当我使用 PTRACE_SYSCALL
时它工作正常,但是当我使用 PTRACE_SINGLESTEP
我找不到只打印系统调用的方法。
这是我使用的代码,也许有人可以帮我找出问题所在:
pid_t child;
long orig_eax;
user_regs_struct regs;
child = fork();
if (child == 0) {
ptrace(PTRACE_TRACEME, 0, 0, 0);
execve("/home/architek/a.out", {"/home/architek/a.out", NULL}, envp);
} else {
waitpid(child, &status, 0);
while (WIFSTOPPED(status)) {
orig_eax = ptrace(PTRACE_PEEKUSER, child, 8 * ORIG_RAX, NULL);
ptrace(PTRACE_GETREGS, child, NULL, ®s);
call_printer(®s, child);
ptrace(PTRACE_SINGLESTEP, child, 0, 0);
waitpid(child, &status, 0);
}
}
如果您不能使用 PTRACE_SYSCALL
来停止子进程 before/after 系统调用,那么您将不得不手动检测系统调用何时发生。我怀疑检查 strace
的源代码会有帮助,因为 strace
很可能使用 PTRACE_SYSCALL
,没有理由手动解码指令。
假设您在 x86-64 上工作,您可以按照以下方式进行操作:
- 使用
PTRACE_SINGLESTEP
,保持一次单步执行一条指令。
- 每条指令,用
PTRACE_PEEKTEXT
取指令指针指向的下一条指令
- 通过将字节与 opcode of
syscall
进行比较来检查指令是否为 syscall
指令,这是两个字节:0x0f 0x05
。由于 x86 是小端,这意味着检查 ptrace(PTRACE_PEEKDATA, ...)
的 return 值是否将两个最低有效字节设置为 0x050f
.
注意:如果您使用的是另一种体系结构,或者如果您还想检测 32 位系统调用,则只需检查步骤 3 中的 different/more 值. 在 Linux x86-64 上,有多种方法可以使用不同的操作码发出系统调用。例如,Linux 上的 32 位系统调用是通过 int 0x80
(操作码 0xcd 0x80
)完成的。检查 以获取列表。
这是一个例子:
#include <errno.h>
long opcode;
// ...
waitpid(child, &status, 0);
while (WIFSTOPPED(status)) {
ptrace(PTRACE_GETREGS, child, NULL, ®s);
errno = 0;
opcode = ptrace(PTRACE_PEEKTEXT, child, regs.rip, 0);
if (opcode == -1 && errno != 0) {
perror("ptrace(PTRACE_PEEK_DATA) failed");
exit(1);
}
if (((unsigned long)opcode & 0xffff) == 0x050f) {
// Child about to execute a syscall instruction,
// check the registers to know more...
}
ptrace(PTRACE_SINGLESTEP, child, 0, 0);
waitpid(child, &status, 0);
}
我们有一个学校项目需要重新编码“strace”。
我们只能拦截像写和读这样的系统调用,但我们不能使用PTRACE_SYSCALL
。我正在寻找一种使用 PTRACE_SINGLESTEP
的方法,我已经编写了一种打印系统调用的方法,当我使用 PTRACE_SYSCALL
时它工作正常,但是当我使用 PTRACE_SINGLESTEP
我找不到只打印系统调用的方法。
这是我使用的代码,也许有人可以帮我找出问题所在:
pid_t child;
long orig_eax;
user_regs_struct regs;
child = fork();
if (child == 0) {
ptrace(PTRACE_TRACEME, 0, 0, 0);
execve("/home/architek/a.out", {"/home/architek/a.out", NULL}, envp);
} else {
waitpid(child, &status, 0);
while (WIFSTOPPED(status)) {
orig_eax = ptrace(PTRACE_PEEKUSER, child, 8 * ORIG_RAX, NULL);
ptrace(PTRACE_GETREGS, child, NULL, ®s);
call_printer(®s, child);
ptrace(PTRACE_SINGLESTEP, child, 0, 0);
waitpid(child, &status, 0);
}
}
如果您不能使用 PTRACE_SYSCALL
来停止子进程 before/after 系统调用,那么您将不得不手动检测系统调用何时发生。我怀疑检查 strace
的源代码会有帮助,因为 strace
很可能使用 PTRACE_SYSCALL
,没有理由手动解码指令。
假设您在 x86-64 上工作,您可以按照以下方式进行操作:
- 使用
PTRACE_SINGLESTEP
,保持一次单步执行一条指令。 - 每条指令,用
PTRACE_PEEKTEXT
取指令指针指向的下一条指令 - 通过将字节与 opcode of
syscall
进行比较来检查指令是否为syscall
指令,这是两个字节:0x0f 0x05
。由于 x86 是小端,这意味着检查ptrace(PTRACE_PEEKDATA, ...)
的 return 值是否将两个最低有效字节设置为0x050f
.
注意:如果您使用的是另一种体系结构,或者如果您还想检测 32 位系统调用,则只需检查步骤 3 中的 different/more 值. 在 Linux x86-64 上,有多种方法可以使用不同的操作码发出系统调用。例如,Linux 上的 32 位系统调用是通过 int 0x80
(操作码 0xcd 0x80
)完成的。检查
这是一个例子:
#include <errno.h>
long opcode;
// ...
waitpid(child, &status, 0);
while (WIFSTOPPED(status)) {
ptrace(PTRACE_GETREGS, child, NULL, ®s);
errno = 0;
opcode = ptrace(PTRACE_PEEKTEXT, child, regs.rip, 0);
if (opcode == -1 && errno != 0) {
perror("ptrace(PTRACE_PEEK_DATA) failed");
exit(1);
}
if (((unsigned long)opcode & 0xffff) == 0x050f) {
// Child about to execute a syscall instruction,
// check the registers to know more...
}
ptrace(PTRACE_SINGLESTEP, child, 0, 0);
waitpid(child, &status, 0);
}