使用ptrace()取消系统调用

时间:2012-08-18 01:43:27

标签: c linux ptrace

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

以下是测试程序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应该仍然存在。

如何实施此要求?

3 个答案:

答案 0 :(得分:7)

好像有时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);
}

编辑:我尝试使用此代码,所有wroks都很好(文件abc.out在执行CALL_REMOVE后仍然存在)

/*
 * 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# 

答案 1 :(得分:1)

有很多低级/聪明(并且容易出错)的方法来解决这个问题,但是现代Linux内核(3.x,你的发行版可能需要一个后向移植补丁)支持一些名为seccomp的东西,它允许你限制系统调用一个进程就可以了。沙盒使用此功能,包括Chromium。

您可以在StackOverflow here或Google上查看有关此问题的讨论,以获取文档和示例实现。那里有很多信息。

答案 2 :(得分:0)

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

启动系统调用后,无法取消它。但是,您可以修改系统调用的参数,也可以修改返回的返回值。因此,您可以执行以下操作:

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

2)用无效的号码替换系统电话号码。为此,请读取被跟踪的进程的寄存器,对其进行修改,然后将其写回到进程中。

3)继续该过程;系统调用将被执行,并且由于进程调用了未实现的系统调用,因此内核将返回错误。

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