Introduction
程序的反调试是保护程序不被它人轻易逆向的必备技术,本文将探讨几个常见的 Linux 平台下的反调试技术,供大家参考。
int3
int3 其实就是专门为调试器准备的一个指令,调试器把想下断点的地方的机器码修改为 0xCC
,CPU运行到此处时便会触发 SIGTRAP
中断,从而被调试器接管。
同时,用户空间也会产生相应的 SIGTRAP 信号,我们只要注册回调函数就可以忽略该信号。
#include<stdio.h>
#include<signal.h>
void handler(int signo){}
int main(void)
{
signal(SIGTRAP, handler);
__asm__("nop\n\t"
"int3\n\t");
printf("Hello from main!\n");
return 0;
}
当然,触发其他形式的信号也是可以的。只需要注册相应的回调函数即可。(甚至可以利用信号量编写一个VM,能够极大提高逆向的难度)
获取进程信息
Linux 下的 ID
Linux中,进程都拥有以下的ID
- Process ID(PID)
Linux中标识进程的一个数字,它的值是不确定的,是由系统分配的(但是有一个例外,启动阶段,kernel运行的第一个进程是init,它的PID是1,是所有进程的最原始的父进程),每个进程都有唯一PID,当进程退出运行之后,PID就会回收,可能之后创建的进程会分配这个PID - Parent Process ID(PPID)
字面意思,父进程的PID - Process Group ID(PGID)
PGID就是进程所属的Group的Leader的PID,如果PGID=PID,那么该进程是Group Leader - Session ID(SID)
和PGID非常相似,SID就是进程所属的Session Leader的PID,如果SID==PID,那么该进程是session leader
获取父进程信息
调试器要附加进程,必须成为该进程的父进程,也就是说主程序一定是调试器的子进程。
通过getpid,我们可以获取到 /proc/pid/cmdline 下程序的启动参数。
若启动参数不为 -bash
或 -init
,那么就可以判断其使用了调试器。
#include<stdio.h>
#include<unistd.h>
#include<fcntl.h>
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: %d\n", ppid);
if (get_name_by_pid(ppid, name))
return -1;
printf("%s\n\n", name);
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
在同一个终端内,程序的 sid 是不会变的,等于 group leader 的 pid。
而如果使用了调试器,程序的父进程便不再是 -bash
,而是 -bash
的子进程。
所以 getsid(getpid()) ≠ getppid()
,故检测出程序使用了调试器
#include<stdio.h>
#include<unistd.h>
int main(void)
{
printf("getsid: %d\n", getsid(getpid()));
printf("getppid: %d\n", getppid());
if (getsid(getpid()) != getppid()) {
printf("traced!\n");
exit(0);
}
printf("OK\n");
return 0;
}
getenv
bash有一个环境变量叫$_
,它保存的是上一个执行的命令的最后一个参数。
getenv 用于获取环境变量,若上一个执行的命令不是 argv[0],则说明在调用程序之前,可能启动了调试器。
#include<stdio.h>
#include<stdlib.h>
#include<string.h>
int main(int argc, char *argv[]){
printf("getenv(_): %s\n", getenv("_"));
printf("argv[0]: %s\n", argv[0]);
if(strcmp(argv[0], (char *)getenv("_")))
printf("traced!\n");
else
printf("OK\n");
return 0;
}
ptrace
调试器在调试目标程序时会进行 fork() 产生子进程,并且 exec() 来执行相应的程序。
如果对自己所在的进程进行 ptrace 时,该进程已经被 gdb 附加了,那么 ptrace 就会报错返回 -1,这也从另外一种角度揭示了调试器的存在。
ptrace 的更多功能: https://www.cnblogs.com/heixiang/p/10988992.html
#include<stdio.h>
#include<unistd.h>
#include<sys/ptrace.h>
int main(void)
{
if ( ptrace(PTRACE_TRACEME, 0, 0, 0) < 0 ) {
printf("traced!\n");
return 1;
}
printf("OK\n");
return 0;
}
alarm
设置定时器,让程序在指定时间内自动退出。