使用ptrace()取消系统调用

10

出于某些安全目的,我使用ptrace来获取系统调用号,如果它是一个危险的调用(比如unlink的调用号10),我想取消这个系统调用。

这里是测试程序 del.c 的源代码。使用 gcc -o del del.c 进行编译。

#include <stdio.h>
#include <stdlib.h>
int main()
{
    remove("/root/abc.out");
    return 0;
}

这是安全管理源代码test.c。使用gcc -o test test.c进行编译。

#include <signal.h>
#include <syscall.h>
#include <sys/ptrace.h>
#include <sys/types.h>
#include <sys/wait.h>
#include <unistd.h>
#include <errno.h>
#include <sys/user.h>
#include <sys/reg.h>
#include <sys/syscall.h>

int main()
{
    int i;
    pid_t child;
    int status;
    long orig_eax;
    child = fork();
    if(child == 0) {
        ptrace(PTRACE_TRACEME, 0, NULL, NULL);
        execl("/root/del", "del",  NULL);
    }
    else {
        i = 0;
        while(1){
            wait(&status);
            if (WIFEXITED(status) || WIFSIGNALED(status) )break;

            orig_eax = ptrace(PTRACE_PEEKUSER,
                          child, 4 * ORIG_EAX,
                          NULL);
            if (orig_eax == 10){
                fprintf(stderr, "Got it\n");
                kill(child, SIGKILL);
            }
            printf("%d time,"
               "system call %ld\n", i++, orig_eax);
            ptrace(PTRACE_SYSCALL, child, NULL, NULL);
        }
    }
    return 0;
}

创建abc.out文件,然后运行测试程序:

cd /root
touch abc.out
./test

文件/root/abc.out应该仍然存在。

我如何实现这个要求?


1
你有在采取任何措施强制分叉进程等待以查看你的安全管理器是否将其终止吗?据我所知,waitpid() 将使当前进程等待直到分叉进程终止。因此,您将无法停止系统调用。当您能够获取系统调用代码时,分叉进程已经执行了它。 - aroth
@aroth,我确信 waitpid() 能够工作,我的问题是如何取消这个系统调用? - laifjei
3个回答

8

看起来有时候PTRACE_KILL不能很好地工作,您可以使用kill代替:

if (orig_eax == 10)
{
    kill(pid, SIGKILL);
}

编辑:我在我的电脑上进行了测试(Ubuntu内核3.4),使用这个程序一切正常:

#include <sys/ptrace.h>
#include <sys/types.h>
#include <sys/wait.h>
#include <unistd.h>
#include <sys/reg.h>
#include <stdio.h>

int main(int argc, char **argv)
{   
    pid_t child;
    long orig_eax;
    int status;

    child = fork();
    if(child == 0) 
    {
        ptrace(PTRACE_TRACEME, 0, NULL, NULL);
        execl("/bin/ls", "ls", NULL);
    }
    else 
    {
        /* Both wait and waitpid works */
        //wait(NULL);
        waitpid(child, &status, 0);
        orig_eax = ptrace(PTRACE_PEEKUSER, child, 4 * ORIG_EAX, NULL);
        /* Tracking execve syscall */
        if (orig_eax == 11)
        {
            /* Both PTRACE_KILL and kill() works on my 3.4.4 Kernel */
            fprintf(stdout, "GOT IT\n");
            //ptrace(PTRACE_KILL, child, NULL, NULL);
            kill(child, SIGKILL);
        }
    }

    return 0;
}

更新:问题在于您使用了10来跟踪系统调用,而不是11(因为您执行了execve命令),这段代码将适用于您的rm命令:

if (orig_eax == 11)
{
    /* Both PTRACE_KILL and kill() works on my 3.4.4 Kernel */
    fprintf(stdout, "INSIDE THE TRAP, FILE WILL NOT BE REMOVED\n");
    ptrace(PTRACE_KILL, child, NULL, NULL);
    //kill(child, SIGKILL);
}

编辑: 我尝试了这段代码,一切都正常(在执行CALL_REMOVE后,文件abc.out仍然存在)。

/*
 * REMOVE.c
 * gcc -Wall REMOVE.c -o REMOVE
 */

#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>

int main(int argc, char **argv)
{
        /* Both calls work */
        //remove("/root/abc.out");
        unlink("/root/abc.out");

        return 0;
}

/*
 * CALL_REMOVE.c
 * gcc -Wall CALL_REMOVE.c -o CALL_REMOVE
 */

#include <signal.h>
#include <syscall.h>
#include <sys/ptrace.h>
#include <sys/types.h>
#include <sys/wait.h>
#include <unistd.h>
#include <errno.h>
#include <sys/user.h>
#include <sys/reg.h>
#include <sys/syscall.h>
#include <stdio.h>
#include <string.h>

