前言
在很多逆向分析的时候,我们看字符串信息经常能看到TracerPid等关键字,这是一个反调试常用的手段。这次我们就来实现这个检测TracerPid的反调试。
实验
使用ps查看我们运行的包pid
ps | grep "com.shark.jnireflect"
u0_a81 27178 182 915256 42908 ffffffff 4008673c S com.shark.jnireflect
查看本程序的/proc/{PID}/status文件
cat /proc/27178/status
Name: hark.jnireflect /*进程的程序名*/
State: S (sleeping)
Tgid: 27178 /*线程组号*/
Pid: 27178 /*进程pid process id*/
PPid: 182 /*父进程的pid parent processid*/
TracerPid: 0 /*跟踪进程的pid*/
Uid: 10081 10081 10081 10081
Gid: 10081 10081 10081 10081
FDSize: 256
Groups: 50081
VmPeak: 915256 kB
VmSize: 885044 kB
VmLck: 0 kB
VmPin: 0 kB
VmHWM: 42936 kB
VmRSS: 42908 kB
VmData: 16076 kB
VmStk: 136 kB
VmExe: 20 kB
VmLib: 49292 kB
VmPTE: 152 kB
VmSwap: 0 kB
Threads: 11
SigQ: 1/12274
SigPnd: 0000000000000000
ShdPnd: 0000000000000000
SigBlk: 0000000000001204
SigIgn: 0000000000000000
SigCgt: 00000002000094f8
CapInh: 0000000000000000
CapPrm: 0000000000000000
CapEff: 0000000000000000
CapBnd: fffffff000000000
Cpus_allowed: f
Cpus_allowed_list: 0-3
voluntary_ctxt_switches: 109
nonvoluntary_ctxt_switches: 371
我注释了一些我们常看到的信息,其他的对于我们来说不那么重要
TracerPid就是调试此进程的pid,在同一时刻只能有一个进程调试本进程
调试
使用ida进行调试后在查看TracerPid
Name: hark.jnireflect
State: t (tracing stop)
Tgid: 27178
Pid: 27178
PPid: 182
TracerPid: 27519
Uid: 10081 10081 10081 10081
Gid: 10081 10081 10081 10081
FDSize: 256
Groups: 50081
VmPeak: 915256 kB
VmSize: 885044 kB
VmLck: 0 kB
VmPin: 0 kB
VmHWM: 43332 kB
VmRSS: 43332 kB
VmData: 16076 kB
VmStk: 136 kB
VmExe: 20 kB
VmLib: 49292 kB
VmPTE: 152 kB
VmSwap: 0 kB
Threads: 11
SigQ: 1/12274
SigPnd: 0000000000000000
ShdPnd: 0000000000000000
SigBlk: 0000000000001204
SigIgn: 0000000000000000
SigCgt: 00000002000094f8
CapInh: 0000000000000000
CapPrm: 0000000000000000
CapEff: 0000000000000000
CapBnd: fffffff000000000
Cpus_allowed: f
Cpus_allowed_list: 0-3
voluntary_ctxt_switches: 119
nonvoluntary_ctxt_switches: 373
可以看到TracerPid有了值,并且State变成了t状态
使用ps查看调试进程是什么
ps |grep 27519
root 27519 27074 10488 8696 ffffffff 00000000 S ./android_server
可以看到就是我们的android_server程序
当我们使用IDA attach程序的时候,在/proc/{PID}/status文件的TracerPid字段会写入调试程序的PID
也就是说使用TracerPid反调试的原理就是检测这个字段是否为0,为0说明没有被调试,不为0说明正在被调试,检测调试器直接退出就可以达到反调试的效果
代码实现
#include <jni.h>
#include <string>
#include "antidebug.h"
#define NULL 0
#define CHECK_TIME 10
#define LOGI(...) __android_log_print(ANDROID_LOG_INFO, "shark chilli", __VA_ARGS__)
#define LOGE(...) __android_log_print(ANDROID_LOG_ERROR, "shark chilli", __VA_ARGS__)
pthread_t id_anti_debug = NULL;
void readStatus() {
FILE *fd;
char filename[128];
char line[128];
pid_t pid = syscall(__NR_getpid);
LOGI("PID : %d", pid);
sprintf(filename, "/proc/%d/status", pid);// 读取proc/pid/status中的TracerPid
while (1) {
fd = fopen(filename, "r");
while (fgets(line, 128, fd)) {
if (strncmp(line, "TracerPid", 9) == 0) {
//TracerPid值
int status = atoi(&line[10]);
LOGI("########## status = %d, %s", status, line);
fclose(fd);
syscall(__NR_close, fd);
if (status != 0) {
LOGI("########## FBI WARNING ##########");
LOGI("######### FIND DEBUGGER #########");
kill(pid, SIGKILL);
return;
}
break;
}
}
sleep(CHECK_TIME);
}
}
void checkAnti() {
LOGI("Call readStatus...");
readStatus();
}
void anti_debug() {
LOGI("Call anti_debug...");
//创建线程
if (pthread_create(&id_anti_debug, NULL, (void *(*)(void *)) &checkAnti, NULL) != 0) {
LOGE("Failed to create a debug checking thread!");
exit(-1);
};
//线程分离 释放资源
pthread_detach(id_anti_debug);
}
extern "C" JNIEXPORT void JNICALL
Java_com_shark_tracerpidapp_MainActivity_checkTracerPid(
JNIEnv *env,
jobject /* this */) {
anti_debug();
}
上面的代码我在java层的MainActivity的onCreate中调用了
这里开启一个线程在线程中一直去查询自己status文件中TracerPid的值,当TracerPid不为0的时候说明有调试,我们调用kill将其杀死就可以了
使用ida进行调试,断下后输出如下
11-06 14:17:47.283 14196-14196/com.shark.tracerpidapp I/shark chilli: Call anti_debug...
11-06 14:17:47.283 14196-14218/com.shark.tracerpidapp I/shark chilli: Call readStatus...
11-06 14:17:47.283 14196-14218/com.shark.tracerpidapp I/shark chilli: PID : 14196
11-06 14:17:47.283 14196-14218/com.shark.tracerpidapp I/shark chilli: ########## status = 0, TracerPid: 0
11-06 14:17:57.293 14196-14218/com.shark.tracerpidapp I/shark chilli: ########## status = 0, TracerPid: 0
11-06 14:18:07.293 14196-14218/com.shark.tracerpidapp I/shark chilli: ########## status = 0, TracerPid: 0
11-06 14:18:17.293 14196-14218/com.shark.tracerpidapp I/shark chilli: ########## status = 0, TracerPid: 0
11-06 14:18:27.293 14196-14218/com.shark.tracerpidapp I/shark chilli: ########## status = 0, TracerPid: 0
11-06 14:18:37.293 14196-14218/com.shark.tracerpidapp I/shark chilli: ########## status = 0, TracerPid: 0
可以看到在没调试前我们的日志一直在循环输出,说明一直在循环检测TracerPid
将ida运行
11-06 14:19:17.303 14196-14218/com.shark.tracerpidapp I/shark chilli: ########## status = 0, TracerPid: 0
11-06 14:26:07.143 14196-14218/com.shark.tracerpidapp I/shark chilli: ########## status = 13967, TracerPid: 13967
11-06 14:26:07.143 14196-14218/com.shark.tracerpidapp I/shark chilli: ########## FBI WARNING ##########
11-06 14:26:07.143 14196-14218/com.shark.tracerpidapp I/shark chilli: ######### FIND DEBUGGER #########
我们的ida直接退出了,说明成功了!
这里我们并没有进入断点后就退出,是在执行的时候才退出的
我们来改进一下
void readStatus() {
FILE *fd;
char filename[128];
char line[128];
pid_t pid = syscall(__NR_getpid);
LOGI("PID : %d", pid);
sprintf(filename, "/proc/%d/status", pid);// 读取proc/pid/status中的TracerPid
if (fork() == 0) {
while (1) {
fd = fopen(filename, "r");
while (fgets(line, 128, fd)) {
if (strncmp(line, "TracerPid", 9) == 0) {
int status = atoi(&line[10]);
LOGI("########## status = %d, %s", status, line);
fclose(fd);
syscall(__NR_close, fd);
if (status != 0) {
LOGI("########## FBI WARNING ##########");
LOGI("######### FIND DEBUGGER #########");
kill(pid, SIGKILL);
return;
}
break;
}
}
sleep(CHECK_TIME);
}
} else {
LOGE("fork error");
}
}
很简单就是改成了在子进程里面跑我们的反调试逻辑.
运行使用ps查看应用
root@hammerhead:/ # ps |grep "shark"
u0_a82 14700 182 915140 42784 ffffffff 4008673c S com.shark.tracerpidapp
u0_a82 14721 14700 875236 28968 c01b9228 40085e84 S com.shark.tracerpidapp
可以看到有两个进程了
我们再次使用IDA进行调试
image.png选择父进程
image.png
直接闪退
这里有个问题我们的子进程是可以被调试的,也就是可以在子进程中绕过调试或者直接杀死子进程
再来改进
void readStatus() {
FILE *fd;
char filename[128];
char line[128];
pid_t pid = syscall(__NR_getpid);
LOGI("PID : %d", pid);
sprintf(filename, "/proc/%d/status", pid);//读取/proc/pid/status中的TracerPid
if (fork() == 0) {
int pt = ptrace(PTRACE_TRACEME, 0, 0, 0); //子进程反调试
if (pt == -1)
exit(0);
while (1) {
fd = fopen(filename, "r");
while (fgets(line, 128, fd)) {
if (strncmp(line, "TracerPid", 9) == 0) {
int status = atoi(&line[10]);
LOGI("########## status = %d, %s", status, line);
fclose(fd);
syscall(__NR_close, fd);
if (status != 0) {
LOGI("########## FBI WARNING ##########");
LOGI("######### FIND DEBUGGER #########");
kill(pid, SIGKILL);
return;
}
break;
}
}
sleep(CHECK_TIME);
}
} else {
LOGE("fork error");
}
}
上面加上了这段关键代码
int pt = ptrace(PTRACE_TRACEME, 0, 0, 0); //子进程反调试
if (pt == -1)
exit(0);
我们在子进程中使用ptrace,将请求类型设为PTRACE_TRACEME,表示让父进程跟踪自己,而进程在同一时间,只能被一个调试器调试或者跟踪,所以这里就是一个父进程,一个子进程,子进程通过读取父进程的/proc/{PID}/status文件保护父进程不被调试,同时让父进程跟踪自己,保护自己不被调试,如果ptrace失败,说明有调试器已经在调试自己,直接退出
运行,使用cat查看子进程TracerPid
root@hammerhead:/ # ps |grep "shark"
u0_a82 15097 182 915132 42776 ffffffff 4008673c S com.shark.tracerpidapp
u0_a82 15117 15097 875236 29060 c01b9228 40085e84 S com.shark.tracerpidapp
root@hammerhead:/ # cat /proc/15097/status |grep "TracerPid"
TracerPid: 0
root@hammerhead:/ # cat /proc/15117/status |grep "TracerPid"
TracerPid: 15097
root@hammerhead:/ #
上面可以看到,我们的子进程确实被父进程调试了
image.png使用ida调试后效果如上图
网友评论