<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/tips/13245

                    Author:leonnewton

                    0x00 序


                    目前對Android模擬器的檢測,主要是從特定的系統值來進行區分的。例如,getDeviceId()、getLine1Number()這類函數,還有android.os.Build類記錄的一系列值等等。但是偶然發現有位老外提出了用cache來區分模擬器和真機的idea,但是這位老外可能當時比較懶,沒有具體的細節,寫了個簡單的PoC后把Evaluation空著了,也沒有實驗,所以并不知道這個方法是否真的有效。因此,本文就把檢測的整個方法從原理到實現完整地展現出來。

                    0x01 ARM和x86


                    由于現在大部分的Android手機都是ARM架構的,因此首先看一下ARM架構和x86架構在cache上的區別。兩者簡明的區別如下圖所示。

                    圖1:ARM和x86 cache區別

                    從圖中我們可以看出,在CPU和內存之間,可以存在幾級cache,這里是L1和L2。cache的作用是加速,把指令緩存起來,就不用到低速的內存中去取了。x86的cache都是連續的,但是ARM把L1 cache分成了平行的2塊,也就是I-Cache和D-Cache。這種將程序指令儲存和數據儲存分開的存儲器結構叫哈佛架構(Harvard architecture),而把程序指令存儲器和數據存儲器合并在一起的叫馮·諾伊曼結構(von Neumann architecture)。

                    那么問題就來了,在指令和數據分開存儲的結構中,這兩個cache不是同步的,因此一個特定地址的數據值在一個cache中更新了,但是在另一個cache就沒有更新。比如往數據cache中寫了數據,指令cache中是不會寫入這個數據的。

                    而目前Android SDK提供的模擬器是基于QEMU的,QEMU是一個開源的模擬處理器的軟件,詳細可以看維基QEMU。所以模擬器是沒有分開的cache,模擬器只有一個整塊的cache。

                    于是就有了下面利用cache來檢測模擬器的思路。

                    0x02 思路


                    先看下思路的流程圖:

                    圖2:檢測思路

                    左邊的是真機上發生的情況,右邊是模擬器發生的情況,下面詳述一下操作和后果。

                    第一步:
                    執行一個地址上的指令,假設就是$address這個地址。那么在真機上,指令會寫到I-Cache上,模擬器直接寫到cache上(因為模擬器就一個整塊的cache)。

                    第二步:
                    $address寫入一個新指令。注意,這就有區別了,真機上的新指令會寫入D-Cache,而在模擬器直接寫到cache上。

                    第三步:
                    執行$address的指令。那么此時,在真機上,會從I-Cache讀指令,也就是會執行第一步的指令。模擬器直接從cache上讀指令,會執行第二步的新指令。

                    當然有可能發生在真機上的指令cache被洗掉了,但是實驗下來可能性還是比較小的。

                    0x03 show me the code


                    首先是設計一段代碼,會向一個特定的地址重新寫一個指令。然后由于要重新回到原來的地址再執行一遍,因此可以用一個循環來實現。代碼如下:

                    #!cpp
                    __asm __volatile (
                    1 "stmfd sp!,{r4-r8,lr}\n"
                    2 "mov r6,#0\n"  用來統計循環次數,debug用的
                    3 "mov r7,#0\n"  為r7賦初值
                    4 "mov r8,pc\n"  4、7行用來獲得覆蓋$address“新指令”的地址
                    5 "mov r4,#0\n"  為r4賦初值
                    6 "add r7,#1\n"  用來覆蓋$address的“新指令”
                    7 "ldr r5,[r8]\n" 
                    8 "code:\n"
                    9 "add r4,#1\n"  這就是$address,是對r4加1
                    10 "mov r8,pc\n"  10,11,12行的作用就是把第6行的指令寫到第9行
                    11 "sub r8,#12\n"
                    12 "str r5,[r8]\n"
                    13 "add r6,#1\n"   r6用來計數
                    14 "cmp r4,#10\n"  控制循環次數
                    15 "bge out\n"
                    16 "cmp r7,#10\n"   控制循環次數
                    17 "bge out\n"
                    18 "b code\n"      10次內的循環調回去
                    19 "out:\n"
                    20 "mov r0,r4\n"    把r4的值作為返回值
                    21 "ldmfd sp!,{r4-r8,pc}\n"
                    );
                    

                    注釋已經解釋得比較清晰了。也就是說,r4如果是10,那么就是執行的是舊指令,是在真機上。如果r4等于1,那就是執行了舊指令,是在模擬器上。

                    這里會遇到一個問題,就是我們是沒有寫代碼段的權限的,解決方案是mmap一段可寫的,把編譯好的機器碼復制進去,再跳過去執行。

                    #!cpp
                    void (*call)(void);
                    #define PROT PROT_EXEC|PROT_WRITE|PROT_READ
                    #define FLAGS MAP_ANONYMOUS| MAP_FIXED |MAP_SHARED
                    char code[]=
                    "\xF0\x41\x2D\xE9\x00\x60\xA0\xE3\x00\x70\xA0\xE3\x0F\x80\xA0\xE1"
                    "\x00\x40\xA0\xE3\x01\x70\x87\xE2\x00\x50\x98\xE5\x01\x40\x84\xE2"
                    "\x0F\x80\xA0\xE1\x0C\x80\x48\xE2\x00\x50\x88\xE5\x01\x60\x86\xE2"
                    "\x0A\x00\x54\xE3\x02\x00\x00\xAA\x0A\x00\x57\xE3\x00\x00\x00\xAA"
                    "\xF5\xFF\xFF\xEA\x04\x00\xA0\xE1\xF0\x81\xBD\xE8";
                    void *exec = mmap((void*)0x10000000,(size_t)4096 ,PROT ,FLAGS,-1,(off_t)0);
                    memcpy(exec ,code,sizeof(code)+1);
                    call=(void*)0x10000000;
                    call();
                    

                    申請了一段內存,然后把匯編代碼的機器碼復制過去,接著跳到這塊內存執行。然后我們在后面取r4的值即可。

                    #!cpp
                    __asm __volatile (
                    "mov %0,r0\n"
                    :"=r"(a)
                    :
                    :
                    );
                    

                    把r0,也就是r4的值放到a變量中。然后根據a的值返回不同的值就可以了。方便在應用里判斷結果。

                    0x04 調試


                    調試的方法可以見鄭博士的文章安卓動態調試七種武器之孔雀翎 – Ida Pro

                    整個調試的過程是,把上一節的代碼編譯成一個so共享庫,返回值是r0也就是r4的值(a變量),然后在應用中根據返回值來判斷在什么環境中運行。

                    在進入10000000前下斷點,然后F7進去。

                    進入以后,在mov r0,r4的時候下斷,F9執行,這時候看到r4的值是10,這是在真機上測試的結果。可以看到原先add r4,#1 已經變成了add r7,#1,但是實際執行的還是add r4,#1。

                    在模擬器執行的結果如下,可以看到r4的值是1,r7是10,所以執行的是新指令,是在模擬器上:

                    0x05 測試


                    不知道在其他機器上是否可行,大家可以從https://github.com/leonnewton/cache_test下載進行測試。

                      <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>

                                      这里只有精品视频