int main(int argc, char **argv)
{
        int i;
        pid_t child;
        int status;
        long orig_eax;
        int kill_ret = 0;

        child = fork();

        if(child == 0)
        {
                ptrace(PTRACE_TRACEME, 0, NULL, NULL);
                execl("/root/REMOVE", "REMOVE",  NULL);
        }
        else
        {
                i = 0;
                while(1)
                {
                        wait(&status);
                        if (WIFEXITED(status) || WIFSIGNALED(status) )
                                break;

                        orig_eax = ptrace(PTRACE_PEEKUSER, child, 4 * ORIG_EAX, NULL);
                        if (orig_eax == 10)
                        {
                                fprintf(stderr, "Got it\n");
                                kill_ret = kill(child, SIGKILL);
                                if (kill_ret == -1)
                                {
                                    fprintf(stderr, "Failed to kill ---> %s\n", strerror(errno));
                                }
                        }
                        printf("%d time, system call %ld\n", i++, orig_eax);
                        ptrace(PTRACE_SYSCALL, child, NULL, NULL);
                }
        }

        return 0;
}

我们得到了这个输出:
root@UnixServer:/root# ll
total 28K
-rw-r--r-- 1 root root    6 2012-08-18 19:37 abc.out
-rw-r--r-- 1 root root 1023 2012-08-18 19:39 CALL_REMOVE.c
-rw-r--r-- 1 root root  213 2012-08-18 19:39 REMOVE.c
-rwxr-xr-x 1 root root 7,3K 2012-08-18 19:39 CALL_REMOVE
-rwxr-xr-x 1 root root 7,0K 2012-08-18 19:39 REMOVE
root@UnixServer:/root# ./CALL_REMOVE 
0 time, system call 11
1 time, system call 45
2 time, system call 45
3 time, system call 33
4 time, system call 33
5 time, system call 192
6 time, system call 192
7 time, system call 33
8 time, system call 33
9 time, system call 5
10 time, system call 5
11 time, system call 197
12 time, system call 197
13 time, system call 192
14 time, system call 192
15 time, system call 6
16 time, system call 6
17 time, system call 33
18 time, system call 33
19 time, system call 5
20 time, system call 5
21 time, system call 3
22 time, system call 3
23 time, system call 197
24 time, system call 197
25 time, system call 192
26 time, system call 192
27 time, system call 192
28 time, system call 192
29 time, system call 192
30 time, system call 192
31 time, system call 6
32 time, system call 6
33 time, system call 192
34 time, system call 192
35 time, system call 243
36 time, system call 243
37 time, system call 125
38 time, system call 125
39 time, system call 125
40 time, system call 125
41 time, system call 125
42 time, system call 125
43 time, system call 91
44 time, system call 91
Got it
45 time, system call 10
root@UnixServer:/root# ll
total 28K
-rw-r--r-- 1 root root    6 2012-08-18 19:37 abc.out
-rw-r--r-- 1 root root 1023 2012-08-18 19:39 CALL_REMOVE.c
-rw-r--r-- 1 root root  213 2012-08-18 19:39 REMOVE.c
-rwxr-xr-x 1 root root 7,3K 2012-08-18 19:39 CALL_REMOVE
-rwxr-xr-x 1 root root 7,0K 2012-08-18 19:39 REMOVE
root@UnixServer:/root# 

@laifjei:这很奇怪!如果你使用wait(NULL)而不是waitpid()会怎样呢? - TOC
@laifjei:在我的Ubuntu(3.4内核)上,无论是(wait, waitpid)还是(PTRACE_KILL, kill())都很好用。你使用的是什么系统内核? - TOC
Linux ubuntu 2.6.38-8-generic #42-Ubuntu SMP Mon Apr 11 03:31:50 UTC 2011 i686 i686 i386 GNU/Linux - laifjei
我有一个解决方案:修改sys_call的参数,使sys_call失败。这样行得通吗? - laifjei
嗯,我认为在你的情况下这个问题并不是很有趣,因为你正在尝试捕获特定的系统调用。奇怪的是kill命令不起作用! - TOC
显示剩余6条评论

1

有很多低级/巧妙(也容易出错)的方法来解决这个问题,但现代Linux内核(3.x,您的发行版可能需要一个后向端口补丁)支持一种称为seccomp的东西,它允许您限制进程可以进行的系统调用。沙盒使用此功能,包括Chromium。

您可以在StackOverflow 这里看到有关此问题的讨论,或者只需搜索文档和示例实现。有很多信息可供参考。


0

改写 https://nullprogram.com/blog/2018/06/23/

一旦系统调用被启动,就没有办法取消它。但是,您可以修改系统调用的参数或其返回值。因此,您可以执行以下操作:

1)等待被跟踪进程调用系统调用。

2)将系统调用号替换为无效内容。要做到这一点,需要读取被跟踪进程的寄存器,对其进行修改,并将其写回进程。

3)继续进程;系统调用将被执行,内核将返回错误,因为进程调用了未实现的系统调用。

4)当ptrace跟踪系统调用返回时,您可以选择将内核的错误代码替换为其他内容(例如,将内核的“未实现的系统调用”替换为“无权限错误”)。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接