利用动态加载技术加固APK原理解析

前言

《从底层分析PathClassLoader和DexClassLoader的区别,基于Android4.4》中分析了Android虚拟机的两种类加载器,利用动态加载技术实现APK安全加固依赖于DexClassLoader的使用。

为了更好的理解本文的内容,建议读《深入理解Java虚拟机》中的 “”虚拟机类加载机制“”,老罗的《Android系统源代码情景分析》中Application和四大组件的启动过程。

本人在刚开始做APK加固时也是参考了别人的文章,但是只有深入到源码分析并且亲手实践才知道有些代码为什么是这样写的。

虚拟机中的类加载分析


根据《深入理解Java虚拟机》描述,类从被加载到虚拟机内存开始,到卸载出内存为止,它的整个生命周期包括:加载、验证、准备、解析、初始化、使用的卸载。其中验证、准备、解析统称为连接。其中解析和初始化没有先后顺序。
Dex相当于Class字节码文件的集合,符合Java虚拟机规范。根据Java类加载描述,类加载的时机没有强行约束,但当遇到以下情况必须进行初始化:

  • 遇到new、putstatic、getstatic、invokestatic字节码指令时;
  • 使用java.lang.reflect的方法对类进行反射调用时;
  • 初始化一个类时,先初始化其父类;
  • 虚拟机启动时,需要初始化包含main函数的类;
  • 使用JDK1.7的动态语言支持时;

类加载器

虚拟机设计团队把类加载阶段中的“通过一个类的全限定名来获取描述此类的二进制字节码流”这个动作放到Java虚拟机外部实现,以便让程序自己决定如何获取所需的类,实现这个动作的模块称为“类加载器”。

类加载器除了具有加载类的功能,另一个意义是每一个类加载器都有一个独立的类命名空间,比较两个类是否相等只对同一个类加载器才有意义。假设同一个Class文件,一个由系统的类加载器加载,另一个由程序自定义的类加载器加载,虽然是同一Class文件却对应不同类加载器的两个实例,在虚拟机中是不相等的。

双亲委派模型


前文说到用户可以自定义类加载器,并且每个类加载器加载的同一Class并不相等。但对于某些公共的类,比如在Java SDK中定义的Class,我们没有必要因为自定义类加载器的不同而加载出不同的Class对象。因此在JDK1.2期间引入并被广泛应用于以后的所有Java虚拟机中。

双亲委派模型的工作原理是:如果一个类加载器收到了类加载的请求,它首先不会自己去尝试加载这个类,而是把这个请求委派给父类加载器去完成,每一个层次的类加载器都是如此,因此所有的加载请求最终都应该传送到顶层的启动类加载器中,只有当父加载器反馈自己无法完成这个加载请求(它的搜索范围中没有找到所需的类)时,子加载器才会尝试自己去加载。

因此双亲委派模型能够保证例如Java SDK中的公共Class在不同自定义加载器中也是相等的,因为不同的自定义类加载器都有相同的父类加载器。保证了公共类的一致性。
双亲委派模型对于保证Java程序的稳定运作很重要,但它的实现却非常简单,实现双亲委派的代码都集中在java.lang.ClassLoader的loadClass()方法之中:先检查是否已经被加载过,若没有加载则调用父加载器的loadClass()方法,若父加载器为空则默认使用启动类加载器作为父加载器。如果父类加载失败,抛出ClassNotFoundException异常后,再调用自己的findClass()方法进行加载。

分析Anrdoid类加载器的创建及Application的初始化

什么时候Android程序被启动?
大多数人都知道在桌面点击一个应用的图标可以启动该应用的Activity,其实不光是Activity,通过远程调用Service、ContentProvider,乃至发送一个广播都会启动目标进程。当然根据厂家的适配,通过静态广播唤醒进程并不一定可行。实测在Android 4.1 虚拟机上是可行的。

Android进程如何启动?
ActivityThread中的main函数是Android进程的唯一入口,当启动四大组件会先检查目标的进程有没有启动,若没有启动会通过Zygote孵化一个Java进程并执行ActivityThread类的main函数。
此函数中会执行attach函数,通过Binder机制与ActivityManagerService(简称AMS)通信,将自己attach到AMS的ProcessRecord对象,反过来在由AMS调用ActivityThread的handleBindApplication函数:

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

这里调用了getPackageInfoNoCheck函数给data.info赋值,而data.info实际是LoadedApk对象,再来看该函数:

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对象会先从mPackages(API19及以下是HashMap类型,以上是ArrayMap类型)中尝试获取一个弱引用,若找不到该引用则会调用构造方法创建一个新的。

而观察getPackageInfoNoCheck,发现传入的baseLoader参数为空,这意味着默认类加载器没有父加载器,LoadedApk 构造源码如下:

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)正是构造默认类加载器的函数:

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是如何创建默认类加载器的,系统由ApplicaionLoaders.getDefault().getClassLoader创建PathClassLoader作为默认的类加载器。然后由这个ClassLoader去加载DEX中的各个类。

接着回到handleBindApplication函数:

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);
    }
}

