zoukankan      html  css  js  c++  java
  • 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的结果:
    ");
            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
    ",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
    ", 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.
    ");
            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
    ",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中,符号名

    _ZN3art3Dbg15gDebuggerActiveE,art无法使用dlopen在打开so文件了
    所以只能在内存搜索,手动查找
    八.ptrace检测
    原理:一个进程只能被ptrace一次,可以自己ptrace自己,如果一节被调试器ptrace了,自己ptrace肯定ptrace不了,根据返回值进行判断
    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,说明就有问题。

    反调试代码:

    未完

  • 相关阅读:
    86. Partition List
    328. Odd Even Linked List
    19. Remove Nth Node From End of List(移除倒数第N的结点, 快慢指针)
    24. Swap Nodes in Pairs
    2. Add Two Numbers(2个链表相加)
    92. Reverse Linked List II(链表部分反转)
    109. Convert Sorted List to Binary Search Tree
    138. Copy List with Random Pointer
    为Unity的新版ugui的Prefab生成预览图
    ArcEngine生成矩形缓冲区
  • 原文地址:https://www.cnblogs.com/YenKoc/p/14043654.html
Copyright © 2011-2022 走看看