Home > Back-end >  debugging with ptrace not always find the process successfully
debugging with ptrace not always find the process successfully

Time:10-28

I am playing with ptrace and something very strange happening. I call the parent (tracer) program that execute the child (tracee) program then I PTRACE_SINGLESTEP to watch each instruction and I notice that some of the calls fails with error process not found until all of them fails. I would like to know why. BTW just to be sure if the parent has X group, X owner, and the child has Y group, Y owner. The child permission will not be changed to X if the parent will start trace him right?

parent code

#include <stdio.h>
#include <errno.h>
#include <string.h>
#include <sys/ptrace.h>
#include <sys/types.h>
#include <sys/wait.h>
#include <unistd.h>

#include <sys/syscall.h>   
#include <sys/user.h>
#include <sys/reg.h>


int debug_process()
{
    setvbuf(stdout, 0, 2, 0);
    pid_t pid;
    long orig_eax;
    int status;
    struct user_regs_struct regs;
    unsigned int *addr = 0x0804a024;
    int i = 0;
    pid = fork();
    
    if(pid == 0) {
        if (execve("child.o", NULL, NULL) == -1)
            printf("%s", "Could not execve\n");
        return 0;
    }
    else {
        sleep(2);
        kill(pid, SIGINT);
        while(1) 
        {
            int output = ptrace(PTRACE_PEEKDATA, pid, (void *)addr, 0);
            if (output == -1)
                perror(NULL);
            printf("output: %d\n", output);
            
            output = ptrace(PTRACE_GETREGS, pid, NULL, &regs);
            if (output == -1)
                perror(NULL);
            printf("output: %d\n", output);
            
            printf("%X called with eip: %X ebx: %X, ecx: %X, edx: %X\n",
               regs.eax, regs.eip, regs.ebx,
               regs.ecx, regs.edx);
            getchar();

            output = ptrace(PTRACE_SINGLESTEP, pid, 0, 0);
            if (output == -1)
                perror(NULL);

            printf("output: %d\n", output);
            printf("%s", "-----------------------------------------\n");
        
        }
    }
    
    return 0;
}
    
int main()
{
    debug_process();
}

child code

#include <stdio.h>
#include <errno.h>
#include <string.h>
#include <sys/ptrace.h>
#include <sys/types.h>
#include <sys/wait.h>
#include <unistd.h>

#include <sys/syscall.h>   
#include <sys/user.h>
#include <sys/reg.h>



int i = 143;
int main()
{
    setvbuf(stdout, 0, 2, 0);
    ptrace(PTRACE_TRACEME, 0, 0, 0);
    printf("child4 starts...\n");
    while(i != 245) {
        printf("hello from child\n");   
    }
        
    printf("child4 outside loop...\n");
}

output:

...
hello from childNo such process
output: -1
output: 0
FFFFFE00 called with eip: F779FB59 ebx: 1, ecx: F7788DA7, edx: 1

output: 0
-----------------------------------------
No such process
output: -1
No such process
output: -1
FFFFFE00 called with eip: F779FB59 ebx: 1, ecx: F7788DA7, edx: 1


output: 0
-----------------------------------------
No such process
output: -1
No such process
output: -1
FFFFFE00 called with eip: F779FB59 ebx: 1, ecx: F7788DA7, edx: 1

output: 0
-----------------------------------------
No such process
output: -1
No such process
output: -1
FFFFFE00 called with eip: F779FB59 ebx: 1, ecx: F7788DA7, edx: 1

output: 0
-----------------------------------------
No such process
output: -1
No such process
output: -1
FFFFFE00 called with eip: F779FB59 ebx: 1, ecx: F7788DA7, edx: 1

output: 0
-----------------------------------------
No such process
output: -1
No such process
output: -1
FFFFFE00 called with eip: F779FB59 ebx: 1, ecx: F7788DA7, edx: 1

output: 0
-----------------------------------------
No such process
output: -1
No such process
output: -1
FFFFFE00 called with eip: F779FB59 ebx: 1, ecx: F7788DA7, edx: 1

output: 0
-----------------------------------------
No such process
output: -1
No such process
output: -1
FFFFFE00 called with eip: F779FB59 ebx: 1, ecx: F7788DA7, edx: 1

output: 0
-----------------------------------------
No such process
output: -1
No such process
output: -1
FFFFFE00 called with eip: F779FB59 ebx: 1, ecx: F7788DA7, edx: 1

output: 0
-----------------------------------------
No such process
output: -1
No such process
output: -1
FFFFFE00 called with eip: F779FB59 ebx: 1, ecx: F7788DA7, edx: 1

output: 0
-----------------------------------------
output: 143
output: 0
1 called with eip: F763E0C4 ebx: F7788D60, ecx: F7788DA7, edx: 1

