Log in

View Full Version : ptrace detection in complex apps


Giaour
May 21st, 2007, 06:15
Is there a method to detect if a program being debugged using ptrace if the app in question is complex enough?

Classics..
Code:

if (ptrace(PTRACE_TRACEME, 0, 0, 0) < 0) {
printf("ptraced\n";
exit(1);
}

Is not working because complex-enough-app usually relies on proper signal handling which is ruined by ptrace.

"nanosleep" method which is described at http://www.secureprogramming.com/?action=view&feature=recipes&recipeid=8 is also not working in modern glibc (or that's just me?) and also causes significatant slowdown (one second until signal is delivered) of application startup.

LKM-based solutions should work (ofcourse), but I'd really like to avoid it.

gera
May 21st, 2007, 07:02
Quote:
[Originally Posted by Giaour;65826]
Code:

if (ptrace(PTRACE_TRACEME, 0, 0, 0) < 0) {
printf("ptraced\n";
exit(1);
}

Is not working because complex-enough-app usually relies on proper signal handling which is ruined by ptrace.


Although it's true that applications will rely on signaling, nothing prevents you from re-delivering the signal. I'm not sure what you meen by "ruined by ptrace", but if doing ptrace(TRACEME) prevents signals from being delivered correctly, then you could use a second process ptracing the first process, and redeliver the signals from one to the other (in fact, I'm almost sure mutual ptracing is possible).

As a suggestion, the best way to learn how to use ptrace() is to strace strace, strace ltrace and strace gdb.

Giaour
May 21st, 2007, 07:17
Quote:
[Originally Posted by gera;65828]Although it's true that applications will rely on signaling, nothing prevents you from re-delivering the signal. I'm not sure what you meen by "ruined by ptrace", but if doing ptrace(TRACEME) prevents signals from being delivered correctly, then you could use a second process ptracing the first process, and redeliver the signals from one to the other (in fact, I'm almost sure mutual ptracing is possible).

As a suggestion, the best way to learn how to use ptrace() is to strace strace, strace ltrace and strace gdb.

Ruined by ptrace means that any signal causes ptrace'd process to stop and wait for parent to do PTRACE_CONT on it. And since there's no parent.. well you got the idea.
I tried fork'ing the process where parent stays in wait() loop and does PTRACE_CONT every time child stops and child does PTRACE_TRACEME. It seems to be working, but some heavyweight GUI apps have problems with drawing windows and I can't find the source yet. Even if i resolve the problem, having 2 processes just for ptrace detection is a litte overkill IMHO.

Are there any other differences in behaviour between ptraced and normal processes that could be used?

gera
May 21st, 2007, 07:32
Try your faulty program with strace and ltrace. If your program works correctly under strace, then twin ptracing must work for you, and you may be doing something incorrectly. (are you forwarding the correct signal with PTRACE_CONT? you must put the signal number as argument to ptrace() as I remember).

You can also try the application inside gdb, tweaking how signals get handled (like say it not to stop, and do forward to debugee)

0xf001
May 21st, 2007, 07:40
hi,

you can look for the imported symbol ptrace() for example. that would work statically. when the syscall is executed via int 0x80, then it depends how tricky it is coded.

when you can disassemble it with objdump, you can search for in 0x80 and if you can, for int 0x80 and eax set to the syscall # of ptrace.
else with another disassembler, or probably even more easy to find ptrace is by setting a bpx on ptrace in gdb and to run your executable.

regards, 0xf001

ps: http://www.woodmann.com/0xf001/filez/linux_anti_anti_debugging4CBJ.txt gives some more ideas and background

Giaour
May 21st, 2007, 15:41
Quote:
[Originally Posted by 0xf001;65832]
you can look for the imported symbol ptrace() for example. that would work statically. when the syscall is executed via int 0x80, then it depends how tricky it is coded.

when you can disassemble it with objdump, you can search for in 0x80 and if you can, for int 0x80 and eax set to the syscall # of ptrace.
else with another disassembler, or probably even more easy to find ptrace is by setting a bpx on ptrace in gdb and to run your executable.

I think you misunderstood, its not complex app that has ptrace detection and I'm unable to find it out. Its rather opposite, I need to add ptrace detection to complex app which uses signals and stuff.
Classical PTRACE_TRACEME way only works on apps that do not use signals, forking and using parent process to control tracing should work but quite awkward.

proxy
May 22nd, 2007, 12:40
um, why not detach after you are satisfied that you are not being ptraced?

if (ptrace(PTRACE_TRACEME, 0, 0, 0) < 0) {
printf("ptraced\n";
exit(1);
} else {
ptrace(PTRACE_DETACH, 0, 0, 0);
}

obviously this allows a debugger to attach mid-execution, but you could do this periodically on a seperate thread or something.

proxy

0xf001
May 22nd, 2007, 13:21
hi,

i guess he wants to stay debugged, to keep the anti-debugging alive

i understand now what u mean, giaour ...

i would suggest, at the beginning of the program you call fork(), which duplicates your running process. the return val = 0 after the call means you are the parent.

each signal stops the debugged process and in the parent you need to ptrace_continue it, and pass the signal to your signal handler in the parent process, which you should allready have defined, as i understand your complex app.

[EDIT]
Quote:
forking and using parent process to control tracing should work but quite awkward.


i see you obviously thought about that, but its not awkward or anything imo, its pretty well defined and documented everything ... just before you code it sometimes i, too, think of things would be very complicated, but in the end its totally not. google for example code of signal passing etc ... but it shouldnt be necessary after a relaxed time of studying man pages and coding a little test code

regards, 0xf001

Giaour
May 22nd, 2007, 20:15
Quote:
[Originally Posted by proxy;65862]um, why not detach after you are satisfied that you are not being ptraced?

if (ptrace(PTRACE_TRACEME, 0, 0, 0) < 0) {
printf("ptraced\n";
exit(1);
} else {
ptrace(PTRACE_DETACH, 0, 0, 0);
}

obviously this allows a debugger to attach mid-execution, but you could do this periodically on a seperate thread or something.

PTRACE_DETACH can be only called from parent process, not from process being traced. Tried that

Quote:
[Originally Posted by 0xf001]
i see you obviously thought about that, but its not awkward or anything imo, its pretty well defined and documented everything ... just before you code it sometimes i, too, think of things would be very complicated, but in the end its totally not. google for example code of signal passing etc ... but it shouldnt be necessary after a relaxed time of studying man pages and coding a little test code

Looks like this is gonna be the solution. Thanks for ideas.

0xf001
May 23rd, 2007, 08:29
giaour,

please post when you are stuck. basically i guess after your fork() call to enter
a loop which checks your signal for sig_stopped etc which indicates the program exitted. you do so using the macros like WIFEXITED etc.

i paste a part of code which i used for a similar thing, that might help you get running, shows the macrousage ... (parent code), u can use it to monitor what kind of signals you get, when putting into a loop.

Code:
void check_wait_status (int status)
{
if (WIFEXITED(status)) {
printf("\texited, status=%d\n", WEXITSTATUS(status));
} else if (WIFSIGNALED(status)) {
printf("\tkilled by signal %d\n", WTERMSIG(status));
} else if (WIFSTOPPED(status)) {
printf("\tstopped by signal %d\n", WSTOPSIG(status));
} else if (WIFCONTINUED(status)) {
printf("\tcontinued\n";
}
}

void attach_p (int pid)
{
int status = 0;

printf("attaching to process\n";
if ((ptrace(PTRACE_ATTACH, pid, NULL, NULL)) == -1) err(1,0);
printf("waiting for process ...\n";
waitpid(pid, &status, WUNTRACED | WCONTINUED);
check_wait_status(status);
}


regards, 0xf001