利用動(dòng)態(tài)加載技術(shù)加固APK原理解析

前言

《從底層分析PathClassLoader和DexClassLoader的區(qū)別,基于Android4.4》中分析了Android虛擬機(jī)的兩種類加載器匣沼,利用動(dòng)態(tài)加載技術(shù)實(shí)現(xiàn)APK安全加固依賴于DexClassLoader的使用贴彼。

為了更好的理解本文的內(nèi)容昙楚,建議讀《深入理解Java虛擬機(jī)》中的 “”虛擬機(jī)類加載機(jī)制“”,老羅的《Android系統(tǒng)源代碼情景分析》中Application和四大組件的啟動(dòng)過程枉氮。

本人在剛開始做APK加固時(shí)也是參考了別人的文章危尿,但是只有深入到源碼分析并且親手實(shí)踐才知道有些代碼為什么是這樣寫的。

虛擬機(jī)中的類加載分析


根據(jù)《深入理解Java虛擬機(jī)》描述渗磅,類從被加載到虛擬機(jī)內(nèi)存開始,到卸載出內(nèi)存為止,它的整個(gè)生命周期包括:加載始鱼、驗(yàn)證仔掸、準(zhǔn)備、解析医清、初始化起暮、使用的卸載。其中驗(yàn)證会烙、準(zhǔn)備负懦、解析統(tǒng)稱為連接。其中解析和初始化沒有先后順序柏腻。
Dex相當(dāng)于Class字節(jié)碼文件的集合纸厉,符合Java虛擬機(jī)規(guī)范。根據(jù)Java類加載描述五嫂,類加載的時(shí)機(jī)沒有強(qiáng)行約束颗品,但當(dāng)遇到以下情況必須進(jìn)行初始化:

  • 遇到new、putstatic沃缘、getstatic躯枢、invokestatic字節(jié)碼指令時(shí);
  • 使用java.lang.reflect的方法對(duì)類進(jìn)行反射調(diào)用時(shí)槐臀;
  • 初始化一個(gè)類時(shí)锄蹂,先初始化其父類;
  • 虛擬機(jī)啟動(dòng)時(shí)峰档,需要初始化包含main函數(shù)的類败匹;
  • 使用JDK1.7的動(dòng)態(tài)語言支持時(shí)寨昙;

類加載器

虛擬機(jī)設(shè)計(jì)團(tuán)隊(duì)把類加載階段中的“通過一個(gè)類的全限定名來獲取描述此類的二進(jìn)制字節(jié)碼流”這個(gè)動(dòng)作放到Java虛擬機(jī)外部實(shí)現(xiàn)讥巡,以便讓程序自己決定如何獲取所需的類,實(shí)現(xiàn)這個(gè)動(dòng)作的模塊稱為“類加載器”舔哪。

類加載器除了具有加載類的功能欢顷,另一個(gè)意義是每一個(gè)類加載器都有一個(gè)獨(dú)立的類命名空間,比較兩個(gè)類是否相等只對(duì)同一個(gè)類加載器才有意義捉蚤。假設(shè)同一個(gè)Class文件抬驴,一個(gè)由系統(tǒng)的類加載器加載,另一個(gè)由程序自定義的類加載器加載缆巧,雖然是同一Class文件卻對(duì)應(yīng)不同類加載器的兩個(gè)實(shí)例布持,在虛擬機(jī)中是不相等的。

雙親委派模型


前文說到用戶可以自定義類加載器陕悬,并且每個(gè)類加載器加載的同一Class并不相等题暖。但對(duì)于某些公共的類,比如在Java SDK中定義的Class,我們沒有必要因?yàn)樽远x類加載器的不同而加載出不同的Class對(duì)象胧卤。因此在JDK1.2期間引入并被廣泛應(yīng)用于以后的所有Java虛擬機(jī)中唯绍。

雙親委派模型的工作原理是:如果一個(gè)類加載器收到了類加載的請(qǐng)求,它首先不會(huì)自己去嘗試加載這個(gè)類枝誊,而是把這個(gè)請(qǐng)求委派給父類加載器去完成况芒,每一個(gè)層次的類加載器都是如此,因此所有的加載請(qǐng)求最終都應(yīng)該傳送到頂層的啟動(dòng)類加載器中叶撒,只有當(dāng)父加載器反饋?zhàn)约簾o法完成這個(gè)加載請(qǐng)求(它的搜索范圍中沒有找到所需的類)時(shí)绝骚,子加載器才會(huì)嘗試自己去加載。

因此雙親委派模型能夠保證例如Java SDK中的公共Class在不同自定義加載器中也是相等的祠够,因?yàn)椴煌淖远x類加載器都有相同的父類加載器皮壁。保證了公共類的一致性。
雙親委派模型對(duì)于保證Java程序的穩(wěn)定運(yùn)作很重要哪审,但它的實(shí)現(xiàn)卻非常簡(jiǎn)單蛾魄,實(shí)現(xiàn)雙親委派的代碼都集中在java.lang.ClassLoader的loadClass()方法之中:先檢查是否已經(jīng)被加載過,若沒有加載則調(diào)用父加載器的loadClass()方法湿滓,若父加載器為空則默認(rèn)使用啟動(dòng)類加載器作為父加載器滴须。如果父類加載失敗,拋出ClassNotFoundException異常后叽奥,再調(diào)用自己的findClass()方法進(jìn)行加載扔水。

分析Anrdoid類加載器的創(chuàng)建及Application的初始化

什么時(shí)候Android程序被啟動(dòng)?
大多數(shù)人都知道在桌面點(diǎn)擊一個(gè)應(yīng)用的圖標(biāo)可以啟動(dòng)該應(yīng)用的Activity,其實(shí)不光是Activity朝氓,通過遠(yuǎn)程調(diào)用Service魔市、ContentProvider,乃至發(fā)送一個(gè)廣播都會(huì)啟動(dòng)目標(biāo)進(jìn)程赵哲。當(dāng)然根據(jù)廠家的適配,通過靜態(tài)廣播喚醒進(jìn)程并不一定可行待德。實(shí)測(cè)在Android 4.1 虛擬機(jī)上是可行的。

