Android反调试手段收集

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

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
如果不想编译内核也可以修改手机内核绕过反调试//www.greatytc.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

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 216,372评论 6 498
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 92,368评论 3 392
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 162,415评论 0 353
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 58,157评论 1 292
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 67,171评论 6 388
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 51,125评论 1 297
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 40,028评论 3 417
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 38,887评论 0 274
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 45,310评论 1 310
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 37,533评论 2 332
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 39,690评论 1 348
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 35,411评论 5 343
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 41,004评论 3 325
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 31,659评论 0 22
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,812评论 1 268
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 47,693评论 2 368
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 44,577评论 2 353