For starters, I've found out that I can send a
SIGTRAP
multiple ways. The easier is to just raise
it:1 2 | #include <signal.h> raise (SIGTRAP); // or SIGINT if you also like to live dangerously |
1 2 3 | __asm__( "int $3" ); // or __asm__( "int3" ); |
The INT 3 instruction explicitly calls the breakpoint exception (#BP) handler.So I'm gonna stay with "does the same", as it is a deliberately tricky one.
But if you're not running through a debugger, it will leak to the kernel and that eventually stops your program (like, saying
"Trace/BPT trap". BPT --> breakpoint
, see?). We need to emit this signal conditionally, which leads us to the next problem: at any given time, are we debugged/traced or not?Time to look up some anti-debug techniques.
The first idea that got me is to simply try to have the program to debug itself [terms and conditions apply], with
PTRACE_TRACEME
. The trick is that this call will fail if we are already debugged/traced. Then we can just flip a flag and have our signals protected with a condition.1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 | #include <stdio.h> #include <stdlib.h> #include <signal.h> // raise, SIG* #include <errno.h> // errno #include <string.h> // strerror #include <sys/ptrace.h> // ptrace #include <sys/types.h> int has_dbg() { long rc = ptrace(PTRACE_TRACEME, 0, 0, 0); if (rc < 0) { printf ( "traceme resulted in %ld, errno: %d, %s\n" , rc, errno , strerror ( errno )); return 1; } else { return 0; } } |
I've poked around with fork-and-traceme, but it's cumbersome. But I found an elegant solution on stackoverflow (where else): Just check
/proc/self/status
if TracerPid is zero or not. Genius.Caveats:
ps
also reads /proc/* for status info so it must be pretty standard.So our check looks like the following:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 | #include <stdio.h> #include <stdlib.h> //atoi #include <signal.h> // raise, SIG* #include <string.h> // strstr, strerror #include <fcntl.h> // open #include <unistd.h> // read, close int has_dbg() { char buf[2048], *tracer_pid; int debugger_present = 0; // We say "no" if not found or error, as we are cowards. static const char TracerPid[] = "TracerPid:" ; ssize_t num_read; int status_fd = open( "/proc/self/status" , O_RDONLY); if (status_fd == -1) return 0; num_read = read(status_fd, buf, sizeof (buf)); close(status_fd); if (num_read > 0) { buf[num_read] = 0; tracer_pid = strstr (buf, TracerPid); // Look for "TracerPid" if (tracer_pid) debugger_present = !! atoi (tracer_pid + sizeof (TracerPid) - 1); // parse an int, and bool-ify it. } return debugger_present; } int main( int argc, char **argv) { if (has_dbg()) { printf ( "can has\n" ); } else { printf ( "no no\n" ); } return 0; } |
Now, we can do things like:
1 2 3 4 5 6 7 8 9 10 11 12 | #define HALP do{if(has_dbg()){__asm__("int $3");}}while(0) // ... printf ( "foo\n" ); if (3.14 >= 3.14) { printf ( "bar\n" ); HALP; printf ( "baz\n" ); } printf ( "quux\n" ); |
Note:
If you love to cut on characters you have to type, I suggest the raise version in the long run:
- raise variant uses
20 + n*14
chars. (headers, you know) - asm variant uses
n*17
chars.
No comments :
Post a Comment