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,说明就有问题。

    反调试代码:

    未完

  • 相关阅读:
    HP惠普战66电源黄灯闪烁无法充电
    C#.NET rabbit mq 持久化时报错 durable
    手动解压安装mysql8.0 on windows my.ini
    C#.NET MySql8.0 EF db first
    EF MYSQL 出现:输入字符串的格式不正确
    EF MYSQL DB FIRST 出现2次数据库名
    mysql windows 下配置可远程连接
    团队项目的Git分支管理规范
    一个简单的软件测试流程
    微服务架构下的质量迷思——混沌工程
  • 原文地址:https://www.cnblogs.com/YenKoc/p/14043654.html
Copyright © 2011-2022 走看看