APK加固之動(dòng)態(tài)替換Application

想要替換 ProxyApplication首先對(duì) Application 啟動(dòng)源碼很熟悉才能對(duì)它進(jìn)行操作躏救,下面由我來帶著大家一起進(jìn)入源碼的世界吧颖杏。

Application 綁定過程

今天主要從 ActivityThread => main() 開始,下面以一個(gè)流程圖來說明一下:

image

XML 中如何解析我們的 Application

  • ActivityThread.java

    mian() -> thread.attach() -> attachApplication() -> 接收 AMS 發(fā)過來的參數(shù)之后 sendMessage(H.BIND_APPLICATION)-> 處理 BIND_APPLICATION -> handleBindApplication() 在這里準(zhǔn)備好 application - > Application app = data.info.makeApplication() - > mInitialApplication = app;

  • LoadedApk.java

    這個(gè)類就是 APK 在內(nèi)存中的表示政溃,可以得到如代碼趾访,資料,功能清單等信息

    1. 通過 mApplicationInfo.className 得到我們注冊(cè)的全類名
    2. app = mActivityThread.mInstrumentation.newApplication () 創(chuàng)建 application
    3. 接下來會(huì)使用 appContext.setOuterContext(app)
    4. mApplication = app

反射需要替換的內(nèi)容

  • ContextImpl -> mOuterContext(app) 通過 Application 的 attachBaseContext 回調(diào)參數(shù)獲取
  • ActivityThread -> mAllApplication(arrayList) 通過 ContextImpl 的 mMainThread 屬性獲取
  • LoadedApk -> mApplication 通過 ContextImpl 的 mPackageInfo 屬性獲取

反射開始替換 Application

        boolean isBindReal;
        Application delegate;
        private void bindRealApplicatin() throws Exception {
            if (isBindReal) {
                return;
            }
            if (TextUtils.isEmpty(app_name)) {
                return;
            }
            //得到attachBaseContext(context) 傳入的上下文 ContextImpl
            Context baseContext = getBaseContext();
            //創(chuàng)建用戶真實(shí)的application (MyApplication)
            Class<?> delegateClass = Class.forName(app_name);
            delegate = (Application) delegateClass.newInstance();
            //得到attach()方法
            Method attach = Application.class.getDeclaredMethod("attach", Context.class);
            attach.setAccessible(true);
            attach.invoke(delegate, baseContext);

    //        ContextImpl---->mOuterContext(app)   通過Application的attachBaseContext回調(diào)參數(shù)獲取
            Class<?> contextImplClass = Class.forName("android.app.ContextImpl");
            //獲取mOuterContext屬性
            Field mOuterContextField = contextImplClass.getDeclaredField("mOuterContext");
            mOuterContextField.setAccessible(true);
            mOuterContextField.set(baseContext, delegate);

    //        ActivityThread--->mAllApplications(ArrayList)       ContextImpl的mMainThread屬性
            Field mMainThreadField = contextImplClass.getDeclaredField("mMainThread");
            mMainThreadField.setAccessible(true);
            Object mMainThread = mMainThreadField.get(baseContext);

    //        ActivityThread--->>mInitialApplication
            Class<?> activityThreadClass=Class.forName("android.app.ActivityThread");
            Field mInitialApplicationField = activityThreadClass.getDeclaredField("mInitialApplication");
            mInitialApplicationField.setAccessible(true);
            mInitialApplicationField.set(mMainThread,delegate);
    //        ActivityThread--->mAllApplications(ArrayList)       ContextImpl的mMainThread屬性
            Field mAllApplicationsField = activityThreadClass.getDeclaredField("mAllApplications");
            mAllApplicationsField.setAccessible(true);
            ArrayList<Application> mAllApplications =(ArrayList<Application>) mAllApplicationsField.get(mMainThread);
            mAllApplications.remove(this);
            mAllApplications.add(delegate);

    //        LoadedApk------->mApplication                      ContextImpl的mPackageInfo屬性
            Field mPackageInfoField = contextImplClass.getDeclaredField("mPackageInfo");
            mPackageInfoField.setAccessible(true);
            Object mPackageInfo=mPackageInfoField.get(baseContext);

            Class<?> loadedApkClass=Class.forName("android.app.LoadedApk");
            Field mApplicationField = loadedApkClass.getDeclaredField("mApplication");
            mApplicationField.setAccessible(true);
            mApplicationField.set(mPackageInfo,delegate);

            //修改ApplicationInfo className   LooadedApk
            Field mApplicationInfoField = loadedApkClass.getDeclaredField("mApplicationInfo");
            mApplicationInfoField.setAccessible(true);
            ApplicationInfo mApplicationInfo = (ApplicationInfo)mApplicationInfoField.get(mPackageInfo);
            mApplicationInfo.className=app_name;

            delegate.onCreate();
            isBindReal = true;
        }

現(xiàn)在重新簽名打包完成董虱,啟動(dòng)我們的 APK 看下 Log

image
2019-06-04 23:17:30.892 6064-6064/com.yk.dexdeapplication I/DevYK: provider onCreate:com.example.proxy_core.ProxyApplication@1ec3c70
2019-06-04 23:17:30.892 6064-6064/com.yk.dexdeapplication I/DevYK: provider onCreate:com.example.proxy_core.ProxyApplication@1ec3c70
2019-06-04 23:17:30.892 6064-6064/com.yk.dexdeapplication I/DevYK: provider onCreate:com.example.proxy_core.ProxyApplication
2019-06-04 23:17:30.895 6064-6064/com.yk.dexdeapplication I/DevYK: MyApplication onCreate()
2019-06-04 23:17:30.995 6064-6064/com.yk.dexdeapplication I/DevYK: activity:com.yk.dexdeapplication.App@300b5f6
2019-06-04 23:17:30.995 6064-6064/com.yk.dexdeapplication I/DevYK: activity:com.yk.dexdeapplication.App@300b5f6
2019-06-04 23:17:30.995 6064-6064/com.yk.dexdeapplication I/DevYK: activity:com.yk.dexdeapplication.App
2019-06-04 23:17:31.001 6064-6064/com.yk.dexdeapplication I/DevYK: provider delete:com.example.proxy_core.ProxyApplication@1ec3c70
2019-06-04 23:17:31.021 6064-6064/com.yk.dexdeapplication I/DevYK: service:com.yk.dexdeapplication.App@300b5f6
2019-06-04 23:17:31.021 6064-6064/com.yk.dexdeapplication I/DevYK: service:com.yk.dexdeapplication.App@300b5f6
2019-06-04 23:17:31.021 6064-6064/com.yk.dexdeapplication I/DevYK: service:com.yk.dexdeapplication.App
2019-06-04 23:17:31.022 6064-6064/com.yk.dexdeapplication I/DevYK: reciver:android.app.ReceiverRestrictedContext@9b92293
2019-06-04 23:17:31.022 6064-6064/com.yk.dexdeapplication I/DevYK: reciver:com.yk.dexdeapplication.App@300b5f6
2019-06-04 23:17:31.022 6064-6064/com.yk.dexdeapplication I/DevYK: reciver:com.yk.dexdeapplication.App

注意看 LOG

MyApplication onCreate()

這里已經(jīng)替換成我們自己的 MyApplication , 而且 Activity 和 Service 獲取上下文也已經(jīng)是我們替換成功的 Applicaton扼鞋。但是...也許有的眼神比較好的已經(jīng)看出問題了,為什么內(nèi)容提供者 Context 還是代理的 Application 而且比我們自己的應(yīng)用還要先執(zhí)行,那么我們帶著這個(gè)問題去看 Application onCreate 之前做了什么事兒云头。

image

我們點(diǎn)擊 installlContentProviders(app,providers);

注意這里傳進(jìn)去的還是 代理 Context

image

重點(diǎn)在最后

image

注意看我 勾畫 的圈里面的邏輯判斷捐友,判斷當(dāng)前應(yīng)用的包名是否跟 XML 中的包名一致,如果一致我們就賦值盘寡,再次提醒下 這里的 context 是我們代理的 context ,那么我們?cè)趺醋隼粘睿覀冊(cè)诖碇兄貙?PackageName 只要都不等 那么就會(huì)走 else 會(huì)根據(jù)包名創(chuàng)建一個(gè) Context

    /**
     * 讓代碼走入if中的第三段中
     * @return
     */
    @Override
    public String getPackageName() {
        if(!TextUtils.isEmpty(app_name)){
            return "";
        }
        return super.getPackageName();
    }

    @Override
    public Context createPackageContext(String packageName, int flags) throws PackageManager.NameNotFoundException {
       if(TextUtils.isEmpty(app_name)){
           return super.createPackageContext(packageName, flags);
       }
        try {
            bindRealApplicatin();
        } catch (Exception e) {
            e.printStackTrace();
        }
        return delegate;

    }

  1. 首先判斷我們自己 XML 中的 app_name 是否為空
  2. 如果不為空,我們傳入一個(gè) 空包
  3. SDK 會(huì)判斷是否跟 XML 中的 pck 一樣竿痰,最后走 else 我們?cè)谥貙?createPackageContext 傳入我們自己應(yīng)用的包名脆粥。會(huì)生成一個(gè) Context

最后我們來驗(yàn)證一下:

image

<figcaption></figcaption>

2019-06-05 00:12:30.271 7570-7570/com.yk.dexdeapplication I/DevYK: MyApplication onCreate()
2019-06-05 00:12:30.273 7570-7570/com.yk.dexdeapplication I/DevYK: provider onCreate:com.yk.dexdeapplication.App@1ec3c70
2019-06-05 00:12:30.273 7570-7570/com.yk.dexdeapplication I/DevYK: provider onCreate:com.yk.dexdeapplication.App@1ec3c70
2019-06-05 00:12:30.273 7570-7570/com.yk.dexdeapplication I/DevYK: provider onCreate:com.yk.dexdeapplication.App
2019-06-05 00:12:30.381 7570-7570/com.yk.dexdeapplication I/DevYK: activity:com.yk.dexdeapplication.App@1ec3c70
2019-06-05 00:12:30.381 7570-7570/com.yk.dexdeapplication I/DevYK: activity:com.yk.dexdeapplication.App@1ec3c70
2019-06-05 00:12:30.381 7570-7570/com.yk.dexdeapplication I/DevYK: activity:com.yk.dexdeapplication.App
2019-06-05 00:12:30.387 7570-7570/com.yk.dexdeapplication I/DevYK: provider delete:com.yk.dexdeapplication.App@1ec3c70
2019-06-05 00:12:30.406 7570-7570/com.yk.dexdeapplication I/DevYK: service:com.yk.dexdeapplication.App@1ec3c70
2019-06-05 00:12:30.406 7570-7570/com.yk.dexdeapplication I/DevYK: service:com.yk.dexdeapplication.App@1ec3c70
2019-06-05 00:12:30.406 7570-7570/com.yk.dexdeapplication I/DevYK: service:com.yk.dexdeapplication.App
2019-06-05 00:12:30.408 7570-7570/com.yk.dexdeapplication I/DevYK: reciver:android.app.ReceiverRestrictedContext@b7a3b82
2019-06-05 00:12:30.408 7570-7570/com.yk.dexdeapplication I/DevYK: reciver:com.yk.dexdeapplication.App@1ec3c70
2019-06-05 00:12:30.408 7570-7570/com.yk.dexdeapplication I/DevYK: reciver:com.yk.dexdeapplication.App

日志中除了 BroadCase Context 是系統(tǒng)的以外,所有的 Context 都是我們替換的 Application Context影涉。完美解決变隔。不過這里有一個(gè)隱藏 BUG ,據(jù)說面試題會(huì)問那么是什么勒蟹倾?

可以在廣播中使用 context 在開啟一個(gè)廣播或者綁定一個(gè)服務(wù)嗎匣缘?

我們其實(shí)可以帶著這個(gè)問題看下源碼

H -> RECEIVER 消息

image

<figcaption></figcaption>

image

