Android模擬器識別技術

看書的小蝸牛發表於2017-08-04

Android模擬器常常被用來刷單,如何準確的識別模擬器成為App開發中的一個重要模組,目前也有專門的公司提供相應的SDK供開發者識別模擬器。 目前流行的Android模擬器主要分為兩種,一種是基於Qemu,另一類是基於Genymotion,網上現在流行用一些模擬器特徵進行鑑別,比如:

  • 通過判斷IMEI是否全部為0000000000格式(>=6.0的國產ROM可能直接返回00000000000000,也要區分)
  • 判斷Build中的一些模擬器特徵值
  • 匹配Qemu的一些特徵檔案以及屬性
  • 通過獲取cpu資訊,將x86的給過濾掉(真機一般都是基於ARM)

等等,不過裡面的很多手段都能通過改寫ROM或者Xposed作假,讓判斷的效能打折扣。其實,現在絕大部分手機都是基於ARM架構,可以將其他CPU架構給忽略不計,模擬器全部執行在PC上,因此,只需要判斷是執行的裝置否是ARM架構即可。

ARM與PC的X86在架構上有很大區別,ARM採用的哈弗架構將指令儲存跟資料儲存分開,與之對應的,ARM的一級快取分為I-Cache(指令快取)與D-Cahce(資料快取),而X86只有一塊快取,如果我們將一段程式碼可執行程式碼動態對映到記憶體,在執行的時候,X86架構上是可以修改這部分程式碼的,而ARM的I-Cache可以看做是隻讀,因為,當我們往PC對應的地址寫指令的時候,其實是往D-Cahce寫,而I-Cache中的指令並未被更新,這樣,兩端程式就會在ARM與x86上有不同的表現,根據計算結果便可以知道究竟是還在哪個平臺上執行。但是,無論是x86還是ARM,只要是靜態編譯的程式,都沒有修改程式碼段的許可權,所以,首先需要將上面的彙編程式碼翻譯成可執行檔案,再需要申請一塊記憶體,將可執行程式碼段對映過去,執行。 以下實現程式碼是測試程式碼的核心,主要就是將地址e2844001的指令add r4, r4, #1,在執行中動態替換為e2877001的指令add r7, r7, #1,這裡目標是ARM-V7架構的,要注意它採用的是三級流水,PC值=當前程式執行位置+8。通過arm交叉編譯鏈編譯出的可執行程式碼如下:

8410:       e92d41f0        push    {r4, r5, r6, r7, r8, lr}
8414:       e3a07000        mov     r7, #0
8418:       e1a0800f        mov     r8, pc      // 本平臺針對ARM7,三級流水  PC值=當前程式執行位置+8
841c:       e3a04000        mov     r4, #0
8420:       e2877001        add     r7, r7, #1
    ....
842c:       e1a0800f        mov     r8, pc
8430:       e248800c        sub     r8, r8, #12   // PC值=當前程式執行位置+8
8434:       e5885000        str     r5, [r8]
8438:       e354000a        cmp     r4, #10
843c:       aa000002        bge     844c <out>
.....
複製程式碼

如果是在ARM上執行,e2844001處指令無法被覆蓋,最終執行的是add r4,#1 ,而在x86平臺上,執行的是add r7,#1 ,程式碼執行完畢, r0的值在模擬器上是1,而在真機上是10。之後,將上述可執行程式碼通過mmap,對映到記憶體並執行即可,具體做法如下,將可執行的二進位制程式碼直接拷貝可執行程式碼區,去執行

void (*asmcheck)(void);
int detect() {
        //可執行二進位制程式碼
    char code[] =
        "\xF0\x41\x2D\xE9"
        "\x00\x70\xA0\xE3"
        "\x0F\x80\xA0\xE1"
        "\x00\x40\xA0\xE3"
        "\x01\x70\x87\xE2"
        "\x00\x50\x98\xE5"
        "\x01\x40\x84\xE2"
        ....
    // 對映一塊可執行記憶體 PROT_EXEC
    void *exec = mmap(NULL, (size_t) getpagesize(), PROT_EXEC|PROT_WRITE|PROT_READ, MAP_ANONYMOUS | MAP_SHARED, -1, (off_t) 0);
    memcpy(exec, code, sizeof(code) + 1);
       //強制賦值到函式
    asmcheck = (void *) exec;
      //執行函式
    asmcheck();
    __asm __volatile (
    "mov %0,r0 \n"
    :"=r"(a)
    );
    munmap(exec, getpagesize());
    return a;
}
複製程式碼

經驗證, 無論是Android自帶的模擬器,還是夜神模擬器,或者Genymotion造假的模擬器,都能準確識別。在32位真機上完美執行,但是在64位的真機上可能會存在相容性問題,可能跟arm64-v8a的指令集不同有關係,也希望人能指點。為了防止在真機上出現崩潰,最好還是單獨開一個程式服務,利用Binder實現模擬器鑑別的查詢。

另外,對於Qemu的模擬器還有一種任務排程的檢測方法,但是實驗過程中發現不太穩定,並且僅限Qemu,不做參考,不過這裡給出原文連結: DEXLabs

僅供參考,歡迎指正

作者:看書的小蝸牛 原文連結 Android模擬器識別技術

Github連結 CacheEmulatorChecker

參考文件

QEMU emulation detection
DEXLabs

相關文章