Linux下的反调试技术

作者:西邮 王聪

如何防止自己的程序被调试器跟踪,这是一个很有趣的话题,也是反逆向工程中的一个重要话题。这里简单介绍一下Linux平台上的反调试技术。

(本文主要参考:http://blog.txipinet.com/2006/10/05/37-tecnicas-anti-debugging-sencillas-para-gnu-linux/
做人要厚道,转载请指明出处!

一. int3指令

Intel Software Developer’s Manual Volume 2A中提到:

The INT 3 instruction generates a special one byte opcode (CC) that is intended for
calling the debug exception handler. (This one byte form is valuable because it can be
used to replace the first byte of any instruction with a breakpoint, including other one
byte instructions, without over-writing other code).

int3是一个特殊的中断指令(从名字上也看得出来),专门用来给调试器使用。这时,我们应该很容易想到,要反调试,只要插入int3来迷惑调试器即可。不过,这会不会影响正常的程序?会!因为int3会在用户空间产生SIGTRAP。没关系,我们只要忽略这个信号就可以了。

include

include

void handler(int signo)
{}

int main(void)
{
signal(SIGTRAP, handler);
asm(“nopnt”
“int3nt”);
printf(“Hello from main!n”);
return 0;
}

二. 文件描述符

这是一个很巧妙的办法,不过它只对gdb之类的调试器有效。方法如下:

include

include

include

int main(void)
{
if(close(3) == -1) {
printf(“OKn”);
} else {
printf(“traced!n”);
exit(-1);
}
return 0;
}

gdb要调试这个程序时会打开一个额外的文件描述符来读这个可执行文件,而这个程序正是利用了这个“弱点”。当然,你应该能猜到,这个技巧对strace是无效的。

三. 利用getppid

和上面一个手法类似,不过这个更高明,它利用getppid来进行探测。我们知道,在Linux上要跟踪一个程序,必须是它的父进程才能做到,因此,如果一个程序的父进程不是意料之中的bash等(而是gdb,strace之类的),那就说明它被跟踪了。程序代码如下:

include

include

include

include

include

include

include

int get_name_by_pid(pid_t pid, char* name)
{
int fd;
char buf[1024] = {0};
snprintf(buf, 1024, “/proc/%d/cmdline”, pid);
if ((fd = open(buf, O_RDONLY)) == -1)
return -1;
read(fd, buf, 1024);
strncpy(name, buf, 1023);
return 0;
}

int main(void)
{
char name[1024];
pid_t ppid = getppid();
printf(“getppid: %dn”, ppid);

    if (get_name_by_pid(ppid, name))
    return -1;
if (strcmp(name, "bash") == 0 ||
    strcmp(name, "init") == 0)
        printf("OK!n");
else if (strcmp(name, "gdb") == 0 ||
    strcmp(name, "strace") == 0 ||
    strcmp(name, "ltrace") == 0)
    printf("Traced!n");
else
    printf("Unknown! Maybe traced!n");

return 0;

}

同样的手法,一个更简单的方式是利用session id。我们知道,不论被跟踪与否,session id是不变的,而ppid会变!下面的程序就利用了这一点。

include

include

include

int main(void)
{
printf(“getsid: %dn”, getsid(getpid()));
printf(“getppid: %dn”, getppid());

    if (getsid(getpid()) != getppid()) {
    printf("traced!n");
    exit(EXIT_FAILURE);
}
    printf("OKn");

return 0;

}

四. 利用环境变量

bash有一个环境变量叫$_,它保存的是上一个执行的命令的最后一个参数。如果在被跟踪的状态下,这个变量的值是会发生变化的(为什么?)。下面列出了几种情况:


argv[0] getenv(“_”)
shell ./test ./test
strace ./test /usr/bin/strace
ltrace ./test /usr/bin/ltrace
gdb /home/user/test (NULL)

所以我们也可以据此来判断。

include

include

include

int main( int argc, char *argv[])
{
printf(“getenv(): %sn”, getenv(““));
printf(“argv[0]: %sn”, argv[0]);

if(strcmp(argv[0], (char *)getenv("_"))) {
    printf("traced!n");
    exit(-1);
}

printf("OKn");
    return 0;

}

五. 利用ptrace

很简单,如果被跟踪了还再调用ptrace(PTRACE_TRACEME…)自然会不成功。

include

include

int main(void)
{
if ( ptrace(PTRACE_TRACEME, 0, 1, 0) < 0 ) {
printf("traced!n");
return 1;
}
printf("OKn");
return 0;
}

Happy hacking!