Android多開檢測的另一個思路
之前寫了篇檢測多開的文章後,經過幾個月的時間,基本上都已經被各多開軟體繞過了。最近無意中發現了一些新特徵,在特定環境下可以用來檢測多開環境,特此來分享一下。
起因
某次在多開環境下執行demo,發現動態庫載入失敗了,錯誤資訊如下:
java.lang.UnsatisfiedLinkError: dlopen failed: "/data/app/【手動打碼】/lib/arm64/【手動打碼】.so" is 64-bit instead of 32-bit at java.lang.Runtime.loadLibrary0(Runtime.java:1016) at java.lang.System.loadLibrary(System.java:1660) ... 省略
這個錯誤沒什麼好多說的,很明顯,動態庫是64位的而App執行在32位下,因此載入失敗了。其實之前也看到過類似的現象,本應在64位下執行的App到多開環境下就變為32位環境了,只是之前並沒有去深究,這次遇到後仔細想了一下,在某些情況下可以用來檢測多開環境。
解釋
對於64位的手機,會啟動2個zygote,zygote
和zygote64
。
root670 1 435978427292 poll_schedule_timeout 7f7b47058c S zygote64 root671 1 169657611120 poll_schedule_timeout eb823684S zygote
以64位執行的App將由zygote64
fork而來,而以32位執行的App將由zygote
fork出來。可以從下面的程式碼看出來,ZygoteProcess.java
中,startViaZygote
方法會通過openZygoteSocketIfNeeded
方法選擇合適的zygote。
private Process.ProcessStartResult startViaZygote(final String processClass, final String niceName, final int uid, final int gid, final int[] gids, int debugFlags, int mountExternal, int targetSdkVersion, String seInfo, String abi, String instructionSet, String appDataDir, String invokeWith, String[] extraArgs) throws ZygoteStartFailedEx { // 省略... synchronized (mLock) { return zygoteSendArgsAndGetResult(openZygoteSocketIfNeeded(abi), argsForZygote); } } @GuardedBy("mLock") private ZygoteState openZygoteSocketIfNeeded(String abi) throws ZygoteStartFailedEx { Preconditions.checkState(Thread.holdsLock(mLock), "ZygoteProcess lock not held"); if (primaryZygoteState == null || primaryZygoteState.isClosed()) { try { primaryZygoteState = ZygoteState.connect(mSocket); } catch (IOException ioe) { throw new ZygoteStartFailedEx("Error connecting to primary zygote", ioe); } } if (primaryZygoteState.matches(abi)) { return primaryZygoteState; } // The primary zygote didn't match. Try the secondary. if (secondaryZygoteState == null || secondaryZygoteState.isClosed()) { try { secondaryZygoteState = ZygoteState.connect(mSecondarySocket); } catch (IOException ioe) { throw new ZygoteStartFailedEx("Error connecting to secondary zygote", ioe); } } if (secondaryZygoteState.matches(abi)) { return secondaryZygoteState; } throw new ZygoteStartFailedEx("Unsupported zygote ABI: " + abi); }
那麼如何確定App是以64位還是以32位執行呢?這就取決於動態庫,如果只有32位的動態庫(armeabi、armeabi-v7a),那麼就會以32位執行,如果有64位的動態庫(arm64-v8a),那麼就以64位執行,不存在動態庫則預設以64位執行。
如何證明?我們寫3個demo來實際看一下。
-
demo1: 包名
top.darkness463.whichzygote
,不加動態庫。 -
demo2: 包名
top.darkness463.zygote32
,加一個armeabi
動態庫。 -
demo3: 包名
top.darkness463.zygote64
,加armeabi
和arm64-v8a
動態庫。
root6701435978427692 poll_schedule_timeout 7f7b47058c S zygote64 root6711169657611416 poll_schedule_timeout eb823684S zygote u0_a1779878670 445608063412 SyS_epoll_wait7f7b47046c S top.darkness463.whichzygote u0_a18013690671 179170058664 SyS_epoll_waiteb8234acS top.darkness463.zygote32 u0_a17913871670 445652464716 SyS_epoll_wait7f7b47046c S top.darkness463.zygote64
可以看到,demo1和demo3的父程序是zygote64
,而demo2的父程序是zygote
。
那麼為何在多開環境下會出現動態庫載入失敗的情況呢?原因就在於我那個demo有arm64-v8a
的動態庫,在安裝時,系統會把該64位動態庫拷到/data/app/【包名】/lib/arm64/
下,然後那款多開軟體只有32位的動態庫,因此是以32位執行的,此時去/data/app/【包名】/lib/arm64/
路徑下載入64位的動態庫必然導致失敗。
開啟思路的話,這也可以是一種檢測多開環境的方式。
侷限性
其實與其說這是檢測多開的方法,倒不如說這是多開軟體的bug。看了幾款排名靠前的多開軟體,都只有32位的動態庫,但它們完全可以加上64位動態庫來避免這個問題。
另外,為了減小apk的體積,絕大多數App只會新增armeabi
平臺,而不會新增arm64-v8a
平臺的動態庫,所以這種檢測方法在很多App上本身就是不成立的。
補遺
之前那篇文章提到過一個通過/proc/self/maps
來檢測多開的方式,當時提到這個方法的缺點是需要收集所有多開App的包名,但真正搞事的人很可能不會拿市面上的多開軟體來作惡,他們可能利用開源的多開軟體改成亂七八糟的包名,之前我甚至見過命名成com.tencent.qqlite
來進行偽裝的。之後我又做了一些工作,也和大家分享一下。
思路還是從/proc/self/maps
中的動態庫出發。在這裡直接給出結論了不再詳細討論了。
-
/proc/self/maps
中出現包含/vbox/data/
、/shadow/data/
、/virtual/data/
的動態庫,則執行在多開環境下。主要是因為很多多開軟體都是基於開源或者抄來抄去的,所有目錄名無外乎這麼幾種,但不排除會有多開軟體修改掉名字的情況。 -
從
/proc/self/maps
載入的動態庫路徑我們可以解析到包名,如果自己的App並不會載入其他App的動態庫(第三方登入可能會把其他App的動態庫載入進去)的話,出現非自己包名的動態庫可能疑似執行在多開環境下。服務端可以建立一套自動解析包名 + 新增到黑名單的流程。