Can't make PTRACE_SEIZED(ptrace request) work

Issue

I’m playing with ptrace and trying to get register system calls value(orig_rax) of my process, I have no problem doing it with PTRACE_TRACEME request(tracer/tracee), then I tried with PTRACE_ATTACH(attach the process at runtime) and got no problem, but now I try with PTRACE_SEIZED and I have some problem to get those system call, I just cannot parse properly my process.
I know that the difference between PTRACE_ATTACH and PTRACE_SEIZED are that with seized I need to stop myself the process(I use PTRACE_INTERRUPT for that).
Below is my code: all printf and if (i == 3) break; after the while(1) are for debug purpose.
So what I’m simply trying to do is to parse my program by attaching my process with PTRACE_SEIZED request (from ptrace function) and extract all system calls(orig_rax register) done (more particularly isolate those done with my execl function,which call /bin/ls, in my main).
When I run my code all ptrace_function return 0 and the process received well the stopping signal(WIFSTOPPED(status) == true), but rip register seem not to change (sometimes it change and I get some correct orig_rax value only once and after nothing I get always the same value)
I also tried with PTRACE_SINGLESTEP instead of PTRACE_SYSCALL, and it worked only once in my while(1), after RIP is always the same value.
(I did some research on google and found no example of process parsing with PTRACE_SEIZED, i’m a begginer with ptrace, kernel, signal stuff, so maybe my miscomprehension come from here).
Any help, examples, helpful links are appreciated. I hope I’m clear enough.
Thanks.

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

int             seized_process(pid_t pid)
{
        int                                             status;
        struct user_regs_struct regs;
        int                                             pt_ret;
        siginfo_t                               siginfo;
        int                                     i = 0;

        pt_ret = ptrace(PTRACE_SEIZE, pid, NULL, NULL);
        printf("PTRACE_SEIZE ret: %d\n", pt_ret);
        printf("parent :%d \n", pid);

        while (1) {
                if (i == 3)
                        break;
                pt_ret = ptrace(PTRACE_INTERRUPT, pid, NULL, NULL);
                printf("PTRACE_INTERRUPT ret: %d\n", pt_ret);
                waitpid(pid, &status, 0);
                printf("waitpid status: %d\n", status);
                if (WIFSTOPPED(status))
                        printf("process received stopping signal(Group-stop)\n");

                pt_ret = ptrace(PTRACE_GETSIGINFO, pid, NULL, &siginfo);
                printf("PTRACE_SIGINFO ret: %d, sig num: %d, si_code: %d \n",
                                pt_ret, siginfo.si_signo, siginfo.si_code);
                pt_ret = ptrace(PTRACE_GETREGS, pid, NULL, &regs);
                printf("PTRACE_GETREGS ret: %d rip %#llx, orig_rax: %#llx,"
                           " rax : %#llx\n", pt_ret, regs.rip, regs.orig_rax,
                           regs.rax);

                pt_ret = ptrace(PTRACE_SYSCALL, pid, NULL,
                                WSTOPSIG(status));
                printf("PTRACE_SYSCALL ret : %d\n", pt_ret);

                if (WIFEXITED(status)) {
                        printf("WIFEXITED(status)\n");
                        break ;
                }
                else if (WSTOPSIG(status)) {
                        printf("WSTOPSIG(status)\n");
                }
                printf("\n---------------------\n");
                i++;
        }
        ptrace(PTRACE_DETACH, pid, NULL, NULL);
}

int main(int argc, char *argv[])
{
        pid_t   child;

        child = fork();
        printf("fork value: %d \n", child);
        if (child == 0) {
                printf("child\n");
                execl("/bin/ls", "ls", NULL);
        }
        else {
                seized_process(child);
        }
    return 0;
}

Solution

From the ptrace man page …

Under the section for PTRACE_SEIZE, we have:

Unlike PTRACE_ATTACH, PTRACE_SEIZE does not stop the process.

Under the PTRACE_TRACEME, we have:

For requests other than PTRACE_ATTACH, PTRACE_SEIZE, PTRACE_INTERRUPT, and PTRACE_KILL, the tracee must be stopped.

So, for the tracer to be able to examine the tracee’s registers, etc. the tracee must be stopped. But, PTRACE_SEIZE [by itself] doesn’t do that.

So, you’d need to use PTRACE_ATTACH or do something else in addition to PTRACE_SEIZE to stop the process before trying to examine its registers or memory.

When you think about it, there isn’t much value to getting a snapshot of a process that is running because you’d wouldn’t get an atomic view of it. You’d be “racing” against it [just like two threads racing on a global value]. That is, (e.g.) you grab eax, then memory, but they might have already changed values between the ptrace calls.


UPDATE:

I know i have to stop my process(tracee), that’s why i use: pt_ret = ptrace(PTRACE_INTERRUPT, pid, NULL, NULL);

In your loop, you do PTRACE_INTERRUPT to stop the process. Then, you grab the data, print it, and loop.

But, in your loop, you never do anything to resume the tracee (e.g. PTRACE_CONT), so once the first iteration of your loop is done, the traced process remains stopped. It only resumes after you exit your loop and do the PTRACE_DETACH.


UPDATE #2:

Yes. PTRACE_SYSCALL will resume the traced process. But, the tracer’s ptrace call will return immediately. Then, at the loop top, you do another PTRACE_INTERRUPT.

This means that the traced process will be scheduled to run, but might get stopped [again] before it gets the chance to run. (i.e. the tracer and tracee are “racing”). This is probably close to what you’re seeing.

Also, I’d increase the number of iterations in the tracer as 3 is a bit small to see much.

Try doing the PTRACE_INTERRUPT before the loop. This makes PTRACE_SEIZE followed by PTRACE_INTERRUPT the moral equivalent of PTRACE_ATTACH.

Then, the loop of: waitpid, print, PTRACE_SYSCALL should work without racing. Having PTRACE_INTERRUPT in the same loop as PTRACE_SYSCALL [sort of] defeats the purpose of PTRACE_SYSCALL.

Using PTRACE_INTERRUPT [after the initial one] only makes sense if you do PTRACE_CONT.

However, when doing PTRACE_SYSCALL, it might make sense to loop on the waitpid with WNOHANG. If you don’t get a stop within a reasonable amount of time, that means the target isn’t doing any syscalls. At that point, you might issue the PTRACE_INTERRUPT then, to force the stop so you can examine state.

With PTRACE_INTERRUPT above the loop, try PTRACE_SINGLESTEP to reduce the granularity.

ls is a bit opaque as far as single stepping goes (e.g. you’d probably have to install the debuginfo package to get a symbol table).

Try writing a simple target/tracee program where you precisely control the behavior. You can generate a full disassembly of it. And, then match the addresses against the rip values you get back from single step tracing.

Answered By – Craig Estey

This Answer collected from stackoverflow, is licensed under cc by-sa 2.5 , cc by-sa 3.0 and cc by-sa 4.0

Leave a Reply

(*) Required, Your email will not be published