How to use ptrace(2) to change behaviour of syscal

2019-04-02 04:47发布

Are there any guides or examples (especially ARM ones) or libraries of using ptrace to affect execution of other process? For example, to make it believe that some data is appeared on file descriptor (i.e. release select/poll with some result and "answer" the following read syscall before the kernel). Expecting something involving PTRACE_SYSEMU.

Can it be done in portable way? I want something like libc-overriding LD_PRELOAD trick, but which can be attached at runtime.

Can it be done with some gdb commands?

Ideal variant would be if there is some library where I can easily and portably hook into syscalls and edit them before of after the actual call is made (or emulate them), like when doing it using LD_PRELOAD.

@link Any good guides on using PTRACE_SYSEMU?

1条回答
兄弟一词,经得起流年.
2楼-- · 2019-04-02 05:30

You can use the PTRACE_SYSCALL request: it restarts the child process (just like PTRACE_CONT) but arranges for it to stop at the next entry to or exit from a system call. For example (assume a kernel built for x86):

#include <sys/ptrace.h>
#include <signal.h>
#include <linux/user.h>
#include <sys/types.h>
#include <sys/wait.h>

int main(int argc, char **argv)
{
    int status = 0, pid, r;
    struct user_regs_struct uregs;

    if ((pid = fork()) == 0) {
        printf("pid = %d, ppid = %d\n", getpid(), getppid());
        ptrace(PTRACE_TRACEME, 0, 0, 0);
        kill(getpid(), SIGINT);
        r = getpid();
        printf("%d\n", r);
    } else {
        wait(&status);
        ptrace(PTRACE_SYSCALL, pid, 0, 0);
        wait(&status);
        ptrace(PTRACE_GETREGS, pid, 0, &uregs);

        /* this prints the syscall number of getpid */
        printf("syscall nr: %d\n", uregs.orig_eax);
        /* 64 is syscall number of getppid */
        uregs.orig_eax = 64;
        ptrace(PTRACE_SETREGS, pid, 0, &uregs);
        ptrace(PTRACE_CONT, pid, 0, 0);
        wait(&status);
        if(WIFEXITED(status))
            printf("we're done\n");
    }
}

The child prints its PID and delivers a signal to itself. Because of the prior call to ptrace() this means it will be stopped.

The parent waits for this to happen and restarts the child with PTRACE_SYSCALL, then waits. Next, the child invokes the getpid system call and it is stopped once again. The parent process uses the PTRACE_GETREGS invocation to peek into the child's registers, where eax holds the system call number. The parent changes this to the system call number of getppid, then once again allows the child to continue. Because the system call number has changed prior to the invocation of the system call, the child will now invoke getppid instead of getpid.

Using ptrace for this purpose may be portable, but I have not tested it. In gdb, you can also use the catch syscall command.

查看更多
登录 后发表回答