Search code examples
clinuxptrace

Cancel a system call with ptrace()


For some security purpose, I use ptrace to get the syscall number, and if it's a dangerous call (like 10 for unlink), I want to cancel this syscall.

Here's the source code for the test program del.c. Compile with gcc -o del del.c.

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

Here's the security manager source code test.c. Compile with 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;
}

Create the abc.out file, then run the test program:

cd /root
touch abc.out
./test

The file /root/abc.out should still exist.

How do I implement this requirement?


Solution

  • Well it seems that sometimes PTRACE_KILL does not work very well, you can use kill instead:

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

    EDIT : I test on my machine (Ubuntu kernel 3.4) with this program and all is ok:

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

    UPDATE : The problem is that you are using 10 for tracking system call instead of 11(because you are executing execve command), this code will work with your rm command:

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

    EDIT : I try this code and all wroks fine (the file abc.out still exist after the execution of 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;
    }
    

    We got this output:

    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#