so層反調試方法以及部分反反調試的方法


1.檢測ida遠程調試所占的常用端口23946,是否被占用

//檢測idaserver是否占用了23946端口
void CheckPort23946ByTcp() {
    FILE* pfile=NULL;
    char buf[0x1000]={0};
    //執行命令
    char* strCatTcp="cat /proc/net/tcp | grep :5D8A";
    //char* strNetstat="netstat -apn | grep :23946"
    pfile=popen(strCatTcp,"r");
    //說明是沒有被調試
    if(NULL==pfile)
    {
        return;
    }
    //獲取執行命令后的結果,並存入buf字符數組中
    while(fgets(buf,sizeof(buf),pfile))
    {
        printf("執行 cat /proc/net/tcp | grep :5D8A的結果:\n");
        printf("%s",buf);
    }
    pclose(pfile);
}

上面的netstat -apn | grep 23946 那個-apn是必須加的,之前看大佬的pdf好像漏了

反反調試方法:

1.直接nop掉

2.匯編級直接改寄存器值繞過

3. 既然是檢測23946端口,那我就不運行在23946端口了,換一個端口運行

二.調試器進程名檢測

原理: android調試時需要運行androidserver,androidserver64,gdb,gdbserver等進程

反調試代碼:

void SerachObjectProcess()
{
    FILE* pfile=NULL;
    char buf[0x1000]={0};
    //執行命令
    //pfile=popen("ps | awk'{print $9}'","r");
    pfile=popen("ps","r");
    if(pfile==NULL) {
        printf("命令打開失敗");
        return;
    }
    //獲取查詢結果
    while(fgets(buf,sizeof(buf),pfile))
    {
        //打印進程
        printf("遍歷進程:%s\n",buf);
        //查找子串
        char* strA=NULL;
        char *strB = NULL;
        char *strC=NULL;
        char *strD=NULL;
        //IDA檢測
        strA=strstr(buf,"android_server");
        //gdb檢測
        strB=strstr(buf,"gdbserver");
        strC=strstr(buf,"gdb");
        strD=strstr(buf,"fuwu");
        if(strA||strB||strC||strD) {
            printf("被調試了,%s\n", buf);
            return;
        }
    }
    pclose(pfile);

}

反反調試:

直接修改調試器server的名字,運行的話./自定義的server名字 -p xxxx(自定義端口) 

三.父進程名檢測

原理:附加調試時,父進程名都為zygote,有時候調試會使用可執行文件直接加載so文件進行調試,所以如果父進程名非zygote的話,必然是被調試的,充分非必要條件

反調試代碼:

void CheckParents()
{
    char strPpidCmdline[0x100]={0};
    snprintf(strPpidCmdline, sizeof(strPpidCmdline),"proc/%d/cmdline",getppid());
    int file=open(strPpidCmdline,O_RDONLY);
    if(file<0)
    {
        printf("打開文件錯誤");
        return;
    }
    //初始化一下
    memset(strPpidCmdline,0, sizeof(strPpidCmdline));
    //將文件內容讀入內存中,方便比較
    ssize_t  ret=read(file,strPpidCmdline, sizeof(strPpidCmdline));
    if(-1==ret)
    {
        printf("讀入內存失敗");
        return;
    }
    char* sRet=strstr(strPpidCmdline,"zygote");
    if(sRet==NULL)
    {
        printf("被調試了");
        return;
    }
    int i=0;
    return;
}

反反調試:
那就直接附加調試唄,其他方法暫時我也不知道233

四.自身進程名檢測

原理: 和上文一樣如果用可執行文件加載so配合脫殼的話,進程名也會發生改變,檢測是否是apk那種com.xxx.xx

五:檢測線程的數量

原理: 正常apk啟動時是要有許多進程要啟動的,而如果用可執行文件加載so文件,那么必然只有一個線程

反調試代碼:

void CheckTaskCount()
{
    char buf[0x100]={0};
    char* str="/proc/%d/task";
    snprintf(buf,sizeof(buf),str,getpid());
    //打開目錄
    DIR* pdir=opendir(buf);
    if(!pdir)
    {
        perror("CheckTaskCount open() fail.\n");
        return;
    }
    //查看目錄下文件的個數
    struct dirent* pde=NULL;
    int count=0;
    while((pde=readdir(pdir)))
    {
        //字符過濾,每個文件都是一個線程id
        if((pde->d_name[0]<='9')&&(pde->d_name[0]>='0'))
        {
            count++;
            printf("%d 線程名稱:%s\n",count,pde->d_name);
        }
        if(count<=1)
        {
            //說明被調試了
            printf("被調試了");
        }
        return;
    }

}

