美文网首页
Android反调试手段收集

Android反调试手段收集

作者: M_天河 | 来源:发表于2020-08-28 18:17 被阅读0次

    恶意应用可以用来躲避杀软监测,正常应用也可以用来保护代码不被窃取。

    1,ptrace检测

    一个进程只能被一个进程追踪,程序中添加追踪自身的代码导致ida附加失败,无线实现调试。

    void ptraceCheck()
    {
         ptrace(PTRACE_TRACEME, 0, 0, 0);
    }
    

    或者检测返回值,

    void ptraceCheck()
    {
        int ck=ptrace(PTRACE_TRACEME, 0, 0, 0);
        if(ck == -1)
        {
            LOGA("进程正在被调试\n");
            return;
        }else
        {
            LOGB("ptrace的返回值为:%d\n",ck);
            return;
         }
    }
    
    2,检测TracerPid的值

    原理同1,正常情况下TracePid为0,进程被追踪时TracePid的值会变成追踪进程的Pid

    void anti_debug02(){
        const int bufsize=1024;
        char filename[bufsize];
        char line [bufsize];
        int pid=getpid();//获取目前进程的进程的Pid
        FILE *fp;
        sprintf(filename,"proc/%d/status",pid);
        fp=fopen(filename,"r");//
        if (fp!= NULL){
            while(fgets(line,bufsize,fp)){
                if(strncmp(line,"TracerPid",9)==0){
                    int status=atoi(&line[10]);
                    if(status!=0){
                        fclose(fp);//先关闭
                        LOGD("%s","antidebug02 run  exit");
                        int ret=kill(pid,SIGKILL);
                    }
                break;
               }
            LOGD("%s","no antidebug02 run");
          }
    }
    

    对抗方法:修改内核代码编译刷机,代码修改位置kernel/msm/fs/proc/base.ckernel/msm/fs/proc/array.c
    如果不想编译内核也可以修改手机内核绕过反调试https://www.jianshu.com/p/91aa37f3a972
    反对抗方法:创建一个子进程,让子进程主动ptrace自身设为调试状态,此时的子进程的tracepid应该不为0。如果检测到子进程的tracepid为0,说明源码被修改了。

    3,检测ida常用端口

    ida的默认端口是23946,检测端口是否被占用确认是否是调试状态。

    void CheckPort23946()
    {
        FILE* pfile=NULL;
        char buf[0x1000]={0};
        char* strCatTcp= "cat /proc/net/tcp |grep :5D8A";
        //char* strNetstat="netstat |grep :23946";
        pfile=popen(strCatTcp,"r");
        if(NULL==pfile)
        {
          LOGA("未发现23946端口占用\n");
          return;
        }
        while(fgets(buf,sizeof(buf),pfile))
        {
            // 检测到23946被占用
            LOGA("执行cat /proc/net/tcp |grep :5D8A的结果:%s\n",buf);
        }//while
        pclose(pfile);
    }
    

    对抗方法:ida调试时可通过-p命令修改端口
    tips:可直接修改ida中的android_server文件,免去每次都要加参数的麻烦。

    4,检测Android_server文件是否存在
    void checkAndroid_serverFile(){
        const char* rootPath = "/data/local/tmp";
        DIR* dir;
        dir = opendir(rootPath);
        if (dir!= NULL) {
            dirent *currentDir;
            while ((currentDir = readdir(dir)) != NULL) {
                if(strncmp(currentDir->d_name,"android_server",14)==0){
                    LOGD("%s",currentDir->d_name);
                    LOGD("%s","发现android_server");
                }
            }
            closedir(dir); 
        } else{
            LOGD("%s","dir not access");
        }
    }
    

    对抗方法:换个文件名即可;

    5,检测android_server进程是否存在

    执行ps命令获取进程列表查找到android_server名即可确认在调试

    void SearchObjProcess()
    {
        FILE* pfile=NULL;
        char buf[0x1000]={0};
        pfile=popen("ps","r");
        if(pfile == NULL)
        {
            LOGA("ps命令失败!\n");
            return;
        }
        while(fgets(buf,sizeof(buf),pfile))
        {
            LOGB("遍历进程:%s\n",buf);
            char* strA=NULL;
            strA=strstr(buf,"android_server");        
            if(strA != NULL)
            {
                LOGB("发现调试进程:%s\n",buf);
            }
        }
        pclose(pfile);
    }
    
    6,检测apk中的线程数量

    正常apk运行加载so时会由多个线程,写可执行文件加载so的时候只有一个线程,可以检测线程数量来判断运行环境是否正常。

    void CheckThreadNum()
    {
        char buf[0x100] = {0};
        char* str = "/proc/%d/task";
        snprintf(buf, sizeof(buf), str, getpid());
        DIR* pdir = opendir(buf);
        if (!pdir)
        {
            LOGA("任务文件打开失败。\n");
            return;
        }
        struct dirent* pde=NULL;
        int Num=0;
        while ((pde = readdir(pdir)))
        {
            if ((pde->d_name[0] <= '9') && (pde->d_name[0] >= '0'))
            {
                ++Count;
                LOGB("%d 线程名称:%s\n",Num,pde->d_name);
            }
         }
        LOGB("线程个数为:%d",Num);
        if(Num<=1)
        {
            LOGA("只有一个线程,确定是调试状态!\n");
        }
        int i=0;
        return;
    }
    
    
    7,检测调试状态下的软件断点

    调试时在函数中下了断点,地址就会被改成bkpt指令,可以通过在函数中搜索bkpt指令来检测断点。

    void checkBreakPoint(){
        Elf32_Ehdr *elfhdr;
        Elf32_Phdr *pht;
        unsigned int size, base, offset,phtable;
        int n, i,j;
        char *p;
        base = GetLibAddr();
        if(base == 0){
            LOGD("find base error/n");
            return;
        }
        elfhdr = (Elf32_Ehdr *) base;
        phtable = elfhdr->e_phoff + base;
        for(i=0;i<elfhdr->e_phnum;i++){
            pht = (Elf32_Phdr*)(phtable+i*sizeof(Elf32_Phdr));
            if(pht->p_flags&1){
                offset = pht->p_vaddr + base + sizeof(Elf32_Ehdr) + sizeof(Elf32_Phdr)*elfhdr->e_phnum;
                LOGD("offset:%X ,len:%X",offset,pht->p_memsz);
                p = (char*)offset;
                size = pht->p_memsz;
                for(j=0,n=0;j<size;++j,++p){
                    if(*p == 0x10 && *(p+1) == 0xde){
                        n++;
                        LOGD("### find thumb bpt %X /n",p);
    
                    }else if(*p == 0xf0 && *(p+1) == 0xf7 && *(p+2) == 0x00 && *(p+3) == 0xa0){
                        n++;
                        LOGD("### find thumb2 bpt %X /n",p);
                    }else if(*p == 0x01 && *(p+1) == 0x00 && *(p+2) == 0x9f && *(p+3) == 0xef){
                        n++;
                        LOGD("### find arm bpt %X /n",p);
                    }
                }
                LOGD("### find breakpoint num: %d/n",n);
            }
        }
    
    }
    
    8,检测代码运行时间差

    利用调试时函数的运行时间差来检测,过长则判定为调试

    int gettimeofday(struct timeval *tv, struct timezone *tz);
    void checkTimeDiff()
    {
        int pid = getpid();
        struct timeval t1;
        struct timeval t2;
        struct timezone tz;
        gettimeofday(&t1, &tz);
        gettimeofday(&t2, &tz);
        int timeoff = (t2.tv_sec) - (t1.tv_sec);
        if (timeoff > 1) {
            int ret = kill(pid, SIGKILL);
            return ;
        }
    }
    
    9,单步调试陷阱

    调试器从下断点到执行断点的过程分析:

    1. 保存:保存目标处指令
    2. 替换:目标处指令替换为断点指令
    3. 命中断点:命中断点指令(引发中断 或者说发出信号)
    4. 收到信号:调试器收到信号后,执行调试器注册的信号处理函数。
    5. 恢复:调试器处理函数恢复保存的指令
    6. 回退:回退PC寄存器
    7. 控制权回归程序.

    主动设置断点指令/注册信号处理函数的反调试方案:

    1. 在函数中写入断点指令
    2. 在代码中注册断点信号处理函数
    3. 程序执行到断点指令,发出信号

    分两种情况:

    1. 非调试状态
      进入自己注册的函数,NOP指令替换断点指令,回退PC后正常指令。
      (执行断点发出信号—进入处理信号函数—NOP替换断点—退回PC)
    2. 调试状态
      进入调试器的断点处理流程,他会恢复目标处指令失败,然后回退PC,进入死循环。
    10,利用ida先截获信号的特性

    IDA会首先截获信号,导致进程无法接收到信号,导致不会执行信号处理函数。将关键流程
    放在信号处理函数中,如果没有执行,就是被调试状态。

    #include <stdio.h>
    #include <signal.h>
    #include <unistd.h>
    void myhandler(int sig)
    {
        //signal(5, myhandler);
        printf("myhandler.\n");
        return;
    }
    int g_ret = 0;
    int main(int argc, char **argv)
    {
        // 设置SIGTRAP信号的处理函数为myhandler()
        g_ret = (int)signal(SIGTRAP, myhandler);
        if ( (int)SIG_ERR == g_ret )
        printf("信号返回错误!\n");
        printf("signal ret value is %x\n",(unsigned char*)g_ret);
        raise(SIGTRAP);
        raise(SIGTRAP);
        raise(SIGTRAP);
        kill(getpid(), SIGTRAP);
        printf("main.\n");
        return 0;
    }
    
    

    参考:http://zt.360.cn/1101061855.php?dtid=1101061451&did=210078060

    相关文章

      网友评论

          本文标题:Android反调试手段收集

          本文链接:https://www.haomeiwen.com/subject/vqotrktx.html