如何使用ptrace跳过系统调用?

时间:2016-08-26 12:57:00

标签: linux x86-64 ptrace

我正在尝试用ptrace编写一个跟踪孩子所有系统调用的程序。

现在我有一个禁止孩子的系统调用列表。我可以使用ptrace跟踪所有系统调用,但我只是不知道如何跳过特定的系统调用。

目前,我的跟踪(父)进程在每次进入或退出系统调用(PTRACE_SYSCALL)时都会收到一个信号。但如果孩子试图进入禁止的系统呼叫,那么我不想让孩子跳过该呼叫并转到下一步。此外,当我这样做时,我希望孩子知道有一个权限被拒绝错误,所以我将设置errno = 13,这还够吗?

更新: gdb提供了跳过一行的这个功能.gdb使用什么机制?

如何实现?

更新: 使用ptrace实现此目的的最佳方法是将原始系统调用重定向到其他系统调用,例如 nanosleep()调用。此调用将失败,因为它将收到非法参数。然后你只需要将EAX中的返回码更改为-EACCES,以假装由于Permission denied错误而导致调用失败。

2 个答案:

答案 0 :(得分:4)

我发现两个大学讲座提到无法中止已发起的系统调用,这是ptrace的一个缺点(该联机帮助页提到了一个PTRACE_SYSEMU宏看起来可以做到这一点,但是较新的标题没有它)。从理论上讲,你可以利用ptrace入口和出口停止来抵消你不想要的调用 - 通过交换导致系统调用失败或什么也不做的虚假参数,或者通过注入代码来反击以前的系统调用,但这似乎非常hacky。

在Linux上,您应该能够通过 seccomp 实现目标:

#include <fcntl.h>
#include <seccomp.h>
#include <errno.h>
#include <sys/stat.h>
#include <sys/types.h>
#include <unistd.h>
#include <fcntl.h>
#include <stdlib.h>
#include <stdio.h>

static int set_security(){
    int rc = -1;
    scmp_filter_ctx ctx;
    struct scmp_arg_cmp arg_cmp[] = { SCMP_A0(SCMP_CMP_EQ, 2) };

    ctx = seccomp_init(SCMP_ACT_ERRNO(ENOSYS));
    /*ctx = seccomp_init(SCMP_ACT_ALLOW);*/
    if (ctx == NULL)
        goto out;

    rc = seccomp_rule_add(ctx, SCMP_ACT_ALLOW, SCMP_SYS(exit), 0);
    if (rc < 0)
        goto out;
    rc = seccomp_rule_add(ctx, SCMP_ACT_ALLOW, SCMP_SYS(close), 0);
    if (rc < 0)
        goto out;

    rc = seccomp_rule_add(ctx, SCMP_ACT_ALLOW, SCMP_SYS(write), 1,
            SCMP_CMP(0, SCMP_CMP_EQ, 1));
    if (rc < 0)
        goto out;

    rc = seccomp_rule_add_array(ctx, SCMP_ACT_ALLOW, SCMP_SYS(write), 1,
            arg_cmp);
    if (rc < 0)
        goto out;

    rc = seccomp_load(ctx);
    if (rc < 0)
        goto out;

    /* ... */

out:
    seccomp_release(ctx);
    return -rc;
}
int main(int argc, char *argv[])
{
    int fd;
    const char out_msg[] = "stdout test\n";
    const char err_msg[] = "stderr test\n";
    if(0>set_security())
        return 1;
    if(0>write(1, out_msg, sizeof(out_msg)))
        perror("Write stdout");
    if(0>write(2, err_msg, sizeof(err_msg)))
        perror("Write stderr");

    //This should fail with ENOSYS
    if(0>(fd=open("/dev/zero", O_RDONLY)))
        perror("open");

    exit(0);

}

答案 1 :(得分:3)

如果要禁用系统调用,最简单的方法是使用符号插入,而不是ptrace。

创建一个提供gettimeofday函数的共享库,该函数只设置errno并在不进行系统调用的情况下返回。

使用LD_PRELOAD=./my_library.so ./a.out在libc之前加载它。

这不适用于静态链接libc的二进制文件,或使用内联系统调用而不是libc包装器的二进制文件(例如mov eax, SYS_gettimeofday / syscall)。您可以反汇编二进制文件并查找syscall(x86-64)或int 0x80(i386 ABI)来检查它。

请注意,glibc的gettimeofday和clock_gettime实现实际上从不进行真正的系统调用;相反,他们使用RDTSC和内核导出的VDSO页面来找出如何将时间戳计数器实时缩放。 (因此拦截库函数是你唯一的希望;无论如何,一个strace风格的方法都无法捕获它们。)

BTW,系统调用失败返回负值错误值。例如在x86-64,rax = -EPERM。 glibc系统调用包装器负责检测负值并设置errno全局变量。因此,如果您使用ptrace拦截syscall指令,那就是您需要做的事情。

re:编辑:gdb跳过行

gdb可以通过使用ptrace在其他位置继续执行来跳过一行。但是,只有当你已经停在那里时才有效。因此,要使用它来“跳过”系统调用,您必须在在每个系统调用站点设置要在整个过程中阻止的断点。

这听起来不是一个有用的方法。如果某人正在积极尝试击败它,他们可以只是JIT编译一些直接进行系统调用的代码。您可以阻止进程映射可写和可执行的内存,并在每次检测到进程发生的故障时扫描它以进行系统调用,该进程跳入请求可执行的内存但您的机制只是将其设置为可写。 (因此,在幕后,您可以捕获硬件生成的异常,并将页面从可写入翻转为可执行文件并进行扫描,或者返回可写但不可执行。)

这听起来好像很多内核黑客正确实现,当你需要使用seccomp(参见另一个答案)时,如果你需要一些对变通方法和静态二进制文件有抵抗力的东西。