Android native反调试检测及原理剖析

一、检测Android——server文件

void check(){
    
    
const char* Path="/data/local/tmp";
dir =opendir(Path);
int pid =getpid();
if(dir!=NULL){
    
    
    dirent *currentDir;
    while((currentDir=readdir(dir)!=NULL)){
    
    
        //使用readdir函数读取当前目录下的每一个文件
        if(strncmp(currentDir->d_name,"android_server",14)==0)//currentDir->de_name结构体能够获取当前的文件名
        {
    
    
            kill(pid,SIGKILL);
        }
    }
    closedir(dir);//关闭文件
    }else{
    
    
        
    }
}

分析
这算的上是最简单的一种反调试检测了,原理很简单,使用currentDir结构体检测获取
/data/local/tmp目录下是否有android_server文件

反制措施:
反制措施有很多种,最便捷的方法就是进入/data/local/tmp目录下,把文件名改掉就行了。

二、检测进程名

这种方法之前已经分析过,这里进行更详细的分析

void check()
{
    
    
    const int bufsize=1024;
    char filename[bufsize];
    char line[bufsize];
    char name[bufsize];
    char nameline[bufsize];
    //获取Tracepid的值
    int pid=getpid();
    sprint(filename,"/proc/%d/status",pid);
    FILE *fd=fopen(filename,"r");
    if(fd!=NULL)
    {
    
    
        while(fgets(line,bufsize,fd))
        {
    
    
           //遍历读取到“TracePid”
           if(strstr(line,"TracePid")!=NULL)
            {
    
    
            //判断TracePid的第10个数是否为0
                int statue=atoi(&line[10]);
                if(statue!=0)
                {
    
    
                //不等于0时,将当前的statue写入到name中
                    sprint(name,"/proc/%d/cmdline",statue);
                    //读取文件fdname
                    FILE *fdname=fopen(name,"r");
                    if(fdname!=NULL)
                    {
    
    
                    //遍历找到“android_server”
                        while(fgets(nameline,bufsize,fdname))
                        {
    
    
                            if(strstr(nameline,"android_server"!=NULL))
                            {
    
    
                                int ret =kill(pid,SIGKILL);
                                //kill进程
                            }
                        }
                    }
                    fclose(fdname);
                }
            }
        }
    }
    fclose(fd);
}

strstr:定义:char *strstr(const char *haystack, const char *needle) 功能haystack – 要被检索的 C 字符串。 needle – 在 haystack 字符串内要搜索的小字符串。该函数返回在 haystack 中第一次出现 needle 字符串的位置,如果未找到则返回 null

atoi: 定义:int atoi(const char *str),功能:str – 要转换为整数的字符串。该函数返回转换后的长整数,如果没有执行有效的转换,则返回零。

分析:首先检测Tracepid的值是否为0,如果为0,在遍历检测是否有android_server,如果有直接kill进程

反制措施: 还是简单的说一下,一般这种检测,我们会尝试在ida下断,在运行到指定函数时将其返回值改为0,当然也可以采用其他的方法比如nop掉跳转指令,让它不进行检测,复杂一点的有个方法可以从根本上解决这个反调试问题,那就是刷机修改源码,这里贴一个大佬的帖子https://se8s0n.github.io/2019/04/19/%E5%B0%9D%E8%AF%95%E7%BB%95%E8%BF%87TracePID%E5%8F%8D%E8%B0%83%E8%AF%95%E4%BA%8C%E2%80%94%E2%80%94%E4%BB%8E%E6%BA%90%E7%A0%81%E5%85%A5%E6%89%8B/

三、检测常用的端口

void check()
{
    
    
    FILE* pfile=NULL;
    char buf[0x10000]={
    
    0};
    //使用代码执行cmd命令
    char* strCatTcpPort="cat /proc/net/tcp |grep :5D8A";
    pfile = popen(strCatTcpPort,"r");
    int pid=getpid();//获取进程的pid
    if(NULL==pfile)
    {
    
    
        //命令运行失败
        return;
    }
    //读取pfile文件内容,如果android_server没有启动的话,常文件内不会有内容
    while(fgets(buf,sizeof(buf),pfile))
    {
    
    
        //检测到23946端口被占用,直接kill进程
        int ret=kill(pid,SIGKILL);
    }
    pclose(pfile);
}

分析:
这里使用了cat /proc/net/tcp |grep :5D8A命令,读取了23946端口被使用后的产生的数据,在使用strCatTcpPort获取的其数据,然后写入pfile中,最后

ps:fget函数
如果成功,该函数返回相同的 str 参数。
如果到达文件末尾或者没有读取到任何字符

**模拟执行效果:**当我们没有启动android_server时,使用了cat /proc/net/tcp |grep :5D8A命令效果:

在这里插入图片描述
没有任何输出

而我们使用了启动了android_server后
在这里插入图片描述
反制措施:
既然是检测端口,那么直接更改掉端口android_server就可以了
使用./android_server -p后面跟上你的端口
在这里插入图片描述

四、轮循检测

void anti_debugger()
{
    
    
    pthread_t tid;
    pthread_create(&tid,NULL,&anti_debug_thread,NULL);
}
void *anti_debugger_thread(void*data)
{
    
    
    pid_t pid=getpid();
    while(true)
    {
    
    
        check_debugger(pid)
    }
}
bool check_debugger(pid_t pid)
{
    
    
    const int pathSize=256;
    const int bufSize=1024;
    char path[pathSize];
    char line[bufSize];
    snprintf(path,sizeof(path)-1,"/proc/%d/status",pid);
    bool result =true;
    FILE *fp=fopen(path,"rt");
    if(fp!=NULL)
    {
    
    
        while(fgets(line,sizeof(line),fp))
        {
    
    
            if(strncmp(line,TRACERPID,TRACERPID_LEN)==0)
            {
    
    
                pid_t tracerPid=0;
                sscanf(line,"%*s%d",&tracerPid);
                if(!tracerPid)
                result =false
                break;
            }
        }
        fclose(fp);
    }
    return result;
}

分析:也是检测pid判断其是否为0,如果不为0就进行死循环,这种方法缺点很明显就是占用内存,一般不常见

五、fork子进程调试父进程

void protect_father()
{
    
    
    pid_t ppid=getppid();
    //获取父进程pid
    attach(ppid);
}
void attach(pid_t pid)
{
    
    
        long err =ptrace(PTRACE_ATTACH,pid,NULL,NULL);
    if(err<0)
    {
    
    
        perror("PTRACE_ATTACH");
        exit(EXIT_FAILURE);
        
    }
}

分析:由于父进程被子进程调试这里就可以考虑去直接调试其子进程。

六、自己ptrace自己

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

这个之前文章已经讲得很详细说过了

总结

so层反调试过掉有很多方法,最主要的方法就是nop法动态调试修改返回值等方法,需要注意的是,现在很多的反调试都不会只是单纯的反调试,同时还会和许多加密算法联系在一起,这样就会大大增加了分析反调试的能力。

猜你喜欢

转载自blog.csdn.net/weixin_43632667/article/details/105353383