解決MIUI8的凍結反彈

看到這個標題我覺得某司的程序員又要緊張一下了,怎么好不容易搞出了個凍結反彈又被人搞了。恩,要搞的就是這種流氓行為。

首先來看一下具體的現象,所謂的凍結反彈,就是當你使用pm disable使一個 APP 處于凍結狀態后,重啟手機,APP 自動解凍了。典型的例子就是 MIUI 內置的音樂、視頻等。另外還有刪除指定 APP 重啟就卡 MIUI Logo 不能進系統的,這對于取了 root 想自己改改系統的人來說,簡直不可忍。

那么廢話不多說,直接來看解決方案,可靠的解決方案有三種,BOOT_COMPLETED消息,修改service.jar/service.odex,使用Xposed動態修改。


方法一


第一種是最簡單的,維護一個列表,當有 APP 被凍結或解凍時,即修改列表內成員,在隨后的重啟過程中,接收BOOT_COMPLETED消息,并對列表內的 APP 再次進行凍結,具體的代碼是這樣的:

class BootReceiver : BroadcastReceiver() {
    companion object { var inited = false }
    override fun onReceive(context: Context?, intent: Intent?) {
        if (!inited) {
            inited = true
            val pref = context?.getSharedPreferences(XpStatus.PREF, 0)
            if (pref!!.getBoolean(XpStatus.KEY_PREVENT_FREEZE_REVERSE, false)) 
                context?.startService(Intent(context, FreezeService::class.java))
        }
    }
}

class FreezeService : Service() {
    override fun onBind(intent: Intent?): IBinder? = null
    override fun onStartCommand(intent: Intent?, flags: Int, startId: Int): Int {
        thread { NativeAPI.freezeOnLoad() }
        return super.onStartCommand(intent, flags, startId)
    }
}

同時改一下 AndroidManifest.xml 就好:

<service android:name=".FreezeService"/>
<receiver android:name=".BootReceiver">
    <intent-filter>
        <action android:name="android.intent.action.BOOT_COMPLETED"/>
    </intent-filter>
</receiver>

這個情況下,會遇到兩個坑,其一就是在 MIUI 上,必須允許這個 APP 自啟,同時它也不能被綠色守護,阻止運行等 APP 管理,否則會收不到BOOT_COMPLETED消息;第二個坑也是在 MIUI 上,BOOT_COMPLETED收到的時機問題,有可能是在手機啟動后 1 分鐘才收到該消息,于是就會出現用戶以為 APP 自動解凍了,但是過了一陣子那個 APP 又消失(被凍結)了,給用戶非常不好的體驗。

第一個問題,無解,這是小米所設計的機制,繞不過去,可能對于部分用戶來說,好不容易能把 APP 的自啟都干掉了,結果對于這個 APP 又要給自啟權限,非常的不爽。第二個問題在 6.0 和以下版本的 MIUI 中是可以解的,解法就是加入對AUDIO_BECOMING_NOISY消息的監聽:

<receiver android:name=".receiver.BootReceiver">
    <intent-filter>
        <action android:name="android.intent.action.BOOT_COMPLETED"/>
    </intent-filter>
    <intent-filter>
        <action android:name="android.media.AUDIO_BECOMING_NOISY"/>
    </intent-filter>
</receiver>

AUDIO_BECOMING_NOISY發送的時機遠比BOOT_COMPLETED更早,在桌面啟動前,就可以收到這個消息,在此處進行對 APP 的重新凍結是靠譜的。但是需要注意的是,Android N以后,啟動時不再發送此消息,因此這個做法在 Android N 為基礎的 MIUI 上是無效的。

同時也再說一句,同樣是 Android,CM 的 ROM 就不會有BOOT_COMPLETED延遲,也無需進行允許自啟這樣的操作。


方法二


直接修改service.jar/service.odex也是個不錯的方案,反正都已經獲取到 root 了,可以隨意替換。最終修改的是 jar 還是 odex,取決于你從 ROM 里面拿到的文件是哪個,它們基本上沒差別。

為了方便起見,我直接給出反編譯后的 java 代碼,可以參照著修改 smali 代碼:

package com.miui.server;
... ...
public class SecurityManagerService extends Stub {
    ... ...
    private void checkSystemSelfProtection(final boolean onlyCore) {
    ... ...
        while (i$.hasNext()) {
            SecurityManagerService.this.checkEnabled(pm, (String) i$.next());
        }
        ... ...
        if (!(Build.IS_INTERNATIONAL_BUILD || Build.IS_CM_CUSTOMIZATION || Build.IS_CM_CUSTOMIZATION_TEST)) {
            SecurityManagerService.this.enforceAppSignature(platformSignature, "com.xiaomi.market", false);
        }
        ... ...
        if (!SecurityManagerService.this.checkSysAppCrack()) {
            i = 0;
        }
        ... ...
    }
    ... ...
}

代碼太長,就貼這些關鍵的,它們完成了對已凍結 APP 的重新啟用,和對于刪除市場后直接卡 MIUI Logo 的處理,不得不說,這些代碼實在是惡心。

在 smali 內,可以將checkSystemSelfProtection方法整個置空,或是修改checkEnabledenforceAppSignaturecheckSysAppCrack這三個函數,刪除函數體,或是使其返回你要的值。完成后重新打包,并替換回系統的 framework 內即可。

使用這個方法的坑在于,如果系統更新了,你就必須重新做這一系列步驟,當然這一塊代碼可讀性還是比較強的,過于底層的東西很難進行混淆處理,改起來比較方便。


方法三


最終,最完美的方法還是要借助 Xposed 框架,一勞永逸的解決這些問題,不多說,直接給代碼了:

if (param.packageName == "android" 
    || param.packageName == "com.miui.system" 
    || param.packageName == "miui.system") {    
    val clsSMS = XpUtils.findClass(param.classLoader, "com.miui.server.SecurityManagerService")
    if (clsSMS != null) {
        XpUtils.findAndHookMethod("com.miui.server.SecurityManagerService", param.classLoader, "checkSysAppCrack", XC_MethodReplacement.returnConstant(false))
        XpUtils.findAndHookMethod("com.miui.server.SecurityManagerService", param.classLoader, "checkEnabled", PackageManager::class.java, String::class.java, XC_MethodReplacement.returnConstant(null))
        XpUtils.findAndHookMethod("com.miui.server.SecurityManagerService", param.classLoader, "enforcePlatformSignature", Array<Signature>::class.java, XC_MethodReplacement.returnConstant(null))
    }
}

現在,又可以享受凍結不會反彈的 MIUI 了。


后記:

其實在研究的過程中,踩過的坑遠遠不止這三種,Xposed 還有以下的大坑,開發時需注意:

  • 不能使用對應 APP 內的 JNI 庫,因為不在同一進程,如果非要用的話,必須事先將對應架構的 JNI 庫置入 /system/lib/vendor/lib
  • 不能在Xposed 內調用 su,因為 Xposed 執行的時候,su 所對應的上層應用還沒準備好,因此 root 請求會被直接拒絕,從而產生一個 permission denied 異常
  • 不能在 Xposed 內訪問 APP 所對應的 /data/data/ 內的數據,Xposed 進程并沒有這樣的權限,甚至簡單的判斷文件是否存在都只會返回 false
最后編輯于
?著作權歸作者所有,轉載或內容合作請聯系作者
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發布,文章內容僅代表作者本人觀點,簡書系信息發布平臺,僅提供信息存儲服務。

推薦閱讀更多精彩內容

  • Spring Boot 參考指南 介紹 轉載自:https://www.gitbook.com/book/qbgb...
    毛宇鵬閱讀 46,925評論 6 342
  • Spring Cloud為開發人員提供了快速構建分布式系統中一些常見模式的工具(例如配置管理,服務發現,斷路器,智...
    卡卡羅2017閱讀 134,837評論 18 139
  • Android 自定義View的各種姿勢1 Activity的顯示之ViewRootImpl詳解 Activity...
    passiontim閱讀 172,817評論 25 708
  • 等而上是愛情,等而下是獸性,等的是婚姻。2013-9-30# 梁實秋說,男人席間談話,三句就會說到女人身體上。據我...
    曾經而已閱讀 295評論 0 1
  • web.xml applicationContext.xml dispatcher-servlet.xml myb...
    蕊er閱讀 304評論 0 0