https://blog.csdn.net/qq_40732350/article/details/81986548

六:apk進程的fd文件數量差異檢測

原理:/proc/pid/fd目錄下文件數,調試與非調試fd文件數量不同

七.安卓系統自帶的檢測函數

android.os.Debug.isDebuggerConnected(),這個函數是在java層中直接調用就行,

但是如果在native層使用這個也是有辦法的,

1. dvm下的方式

找到進程中的libdvm.so中的dvmDbgIsDebuggerConnect()函數,調用它,通過返回值來判斷程序是否被調試

dlopen(/system/lib/libdvm.so)

dlsym(_Z25dvmDbgIsDebuggerConnect())

typedef unsigned char wbool;
typedef wbool (*ppp)();
void NativeIsDBGConnected()
{
    void* Handle=NULL;
    Handle=dlopen("/system/lib/libdvm.so",RTLD_LAZY);
    if(Handle==NULL)
    {
        return;
    }
    ppp Fun=(ppp)dlsym(Handle,"_Z25dvmDbgIsDebuggerConnect"); //根據動態鏈接庫的句柄和符號名,返回地址
    if(Fun==NULL) {
        printf("獲取函數地址失敗");
        return;
    } else
    {
        wbool ret=Fun();
        if(ret==1)
        {
            printf("被調試了");
            return;
        }
    }
}

2.art模式

結果存放在libart.so中的全局變量gDebuggerActive中,符號名

_ZN3art3Dbg15gDebuggerActiveE,art無法使用dlopen在打開so文件了
所以只能在內存搜索,手動查找
八.ptrace檢測
原理:一個進程只能被ptrace一次,可以自己ptrace自己,如果一節被調試器ptrace了,自己ptrace肯定ptrace不了,根據返回值進行判斷
void checkPtrace()
{
    int iRet;
    iRet=ptrace(PTRACE_TRACEME,0,0,0);
    if(iRet==-1)
    {
        //說明父進程調試失敗,說明進程已經被別的進程ptrace了
        printf("已經被調試了!");
        return;
    } else
    {
        printf("還沒被調試");
    }
}

反反調試:

1. 修改系統源碼,將ptrace返回值直接返回0

2. hook ptrace

3.nop這個函數,或者匯編級修改寄存器繞過

九.函數hash值檢測

原理:文件的函數指令一般固定,如果被下了斷點,指令會發生改變(bkpt斷點指令),可以計算內存中一段指令的hash值,做校驗

十.斷點指令檢測

和上文一樣,如果被下了斷點的話,指令會被替換成(bkpt斷點指令),那么在內存搜索一下不就完事了嗎,注意arm和thumb指令有所區別

反調試代碼:

void checkbkpt(u8* addr,u32 size)
{
    //結果
    u32 uRet=0;
    //斷點指令
    u8 armBkpt[4]={0xf0,0x01,0xf0,0xe7};
    u8 thumbBkpt[2]={0x10,0xde};
    int mode=(u32)addr%2;
    if(1==mode)
    {
        u8* start=(u8*)((u32)addr-1);
        u8* end=(u8*)((u32)start+size);
        while(1)
        {
            if(start>=end)
            {
                uRet=0;
                return;
            }
            if(0==memcmp(start,thumbBkpt,2))
            {
                uRet=1;
                break;
            }
            start=start+2;
        }
    } else{
        //arm
        u8* start=(u8*)addr;
        u8* end=(u8*)((u32)start+size);
        while (1)
        {
            if(start>=end)
            {
                uRet=0;
                return;
            }
            if(0==memcmp(start,armBkpt,4))
            {
                uRet=1;
                break;
            }
            start=start+4;
        }
        
    }
}

十一.安卓系統源碼修改反調試

原理: 直接通過修改安卓源碼修改,ptrace的返回值,使其永遠為零,那么我們可以先自身trace自身,然后再通過子進程再trace一遍,如果還返回為0,說明就有問題。

反調試代碼:

未完

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM