<span id="7ztzv"></span>
<sub id="7ztzv"></sub>

<span id="7ztzv"></span><form id="7ztzv"></form>

<span id="7ztzv"></span>

        <address id="7ztzv"></address>

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

            Author:leonnewton

            0x00 前言


            DEXLabs發表過題為《Detecting Android Sandboxes》的博客,文章提出了一個檢測Android沙箱的方法,并附了PoC。本文對原文的內容進行介紹,并加上筆者自己實際實驗的結果及討論。

            0x01 Qemu的二進制翻譯機制


            看下百度百科對二進制翻譯的解釋:

            二進制翻譯(Binary Translation)是一種直接翻譯可執行二進制程序的技術,能夠把一種處理器上的二進制程序翻譯到另外一種處理器上執行。

            Qemu使用二進制翻譯技術把要翻譯的native代碼,比如ARM下的代碼,翻譯到host系統下一樣或者不同的指令集,比如x86指令集。跟指令集模擬技術相比,二進制翻譯運行速度更快,因為已經翻譯的代碼塊可以進行cache然后直接執行。下面是Qemu二進制翻譯過程的流程圖:

            p1

            從圖中可以看出,當遇到分支指令的時候,就會對后面的代碼進行處理。后面的代碼地址可以在分支指令里面找到,所以會先去找是否有代碼的cache。當命中的時候,代碼塊已經有cache了,代碼已經翻譯過,所以直接執行就行了。當沒有命中的時候,翻譯函數就會去翻譯代碼,一直翻譯到遇到下一個分支指令。然后把翻譯好的代碼放到cache中接著執行。

            0x02 Qemu的一個優化


            物理CPU會在每執行一條指令以后,就把程序計數器加一,程序計數器永遠都是最新的值。那么對于一個虛擬的CPU來說,也應該是在每執行完代碼中的一條指令然后將程序計數器加一,這樣來保證程序計數器是最新的值。然而,由于被翻譯的代碼是在本地執行的,也就是模擬出來的CPU,所以只有在原來代碼需要訪問程序計數器的時候,才需要返回一個最新的正確的值(因為這不會影響host系統上面正常的運行)。Qemu在遇到需要返回最新值的時候會更新程序計數器,其他時候不會每次都更新,來作為一種優化措施。因此,程序計數器會指向一個代碼塊的最開始位置,因為每次進行分支跳轉的時候才需要更新程序計數器。

            0x03 由優化聯想到的


            試想下在多任務的操作系統中,當有中斷發生的時候,上面的優化會導致什么事?由于程序計數器不是每執行一次就更新的,那么虛擬CPU在執行一個代碼塊的時候是不能被中斷的,被中斷后就沒法恢復正確的程序計數器值。所以在運行一個代碼塊的時候,一般不會發生任務調度的情況。整個過程如下圖所示:

            p2

            0x04 實驗驗證


            上面說不能中斷,不能任務調度,那么實驗就人為制造任務調度的情況,然后考察任務調度的情況。記錄發生任務調度的地址,然后統計查看這些地址的分布情況。那么在一個真機的環境中,這些地址應該近似是相等的次數,均勻的分布。每個地址發生調度情況的可能性是相等的。在Qemu虛擬的環境中,應該是在一個代碼塊的最后才會發生任務調度,所以地址分布也不是均勻的,而是變化很大。

            具體的實驗是通過2個線程。一個線程在一個代碼塊中對一個全局變量不斷地加1,每次循環全局變量都賦值為1。另一個線程也是循環,每次都去讀前面的那個全局變量。然后統計讀出來每個數字的次數。流程如下:

            p3

            不斷加1的線程代碼如下:

            #!cpp
            void* thread1(void * data){
                for(;;){
                    __asm__ __volatile__ ("mov r0, %[global];"
                                          "mov r1, #1;"
                                          "add r1, r1, #1;" "str r1, [r0];"
                                          "add r1, r1, #1;" "str r1, [r0];"
                                          "add r1, r1, #1;" "str r1, [r0];"
                                          "add r1, r1, #1;" "str r1, [r0];"
                                          "add r1, r1, #1;" "str r1, [r0];"
                                          "add r1, r1, #1;" "str r1, [r0];"
                                          "add r1, r1, #1;" "str r1, [r0];"
                                          "add r1, r1, #1;" "str r1, [r0];"
                                          "add r1, r1, #1;" "str r1, [r0];"
                                          "add r1, r1, #1;" "str r1, [r0];"
                                          "add r1, r1, #1;" "str r1, [r0];"
                                          "add r1, r1, #1;" "str r1, [r0];"
                                          "add r1, r1, #1;" "str r1, [r0];"
                                          "add r1, r1, #1;" "str r1, [r0];"
                                          "add r1, r1, #1;" "str r1, [r0];"
                                          "add r1, r1, #1;" "str r1, [r0];"
                                          "add r1, r1, #1;" "str r1, [r0];"
                                          "add r1, r1, #1;" "str r1, [r0];"
                                          "add r1, r1, #1;" "str r1, [r0];"
                                          "add r1, r1, #1;" "str r1, [r0];"
                                          "add r1, r1, #1;" "str r1, [r0];"
                                          "add r1, r1, #1;" "str r1, [r0];"
                                          "add r1, r1, #1;" "str r1, [r0];"
                                          "add r1, r1, #1;" "str r1, [r0];"
                                          "add r1, r1, #1;" "str r1, [r0];"
                                          "add r1, r1, #1;" "str r1, [r0];"
                                          "add r1, r1, #1;" "str r1, [r0];"
                                          "add r1, r1, #1;" "str r1, [r0];"
                                          "add r1, r1, #1;" "str r1, [r0];"
                                          "add r1, r1, #1;" "str r1, [r0];"
                                          "add r1, r1, #1;" "str r1, [r0];"
                                          :
                                          :[global] "r" (&global_value)
                                          :
                                          );
            
                }
            }
            

            一共是從2一直加到32。

            另一個線程直接讀全局變量,并且把讀出來的值進行統計

            #!cpp
            for(i=0;i<50000;i++)
                count[global_value]++;
            

            然后計算count數組統計出來的值的方差,看看分布情況的離散程度,這里我嘗試了3個計算方式。

            1. 直接計算原始數據的方差和標準差;
            2. 對所有數據除以最大值,然后計算方差和標準差;

            3. p4,進行離差標準化以后,計算方差和標準差。

            0x05 結果與討論


            一共是循環讀取50000次。

            在模擬器里,3種方式計算出來的方差和標準差如下:

            p5

            每個數字統計的次數如下:

            p6

            除了count[32]其他都是0。

            在真機中,3種方式計算出來的方差和標準差如下:

            p7

            每個數字統計的次數如下:

            p8

            雖然count[32]比其他各個地方都多,但是中間基本都是均勻分布的。

            那么實驗的含義是什么呢。其實就是在一個線程加1的過程中,另一個線程在什么地方打斷了它,然后從讀出來的值看是在哪里打斷了,也就是進行了任務調度。因此從結果來看,確實符合上面的分析,模擬器只在代碼塊的最后,有分支指令的時候進行了任務調度。而在真機中,實驗中發現也是在這里調度最多,但在上面其他位置是基本均勻分布的。大家可以自己設計反應離散程度的指標來判斷是否運行在模擬器。

            <span id="7ztzv"></span>
            <sub id="7ztzv"></sub>

            <span id="7ztzv"></span><form id="7ztzv"></form>

            <span id="7ztzv"></span>

                  <address id="7ztzv"></address>

                      亚洲欧美在线