<pre id="vvttv"><mark id="vvttv"><progress id="vvttv"></progress></mark></pre>
    <pre id="vvttv"></pre>

      <p id="vvttv"></p>

          <p id="vvttv"></p>

                <p id="vvttv"></p>

                <pre id="vvttv"><cite id="vvttv"><progress id="vvttv"></progress></cite></pre>

                  <output id="vvttv"><dfn id="vvttv"><th id="vvttv"></th></dfn></output>

                    <p id="vvttv"></p>

                    原文地址:http://drops.wooyun.org/mobile/16969

                    Author:超六、曲和

                    0x00 時間相關反調試


                    通過計算某部分代碼的執行時間差來判斷是否被調試,在Linux內核下可以通過time、gettimeofday,或者直接通過sys call來獲取當前時間。另外,還可以通過自定義SIGALRM信號來判斷程序運行是否超時。

                    0x01 檢測關鍵文件


                    (1)/proc/pid/status、/proc/pid/task/pid/status

                    在調試狀態下,Linux內核會向某些文件寫入一些進程狀態的信息,比如向/proc/pid/status或/proc/pid/task/pid/status文件的TracerPid字段寫入調試進程的pid,在該文件的statue字段中寫入t(tracing stop):

                    pic1

                    (2)/proc/pid/stat、/proc/pid/task/pid/stat

                    調試狀態下/proc/pid/stat、/proc/pid/task/pid/stat文件中第二個字段是t(T):

                    pic2

                    (3)/proc/pid/wchan、/proc/pid/task/pid/wchan

                    若進程被調試,也會往/proc/pid/wchan、/proc/pid/task/pid/wchan文件中寫入ptrace_stop。

                    0x02 檢測端口號


                    使用IDA動態調試APK時,android_server默認監聽23946端口,所以通過檢測端口號可以起到一定的反調試作用。具體而言,可以通過檢測/proc/net/tcp文件,或者直接system執行命令netstat -apn等。

                    0x03 檢測android_server、gdb、gdbserver


                    在對APK進行動態調試時,可能會打開android_server、gdb、gdbserver等調試相關進程,一般情況下,這幾個打開的進程名和文件名相同,所以可以通過運行狀態下的進程名來檢測這些調試相關進程。具體而言,可以通過打開/proc/pid/cmdline、/proc/pid/statue等文件來獲取進程名。當然,這種檢測方法非常容易繞過――直接修改android_server、gdb、gdbserver的名字即可。

                    0x04 signal


                    信號機制在apk調試攻防中有著非常重要的作用,大部分主流加固廠商都會通過信號機制來增加殼的強度。在反調試中最常見的要數SIGTRAP信號了,SIGTRAP原本是調試器設置斷點時發出的信號,為了能更好的理解SIGTRAP信號反調試,先讓我們看看一下調試器設置斷點的原理:

                    和x86架構類似,arm架構下調試器設置斷點先要完成兩件事:

                    1. 保存目標地址上的數據
                    2. 將目標地址上頭幾個字節替換成arm/thumb下的breakpoint指令

                    Arm架構下各類指令集breakpoint機器碼如下:

                    指令集 Breakpoint機器碼(little endian)
                    Arm 0x01, 0x00, 0x9f, 0xef
                    Thumb 0x01, 0xde
                    Thumb2 0xf0, 0xf7, 0x00, 0xa0

                    調試器設置完斷點之后程序繼續運行,直至命中斷點,觸發breakpoint,這時程序向操作系統發送SIGTRAP信號。調試器收到SIGTRAP信號后,會繼續完成以下幾件事:

                    1. 在目標地址上用原來的指令替換之前的breakpoint指令
                    2. 回退被跟蹤進程的當前pc值

                    當控制權回到原進程時,pc就恰好指向了斷點所在位置,這就是調試器設置斷點的基本原理。在知道上述原理之后,再讓我們繼續分析SIGTRAP反調試的細節,如果我們在程序中間插入一條breakpoint指令,而不做其他處理的話,操作系統會用原來的指令替換breakpoint指令,然而這個breakpoint是我們自定義插入的,該地址上并不存在原指令,所以操作系統就跳過這個步驟,進入下一步回退pc值,即breakpoint的前一條指令。這時就出現問題了,下一條指令還是breakpoint指令,這也就造成了無限循環。

                    為了能繼續正常執行,就需要模擬調試器的操作――替換breakpoint指令,而完成這個步驟的最佳時機就是在自定義signal的handle中。Talk is cheap,show me the code,下面給出此原理的簡單實例:

                    #!cpp
                    char dynamic_ccode[] = {0x1f,0xb4, //push {r0-r4}
                                            0x01,0xde, //breakpoint
                                            0x1f,0xbc, //pop {r0-r4}
                                            0xf7,0x46};//mov pc,lr
                    
                    char *g_addr = 0;
                    
                    void my_sigtrap(int sig){
                    
                        char change_bkp[] = {0x00,0x46}; //mov r0,r0
                        memcpy(g_addr+2,change_bkp,2);
                        __clear_cache((void*)g_addr,(void*)(g_addr+8)); // need to clear cache
                        LOGI("chang bpk to nop\n");
                    
                    }
                    
                    void anti4(){//SIGTRAP
                    
                        int ret,size;
                        char *addr,*tmpaddr;
                    
                        signal(SIGTRAP,my_sigtrap);
                    
                        addr = (char*)malloc(PAGESIZE*2);
                    
                        memset(addr,0,PAGESIZE*2);
                        g_addr = (char *)(((int) addr + PAGESIZE-1) & ~(PAGESIZE-1));
                    
                        LOGI("addr: %p ,g_addr : %p\n",addr,g_addr);
                    
                        ret = mprotect(g_addr,PAGESIZE,PROT_READ|PROT_WRITE|PROT_EXEC);
                        if(ret!=0)
                        {
                            LOGI("mprotect error\n");
                            return ;
                        }
                    
                        size = 8;
                        memcpy(g_addr,dynamic_ccode,size);
                    
                        __clear_cache((void*)g_addr,(void*)(g_addr+size)); // need to clear cache
                    
                        __asm__("push {r0-r4,lr}\n\t"
                                "mov r0,pc\n\t"  //此時pc指向后兩條指令
                                "add r0,r0,#4\n\t"http://+4 是的lr 地址為 pop{r0-r5}
                                "mov lr,r0\n\t"
                                "mov pc,%0\n\t"
                                "pop {r0-r5}\n\t"
                                "mov lr,r5\n\t" //恢復lr
                        :
                        :"r"(g_addr)
                        :);
                    
                        LOGI("hi, i'm here\n");
                        free(addr);
                    
                    }
                    

                    在代碼中主動觸發breakpoint指令,然后在自定義SIGTRAP handle中將breakpoint替換成nop指令,于是程序可以正常執行完畢。

                    其中可使用r_debug-r_brk來觸發異常,其原理即是用到了linker中一些調試特性。Linker中有一個和調試相關的結構體r_debug,其定義如下:

                    #!cpp
                    struct r_debug {
                        int32_t r_version;
                        link_map_t* r_map;
                        void (*r_brk)(void);
                        int32_t r_state;
                        uintptr_t r_ldbase;
                    };  
                    

                    r_debug是以靜態變量的形式存在于linker中,其初始化代碼如下:

                    #!cpp
                    static r_debug _r_debug = {1, NULL, &rtld_db_dlactivity, RT_CONSISTENT, 0};
                    

                    在初始化時,r_debug中的r_brk函數指針被初始化成了rtld_db_dlactivity函數,該函數只是一個空的樁函數:

                    #!cpp
                    /*
                     * This function is an empty stub where GDB locates a breakpoint to get notified
                     * about linker activity.  It can?t be inlined away, can't be hidden.
                     */
                    extern "C" void __attribute__((noinline)) __attribute__((visibility("default"))) rtld_db_dlactivity() {
                    }
                    

                    沒調試下,該函數即為空函數,而在調試狀態下會將該函數的內容改寫為相應指令集的breakpoint指令。所以先注冊自己的signal函數處理breakpoint異常(SIGTRAP),然后在運行時調用該函數,即可觸發自定義SIGTRAP的接管函數。而動態調試時,SIGTRAP會先被調試器接收,這樣不僅能迷惑調試器,還能在自定義接管函數中做一些tricky的事。

                    0x05 檢測軟件斷點


                    上一節說了使用SIGTRAP反調試的原理,由此可以衍生出另一種很常見的反調試方法――檢測軟件斷點。軟件斷點通過改寫目標地址的頭幾字節為breakpoint指令,只需要遍歷so中可執行segment,查找是否出現breakpoint指令即可。實現大致如下:

                    #!cpp
                    unsigned long GetLibAddr() {
                        unsigned long ret = 0;
                        char name[] = "libanti_debug.so";
                        char buf[4096], *temp;
                        int pid;
                        FILE *fp;
                        pid = getpid();
                        sprintf(buf, "/proc/%d/maps", pid);
                        fp = fopen(buf, "r");
                        if (fp == NULL) {
                            puts("open failed");
                            goto _error;
                        }
                        while (fgets(buf, sizeof(buf), fp)) {
                            if (strstr(buf, name)) {
                                temp = strtok(buf, "-");
                                ret = strtoul(temp, NULL, 16);
                                break;
                            }
                        }
                        _error: fclose(fp);
                        return ret;
                    }
                    
                    
                    
                    void anti5(){
                    
                        Elf32_Ehdr *elfhdr;
                        Elf32_Phdr *pht;
                        unsigned int size, base, offset,phtable;
                        int n, i,j;
                        char *p;
                    
                        //從maps中讀取elf文件在內存中的起始地址
                        base = GetLibAddr();
                        if(base == 0){
                            LOGI("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;
                                LOGI("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++;
                                        LOGI("### find thumb bpt %X \n",p);
                                    }else if(*p == 0xf0 && *(p+1) == 0xf7 && *(p+2) == 0x00 && *(p+3) == 0xa0){
                                        n++;
                                        LOGI("### find thumb2 bpt %X \n",p);
                                    }else if(*p == 0x01 && *(p+1) == 0x00 && *(p+2) == 0x9f && *(p+3) == 0xef){
                                        n++;
                                        LOGI("### find arm bpt %X \n",p);
                                    }
                    
                                }
                                LOGI("### find breakpoint num: %d\n",n);
                    
                            }
                        }
                    
                    }
                    

                    大家在使用IDA調試的時候,也許會注意到IDA的代碼窗口和hex view窗口在設置斷點的時候,目標地址的內容并沒有發生改變,其實這是IDA故意將其隱藏了,設置完斷點之后直接用dd dump內存就能看見設置斷點的地址頭幾字節發生了改變。

                    0x06 進程間通信


                    大部分加固會新建進程或者新建線程,在這些新建的線程和進程中完成反調試操作,然而如果這些進程、線程相對獨立的話,很容易通過掛起、殺死的方式直接使得反調試失效。為了保證反調試線程、進程的存活,就需要一種通信方式,定期確認反調試線程、進程依然存活,所以進程間通信是高級反調試不可或缺的方式。在Linux下有很多進程間通信的方式,比如管道、信號、共享內存、套接字(socket)等,下面提供一個通過管道將反調試進程和主進程聯系起來的簡單例子:

                    #!cpp
                    int pipefd[2];
                    int childpid;
                    
                    void *anti3_thread(void *){
                    
                        int statue=-1,alive=1,count=0;
                    
                        close(pipefd[1]);
                    
                        while(read(pipefd[0],&statue,4)>0)
                            break;
                        sleep(1);
                    
                        //這里改為非阻塞
                        fcntl(pipefd[0], F_SETFL, O_NONBLOCK); //enable fd的O_NONBLOCK
                    
                        LOGI("pip-->read = %d", statue);
                    
                        while(true) {
                    
                            LOGI("pip--> statue = %d", statue);
                            read(pipefd[0], &statue, 4);
                            sleep(1);
                    
                            LOGI("pip--> statue2 = %d", statue);
                            if (statue != 0) {
                                kill(childpid,SIGKILL);
                                kill(getpid(), SIGKILL);
                                return NULL;
                            }
                            statue = -1;
                        }
                    }
                    
                    void anti3(){
                        int pid,p;
                        FILE *fd;
                        char filename[MAX];
                        char line[MAX];
                    
                        pid = getpid();
                        sprintf(filename,"/proc/%d/status",pid);// 讀取proc/pid/status中的TracerPid
                        p = fork();
                        if(p==0) //child
                        {
                            close(pipefd[0]); //關閉子進程的讀管道
                            int pt,alive=0;
                            pt = ptrace(PTRACE_TRACEME, 0, 0, 0); //子進程反調試
                            while(true)
                            {
                                fd = fopen(filename,"r");
                                while(fgets(line,MAX,fd))
                                {
                                    if(strstr(line,"TracerPid") != NULL)
                                    {
                                        int statue = atoi(&line[10]);
                                        LOGI("########## tracer pid:%d", statue);
                                        write(pipefd[1],&statue,4);//子進程向父進程寫 statue值
                    
                                        fclose(fd);
                    
                                        if(statue != 0)
                                        {
                                            return ;
                                        }
                    
                                        break;
                                    }
                                }
                                sleep(1);
                    
                            }
                        }else{
                            childpid = p;
                        }
                    }
                    pipe(pipefd);
                    pthread_create(&id_0,NULL,anti3_thread,(void*)NULL);
                    anti3();
                    

                    傳統檢測TracerPid的方法是直接在子進程中循環檢測,一旦發現則主動殺死進程。本實例將循環檢測TracerPid和進程間通信結合,一旦反調試子進程被掛起或被殺死,父進程也會馬上終止,原理大致如下圖:

                    pic3

                    父進程的守護線程在從pipe中read到statue值之前,默認statue值為-1,收到子進程往pipe中寫的statue值之后,重置statue值,如果未被調試,statue值為0,反之則為被調試狀態。該做法的優勢在于,一旦反調試進程被終止或被掛起,守護線程也能馬上發現。

                    當然,如果通過hook或者修改kernel同樣可以輕易的繞過這種反調試。這種做法只是為了演示而寫的簡單例子,真實的進程間通信反調試可以寫的復雜的多,大家可以盡情發揮想象。

                    0x07 dalvik 虛擬機內部相關字段


                    在dalvik虛擬機中自帶了檢測調試器的代碼,其本質是檢測DvmGlobals結構體中的相關字段:

                    #!cpp
                    struct DvmGlobals {
                        …
                        bool   debuggerConnected;    /* debugger or DDMS is connected */
                        bool   debuggerActive;        /* debugger is making requests */
                        …
                    }
                    

                    檢測調試器的函數:

                    #!cpp
                    /*
                     * static boolean isDebuggerConnected()
                     *
                     * Returns "true" if a debugger is attached.
                     */
                    static void Dalvik_dalvik_system_VMDebug_isDebuggerConnected(const u4* args, JValue* pResult)
                    {
                        UNUSED_PARAMETER(args);
                        RETURN_BOOLEAN(dvmDbgIsDebuggerConnected());
                    }
                    

                    本質是檢測該dalvik虛擬機中DvmGlobals結構體中的調試器狀態字段:

                    #!cpp
                    bool dvmDbgIsDebuggerConnected()
                    {
                        return gDvm.debuggerActive;
                    }
                    

                    知道原理之后可以更進一步,不通過這些Dalvik虛擬機的自定義函數,而是直接獲取這些字段值,這樣可以更好的隱藏反調試信息。

                    0x08 IDA arm、thumb指令識別缺陷


                    眾所周知,IDA采用遞歸下降算法來反匯編指令,而該算法最大的缺點在于它無法處理間接代碼路徑,無法識別動態算出來的跳轉。而arm架構下由于存在arm和thumb指令集,就涉及到指令集切換,IDA在某些情況下無法智能識別arm和thumb指令,比如下圖所示代碼:

                    pic4

                    bx r3指令會切換指令集,而參數r3是動態計算出來的,IDA無法失敗r3的值,而默認將bx r3后面的指令當成跳轉地址,將后面地址的指令識別成了arm指令,而實際上其仍為thumb指令。

                    在IDA動態調試時,仍然存在該問題,若在指令識別錯誤的地點寫入斷點,有可能使得調試器崩潰。

                    0x09 Ptrace


                    Ptrace是gdb等調試器實現的核心,通過ptrace可以監控、控制被調試進程的狀態、信號、執行等。而每個進程在同一時刻最多只能被一個調試進程ptrace,根據這個原理,可以主動ptrace自己的關鍵子進程,這樣可以在一定程度上防止子進程被調試。

                    為了防止fork出來的反調試子進程被直接掛起或殺死,可以通過Ptrace的PTRACE_PEEKTEXT、PTRACE_PEEKDATA、PTRACE_POKETEXT等參數來完成父子進程之間的通信,比如子進程中使用的解密密鑰先存于父進程空間,父進程往ptrace的子進程中寫入密鑰后,再解密出關鍵數據。

                    總之,通過ptrace增加父子進程之間的聯系,是十分有效并且廣泛存在于各類加固的反調試方法。

                    0x0A Inotify 監控文件


                    在Linux下,inotify可以實現監控文件系統事件(打開、讀寫、刪除等),加固方案可以通過inotify監控apk自身的某些文件,某些內存dump技術通過/proc/pid/maps、/proc/pid/mem來實現內存dump,所以監控對這些文件的讀寫也能起到一定的反調試效果。

                    0x0B 總結


                    本文總結了主流加固廠商大部分反調試技巧,APK下的反調試技巧和win、linux下的大同小異,核心原理都是類似的。說到底,反調試只能盡可能的增加逆向難度,APK的安全防護絕不能僅僅依靠反調試,APK安全需要從整體架構上入手,在關鍵代碼上加入強混淆,甚至通過vmp來增大關鍵代碼的逆向難度。

                    0x0C Reference


                      <pre id="vvttv"><mark id="vvttv"><progress id="vvttv"></progress></mark></pre>
                      <pre id="vvttv"></pre>

                        <p id="vvttv"></p>

                            <p id="vvttv"></p>

                                  <p id="vvttv"></p>

                                  <pre id="vvttv"><cite id="vvttv"><progress id="vvttv"></progress></cite></pre>

                                    <output id="vvttv"><dfn id="vvttv"><th id="vvttv"></th></dfn></output>

                                      <p id="vvttv"></p>

                                      这里只有精品视频