檢測Android模擬器的方法和程式碼實現
專自:https://bbs.pediy.com/thread-225717.htm
剛剛看了一些關於Detect Android Emulator的開源專案/文章/論文, 我看的這些其實都是13年14年提出的方法, 方法裡大多是檢測一些環境屬性, 檢查一些檔案這樣, 但實際上檢測的思路並不侷限於此. 有的是很直接了當去檢測qemu, 而其它的方法則是旁敲側擊比如檢測adb, 檢測ptrace之類的. 思路也很靈活. 最後看到有提出通過利用QEMU這樣的模擬CPU與物理CPU之間的實際差異(任務排程差異), 模擬感測器和物理感測器的差異, 快取的差異等方法來檢測. 相比檢測環境屬性, 檢測效果會提升很多.
下面我就列出各個資料中所提出的一些方法/思路/程式碼供大家交流學習.
QEMU Properties
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 |
|
這些都是基於一些經驗和特徵來比對的屬性, 這裡的屬性以及之後的一些檔案呀屬性啊之類的我就不再多作解釋.
Device ID
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 |
|
Default Number
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 |
|
IMSI
1 2 3 4 5 6 7 8 9 10 11 12 13 |
|
Build類
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 |
|
運營商名
1 2 3 4 5 |
|
QEMU驅動
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 |
|
QEMU檔案
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 |
|
Genymotion檔案
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 |
|
QEMU管道
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 |
|
設定斷點
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 |
|
以下是對應的c++程式碼
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 |
|
這裡我的描述可能並不準確, 因為並沒有找到相關的資料. 我只能以自己的理解來解釋一下:
SIGTRAP
是偵錯程式設定斷點時發生的訊號, 在nexus5或一加手機等大多數手機都可以觸發. SIGBUS
則是在一個匯流排錯誤, 指標也許訪問了一個有效地址, 但匯流排會因為資料未對齊等原因無法使用, 在nexus4手機上可以觸發. 而bkpt
則是arm的斷點指令, 這是曾經qemu被提出來的一個issue, qemu會因為SIGSEGV
訊號而崩潰, 作者想利用這個崩潰來檢測qemu. 如果程式沒有正常退出或被凍結, 那麼就可以認定很可能是在模擬器裡.
ADB
1 2 3 4 5 6 7 8 |
|
isUserAMonkey()
1 2 3 |
|
這個其實是用於檢測當前操作到底是使用者還是指令碼在要求應用執行.
isDebuggerConnected()
1 2 3 4 5 6 |
|
這個方法是用來檢測除錯, 判斷是否有偵錯程式連線.
ptrace
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 |
|
這個方法是通過檢查/proc/self/status
的TracerPid
項, 這個項在沒有跟蹤的時候預設為0, 當有程式在跟蹤時會修改為對應的pid. 因此如果TracerPid
不等於0, 那麼就可以認為是在模擬器環境.
TCP連線
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 |
|
這個方法是通過讀取/proc/net/tcp
的資訊來判斷是否存在adb. 比如真機的的資訊為0: 4604D20A:B512 A3D13AD8...
, 而模擬器上的對應資訊就是0: 00000000:0016 00000000:0000
, 因為adb通常是反射到0.0.0.0
這個ip上, 雖然埠有可能改變, 但確實是可行的.
TaintDroid
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 |
|
這個比較單純了. 就是通過檢測包名, 檢測Taint
類來判斷是否安裝有TaintDroid
這個汙點分析工具. 另外也還可以檢測TaintDroid
的一些成員變數.
eth0
1 2 3 4 5 6 7 8 9 10 11 |
|
檢測是否存在eth0
網絡卡.
感測器
手機上配備了各式各樣的感測器, 但它們實質上都是基於從環境收集的資訊輸出值, 因此想要模擬感測器是非常具有挑戰性的. 這些感測器為識別手機和模擬器提供了新的機會.
比如在論文Rage Against the Virtual Machine: Hindering Dynamic Analysis of Android Malware
中, 作者對Android模擬器的加速器進行測試, 作者發現Android模擬器上的感測器會在相同的時間間隔內(觀測結果是0.8s, 標準偏差為0.003043)產生相同的值. 顯然對於現實世界的感測器, 這是不可能的.
於是我們可以先註冊一個感測器監聽器, 如果註冊失敗, 就可能是在模擬器中(排除實際裝置不支援感測器的可能性). 如果註冊成功, 那麼檢查onSensorChanged
回撥方法, 如果在連續呼叫這個方法的過程所觀察到的感測器值或時間間隔相同, 那麼就可以認定是在模擬器環境中.
QEMU任務排程
出於效能優化的原因, QEMU在每次執行指令時都不會主動更新程式計數器(PC), 由於翻譯指令在本地執行, 而增加PC需要額外的指令帶來開銷. 所以QEMU只在執行那些從線性執行過程裡中斷的指令(例如分支指令)時才會更新程式計數器. 這也就導致在執行一些基本塊的期間如果發生了排程事件, 那麼也沒有辦法恢復排程前的PC, 也是出於這個原因, QEMU僅在執行基本塊後才發生排程事件, 絕不會執行的過程中發生.
如上圖, 因為排程可能在任意時間發生, 所以在非模擬器環境下, 會觀察到大量的排程點. 而在模擬器環境中, 只能看到特定的排程點.
SMC識別
因為QEMU會跟蹤內碼表的改動, 於是存在一種新穎的方法來檢測QEMU--使用自修改程式碼(Self-Modifying Code, SMC)引起模擬器和實際裝置之間的執行流變化.
ARM處理器包含有兩個不同的緩衝Cache, 一個用於指令訪問(I-Cache), 而另一個用於資料訪問(D-Cache). 但如ARM這樣的哈佛架構並不能保證I-Cache和D-Cache之間的一致性. 因此CPU有可能在新程式碼片已經寫入主存後執行舊的程式碼片(也許是無效的).
這個問題可以通過強迫兩個快取一致得到解決, 這有兩步:
- 清理主存, 以便將D-Cache中新寫入的程式碼移入主存
- 使I-Cache無效, 以便它可以用主存的新內容重新填充.
在原生Android程式碼中, 可以使用cacheflush
函式, 該函式通過系統呼叫完成上述操作.
識別程式碼, 使用一個具有讀寫許可權的記憶體, 其中包含兩個不同函式f1和f2的程式碼, 這兩個函式其實很簡單, 只是單純在一個全域性字串變數的末尾附加各自的函式名稱, 這兩個函式會在迴圈裡交錯執行, 這樣就可以通過結果的字串推斷出函式呼叫序列.
如前所述, 我們呼叫cacheflush
來同步快取. 在實際裝置和模擬器上執行程式碼得到的結果是相同的--每次執行都會產生一致的函式呼叫序列.
接下來我們移除呼叫cacheflush
, 執行相同的操作. 那麼在實際裝置中, 我們每次執行都會觀察到一個隨機的函式呼叫序列, 這也如前所述的那樣, 因為I-Cache可能包含一些舊指令, 每次呼叫的時候快取都不同步所導致的.
而模擬器環境卻不會發生這樣的情況, 而且函式呼叫序列會跟之前沒有移除cacheflush
時完全相同, 也就是每次函式呼叫前快取都是一致的. 這是因為QE