Android進(jìn)程如何啟動(dòng)?
ActivityThread中的main函數(shù)是Android進(jìn)程的唯一入口枫夺,當(dāng)啟動(dòng)四大組件會(huì)先檢查目標(biāo)的進(jìn)程有沒有啟動(dòng)将宪,若沒有啟動(dòng)會(huì)通過Zygote孵化一個(gè)Java進(jìn)程并執(zhí)行ActivityThread類的main函數(shù)。
此函數(shù)中會(huì)執(zhí)行attach函數(shù)橡庞,通過Binder機(jī)制與ActivityManagerService(簡(jiǎn)稱AMS)通信较坛,將自己attach到AMS的ProcessRecord對(duì)象,反過來在由AMS調(diào)用ActivityThread的handleBindApplication函數(shù):

private void handleBindApplication(AppBindData data) {
    mBoundApplication = data;
    ...
    data.info = getPackageInfoNoCheck(data.appInfo, data.compatInfo);
    ...
}

這里調(diào)用了getPackageInfoNoCheck函數(shù)給data.info賦值扒最,而data.info實(shí)際是LoadedApk對(duì)象丑勤,再來看該函數(shù):

public final LoadedApk getPackageInfoNoCheck(ApplicationInfo ai,
                                             CompatibilityInfo compatInfo) {
    return getPackageInfo(ai, compatInfo, null, false, true);
}

private LoadedApk getPackageInfo(ApplicationInfo aInfo, CompatibilityInfo compatInfo,
                                 ClassLoader baseLoader, boolean securityViolation, boolean includeCode) {
    synchronized (mResourcesManager) {
        WeakReference<LoadedApk> ref;
        if (includeCode) {
            ref = mPackages.get(aInfo.packageName);
        } else {
            ref = mResourcePackages.get(aInfo.packageName);
        }
        LoadedApk packageInfo = ref != null ? ref.get() : null;
        if (packageInfo == null || (packageInfo.mResources != null
                && !packageInfo.mResources.getAssets().isUpToDate())) {
            if (localLOGV) Slog.v(TAG, (includeCode ? "Loading code package "
                    : "Loading resource-only package ") + aInfo.packageName
                    + " (in " + (mBoundApplication != null
                    ? mBoundApplication.processName : null)
                    + ")");
            packageInfo =
                    new LoadedApk(this, aInfo, compatInfo, baseLoader,
                            securityViolation, includeCode &&
                            (aInfo.flags&ApplicationInfo.FLAG_HAS_CODE) != 0, registerPackage);
            ...
    }
}

LoadedApk對(duì)象會(huì)先從mPackages(API19及以下是HashMap類型,以上是ArrayMap類型)中嘗試獲取一個(gè)弱引用吧趣,若找不到該引用則會(huì)調(diào)用構(gòu)造方法創(chuàng)建一個(gè)新的法竞。

而觀察getPackageInfoNoCheck除呵,發(fā)現(xiàn)傳入的baseLoader參數(shù)為空,這意味著默認(rèn)類加載器沒有父加載器,LoadedApk 構(gòu)造源碼如下:

public final class LoadedApk {
    ...
    private final ClassLoader mBaseClassLoader;
    private ClassLoader mClassLoader;
    ...
    public LoadedApk(ActivityThread activityThread, ApplicationInfo aInfo,
                     CompatibilityInfo compatInfo, ClassLoader baseLoader,
                     boolean securityViolation, boolean includeCode, boolean registerPackage) {
        ...
        mBaseClassLoader = baseLoader;
        ...
    }

    public ClassLoader getClassLoader() {
        synchronized (this) {
            if (mClassLoader != null) {
                return mClassLoader;
            }

            if (mIncludeCode && !mPackageName.equals("android")) {
                ...
                mClassLoader = ApplicationLoaders.getDefault().getClassLoader(zip, lib,
                        mBaseClassLoader);
                ...
            } else {
                if (mBaseClassLoader == null) {
                    mClassLoader = ClassLoader.getSystemClassLoader();
                } else {
                    mClassLoader = mBaseClassLoader;
                }
            }
            return mClassLoader;
        }
    }
}

ApplicationLoaders.getDefault().getClassLoader(zip,lib, mBaseClassLoader)正是構(gòu)造默認(rèn)類加載器的函數(shù):

class ApplicationLoaders
{
    public static ApplicationLoaders getDefault()
    {
        return gApplicationLoaders;
    }

    public ClassLoader getClassLoader(String zip, String libPath, ClassLoader parent)
    {

        ClassLoader baseParent = ClassLoader.getSystemClassLoader().getParent();

        synchronized (mLoaders) {
            if (parent == null) {
                parent = baseParent;
            }
            if (parent == baseParent) {
                ClassLoader loader = mLoaders.get(zip);
                if (loader != null) {
                    return loader;
                }

                Trace.traceBegin(Trace.TRACE_TAG_ACTIVITY_MANAGER, zip);
                PathClassLoader pathClassloader =
                        new PathClassLoader(zip, libPath, parent);
                Trace.traceEnd(Trace.TRACE_TAG_ACTIVITY_MANAGER);

                mLoaders.put(zip, pathClassloader);
                return pathClassloader;
            }

            Trace.traceBegin(Trace.TRACE_TAG_ACTIVITY_MANAGER, zip);
            PathClassLoader pathClassloader = new PathClassLoader(zip, parent);
            Trace.traceEnd(Trace.TRACE_TAG_ACTIVITY_MANAGER);
            return pathClassloader;
        }
    }

    private final ArrayMap<String, ClassLoader> mLoaders = new ArrayMap<String, ClassLoader>();

    private static final ApplicationLoaders gApplicationLoaders
            = new ApplicationLoaders();
}

到這里我們大體能明白Android Framework是如何創(chuàng)建默認(rèn)類加載器的爪喘,系統(tǒng)由ApplicaionLoaders.getDefault().getClassLoader創(chuàng)建PathClassLoader作為默認(rèn)的類加載器颜曾。然后由這個(gè)ClassLoader去加載DEX中的各個(gè)類。

接著回到handleBindApplication函數(shù):

private void handleBindApplication(AppBindData data) {
    ...
    try {
        Application app = data.info.makeApplication(data.restrictedBackupMode, null);
        mInitialApplication = app;
        if (!data.restrictedBackupMode) {
            List<ProviderInfo> providers = data.providers;
            if (providers != null) {
                installContentProviders(app, providers);
                mH.sendEmptyMessageDelayed(H.ENABLE_JIT, 10*1000);
            }
        }
        try {
            mInstrumentation.onCreate(data.instrumentationArgs);
        }
        catch (Exception e) {
            throw new RuntimeException(
                    "Exception thrown in onCreate() of "
                            + data.instrumentationName + ": " + e.toString(), e);
        }

        try {
            mInstrumentation.callApplicationOnCreate(app);
        } catch (Exception e) {
            if (!mInstrumentation.onException(app, e)) {
                throw new RuntimeException(
                        "Unable to create application " + app.getClass().getName()
                                + ": " + e.toString(), e);
            }
        }
    } finally {
        StrictMode.setThreadPolicy(savedPolicy);
    }
}

在創(chuàng)建完LoadedApk對(duì)象后通過執(zhí)行makeApplication函數(shù)創(chuàng)建Application對(duì)象的實(shí)例秉剑。

創(chuàng)建完Application對(duì)象后通過mInstrumentation的callApplicationOnCreate函數(shù)啟動(dòng)Application泛豪。

我們先來看makeApplication函數(shù)的代碼:

public Application makeApplication(boolean forceDefaultAppClass,
                                   Instrumentation instrumentation) {
    if (mApplication != null) {
        return mApplication;
    }
    Application app = null;
    String appClass = mApplicationInfo.className;
    if (forceDefaultAppClass || (appClass == null)) {
        appClass = "android.app.Application";
    }
    try {
        java.lang.ClassLoader cl = getClassLoader();
        ContextImpl appContext = new ContextImpl();
        appContext.init(this, null, mActivityThread);
        app = mActivityThread.mInstrumentation.newApplication(
                cl, appClass, appContext);
        appContext.setOuterContext(app);
    } catch (Exception e) {
        if (!mActivityThread.mInstrumentation.onException(app, e)) {
            throw new RuntimeException(
                    "Unable to instantiate application " + appClass
                            + ": " + e.toString(), e);
        }
    }
    mActivityThread.mAllApplications.add(app);
    mApplication = app;
    if (instrumentation != null) {
        try {
            instrumentation.callApplicationOnCreate(app);
        } catch (Exception e) {
            if (!instrumentation.onException(app, e)) {
                throw new RuntimeException(
                        "Unable to create application " + app.getClass().getName()
                                + ": " + e.toString(), e);
            }
        }
    }
    return app;
}

如果LoadedApk中已存在mApplication的引用則直接返回,如果沒有就用前文提到的類加載器去創(chuàng)建它,這里的具體實(shí)現(xiàn)是mInstrumentation.newApplication(cl, appClass, appContext)侦鹏。

之后將LoadedApk中的mApplication賦值以便重復(fù)使用诡曙,并將這個(gè)引用添加到mActivityThread.mAllApplications這個(gè)數(shù)組。因?yàn)閭魅氲膇nstrumentation為空所以不執(zhí)行Application對(duì)象的生命周期函數(shù)onCreate略水。

接下來分android.app.Instrumentation這個(gè)類价卤,上文創(chuàng)建Application對(duì)象用到了newApplication函數(shù),執(zhí)行Application的生命周期用到了callApplicationOnCreate函數(shù):

public Application newApplication(ClassLoader cl, String className, Context context)
        throws InstantiationException, IllegalAccessException,
        ClassNotFoundException {
    return newApplication(cl.loadClass(className), context);
}

static public Application newApplication(Class<?> clazz, Context context)
        throws InstantiationException, IllegalAccessException,
        ClassNotFoundException {
    Application app = (Application)clazz.newInstance();
    app.attach(context);
    return app;
}
public void callApplicationOnCreate(Application app) {
        app.onCreate();

分析得知,Application對(duì)象實(shí)際是由Class的newInstance函數(shù)創(chuàng)建的渊涝,這種語法經(jīng)常見于Java反射機(jī)制的調(diào)用慎璧。而callApplicationOnCreate實(shí)際調(diào)用了onCreate函數(shù)。

下面通過分析Android4.4(API 19)源碼得到ActivityThread對(duì)象關(guān)系圖:


到此我們知道Android默認(rèn)的類加載器是如何創(chuàng)建的跨释,并且如何用這個(gè)類加載器創(chuàng)建了Application對(duì)象胸私。

分析Android四大組件的類加載過程

上文分析了Application對(duì)象的創(chuàng)建過程,以及PathClassLoader的創(chuàng)建過程鳖谈。下文中還需要分析四大組件是如何創(chuàng)建及啟動(dòng)的岁疼。為什么不是只分析Activity?除了Activity以外缆娃,Android程序還可以以后臺(tái)Service捷绒、數(shù)據(jù)共享提供器ContentProvider、廣播接收者BroadCastReceiver的形式運(yùn)行贯要。我們需要知道動(dòng)態(tài)加載技術(shù)對(duì)四大組件的創(chuàng)建及生命周期是否可行暖侨。

Activity

以下時(shí)序圖來自于老羅的《Android系統(tǒng)源代碼情景分析》



從一個(gè)Android進(jìn)程啟動(dòng)另一個(gè)Android進(jìn)程的Activity需要先初始化ActivtyThread并創(chuàng)建Application,如上文所述郭毕。然后由AMS通過Binder機(jī)制通知ActivityThread執(zhí)行scheduleLaunchActivity函數(shù):

public final void scheduleLaunchActivity(Intent intent, IBinder token, int ident,
                                         ActivityInfo info, Configuration curConfig, CompatibilityInfo compatInfo,
                                         String referrer, IVoiceInteractor voiceInteractor, int procState, Bundle state,
                                         PersistableBundle persistentState, List<ResultInfo> pendingResults,
                                         List<ReferrerIntent> pendingNewIntents, boolean notResumed, boolean isForward,
                                         ProfilerInfo profilerInfo) {
    updateProcessState(procState, false);
    ActivityClientRecord r = new ActivityClientRecord();
    r.token = token;
    r.ident = ident;
    r.intent = intent;
    r.referrer = referrer;
    r.voiceInteractor = voiceInteractor;
    r.activityInfo = info;
    r.compatInfo = compatInfo;
    r.state = state;
    r.persistentState = persistentState;
    r.pendingResults = pendingResults;
    r.pendingIntents = pendingNewIntents;
    r.startsNotResumed = notResumed;
    r.isForward = isForward;
    r.profilerInfo = profilerInfo;
    updatePendingConfiguration(curConfig);
    sendMessage(H.LAUNCH_ACTIVITY, r);
}

接著在handleMessage函數(shù)接收Message它碎,根據(jù)msg.what繼續(xù)執(zhí)行handleLaunchActivity函數(shù):

public void handleMessage(Message msg) {
    switch (msg.what) {
        case LAUNCH_ACTIVITY: {
            Trace.traceBegin(Trace.TRACE_TAG_ACTIVITY_MANAGER, "activityStart");
            final ActivityClientRecord r = (ActivityClientRecord) msg.obj;

            r.packageInfo = getPackageInfoNoCheck(
                    r.activityInfo.applicationInfo, r.compatInfo);
            handleLaunchActivity(r, null);
            Trace.traceEnd(Trace.TRACE_TAG_ACTIVITY_MANAGER);
        }
        ...
    }
}

private void handleLaunchActivity(ActivityClientRecord r, Intent customIntent) {
    ...
    Activity a = performLaunchActivity(r, customIntent);
    if (a != null) {
        r.createdConfig = new Configuration(mConfiguration);
        Bundle oldState = r.state;
        handleResumeActivity(r.token, false, r.isForward,
                !r.activity.mFinished && !r.startsNotResumed);
        ...
    } else {
        // If there was an error, for any reason, tell the activity
        // manager to stop us.
        try {
            ActivityManagerNative.getDefault()
                    .finishActivity(r.token, Activity.RESULT_CANCELED, null, false);
        } catch (RemoteException ex) {
            // Ignore
        }
    }
}

可以看到performLaunchActivity函數(shù)創(chuàng)建了Activity實(shí)例,handleResumeActivity函數(shù)調(diào)用了Activity的onResume函數(shù):

private Activity performLaunchActivity(ActivityClientRecord r, Intent customIntent) {
    ...
    Activity activity = null;
    try {
        java.lang.ClassLoader cl = r.packageInfo.getClassLoader();
        activity = mInstrumentation.newActivity(
                cl, component.getClassName(), r.intent);
        ...
    } catch (Exception e) {
        if (!mInstrumentation.onException(activity, e)) {
            throw new RuntimeException(
                    "Unable to instantiate activity " + component
                            + ": " + e.toString(), e);
        }
    }
    try {
        Application app = r.packageInfo.makeApplication(false, mInstrumentation);
        if (activity != null) {
            Context appContext = createBaseContextForActivity(r, activity);
            CharSequence title = r.activityInfo.loadLabel(appContext.getPackageManager());
            Configuration config = new Configuration(mCompatConfiguration);
            if (DEBUG_CONFIGURATION) Slog.v(TAG, "Launching activity "
                    + r.activityInfo.name + " with config " + config);
            activity.attach(appContext, this, getInstrumentation(), r.token,
                    r.ident, app, r.intent, r.activityInfo, title, r.parent,
                    r.embeddedID, r.lastNonConfigurationInstances, config,
                    r.referrer, r.voiceInteractor);
      ...
            activity.mStartedActivity = false;
            int theme = r.activityInfo.getThemeResource();
            if (theme != 0) {
                activity.setTheme(theme);
            }
            activity.mCalled = false;
            if (r.isPersistable()) {
                mInstrumentation.callActivityOnCreate(activity, r.state, r.persistentState);
            } else {
                mInstrumentation.callActivityOnCreate(activity, r.state);
            }
            if (!activity.mCalled) {
                throw new SuperNotCalledException(
                        "Activity " + r.intent.getComponent().toShortString() +
                                " did not call through to super.onCreate()");
            }
            r.activity = activity;
            r.stopped = true;
            if (!r.activity.mFinished) {
                activity.performStart();
                r.stopped = false;
            }
            if (!r.activity.mFinished) {
                if (r.isPersistable()) {
                    if (r.state != null || r.persistentState != null) {
                        mInstrumentation.callActivityOnRestoreInstanceState(activity, r.state,
                                r.persistentState);
                    }
                } else if (r.state != null) {
                    mInstrumentation.callActivityOnRestoreInstanceState(activity, r.state);
                }
            }
            if (!r.activity.mFinished) {
                activity.mCalled = false;
                if (r.isPersistable()) {
                    mInstrumentation.callActivityOnPostCreate(activity, r.state,
                            r.persistentState);
                } else {
                    mInstrumentation.callActivityOnPostCreate(activity, r.state);
                }
                if (!activity.mCalled) {
                    throw new SuperNotCalledException(
                            "Activity " + r.intent.getComponent().toShortString() +
                                    " did not call through to super.onPostCreate()");
                }
            }
        }
        r.paused = true;
        mActivities.put(r.token, r);
    } catch (SuperNotCalledException e) {
        throw e;

    } catch (Exception e) {
        if (!mInstrumentation.onException(activity, e)) {
            throw new RuntimeException(
                    "Unable to start activity " + component
                            + ": " + e.toString(), e);
        }
    }
    return activity;
}

這里我們只關(guān)注Activity的創(chuàng)建過程和Activity的生命周期函數(shù)显押。可以看到在Activity對(duì)象創(chuàng)建后傻挂,由mInstrumentation調(diào)用了callActivityOnCreate乘碑、callActivityOnRestoreInstanceState函數(shù),間接調(diào)用了Activity的onCreate、onRestoreInstanceState函數(shù)金拒。

r.packageInfo.getClassLoader()是Activity的類加載器兽肤,newActivity是Activity對(duì)象的創(chuàng)建函數(shù):

public Activity newActivity(ClassLoader cl, String className,
                            Intent intent)
        throws InstantiationException, IllegalAccessException,
        ClassNotFoundException {
    return (Activity)cl.loadClass(className).newInstance();
}

再來看r.packageInfo.getClassLoader()是怎么來的,r是ActivityClientRecord 對(duì)象,最初是由scheduleLaunchActivity函數(shù)創(chuàng)建的,r.packageInfo是在handleMessage函數(shù)中調(diào)用getPackageInfoNoCheck函數(shù)得到的套腹,再看getPackageInfoNoCheck函數(shù):

public final LoadedApk getPackageInfoNoCheck(ApplicationInfo ai,
                                             CompatibilityInfo compatInfo) {
    return getPackageInfo(ai, compatInfo, null, false, true);
}

到這一步跟上文分析創(chuàng)建Application獲取類加載器似曾相識(shí),因?yàn)槭峭瑯拥暮瘮?shù):

private LoadedApk getPackageInfo(ApplicationInfo aInfo, CompatibilityInfo compatInfo,
                                 ClassLoader baseLoader, boolean securityViolation, boolean includeCode) {
    synchronized (mPackages) {
        WeakReference<LoadedApk> ref;
        if (includeCode) {
            ref = mPackages.get(aInfo.packageName);
        } else {
            ref = mResourcePackages.get(aInfo.packageName);
        }
        LoadedApk packageInfo = ref != null ? ref.get() : null;
        if (packageInfo == null || (packageInfo.mResources != null
                && !packageInfo.mResources.getAssets().isUpToDate())) {
            packageInfo =
                    new LoadedApk(this, aInfo, compatInfo, this, baseLoader,
                            securityViolation, includeCode &&
                            (aInfo.flags&ApplicationInfo.FLAG_HAS_CODE) != 0);
            if (includeCode) {
                mPackages.put(aInfo.packageName,
                        new WeakReference<LoadedApk>(packageInfo));
            } else {
                mResourcePackages.put(aInfo.packageName,
                        new WeakReference<LoadedApk>(packageInfo));
            }
        }
        return packageInfo;
    }
}

這里跟Application獲取ClassLoader實(shí)際上是同樣的原理资铡,首先從mPackages嘗試獲取WeakReference<LoadedApk> ref电禀,如果能取到就直接返回,如果取不到就重新創(chuàng)建一個(gè)LoadedApk笤休。

這里注意ref是一個(gè)弱引用尖飞,因此它不會(huì)存活到下一次虛擬機(jī)反生GC后。所以每次GC后都會(huì)重新創(chuàng)建LoadedApk并放到mPackages里店雅。但是在前文講述ActivityThread啟動(dòng)Application的過程中政基,mBoundApplication作為ActivtyThread中的字段引用了AppBindData對(duì)象,AppBindData對(duì)象的info(LoadedApk)也被間接的強(qiáng)引用了闹啦,所以對(duì)于執(zhí)行了bindApplication的Android應(yīng)用程序應(yīng)該不存在LoadedApk被回收的情況沮明,這一點(diǎn)通過DDMS模擬GC反射查看LoadedApk是否為NULL。我的測(cè)試結(jié)果中說明它是不會(huì)再GC后為NULL的印證了這點(diǎn)窍奋。

Service

startService函數(shù)定義在ContextWrapper中荐健,間接調(diào)用Context的startService函數(shù),這個(gè)函數(shù)是抽象函數(shù)琳袄,實(shí)際調(diào)用的是ContextImpl的startService函數(shù)摧扇。接著調(diào)用ContextImpl內(nèi)部的startServiceCommon函數(shù),最后執(zhí)行ActivityManagerNative.getDefault().startService函數(shù),實(shí)際是調(diào)用AMP(ActivityManagerProxy)的startService方法,在此函數(shù)中通過IBinder的transact函數(shù)發(fā)送START_SERVICE_TRANSACTION消息挚歧,最后由ActivityManagerNative在onTransact函數(shù)收到通知扛稽,并執(zhí)行startService函數(shù),該函數(shù)由AMS實(shí)現(xiàn)滑负。

在AMS中執(zhí)行startService函數(shù)在张,間接調(diào)用ActiveServices類的startServiceLocked、startServiceInnerLocked矮慕、bringUpServiceLocked函數(shù)帮匾,如果該Service所在進(jìn)程未啟動(dòng),則調(diào)用AMS類中的startProcessLocked函數(shù)去創(chuàng)建目標(biāo)進(jìn)程痴鳄。這里通過Process.start靜態(tài)函數(shù)創(chuàng)建目標(biāo)進(jìn)程并執(zhí)行ActivityThread瘟斜,于是又回到了上文中Android進(jìn)程的啟動(dòng)入口以及Application對(duì)象的創(chuàng)建。
在ActivityThread執(zhí)行attch后會(huì)通知AMS調(diào)用attachApplicationLocked痪寻,間接調(diào)用ActiveServices類的attachApplicationLocked函數(shù)螺句,此時(shí)目標(biāo)進(jìn)程已經(jīng)創(chuàng)建完畢。

