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法動態調試修改返回值等方法,需要注意的是,現在很多的反調試都不會只是單純的反調試,同時還會和許多加密算法聯繫在一起,這樣就會大大增加了分析反調試的能力。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章