果然注冊(cè)廣播和綁定服務(wù)會(huì)拋一個(gè)異常荆残。

總結(jié)

image

到這里我們的加固已經(jīng)講完了党觅,從 dex 分包 -> 加密 -> 對(duì)齊 > 簽名 - > 打包壓縮成 APK 。一套完整的流程和代碼都已經(jīng)寫完了凰狞。跟市面上的加固流程原理都幾乎一樣豁陆。懂了原理再去使用第三方就輕車熟路了柑爸。

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個(gè)濱河市盒音,隨后出現(xiàn)的幾起案子表鳍,更是在濱河造成了極大的恐慌,老刑警劉巖祥诽,帶你破解...
    沈念sama閱讀 212,454評(píng)論 6 493
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件譬圣,死亡現(xiàn)場離奇詭異,居然都是意外死亡雄坪,警方通過查閱死者的電腦和手機(jī)厘熟,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 90,553評(píng)論 3 385
  • 文/潘曉璐 我一進(jìn)店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來维哈,“玉大人盯漂,你說我怎么就攤上這事”颗” “怎么了?”我有些...
    開封第一講書人閱讀 157,921評(píng)論 0 348
  • 文/不壞的土叔 我叫張陵帖渠,是天一觀的道長谒亦。 經(jīng)常有香客問我,道長,這世上最難降的妖魔是什么份招? 我笑而不...
    開封第一講書人閱讀 56,648評(píng)論 1 284
  • 正文 為了忘掉前任切揭,我火速辦了婚禮,結(jié)果婚禮上锁摔,老公的妹妹穿的比我還像新娘廓旬。我一直安慰自己,他們只是感情好谐腰,可當(dāng)我...
    茶點(diǎn)故事閱讀 65,770評(píng)論 6 386
  • 文/花漫 我一把揭開白布孕豹。 她就那樣靜靜地躺著,像睡著了一般十气。 火紅的嫁衣襯著肌膚如雪励背。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 49,950評(píng)論 1 291
  • 那天砸西,我揣著相機(jī)與錄音叶眉,去河邊找鬼。 笑死芹枷,一個(gè)胖子當(dāng)著我的面吹牛衅疙,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播鸳慈,決...
    沈念sama閱讀 39,090評(píng)論 3 410
  • 文/蒼蘭香墨 我猛地睜開眼饱溢,長吁一口氣:“原來是場噩夢(mèng)啊……” “哼!你這毒婦竟也來了蝶涩?” 一聲冷哼從身側(cè)響起理朋,我...
    開封第一講書人閱讀 37,817評(píng)論 0 268
  • 序言:老撾萬榮一對(duì)情侶失蹤,失蹤者是張志新(化名)和其女友劉穎绿聘,沒想到半個(gè)月后嗽上,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 44,275評(píng)論 1 303
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡熄攘,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 36,592評(píng)論 2 327
  • 正文 我和宋清朗相戀三年兽愤,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片挪圾。...
    茶點(diǎn)故事閱讀 38,724評(píng)論 1 341
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡浅萧,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出哲思,到底是詐尸還是另有隱情洼畅,我是刑警寧澤,帶...
    沈念sama閱讀 34,409評(píng)論 4 333
  • 正文 年R本政府宣布棚赔,位于F島的核電站帝簇,受9級(jí)特大地震影響徘郭,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜丧肴,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 40,052評(píng)論 3 316
  • 文/蒙蒙 一残揉、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧芋浮,春花似錦抱环、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,815評(píng)論 0 21
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至何暇,卻和暖如春陶夜,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背裆站。 一陣腳步聲響...
    開封第一講書人閱讀 32,043評(píng)論 1 266
  • 我被黑心中介騙來泰國打工条辟, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留,地道東北人宏胯。 一個(gè)月前我還...
    沈念sama閱讀 46,503評(píng)論 2 361
  • 正文 我出身青樓羽嫡,卻偏偏與公主長得像,于是被迫代替她去往敵國和親肩袍。 傳聞我的和親對(duì)象是個(gè)殘疾皇子杭棵,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 43,627評(píng)論 2 350