在ActiveServices的attachApplicationLocked函數(shù)中會(huì)調(diào)用realStartServiceLocked函數(shù)來啟動(dòng)需要的Service橡类。此函數(shù)會(huì)調(diào)用app.thread.scheduleCreateService函數(shù)蛇尚,app.thread其實(shí)是ProcessRecord對(duì)象中一個(gè)ApplicationThreadProxy的代理(IApplicationThread),最后會(huì)通過Binder機(jī)制調(diào)用transact函數(shù)發(fā)送一個(gè)SCHEDULE_CREATE_SERVICE_TRANSACTION的消息,由ApplicationThread接收顾画,并向ActivityThread發(fā)送一個(gè)CREATE_SERVICE的消息取劫,最后在ActivityThread的handleMessage函數(shù)中執(zhí)行handleCreateService函數(shù)匆笤,同樣通過getPackageInfoNoCheck函數(shù)得到LoadedApk對(duì)象,然后通過LoaedApk的類加載器加載Service對(duì)象:

private void handleCreateService(CreateServiceData data) {
  ...
    LoadedApk packageInfo = getPackageInfoNoCheck(
            data.info.applicationInfo, data.compatInfo);
    Service service = null;
    try {
        java.lang.ClassLoader cl = packageInfo.getClassLoader();
        service = (Service) cl.loadClass(data.info.name).newInstance();
    } catch (Exception e) {
        if (!mInstrumentation.onException(service, e)) {
            throw new RuntimeException(
                    "Unable to instantiate service " + data.info.name
                            + ": " + e.toString(), e);
        }
    }

    try {
        ContextImpl context = new ContextImpl();
        context.init(packageInfo, null, this);
        Application app = packageInfo.makeApplication(false, mInstrumentation);
        context.setOuterContext(service);
        service.attach(context, this, data.info.name, data.token, app,
                ActivityManagerNative.getDefault());
        service.onCreate();
        mServices.put(data.token, service);
        try {
            ActivityManagerNative.getDefault().serviceDoneExecuting(
                    data.token, 0, 0, 0);
        } catch (RemoteException e) {
            // nothing to do.
        }
    } catch (Exception e) {
        if (!mInstrumentation.onException(service, e)) {
            throw new RuntimeException(
                    "Unable to create service " + data.info.name
                            + ": " + e.toString(), e);
        }
    }
}

可以看到Service與Activity都是由getPackageInfoNoCheck函數(shù)創(chuàng)建LoadedApk對(duì)象得到類加載器創(chuàng)建的谱邪。

下圖描述了使用代理模式遠(yuǎn)程調(diào)用系統(tǒng)AMS的時(shí)序圖:

ContentProvider

可以看到ContentProvider跟Activity炮捧、Service的創(chuàng)建有些不同,在handleBindApplication函數(shù)內(nèi)部調(diào)用了installContentProviders函數(shù)來安裝ContentProvider惦银,此函數(shù)中循環(huán)調(diào)用了installProvider函數(shù):

private IContentProvider installProvider(Context context,
                                         IContentProvider provider, ProviderInfo info, boolean noisy) {
    ContentProvider localProvider = null;
    if (provider == null) {
        Context c = null;
        ApplicationInfo ai = info.applicationInfo;
        if (context.getPackageName().equals(ai.packageName)) {
            c = context;
        } else if (mInitialApplication != null &&
                mInitialApplication.getPackageName().equals(ai.packageName)) {
            c = mInitialApplication;
        } else {
            try {
                c = context.createPackageContext(ai.packageName,
                        Context.CONTEXT_INCLUDE_CODE);
            } catch (PackageManager.NameNotFoundException e) {
            }
        }
        if (c == null) {
            return null;
        }
        try {
            final java.lang.ClassLoader cl = c.getClassLoader();
            localProvider = (ContentProvider)cl.
                    loadClass(info.name).newInstance();
            provider = localProvider.getIContentProvider();
            if (provider == null) {
                return null;
            }
            localProvider.attachInfo(c, info);
        } catch (java.lang.Exception e) {
            return null;
        }
    }
    synchronized (mProviderMap) {
        // Cache the pointer for the remote provider.
        String names[] = PATTERN_SEMICOLON.split(info.authority);
        for (int i=0; i<names.length; i++) {
            ProviderClientRecord pr = new ProviderClientRecord(names[i], provider,
                    localProvider);
            try {
                provider.asBinder().linkToDeath(pr, 0);
                mProviderMap.put(names[i], pr);
            } catch (RemoteException e) {
                return null;
            }
        }
        if (localProvider != null) {
            mLocalProviders.put(provider.asBinder(),
                    new ProviderClientRecord(null, provider, localProvider));
        }
    }

    return provider;
}

這里的Context c其實(shí)就是我們創(chuàng)建的Application.因此ContentProvider是用Application對(duì)象的getClassLoader函數(shù)返回的類加載器創(chuàng)建的咆课。這里的getClassLoader函數(shù)實(shí)際執(zhí)行的是父類ContextImpl對(duì)象的getClassLoader函數(shù),通過調(diào)用傳入的LoadedApk的getClassLoader函數(shù)來實(shí)現(xiàn)璧函。因此ContentProvider的類加載與上述所述一致傀蚌。

有一點(diǎn)要注意的是ContentProvider是在Application對(duì)象創(chuàng)建以后就創(chuàng)建的,并且先于Application對(duì)象執(zhí)行onCreate函數(shù):

Application app = data.info.makeApplication(data.restrictedBackupMode, null);
mInitialApplication = app;

// don't bring up providers in restricted mode; they may depend on the
// app's custom Application class
if (!data.restrictedBackupMode){
    List<ProviderInfo> providers = data.providers;
    if (providers != null) {
        installContentProviders(app, providers);
        // For process that contains content providers, we want to
        // ensure that the JIT is enabled "at some point".
        mH.sendEmptyMessageDelayed(H.ENABLE_JIT, 10*1000);
    }
}

try {
    mInstrumentation.callApplicationOnCreate(app);
} catch (Exception e) {
    if (!mInstrumentation.onException(app, e)) {
        throw new RuntimeException(
                "Unable to create application " + app.getClass().getName()
                        + ": " + e.toString(), e);
    }
}

BroadCastReceiver

