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, ®s);
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 ...
- In parent,
pid
is unitialized. Did you forget thefork
? - AFAIK, the
PTRACE_TRACEME
is better done in the child after thefork
but beforeexecve
- parent should do
waitpid
at the top of the loop. .o
files are conventionally not for executables- Hardwiring
addr
with a fixed hex value is problematic as the load address [of child'si
] 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, ®s);
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, ®s);
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");
}