Android反调试手段收集

2020-08-28  本文已影响0人  M_天河

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

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

上一篇下一篇

猜你喜欢

热点阅读