APPによる各種デバッグの強化


原文住所:http://drops.wooyun.org/mobile/16969
原文住所:https://blog.csdn.net/xinyu_pan/article/details/62888415
0 x 00時間依存デバッグ
一部のコードの実行時間差を計算してデバッグされたかどうかを判断することで、Linuxカーネルの下でtime、gettimeofday、またはsys callで直接現在の時間を取得できます.また、SIGALRM信号をカスタマイズすることで、プログラムの実行がタイムアウトしたか否かを判断することもできる.
0 x 01キーファイルの検出
(1)/proc/pid/status、/proc/pid/task/pid/status
デバッグ状態では、Linuxカーネルは、/proc/pid/statusまたは/proc/pid/task/pid/statusファイルのTracerPidフィールドにデバッグプロセスのpidを書き込み、そのファイルのstatueフィールドにt(tracing stop):

(2)/proc/pid/stat、/proc/pid/task/pid/stat
デバッグ状態で/proc/pid/stat、/proc/pid/task/pid/statファイルの2番目のフィールドはt(T):

(3)/proc/pid/wchan、/proc/pid/task/pid/wchan
プロセスがデバッグされると、/proc/pid/wchan、/proc/pid/task/pid/wchanファイルにもptrace_が書き込まれます.stop.
0 x 02検出ポート番号
IDAを使用してAPKを動的にデバッグする場合、android_serverはデフォルトで23946ポートをリスニングするので、ポート番号を検出することで一定のデバッグバック機能を果たすことができます.具体的には、/proc/net/tcpファイルを検出したり、直接systemでコマンドnetstat -apn等を実行したりすることができる.
0 x 03検出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信号の逆デバッグをよりよく理解するために、デバッガがブレークポイントを設定する原理を見てみましょう.
x 86アーキテクチャと同様に、armアーキテクチャの下でデバッガがブレークポイントを設定するには、まず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信号を受信した後、以下のことを続けます.
  • 先のbreakpoint命令
  • を宛先アドレスに元の命令で置き換える.
  • 追跡プロセスの現在の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
    ");
     
    }
     
    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
    ",addr,g_addr);
     
        ret = mprotect(g_addr,PAGESIZE,PROT_READ|PROT_WRITE|PROT_EXEC);
        if(ret!=0)
        {
            LOGI("mprotect error
    ");
            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}
    \t"
                "mov r0,pc
    \t"  // pc
                "add r0,r0,#4
    \t"//+4 lr pop{r0-r5}
                "mov lr,r0
    \t"
                "mov pc,%0
    \t"
                "pop {r0-r5}
    \t"
                "mov lr,r5
    \t" // lr
        :
        :"r"(g_addr)
        :);
        LOGI("hi, i'm here
    ");
        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  。
    

    0 x 05 ソフトブレークポイント
    では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
    ");         return;     }       elfhdr = (Elf32_Ehdr *) base;       phtable = elfhdr->e_phoff + base;       for(i=0;ie_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

    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とプロセス の を み わせて、サブプロセスが または されると、 プロセスもすぐに します. は の になります.

    プロセスのデーモンスレッドは、pipeのreadからstatue に する にデフォルトのstatue が-1であり、サブプロセスからpipeに かれたstatue を した 、statue をリセットし、デバッグされていない はstatue が0で、 にデバッグされた になります.このアプローチの は、バックデバッグプロセスが したり したりすると、デーモンスレッドもすぐに されることです.
    もちろん、hookやkernelの を じても、このような デバッグを に することができます.この はプレゼンテーションのために いた な にすぎず、 のプロセス の デバッグは くことができる さが く、 を に することができます.
    0 x 07 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 マシンのカスタム ではなく、これらのフィールド を することで、 デバッグ をよりよく すことができます.
    0 x 08 IDA arm、thumb
    IDAは アルゴリズムを いて を アセンブリすることが られているが,このアルゴリズムの の は コード を できず, に されたジャンプを できないことである. 、armアーキテクチャではarmおよびthumb セットが するため、 セットの り えに し、IDAは、 の に すコードのようなarmおよびthumb をスマートに できない がある.

    bx r 3 は セットを り え、パラメータr 3は に され、IDAはr 3の に することはできないが、デフォルトではbx r 3の の をジャンプアドレスとし、 のアドレスの をarm と しているが、 にはthumb である.
    IDAダイナミックデバッグでは、コマンド エラーの にブレークポイントを き むと、デバッガがクラッシュする があるという が として しています.
    0x09 Ptrace
    Ptraceはgdbなどのデバッガが する であり、ptraceによってデバッグされたプロセスの 、 、 などを 、 することができる. プロセスは、 じ で 1つのデバッグプロセスptraceしか できません.この に づいて、ptrace のキーサブプロセスをアクティブにすることができ、サブプロセスがデバッグされることをある することができます.
    forkからのリバースサブプロセスが または されるのを するために、PtraceのPTRACE_PEEKTEXT、PTRACE_PEEKDATA、PTRACE_POKETEXTなどのパラメータは、サブプロセスで される が プロセス に され、 プロセスがptraceのサブプロセスに を き んだ 、キーデータを するなど、 プロセス の を します.
    するに,ptraceにより プロセス のつながりを させることは, に であり, の された デバッグ に く する.
    0 x 0 A Inotifyモニタファイル
    Linuxでは、inotifyはファイルシステムイベント( く、 み き、 など)を することができ、 はinotifyによってapk のファイルを することができ、 のメモリdump は/proc/pid/maps、/proc/pid/memによってメモリdumpを するため、 はこれらのファイルの み きに しても の デバッグ を たすことができる.
    0 x 0 Bまとめ
    は の メーカーの の デバッグ を して、APKの の デバッグ とwin、linuxの の 、 はすべて しています. 、 デバッグはできるだけ の を めるしかなく、APKの は して デバッグに ることはできない.APKの は のアーキテクチャから し、キーコードに い を え、vmpを じてキーコードの の を める がある.
    0x0C Reference
  • /bionic/linker/linker.h
  • /bionic/linker/linker.cpp
  • http://androidxref.com/4.4.4_r1/xref/bionic/linker/rt.cpp#33
  • http://androidxref.com/4.4.4_r1/xref/dalvik/vm/native/dalvik_system_VMDebug.cpp
  • http://blog.jobbole.com/23632/
  • http://www.spongeliu.com/165.html