一、检测Android——server文件
void check(){
const char* Path="/data/local/tmp";
dir =opendir(Path);
int pid =getpid();
if(dir!=NULL){
dirent *currentDir;
while((currentDir=readdir(dir)!=NULL)){
//使用readdir函数读取当前目录下的每一个文件
if(strncmp(currentDir->d_name,"android_server",14)==0)//currentDir->de_name结构体能够获取当前的文件名
{
kill(pid,SIGKILL);
}
}
closedir(dir);//关闭文件
}else{
}
}
分析:
这算的上是最简单的一种反调试检测了,原理很简单,使用currentDir
结构体检测获取
/data/local/tmp目录下是否有android_server文件
反制措施:
反制措施有很多种,最便捷的方法就是进入/data/local/tmp目录下,把文件名改掉就行了。
二、检测进程名
这种方法之前已经分析过,这里进行更详细的分析
void check()
{
const int bufsize=1024;
char filename[bufsize];
char line[bufsize];
char name[bufsize];
char nameline[bufsize];
//获取Tracepid的值
int pid=getpid();
sprint(filename,"/proc/%d/status",pid);
FILE *fd=fopen(filename,"r");
if(fd!=NULL)
{
while(fgets(line,bufsize,fd))
{
//遍历读取到“TracePid”
if(strstr(line,"TracePid")!=NULL)
{
//判断TracePid的第10个数是否为0
int statue=atoi(&line[10]);
if(statue!=0)
{
//不等于0时,将当前的statue写入到name中
sprint(name,"/proc/%d/cmdline",statue);
//读取文件fdname
FILE *fdname=fopen(name,"r");
if(fdname!=NULL)
{
//遍历找到“android_server”
while(fgets(nameline,bufsize,fdname))
{
if(strstr(nameline,"android_server"!=NULL))
{
int ret =kill(pid,SIGKILL);
//kill进程
}
}
}
fclose(fdname);
}
}
}
}
fclose(fd);
}
strstr:定义:char *strstr(const char *haystack, const char *needle) 功能:haystack – 要被检索的 C 字符串。 needle – 在 haystack 字符串内要搜索的小字符串。该函数返回在 haystack 中第一次出现 needle 字符串的位置,如果未找到则返回 null。
atoi: 定义:int atoi(const char *str),功能:str – 要转换为整数的字符串。该函数返回转换后的长整数,如果没有执行有效的转换,则返回零。
分析:首先检测Tracepid的值是否为0,如果为0,在遍历检测是否有android_server,如果有直接kill进程
反制措施: 还是简单的说一下,一般这种检测,我们会尝试在ida下断,在运行到指定函数时将其返回值改为0,当然也可以采用其他的方法比如nop掉跳转指令,让它不进行检测,复杂一点的有个方法可以从根本上解决这个反调试问题,那就是刷机修改源码,这里贴一个大佬的帖子https://se8s0n.github.io/2019/04/19/%E5%B0%9D%E8%AF%95%E7%BB%95%E8%BF%87TracePID%E5%8F%8D%E8%B0%83%E8%AF%95%E4%BA%8C%E2%80%94%E2%80%94%E4%BB%8E%E6%BA%90%E7%A0%81%E5%85%A5%E6%89%8B/
三、检测常用的端口
void check()
{
FILE* pfile=NULL;
char buf[0x10000]={
0};
//使用代码执行cmd命令
char* strCatTcpPort="cat /proc/net/tcp |grep :5D8A";
pfile = popen(strCatTcpPort,"r");
int pid=getpid();//获取进程的pid
if(NULL==pfile)
{
//命令运行失败
return;
}
//读取pfile文件内容,如果android_server没有启动的话,常文件内不会有内容
while(fgets(buf,sizeof(buf),pfile))
{
//检测到23946端口被占用,直接kill进程
int ret=kill(pid,SIGKILL);
}
pclose(pfile);
}
分析:
这里使用了cat /proc/net/tcp |grep :5D8A
命令,读取了23946端口被使用后的产生的数据,在使用strCatTcpPort获取的其数据,然后写入pfile中,最后
ps:fget函数
如果成功,该函数返回相同的 str 参数。
如果到达文件末尾或者没有读取到任何字符
**模拟执行效果:**当我们没有启动android_server时,使用了cat /proc/net/tcp |grep :5D8A
命令效果:
没有任何输出
而我们使用了启动了android_server后
反制措施:
既然是检测端口,那么直接更改掉端口android_server就可以了
使用./android_server -p
后面跟上你的端口
四、轮循检测
void anti_debugger()
{
pthread_t tid;
pthread_create(&tid,NULL,&anti_debug_thread,NULL);
}
void *anti_debugger_thread(void*data)
{
pid_t pid=getpid();
while(true)
{
check_debugger(pid)
}
}
bool check_debugger(pid_t pid)
{
const int pathSize=256;
const int bufSize=1024;
char path[pathSize];
char line[bufSize];
snprintf(path,sizeof(path)-1,"/proc/%d/status",pid);
bool result =true;
FILE *fp=fopen(path,"rt");
if(fp!=NULL)
{
while(fgets(line,sizeof(line),fp))
{
if(strncmp(line,TRACERPID,TRACERPID_LEN)==0)
{
pid_t tracerPid=0;
sscanf(line,"%*s%d",&tracerPid);
if(!tracerPid)
result =false
break;
}
}
fclose(fp);
}
return result;
}
分析:也是检测pid判断其是否为0,如果不为0就进行死循环,这种方法缺点很明显就是占用内存,一般不常见
五、fork子进程调试父进程
void protect_father()
{
pid_t ppid=getppid();
//获取父进程pid
attach(ppid);
}
void attach(pid_t pid)
{
long err =ptrace(PTRACE_ATTACH,pid,NULL,NULL);
if(err<0)
{
perror("PTRACE_ATTACH");
exit(EXIT_FAILURE);
}
}
分析:由于父进程被子进程调试这里就可以考虑去直接调试其子进程。
六、自己ptrace自己
void anti_debug01()
{
ptrace(PTRACE_TRACEME,0,0,0);
}
这个之前文章已经讲得很详细说过了
总结
so层反调试过掉有很多方法,最主要的方法就是nop法,动态调试修改返回值等方法,需要注意的是,现在很多的反调试都不会只是单纯的反调试,同时还会和许多加密算法联系在一起,这样就会大大增加了分析反调试的能力。