在创建完LoadedApk对象后通过执行makeApplication函数创建Application对象的实例。

创建完Application对象后通过mInstrumentation的callApplicationOnCreate函数启动Application。

我们先来看makeApplication函数的代码:

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的引用则直接返回,如果没有就用前文提到的类加载器去创建它,这里的具体实现是mInstrumentation.newApplication(cl, appClass, appContext)。

之后将LoadedApk中的mApplication赋值以便重复使用,并将这个引用添加到mActivityThread.mAllApplications这个数组。因为传入的instrumentation为空所以不执行Application对象的生命周期函数onCreate。

接下来分android.app.Instrumentation这个类,上文创建Application对象用到了newApplication函数,执行Application的生命周期用到了callApplicationOnCreate函数:

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对象实际是由Class的newInstance函数创建的,这种语法经常见于Java反射机制的调用。而callApplicationOnCreate实际调用了onCreate函数。

下面通过分析Android4.4(API 19)源码得到ActivityThread对象关系图:


到此我们知道Android默认的类加载器是如何创建的,并且如何用这个类加载器创建了Application对象。

分析Android四大组件的类加载过程

上文分析了Application对象的创建过程,以及PathClassLoader的创建过程。下文中还需要分析四大组件是如何创建及启动的。为什么不是只分析Activity?除了Activity以外,Android程序还可以以后台Service、数据共享提供器ContentProvider、广播接收者BroadCastReceiver的形式运行。我们需要知道动态加载技术对四大组件的创建及生命周期是否可行。

Activity

以下时序图来自于老罗的《Android系统源代码情景分析》



从一个Android进程启动另一个Android进程的Activity需要先初始化ActivtyThread并创建Application,如上文所述。然后由AMS通过Binder机制通知ActivityThread执行scheduleLaunchActivity函数:

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函数接收Message,根据msg.what继续执行handleLaunchActivity函数:

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函数创建了Activity实例,handleResumeActivity函数调用了Activity的onResume函数:

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;
}

这里我们只关注Activity的创建过程和Activity的生命周期函数。可以看到在Activity对象创建后,由mInstrumentation调用了callActivityOnCreate、callActivityOnRestoreInstanceState函数,间接调用了Activity的onCreate、onRestoreInstanceState函数。

r.packageInfo.getClassLoader()是Activity的类加载器,newActivity是Activity对象的创建函数:

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

再来看r.packageInfo.getClassLoader()是怎么来的,r是ActivityClientRecord 对象,最初是由scheduleLaunchActivity函数创建的,r.packageInfo是在handleMessage函数中调用getPackageInfoNoCheck函数得到的,再看getPackageInfoNoCheck函数:

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

到这一步跟上文分析创建Application获取类加载器似曾相识,因为是同样的函数:

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实际上是同样的原理,首先从mPackages尝试获取WeakReference<LoadedApk> ref,如果能取到就直接返回,如果取不到就重新创建一个LoadedApk。

这里注意ref是一个弱引用,因此它不会存活到下一次虚拟机反生GC后。所以每次GC后都会重新创建LoadedApk并放到mPackages里。但是在前文讲述ActivityThread启动Application的过程中,mBoundApplication作为ActivtyThread中的字段引用了AppBindData对象,AppBindData对象的info(LoadedApk)也被间接的强引用了,所以对于执行了bindApplication的Android应用程序应该不存在LoadedApk被回收的情况,这一点通过DDMS模拟GC反射查看LoadedApk是否为NULL。我的测试结果中说明它是不会再GC后为NULL的印证了这点。

Service

startService函数定义在ContextWrapper中,间接调用Context的startService函数,这个函数是抽象函数,实际调用的是ContextImpl的startService函数。接着调用ContextImpl内部的startServiceCommon函数,最后执行ActivityManagerNative.getDefault().startService函数,实际是调用AMP(ActivityManagerProxy)的startService方法,在此函数中通过IBinder的transact函数发送START_SERVICE_TRANSACTION消息,最后由ActivityManagerNative在onTransact函数收到通知,并执行startService函数,该函数由AMS实现。

在AMS中执行startService函数,间接调用ActiveServices类的startServiceLocked、startServiceInnerLocked、bringUpServiceLocked函数,如果该Service所在进程未启动,则调用AMS类中的startProcessLocked函数去创建目标进程。这里通过Process.start静态函数创建目标进程并执行ActivityThread,于是又回到了上文中Android进程的启动入口以及Application对象的创建。
在ActivityThread执行attch后会通知AMS调用attachApplicationLocked,间接调用ActiveServices类的attachApplicationLocked函数,此时目标进程已经创建完毕。

在ActiveServices的attachApplicationLocked函数中会调用realStartServiceLocked函数来启动需要的Service。此函数会调用app.thread.scheduleCreateService函数,app.thread其实是ProcessRecord对象中一个ApplicationThreadProxy的代理(IApplicationThread),最后会通过Binder机制调用transact函数发送一个SCHEDULE_CREATE_SERVICE_TRANSACTION的消息,由ApplicationThread接收,并向ActivityThread发送一个CREATE_SERVICE的消息,最后在ActivityThread的handleMessage函数中执行handleCreateService函数,同样通过getPackageInfoNoCheck函数得到LoadedApk对象,然后通过LoaedApk的类加载器加载Service对象:

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函数创建LoadedApk对象得到类加载器创建的。

