【问题标题】:Cancel a system call with ptrace()使用 ptrace() 取消系统调用
【发布时间】:2012-08-14 10:53:47
【问题描述】:

出于某种安全目的,我使用 ptrace 来获取系统调用号,如果这是一个危险的调用(例如 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 应该仍然存在。

如何实现此要求?

【问题讨论】:

  • 您是否正在采取任何措施来强制分叉的进程等待以查看您的安全管理器是否会杀死它?据我了解,waitpid() 将使当前进程等到分叉进程终止。因此,您当然无法停止系统调用。当您能够获取系统调用代码时,分叉的进程已经执行了它。
  • @aroth,我确定 waitpid() 有效,我的问题是如何取消 sys_call?

标签: c linux ptrace


【解决方案1】:

嗯,有时候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 命令),此代码将适用于您的@987654328 @命令:

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);
}

编辑:我尝试了这段代码,一切正常(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# 

【讨论】:

  • 我试了一下,sys_call也成功了。
  • @laifjei :这很奇怪!如果你使用 wait(NULL) 而不是 waitpid() 呢?
  • @laifjei :在我的 Ubuntu(3.4 内核)上,两对(等待,waitpid)和(PTRACE_KILL,kill())都可以正常工作。你用的是什么系统内核​​?
  • Linux ubuntu 2.6.38-8-generic #42-Ubuntu SMP Mon Apr 11 03:31:50 UTC 2011 i686 i686 i386 GNU/Linux
  • 我有一个解决办法:修改 sys_call 的参数,使 sys_call 失败。是这样的吗?
【解决方案2】:

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

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

【讨论】:

    【解决方案3】:

    转述https://nullprogram.com/blog/2018/06/23/

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

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

    2) 用无效的东西替换系统调用号。为此,请读取被跟踪进程的寄存器,修改它们,然后将它们写回进程。

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

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

    【讨论】:

      猜你喜欢
      • 2016-01-30
      • 2017-01-03
      • 2012-04-03
      • 2011-06-09
      • 1970-01-01
      • 2020-11-30
      • 2012-10-23
      • 2011-07-26
      • 1970-01-01
      相关资源
      最近更新 更多