廣播的注冊(cè)分為靜態(tài)和動(dòng)態(tài)兩種蘸吓。無論哪一種最終目的都是在AMS注冊(cè)一個(gè)InnerReceiver對(duì)象善炫,這個(gè)對(duì)象與Android進(jìn)程內(nèi)的BroadCastReceiver向?qū)?yīng),如果AMS接收到廣播库继,首先經(jīng)過InnerReceiver列表的篩選箩艺,然后再通過Binder機(jī)制交給應(yīng)用本地的BroadCastReceiver對(duì)象處理。

靜態(tài)注冊(cè)指的是在AndroidManifest.xml中聲明的接收者宪萄,在系統(tǒng)啟動(dòng)的時(shí)候艺谆,會(huì)由PackageManagerService(以下簡(jiǎn)稱PMS)去解析。當(dāng)AMS調(diào)用PMS的接口來查詢廣播注冊(cè)的時(shí)候拜英,PMS會(huì)查詢記錄并且返回給AMS 静汤。

動(dòng)態(tài)注冊(cè)時(shí)值在Android應(yīng)用啟動(dòng)后由Context(實(shí)際是ContextImpl)的registerReceiver函數(shù)通過Binder遠(yuǎn)程調(diào)用AMS注冊(cè)廣播接收器。

當(dāng)AMS通知Android進(jìn)程處理廣播居凶,會(huì)在ActivityThread執(zhí)行scheduleReceiver函數(shù),接著通過 queueOrSendMessage(H.RECEIVER, r)調(diào)用handleReceiver函數(shù):

private void handleReceiver(ReceiverData data) {
    ...
    String component = data.intent.getComponent().getClassName();
    LoadedApk packageInfo = getPackageInfoNoCheck(
            data.info.applicationInfo, data.compatInfo);
    IActivityManager mgr = ActivityManagerNative.getDefault();
    BroadcastReceiver receiver;
    try {
        java.lang.ClassLoader cl = packageInfo.getClassLoader();
        data.intent.setExtrasClassLoader(cl);
        data.setExtrasClassLoader(cl);
        receiver = (BroadcastReceiver)cl.loadClass(component).newInstance();
    } catch (Exception e) {
        if (DEBUG_BROADCAST) Slog.i(TAG,
                "Finishing failed broadcast to " + data.intent.getComponent());
        data.sendFinished(mgr);
        throw new RuntimeException(
                "Unable to instantiate receiver " + component
                        + ": " + e.toString(), e);
    }

    try {
        Application app = packageInfo.makeApplication(false, mInstrumentation);
        ContextImpl context = (ContextImpl)app.getBaseContext();
        sCurrentBroadcastIntent.set(data.intent);
        receiver.setPendingResult(data);
        receiver.onReceive(context.getReceiverRestrictedContext(),
                data.intent);
    } catch (Exception e) {
        data.sendFinished(mgr);
        if (!mInstrumentation.onException(receiver, e)) {
            throw new RuntimeException(
                    "Unable to start receiver " + component
                            + ": " + e.toString(), e);
        }
    } finally {
        sCurrentBroadcastIntent.set(null);
    }

    if (receiver.getPendingResult() != null) {
        data.finish();
    }
}

可以看到這里跟上述組件的加載很相似虫给,依然用getPackageInfoNoCheck函數(shù)獲取類加載器然后實(shí)例化BroadCastReceiver對(duì)象。

在Android4.1上測(cè)試不管是靜態(tài)注冊(cè)還是動(dòng)態(tài)注冊(cè)侠碧,在創(chuàng)建BroadCastReceiver對(duì)象前一定先啟動(dòng)它所在的進(jìn)程,即執(zhí)行上述bindApplication函數(shù)創(chuàng)建Application對(duì)象抹估。

加固思路

上文中已經(jīng)分析了Android的類加載器和Application、Activity弄兜、Service药蜻、BroadCastReceiver、ContentProvider等組件的類加載過程替饿。

我們知道.dex是Android虛擬機(jī)能夠執(zhí)行的一個(gè)類集合文件语泽,默認(rèn)類加載器以apk包中的dex路徑為參數(shù)。要實(shí)現(xiàn)對(duì)原dex的加密盛垦,我們就只能通過動(dòng)態(tài)加載技術(shù)以插件的形式加載dex湿弦。

首先定義一個(gè)殼Application類,在此類中要實(shí)現(xiàn)對(duì)加密的原.dex文件的解密腾夯,并且動(dòng)態(tài)加載dex颊埃。

如何動(dòng)態(tài)加載原.dex文件?我們可以將原.dex文件解密到指定路徑,然后創(chuàng)建DexClassLoader對(duì)象蝶俱,用此對(duì)象去替換默認(rèn)的PathClassLoader班利。

而替換的時(shí)機(jī)一定要早于各個(gè)組件的創(chuàng)建過程,為此選擇Application的attachBaseContext函數(shù)榨呆,該函數(shù)定義于ContextWrapper類罗标,在Application執(zhí)行attach函數(shù)時(shí)調(diào)用:

final void attach(Context context) {
    attachBaseContext(context);
    mLoadedApk = ContextImpl.getImpl(context).mPackageInfo;
}

還記得Application對(duì)象是LoadedApk類中的makeApplication函數(shù)創(chuàng)建的,而在makeApplication函數(shù)中积蜻,實(shí)際調(diào)用Instrumentation類的newApplication函數(shù):

static public Application newApplication(Class<?> clazz, Context context)
        throws InstantiationException, IllegalAccessException,
        ClassNotFoundException {
    Application app = (Application)clazz.newInstance();
    app.attach(context);
    return app;
}

因此可以得出結(jié)論闯割,一旦通過LoadedApk的makeApplication函數(shù)創(chuàng)建Application對(duì)象,也會(huì)調(diào)用Application中的函數(shù)attachBaseContext函數(shù)竿拆。要在殼程序的Application類中重寫這個(gè)函數(shù)并且解密原.dex文件宙拉,并用DexClassLoader動(dòng)態(tài)加載,然后用反射技術(shù)替換掉LoadedApk對(duì)象中默認(rèn)的類加載器丙笋。這樣在加載其他組件時(shí)使用的就是替換的DexClassLoader谢澈,加載的是原.dex中的類。