output: 0
-----------------------------------------
No such process
output: -1
No such process
output: -1
1 called with eip: F763E0C4 ebx: F7788D60, ecx: F7788DA7, edx: 1
...

Compiled with gcc -m32 parent.c/child.c -o parent.o/child.o
kernel version 4.4.179-0404179-generic

CodePudding user response:

A few issues ...

  1. In parent, pid is unitialized. Did you forget the fork?
  2. AFAIK, the PTRACE_TRACEME is better done in the child after the fork but before execve
  3. parent should do waitpid at the top of the loop.
  4. .o files are conventionally not for executables
  5. Hardwiring addr with a fixed hex value is problematic as the load address [of child's i] can't necessarily be predicted and may change on each invocation.

Here is the refactored code. It is annotated.

child.c:

// child.c

#include <stdio.h>
#include <errno.h>
#include <string.h>
#include <sys/ptrace.h>
#include <sys/types.h>
#include <sys/wait.h>
#include <unistd.h>

#include <sys/syscall.h>
#include <sys/user.h>
#include <sys/reg.h>

int i = 143;
int
main()
{
#if 0
    setvbuf(stdout, 0, 2, 0);
#else
    setlinebuf(stdout);
#endif

// NOTE/BUG: this should be done after fork but before exec by parent
#if 0
    ptrace(PTRACE_TRACEME, 0, 0, 0);
#endif
    printf("child4 starts...\n");

    while (i != 245) {
        printf("hello from child\n");
        usleep(100000);
    }

    printf("child4 outside loop...\n");
}

parent.c:

// parent.c

#include <stdio.h>
#include <errno.h>
#include <string.h>
#include <sys/ptrace.h>
#include <sys/types.h>
#include <sys/wait.h>
#include <unistd.h>

#include <sys/syscall.h>
#include <sys/user.h>
#include <sys/reg.h>

int
debug_process()
{
#if 0
    setvbuf(stdout, 0, 2, 0);
#else
    setlinebuf(stdout);
#endif
    pid_t pid;
    long orig_eax;
    int status;
    struct user_regs_struct regs;

// NOTE/BUG: this won't compile cleanly
#if 0
    unsigned int *addr = 0x0804a024;
#else
    unsigned int *addr = (void *) 0x0804a024;
#endif
    int i = 0;

// NOTE/BUG: missing fork call
#if 1
    pid = fork();
#endif

    if (pid == 0) {
// NOTE/FIX this should be done by tracer just before execve
#if 1
        ptrace(PTRACE_TRACEME, 0, 0, 0);
#endif
        if (execve("./child", NULL, NULL) == -1)
            printf("%s", "Could not execve\n");
        return 0;
    }
    else {
        printf("parent: pid=%d\n",pid);

        sleep(2);
// NOTE/BUG -- if successful, this would just kill the child since it doesn't
// trap this signal
#if 0
        kill(pid, SIGINT);
#endif

        while (1) {
// NOTE/FIX: we need to do a wait before doing ptrace calls
#if 1
            int err = waitpid(pid,&status,0);
            if (err < 0)
                perror("waitpid");
            printf("err=%d\n",err);
#endif

            int output = ptrace(PTRACE_PEEKDATA, pid, (void *) addr, 0);
            if (output == -1)
                perror("ptrace/PEEKDATA");
            printf("output: %d\n", output);

            output = ptrace(PTRACE_GETREGS, pid, NULL, &regs);
            if (output == -1)
                perror("ptrace/GETREGS");
            printf("output: %d\n", output);

            printf("%X called with eip: %X ebx: %X, ecx: %X, edx: %X\n",
                regs.eax, regs.eip, regs.ebx, regs.ecx, regs.edx);

// NOTE/BUG: better to get a full line/command
#if 0
            getchar();
#else
            char buf[100];
            fgets(buf,sizeof(buf),stdin);
#endif

            output = ptrace(PTRACE_SINGLESTEP, pid, 0, 0);
            if (output == -1)
                perror("ptrace/SS");

            printf("output: %d\n", output);
            printf("%s", "-----------------------------------------\n");

        }
    }

    return 0;
}

int
main()
{
    debug_process();
}

In the above code, I've used cpp conditionals to denote old vs. new code:

#if 0
// old code
#else
// new code
#endif

#if 1
// new code
#endif

Note: this can be cleaned up by running the file through unifdef -k


UPDATE:

unsigned int *addr = 0x0804a024; looks like a .text or .data address from a non-PIE executable. A non-PIE can't be ASLRed; static addresses are fixed at link time. Seems reasonable for a toy experiment, vs. trying to parse ELF symbol metadata.

I've added nm parsing below. readelf parsing [which is better] left as an exercise for the reader ;-)

(Unlike the other problems you pointed out at the top of this answer; those are serious problems but also easy to fix.) The child process should probably have volatile int i, although if they compile without optimization that won't matter. A more meaningful name would be better, since the parent has a local i variable. – Peter Cordes

Added volatile

there is a way to make the child survive after sending SIGINT, from the parent? changing the eip to those of the function didn't work.

Look at the signal and sigaction functions. You can use one of those to trap/ignore signals of your choosing. But, I'm still not sure why you would want to send SIGINT to the child/tracee from the given parent code.

and maybe do you know if the child permission changed by ptrace? – nadav levin

No, it doesn't. If both executables are owned by your UID, and have read and executable permissions, then things should be fine. Just doing normal cc commands should ensure this. ls -l should report -rwxr-xr-x.

Here is the parent updated with symbol table parsing:

// parent.c

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

#include <sys/syscall.h>
#include <sys/user.h>
#include <sys/reg.h>

int
debug_process()
{
#if 0
    setvbuf(stdout, 0, 2, 0);
#else
    setlinebuf(stdout);
#endif
    pid_t pid;
    long orig_eax;
    int status;
    struct user_regs_struct regs;
    char buf[100];

// NOTE/BUG: this won't compile cleanly
#if 0
    unsigned int *addr = 0x0804a024;
#else
    unsigned int *addr = (void *) 0x0804a024;
#endif
    int i = 0;

    FILE *fin = popen("nm ./child","r");
    if (fin == NULL) {
        perror("popen");
        exit(1);
    }

    while (fgets(buf,sizeof(buf),fin) != NULL) {
        if (buf[0] == ' ')
            continue;

        char *cp;

        cp = strtok(buf," \n");
        addr = (void *) strtol(cp,NULL,16);

        cp = strtok(NULL," \n");
        if (cp[0] != 'D')
            continue;

        cp = strtok(NULL," \n");
        if (strcmp(cp,"i") == 0) {
            printf("addr=%p\n",addr);
            break;
        }
    }

    fclose(fin);

// NOTE/BUG: missing fork call
#if 1
    pid = fork();
#endif

    if (pid == 0) {
// NOTE/FIX this should be done by tracer just before execve
#if 1
        ptrace(PTRACE_TRACEME, 0, 0, 0);
#endif
        if (execve("./child", NULL, NULL) == -1)
            printf("%s", "Could not execve\n");
        return 0;
    }
    else {
        printf("parent: pid=%d\n",pid);

        sleep(2);
// NOTE/BUG -- if successful, this would just kill the child since it doesn't
// trap this signal
#if 0
        kill(pid, SIGINT);
#endif

        while (1) {
// NOTE/FIX: we need to do a wait before doing ptrace calls
#if 1
            int err = waitpid(pid,&status,0);
            if (err < 0)
                perror("waitpid");
            printf("err=%d\n",err);
#endif

            int output = ptrace(PTRACE_PEEKDATA, pid, (void *) addr, 0);
            if (output == -1)
                perror("ptrace/PEEKDATA");
            printf("output: %d\n", output);

            output = ptrace(PTRACE_GETREGS, pid, NULL, &regs);
            if (output == -1)
                perror("ptrace/GETREGS");
            printf("output: %d\n", output);

            printf("%X called with eip: %X ebx: %X, ecx: %X, edx: %X\n",
                regs.eax, regs.eip, regs.ebx, regs.ecx, regs.edx);

// NOTE/BUG: better to get a full line/command
#if 0
            getchar();
#else
            fgets(buf,sizeof(buf),stdin);
#endif

            output = ptrace(PTRACE_SINGLESTEP, pid, 0, 0);
            if (output == -1)
                perror("ptrace/SS");

            printf("output: %d\n", output);
            printf("%s", "-----------------------------------------\n");

        }
    }

    return 0;
}

int
main()
{
    debug_process();
}

Here is the child updated with volatile:

// child.c

#include <stdio.h>
#include <errno.h>
#include <string.h>
#include <sys/ptrace.h>
#include <sys/types.h>
#include <sys/wait.h>
#include <unistd.h>

#include <sys/syscall.h>
#include <sys/user.h>
#include <sys/reg.h>

// NOTE/BUG: per peter, should have volatile because it will be asynchronously
// changed by the ptracer
#if 0
int i = 143;
#else
volatile int i = 143;
#endif

int
main()
{
#if 0
    setvbuf(stdout, 0, 2, 0);
#else
    setlinebuf(stdout);
#endif

// NOTE/BUG: this should be done after fork but before exec by parent
#if 0
    ptrace(PTRACE_TRACEME, 0, 0, 0);
#endif
    printf("child4 starts i=%p ...\n",&i);

    while (i != 245) {
        printf("hello from child\n");
        usleep(100000);
    }

    printf("child4 outside loop...\n");
}
  • Related