下图描述了使用代理模式远程调用系统AMS的时序图:

ContentProvider

可以看到ContentProvider跟Activity、Service的创建有些不同,在handleBindApplication函数内部调用了installContentProviders函数来安装ContentProvider,此函数中循环调用了installProvider函数:

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其实就是我们创建的Application.因此ContentProvider是用Application对象的getClassLoader函数返回的类加载器创建的。这里的getClassLoader函数实际执行的是父类ContextImpl对象的getClassLoader函数,通过调用传入的LoadedApk的getClassLoader函数来实现。因此ContentProvider的类加载与上述所述一致。

有一点要注意的是ContentProvider是在Application对象创建以后就创建的,并且先于Application对象执行onCreate函数:

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

广播的注册分为静态和动态两种。无论哪一种最终目的都是在AMS注册一个InnerReceiver对象,这个对象与Android进程内的BroadCastReceiver向对应,如果AMS接收到广播,首先经过InnerReceiver列表的筛选,然后再通过Binder机制交给应用本地的BroadCastReceiver对象处理。

静态注册指的是在AndroidManifest.xml中声明的接收者,在系统启动的时候,会由PackageManagerService(以下简称PMS)去解析。当AMS调用PMS的接口来查询广播注册的时候,PMS会查询记录并且返回给AMS 。

动态注册时值在Android应用启动后由Context(实际是ContextImpl)的registerReceiver函数通过Binder远程调用AMS注册广播接收器。

当AMS通知Android进程处理广播,会在ActivityThread执行scheduleReceiver函数,接着通过 queueOrSendMessage(H.RECEIVER, r)调用handleReceiver函数:

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函数获取类加载器然后实例化BroadCastReceiver对象。

在Android4.1上测试不管是静态注册还是动态注册,在创建BroadCastReceiver对象前一定先启动它所在的进程,即执行上述bindApplication函数创建Application对象。

加固思路

上文中已经分析了Android的类加载器和Application、Activity、Service、BroadCastReceiver、ContentProvider等组件的类加载过程。

我们知道.dex是Android虚拟机能够执行的一个类集合文件,默认类加载器以apk包中的dex路径为参数。要实现对原dex的加密,我们就只能通过动态加载技术以插件的形式加载dex。

首先定义一个壳Application类,在此类中要实现对加密的原.dex文件的解密,并且动态加载dex。

如何动态加载原.dex文件?我们可以将原.dex文件解密到指定路径,然后创建DexClassLoader对象,用此对象去替换默认的PathClassLoader。

而替换的时机一定要早于各个组件的创建过程,为此选择Application的attachBaseContext函数,该函数定义于ContextWrapper类,在Application执行attach函数时调用:

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

还记得Application对象是LoadedApk类中的makeApplication函数创建的,而在makeApplication函数中,实际调用Instrumentation类的newApplication函数:

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

因此可以得出结论,一旦通过LoadedApk的makeApplication函数创建Application对象,也会调用Application中的函数attachBaseContext函数。要在壳程序的Application类中重写这个函数并且解密原.dex文件,并用DexClassLoader动态加载,然后用反射技术替换掉LoadedApk对象中默认的类加载器。这样在加载其他组件时使用的就是替换的DexClassLoader,加载的是原.dex中的类。

根据分析ContentProvider是最早在attachBaseContext函数后被加载的。所以其他组件的加载也不会出现问题。

除了在壳Application类的attachBaseContext函数中替换原类加载器。还需要替换掉一些相关对象,例如用原Application对象代替壳Application对象。在LoadedApk的makeApplication函数中可见:

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

这里需要用反射技术将mApplication这个引用置空,这样第二次执行此函数就会新建一个Application对象并赋给mApplication。并且需要从mActivityThread对象中的mAllApplications这个数组移除壳Application对象。

在ActivityThread的handleBindApplication函数中调用完makeApplication函数可见:

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对象,需要用原程序中的Application对象替换它。

注意到installContentProviders函数也引用了app,也需要替换它。

注意到最后由Instrumentation对象执行了callApplicationOnCreate函数,间接调用了壳Application类的onCreate函数,因此反射替换将在壳onCreate函数完成。

到此理清了加壳技术的大部分思路。具体的实现还需要针对不同API版本,以及MultiDex的情况。
详见《利用动态加载技术实现APK安全加固》

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 203,324评论 5 476
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 85,303评论 2 381
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 150,192评论 0 337
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,555评论 1 273
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,569评论 5 365
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,566评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,927评论 3 395
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,583评论 0 257
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,827评论 1 297
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,590评论 2 320
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,669评论 1 329
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,365评论 4 318
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,941评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,928评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,159评论 1 259
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 42,880评论 2 349
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,399评论 2 342

推荐阅读更多精彩内容