Ptrace 为 execve 捕获许多陷阱
Ptrace catching many traps for execve
我正在使用 ptrace 拦截系统调用。一切似乎都工作正常,除了我拦截了 16 个对 execve 的调用(8 个用于预系统调用,8 个 post 系统调用)。
我看过没有它的工作示例,但我正在尝试使用标志 PTRACE_O_TRACESYSGOOD
。
其他 answers to ptrace problems 表示我应该只看到一个 pre/post + 一个信号,但他们没有使用 PTRACE_O_TRACESYSGOOD
.
我的输出如下:
Intercepted rt_sigprocmask[14]
Syscall returned with value 0
Intercepted execve[59]
Syscall returned with value -2
Intercepted execve[59]
Syscall returned with value -2
Intercepted execve[59]
Syscall returned with value -2
Intercepted execve[59]
Syscall returned with value -2
Intercepted execve[59]
Syscall returned with value -2
Intercepted execve[59]
Syscall returned with value -2
Intercepted execve[59]
Syscall returned with value -2
Intercepted execve[59]
Syscall returned with value 0
Tracer: Received signal: 5
Intercepted brk[12]
...
其余输出与 strace
输出匹配。
每个"Intercepted"和"Syscall returned"对应一个waitid()
调用。重现此内容的极简示例代码:
#include <sys/types.h>
#include <stdint.h>
#include <sys/types.h>
#include <sys/user.h>
#include <sys/vfs.h>
#include <sys/ptrace.h>
#include <sys/reg.h> /* For constants ORIG_EAX, etc */
#include <string.h>
#include <sys/wait.h>
#include <sys/syscall.h> /* For SYS_write, etc */
#include <unistd.h>
#include <stdio.h>
int main(){
pid_t pid = fork();
// Child.
if(pid == 0){
ptrace(PTRACE_TRACEME, 0, NULL, NULL);
// Wait for parent to be ready.
raise(SIGSTOP);
execlp("pwd", "pwd", NULL);
return 0;
}
// Tracer.
else{
struct user_regs_struct regs;
bool isPre = true;
int status;
// Wait for child to stop itself.
waitpid(pid, &status, 0);
ptrace(PTRACE_SETOPTIONS, pid, NULL, PTRACE_O_TRACESYSGOOD);
while(true){
ptrace(PTRACE_SYSCALL, pid, 0, 0);
pid = waitpid(pid, &status, 0);
// Check if tracee has exited.
if (WIFEXITED(status)){
return 0;
}
// This is a stop caused by a system call exit-pre/exit-post.
if(WIFSTOPPED(status) && WSTOPSIG(status) == (SIGTRAP |0x80) ){
ptrace(PTRACE_GETREGS, pid, NULL, ®s);
if(isPre){
printf("Intercepted syscall: %llu\n", regs.orig_rax);
isPre = ! isPre;
}else{
printf("Done with system call!\n");
isPre = ! isPre;
}
}else{
printf("Tracer: Received signal: %d\n", WSTOPSIG(status));
}
}
}
return 0;
}
恐怕我误解了 execve
或 PTRACE_O_TRACESYSGOOD
。
我 运行 在 Lubuntu 16.04 上使用内核版本 4.10.0-37-generic。
编辑:修复了系统调用的 return 值。
没什么问题。对 execlp
的一次调用通常会导致对 execve
的多次调用,每次调用(除了最后一次)都会返回 ENOENT
作为错误代码。
尽管 execlp
和 execvp
通常记录在 Unix 和 Linux 手册的第 2 节(系统调用)中,但它们是作为用户空间函数实现的。他们查看 $PATH
并在每个 $PATH
组件和可执行文件名称的串联上调用 execve
,直到一个执行成功或全部失败。
以下是 musl 的一些来源,说明了正在发生的事情:
if (strchr(file, '/'))
return execve(file, argv, envp);
if (!path) path = "/usr/local/bin:/bin:/usr/bin";
...
for(p=path; ; p=z) {
char b[l+k+1];
z = strchr(p, ':');
if (!z) z = p+strlen(p);
if (z-p >= l) {
if (!*z++) break;
continue;
}
memcpy(b, p, z-p);
b[z-p] = '/';
memcpy(b+(z-p)+(z>p), file, k+1);
execve(b, argv, envp);
if (errno == EACCES) seen_eacces = 1;
else if (errno != ENOENT) return -1;
if (!*z++) break;
}
if (seen_eacces) errno = EACCES;
return -1;
我正在使用 ptrace 拦截系统调用。一切似乎都工作正常,除了我拦截了 16 个对 execve 的调用(8 个用于预系统调用,8 个 post 系统调用)。
我看过没有它的工作示例,但我正在尝试使用标志 PTRACE_O_TRACESYSGOOD
。
其他 answers to ptrace problems 表示我应该只看到一个 pre/post + 一个信号,但他们没有使用 PTRACE_O_TRACESYSGOOD
.
我的输出如下:
Intercepted rt_sigprocmask[14]
Syscall returned with value 0
Intercepted execve[59]
Syscall returned with value -2
Intercepted execve[59]
Syscall returned with value -2
Intercepted execve[59]
Syscall returned with value -2
Intercepted execve[59]
Syscall returned with value -2
Intercepted execve[59]
Syscall returned with value -2
Intercepted execve[59]
Syscall returned with value -2
Intercepted execve[59]
Syscall returned with value -2
Intercepted execve[59]
Syscall returned with value 0
Tracer: Received signal: 5
Intercepted brk[12]
...
其余输出与 strace
输出匹配。
每个"Intercepted"和"Syscall returned"对应一个waitid()
调用。重现此内容的极简示例代码:
#include <sys/types.h>
#include <stdint.h>
#include <sys/types.h>
#include <sys/user.h>
#include <sys/vfs.h>
#include <sys/ptrace.h>
#include <sys/reg.h> /* For constants ORIG_EAX, etc */
#include <string.h>
#include <sys/wait.h>
#include <sys/syscall.h> /* For SYS_write, etc */
#include <unistd.h>
#include <stdio.h>
int main(){
pid_t pid = fork();
// Child.
if(pid == 0){
ptrace(PTRACE_TRACEME, 0, NULL, NULL);
// Wait for parent to be ready.
raise(SIGSTOP);
execlp("pwd", "pwd", NULL);
return 0;
}
// Tracer.
else{
struct user_regs_struct regs;
bool isPre = true;
int status;
// Wait for child to stop itself.
waitpid(pid, &status, 0);
ptrace(PTRACE_SETOPTIONS, pid, NULL, PTRACE_O_TRACESYSGOOD);
while(true){
ptrace(PTRACE_SYSCALL, pid, 0, 0);
pid = waitpid(pid, &status, 0);
// Check if tracee has exited.
if (WIFEXITED(status)){
return 0;
}
// This is a stop caused by a system call exit-pre/exit-post.
if(WIFSTOPPED(status) && WSTOPSIG(status) == (SIGTRAP |0x80) ){
ptrace(PTRACE_GETREGS, pid, NULL, ®s);
if(isPre){
printf("Intercepted syscall: %llu\n", regs.orig_rax);
isPre = ! isPre;
}else{
printf("Done with system call!\n");
isPre = ! isPre;
}
}else{
printf("Tracer: Received signal: %d\n", WSTOPSIG(status));
}
}
}
return 0;
}
恐怕我误解了 execve
或 PTRACE_O_TRACESYSGOOD
。
我 运行 在 Lubuntu 16.04 上使用内核版本 4.10.0-37-generic。
编辑:修复了系统调用的 return 值。
没什么问题。对 execlp
的一次调用通常会导致对 execve
的多次调用,每次调用(除了最后一次)都会返回 ENOENT
作为错误代码。
尽管 execlp
和 execvp
通常记录在 Unix 和 Linux 手册的第 2 节(系统调用)中,但它们是作为用户空间函数实现的。他们查看 $PATH
并在每个 $PATH
组件和可执行文件名称的串联上调用 execve
,直到一个执行成功或全部失败。
以下是 musl 的一些来源,说明了正在发生的事情:
if (strchr(file, '/'))
return execve(file, argv, envp);
if (!path) path = "/usr/local/bin:/bin:/usr/bin";
...
for(p=path; ; p=z) {
char b[l+k+1];
z = strchr(p, ':');
if (!z) z = p+strlen(p);
if (z-p >= l) {
if (!*z++) break;
continue;
}
memcpy(b, p, z-p);
b[z-p] = '/';
memcpy(b+(z-p)+(z>p), file, k+1);
execve(b, argv, envp);
if (errno == EACCES) seen_eacces = 1;
else if (errno != ENOENT) return -1;
if (!*z++) break;
}
if (seen_eacces) errno = EACCES;
return -1;