根據(jù)分析ContentProvider是最早在attachBaseContext函數(shù)后被加載的御板。所以其他組件的加載也不會(huì)出現(xiàn)問題锥忿。

除了在殼Application類的attachBaseContext函數(shù)中替換原類加載器。還需要替換掉一些相關(guān)對(duì)象怠肋,例如用原Application對(duì)象代替殼Application對(duì)象敬鬓。在LoadedApk的makeApplication函數(shù)中可見:

if (mApplication != null) {
    return mApplication;
}
mActivityThread.mAllApplications.add(app);
mApplication = app;

這里需要用反射技術(shù)將mApplication這個(gè)引用置空,這樣第二次執(zhí)行此函數(shù)就會(huì)新建一個(gè)Application對(duì)象并賦給mApplication笙各。并且需要從mActivityThread對(duì)象中的mAllApplications這個(gè)數(shù)組移除殼Application對(duì)象钉答。

在ActivityThread的handleBindApplication函數(shù)中調(diào)用完makeApplication函數(shù)可見:

mInitialApplication = app;
if (!data.restrictedBackupMode){
        List<ProviderInfo> providers = data.providers;
        if (providers != null) {
            installContentProviders(app, providers);
            // For process that contains content providers, we want to
            // ensure that the JIT is enabled "at some point".
            mH.sendEmptyMessageDelayed(H.ENABLE_JIT, 10*1000);
        }
}
try {
            mInstrumentation.callApplicationOnCreate(app);
        }...

這里的mInitialApplication也引用了殼Application對(duì)象,需要用原程序中的Application對(duì)象替換它酪惭。

注意到installContentProviders函數(shù)也引用了app,也需要替換它希痴。

注意到最后由Instrumentation對(duì)象執(zhí)行了callApplicationOnCreate函數(shù),間接調(diào)用了殼Application類的onCreate函數(shù)春感,因此反射替換將在殼onCreate函數(shù)完成砌创。

到此理清了加殼技術(shù)的大部分思路。具體的實(shí)現(xiàn)還需要針對(duì)不同API版本鲫懒,以及MultiDex的情況嫩实。
詳見《利用動(dòng)態(tài)加載技術(shù)實(shí)現(xiàn)APK安全加固》

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個(gè)濱河市窥岩,隨后出現(xiàn)的幾起案子甲献,更是在濱河造成了極大的恐慌,老刑警劉巖颂翼,帶你破解...
    沈念sama閱讀 206,839評(píng)論 6 482
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件晃洒,死亡現(xiàn)場(chǎng)離奇詭異慨灭,居然都是意外死亡,警方通過查閱死者的電腦和手機(jī)球及,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 88,543評(píng)論 2 382
  • 文/潘曉璐 我一進(jìn)店門氧骤,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人吃引,你說我怎么就攤上這事筹陵。” “怎么了镊尺?”我有些...
    開封第一講書人閱讀 153,116評(píng)論 0 344
  • 文/不壞的土叔 我叫張陵朦佩,是天一觀的道長(zhǎng)。 經(jīng)常有香客問我庐氮,道長(zhǎng)语稠,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 55,371評(píng)論 1 279
  • 正文 為了忘掉前任旭愧,我火速辦了婚禮颅筋,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘输枯。我一直安慰自己议泵,他們只是感情好,可當(dāng)我...
    茶點(diǎn)故事閱讀 64,384評(píng)論 5 374
  • 文/花漫 我一把揭開白布桃熄。 她就那樣靜靜地躺著先口,像睡著了一般。 火紅的嫁衣襯著肌膚如雪瞳收。 梳的紋絲不亂的頭發(fā)上碉京,一...
    開封第一講書人閱讀 49,111評(píng)論 1 285
  • 那天,我揣著相機(jī)與錄音螟深,去河邊找鬼谐宙。 笑死,一個(gè)胖子當(dāng)著我的面吹牛界弧,可吹牛的內(nèi)容都是我干的凡蜻。 我是一名探鬼主播,決...
    沈念sama閱讀 38,416評(píng)論 3 400
  • 文/蒼蘭香墨 我猛地睜開眼垢箕,長(zhǎng)吁一口氣:“原來是場(chǎng)噩夢(mèng)啊……” “哼划栓!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起条获,我...
    開封第一講書人閱讀 37,053評(píng)論 0 259
  • 序言:老撾萬榮一對(duì)情侶失蹤忠荞,失蹤者是張志新(化名)和其女友劉穎,沒想到半個(gè)月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體委煤,經(jīng)...
    沈念sama閱讀 43,558評(píng)論 1 300
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡堂油,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 36,007評(píng)論 2 325
  • 正文 我和宋清朗相戀三年,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了素标。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片称诗。...
    茶點(diǎn)故事閱讀 38,117評(píng)論 1 334
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡萍悴,死狀恐怖头遭,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情癣诱,我是刑警寧澤计维,帶...
    沈念sama閱讀 33,756評(píng)論 4 324
  • 正文 年R本政府宣布,位于F島的核電站撕予,受9級(jí)特大地震影響鲫惶,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜实抡,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 39,324評(píng)論 3 307
  • 文/蒙蒙 一欠母、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧吆寨,春花似錦赏淌、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,315評(píng)論 0 19
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至辣卒,卻和暖如春掷贾,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背荣茫。 一陣腳步聲響...
    開封第一講書人閱讀 31,539評(píng)論 1 262
  • 我被黑心中介騙來泰國(guó)打工想帅, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留,地道東北人啡莉。 一個(gè)月前我還...
    沈念sama閱讀 45,578評(píng)論 2 355
  • 正文 我出身青樓港准,卻偏偏與公主長(zhǎng)得像,于是被迫代替她去往敵國(guó)和親票罐。 傳聞我的和親對(duì)象是個(gè)殘疾皇子叉趣,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 42,877評(píng)論 2 345

推薦閱讀更多精彩內(nèi)容