so层反调试方法以及部分反反调试的方法
1.检测ida远程调试所占的常用端口23946,是否被占用
//检测idaserver是否占用了23946端口 void CheckPort23946ByTcp() { FILE* pfile=NULL; char buf[0x1000]={0}; //执行命令 char* strCatTcp="cat /proc/net/tcp | grep :5D8A"; //char* strNetstat="netstat -apn | grep :23946" pfile=popen(strCatTcp,"r"); //说明是没有被调试 if(NULL==pfile) { return; } //获取执行命令后的结果,并存入buf字符数组中 while(fgets(buf,sizeof(buf),pfile)) { printf("执行 cat /proc/net/tcp | grep :5D8A的结果:\n"); printf("%s",buf); } pclose(pfile); }
上面的netstat -apn | grep 23946 那个-apn是必须加的,之前看大佬的pdf好像漏了
反反调试方法:
1.直接nop掉
2.汇编级直接改寄存器值绕过
3. 既然是检测23946端口,那我就不运行在23946端口了,换一个端口运行
二.调试器进程名检测
原理: android调试时需要运行androidserver,androidserver64,gdb,gdbserver等进程
反调试代码:
void SerachObjectProcess() { FILE* pfile=NULL; char buf[0x1000]={0}; //执行命令 //pfile=popen("ps | awk'{print $9}'","r"); pfile=popen("ps","r"); if(pfile==NULL) { printf("命令打开失败"); return; } //获取查询结果 while(fgets(buf,sizeof(buf),pfile)) { //打印进程 printf("遍历进程:%s\n",buf); //查找子串 char* strA=NULL; char *strB = NULL; char *strC=NULL; char *strD=NULL; //IDA检测 strA=strstr(buf,"android_server"); //gdb检测 strB=strstr(buf,"gdbserver"); strC=strstr(buf,"gdb"); strD=strstr(buf,"fuwu"); if(strA||strB||strC||strD) { printf("被调试了,%s\n", buf); return; } } pclose(pfile); }
反反调试:
直接修改调试器server的名字,运行的话./自定义的server名字 -p xxxx(自定义端口)
三.父进程名检测
原理:附加调试时,父进程名都为zygote,有时候调试会使用可执行文件直接加载so文件进行调试,所以如果父进程名非zygote的话,必然是被调试的,充分非必要条件
反调试代码:
void CheckParents() { char strPpidCmdline[0x100]={0}; snprintf(strPpidCmdline, sizeof(strPpidCmdline),"proc/%d/cmdline",getppid()); int file=open(strPpidCmdline,O_RDONLY); if(file<0) { printf("打开文件错误"); return; } //初始化一下 memset(strPpidCmdline,0, sizeof(strPpidCmdline)); //将文件内容读入内存中,方便比较 ssize_t ret=read(file,strPpidCmdline, sizeof(strPpidCmdline)); if(-1==ret) { printf("读入内存失败"); return; } char* sRet=strstr(strPpidCmdline,"zygote"); if(sRet==NULL) { printf("被调试了"); return; } int i=0; return; }
反反调试:
那就直接附加调试呗,其他方法暂时我也不知道233
四.自身进程名检测
原理: 和上文一样如果用可执行文件加载so配合脱壳的话,进程名也会发生改变,检测是否是apk那种com.xxx.xx
五:检测线程的数量
原理: 正常apk启动时是要有许多进程要启动的,而如果用可执行文件加载so文件,那么必然只有一个线程
反调试代码:
void CheckTaskCount() { char buf[0x100]={0}; char* str="/proc/%d/task"; snprintf(buf,sizeof(buf),str,getpid()); //打开目录 DIR* pdir=opendir(buf); if(!pdir) { perror("CheckTaskCount open() fail.\n"); return; } //查看目录下文件的个数 struct dirent* pde=NULL; int count=0; while((pde=readdir(pdir))) { //字符过滤,每个文件都是一个线程id if((pde->d_name[0]<='9')&&(pde->d_name[0]>='0')) { count++; printf("%d 线程名称:%s\n",count,pde->d_name); } if(count<=1) { //说明被调试了 printf("被调试了"); } return; } }
https://blog.csdn.net/qq_40732350/article/details/81986548
六:apk进程的fd文件数量差异检测
原理:/proc/pid/fd目录下文件数,调试与非调试fd文件数量不同
七.安卓系统自带的检测函数
android.os.Debug.isDebuggerConnected(),这个函数是在java层中直接调用就行,
但是如果在native层使用这个也是有办法的,
1. dvm下的方式
找到进程中的libdvm.so中的dvmDbgIsDebuggerConnect()函数,调用它,通过返回值来判断程序是否被调试
dlopen(/system/lib/libdvm.so)
dlsym(_Z25dvmDbgIsDebuggerConnect())
typedef unsigned char wbool; typedef wbool (*ppp)(); void NativeIsDBGConnected() { void* Handle=NULL; Handle=dlopen("/system/lib/libdvm.so",RTLD_LAZY); if(Handle==NULL) { return; } ppp Fun=(ppp)dlsym(Handle,"_Z25dvmDbgIsDebuggerConnect"); //根据动态链接库的句柄和符号名,返回地址 if(Fun==NULL) { printf("获取函数地址失败"); return; } else { wbool ret=Fun(); if(ret==1) { printf("被调试了"); return; } } }
2.art模式
结果存放在libart.so中的全局变量gDebuggerActive中,符号名
void checkPtrace() { int iRet; iRet=ptrace(PTRACE_TRACEME,0,0,0); if(iRet==-1) { //说明父进程调试失败,说明进程已经被别的进程ptrace了 printf("已经被调试了!"); return; } else { printf("还没被调试"); } }
反反调试:
1. 修改系统源码,将ptrace返回值直接返回0
2. hook ptrace
3.nop这个函数,或者汇编级修改寄存器绕过
九.函数hash值检测
原理:文件的函数指令一般固定,如果被下了断点,指令会发生改变(bkpt断点指令),可以计算内存中一段指令的hash值,做校验
十.断点指令检测
和上文一样,如果被下了断点的话,指令会被替换成(bkpt断点指令),那么在内存搜索一下不就完事了吗,注意arm和thumb指令有所区别
反调试代码:
void checkbkpt(u8* addr,u32 size) { //结果 u32 uRet=0; //断点指令 u8 armBkpt[4]={0xf0,0x01,0xf0,0xe7}; u8 thumbBkpt[2]={0x10,0xde}; int mode=(u32)addr%2; if(1==mode) { u8* start=(u8*)((u32)addr-1); u8* end=(u8*)((u32)start+size); while(1) { if(start>=end) { uRet=0; return; } if(0==memcmp(start,thumbBkpt,2)) { uRet=1; break; } start=start+2; } } else{ //arm u8* start=(u8*)addr; u8* end=(u8*)((u32)start+size); while (1) { if(start>=end) { uRet=0; return; } if(0==memcmp(start,armBkpt,4)) { uRet=1; break; } start=start+4; } } }
十一.安卓系统源码修改反调试
原理: 直接通过修改安卓源码修改,ptrace的返回值,使其永远为零,那么我们可以先自身trace自身,然后再通过子进程再trace一遍,如果还返回为0,说明就有问题。
反调试代码:
未完