Android Apk加壳原理分析

来源:互联网 发布:现金贬值知乎 编辑:程序博客网 时间:2024/05/16 15:18

    0x00

    阅读本文前,建议读者首先阅读Android加壳原理,参考文章Android中的Apk的加固(加壳)原理解析和实现。如果没有看过这篇文章,本文理解起来比较困难。

    0x01

    下面我们来分析脱壳代码为什么要这样写,核心脱壳代码在ProxyApplication类里面,首先执行成员方法attachBaseContext,然后执行成员方法onCreate。

    那么attachBaseContext是什么时候被执行的呢,为什么先于onCreate执行呢?那就需要看Android的源码了,我们选用的是Android2.3源码。

    我们首先看一张图,这张图表述了从桌面启动一个应用Activity的启动过程。

   

                       图  1

    其中当执行到ApplicationThread.bindApplication时,会向ActivityThreadl类的Handler对象mH发送消息。

        public final void bindApplication(String processName,                ApplicationInfo appInfo, List<ProviderInfo> providers,                ComponentName instrumentationName, String profileFile,                Bundle instrumentationArgs, IInstrumentationWatcher instrumentationWatcher,                int debugMode, boolean isRestrictedBackupMode, Configuration config,                Map<String, IBinder> services) {            if (services != null) {                // Setup the service cache in the ServiceManager                ServiceManager.initServiceCache(services);            }            AppBindData data = new AppBindData();            data.processName = processName;            data.appInfo = appInfo;            data.providers = providers;            data.instrumentationName = instrumentationName;            data.profileFile = profileFile;            data.instrumentationArgs = instrumentationArgs;            data.instrumentationWatcher = instrumentationWatcher;            data.debugMode = debugMode;            data.restrictedBackupMode = isRestrictedBackupMode;            data.config = config;            queueOrSendMessage(H.BIND_APPLICATION, data);        }
    代码位于frameworks\base\core\java\android\app\ActivityThread.java。


    queueOrSendMessage向ActivityThreadl类的Handler对象mH发送消息。

private final void queueOrSendMessage(int what, Object obj, int arg1, int arg2) {        synchronized (this) {            if (DEBUG_MESSAGES) Slog.v(                TAG, "SCHEDULE " + what + " " + mH.codeToString(what)                + ": " + arg1 + " / " + obj);            Message msg = Message.obtain();            msg.what = what;            msg.obj = obj;            msg.arg1 = arg1;            msg.arg2 = arg2;            mH.sendMessage(msg);        }    }
    代码位于frameworks\base\core\java\android\app\ActivityThread.java。


    handler处理BIND_APPLICATION的流程如下。

 private final class H extends Handler {......        }        public void handleMessage(Message msg) {            if (DEBUG_MESSAGES) Slog.v(TAG, ">>> handling: " + msg.what);            switch (msg.what) {......                case BIND_APPLICATION:                    AppBindData data = (AppBindData)msg.obj;                    handleBindApplication(data);                    break;......        }    }
    代码位于frameworks\base\core\java\android\app\ActivityThread.java。


    继续看handleBindApplication,其中data就是ApplicationThread.bindApplication生成的AppBindData对象。

    private final void handleBindApplication(AppBindData data) {mBoundApplication = data;......        data.info = getPackageInfoNoCheck(data.appInfo);......        Application app = data.info.makeApplication(data.restrictedBackupMode, null);        mInitialApplication = app;......        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);            }        }    }
    代码位于frameworks\base\core\java\android\app\ActivityThread.java。

    首先把data赋值给了AppBindData对象mBoundApplication,然后通过getPackageInfoNoCheck得到的LoadedApk对象复制给data.info,之后调用data.info.makeApplication生成Application对象,我们下面来分析下data.info.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;......                return app;    }
    代码位于frameworks\base\core\java\android\app\LoadedApk.java。

    首先通过mApplicationInfo.className获取application的名字,在本例中ProxyApplication。然后通过getClassLoader获取了ClassLoader对象,那么我先来分析下getClassLoader的实现。

    public ClassLoader getClassLoader() {        synchronized (this) {            if (mClassLoader != null) {                return mClassLoader;            }......    }
    我们姑且认为ClassLoader对象mClassLoader不为空,返回LoadedApk对象的成员变量mClassLoader。

    返回到makeApplication,继续看,首先生成了ContextImpl对象,最终调用了mActivityThread.mInstrumentation.newApplication来生成Application对象,并把生成的Context对象放到这个Application对象中。这部分可参考博客Android中Context详解 ---- 你所不知道的Context。再附一张Context类图,帮大家理解。


    那么我们讲了这么多,到底是什么时候执行的ProxyApplication类的方法attachBaseContext的呢?答案就在mActivityThread.mInstrumentation.newApplication,我们继续分析此方法。

    public Application newApplication(ClassLoader cl, String className, Context context)            throws InstantiationException, IllegalAccessException,             ClassNotFoundException {        return newApplication(cl.loadClass(className), context);    }
    代码位于frameworks\base\core\java\android\app\Instrumentation.java。

    其中context对象就是我们刚刚生成的,继续分析newApplication方法。

    static public Application newApplication(Class<?> clazz, Context context)            throws InstantiationException, IllegalAccessException,             ClassNotFoundException {        Application app = (Application)clazz.newInstance();        app.attach(context);        return app;    }
    代码位于frameworks\base\core\java\android\app\Instrumentation.java。
    这个函数首先生成了一个Application对象app,然后调用了他的方法attach,我们来分析这个方法。

final void attach(Context context) {        attachBaseContext(context);    }
    代码位于frameworks\base\core\java\android\app\Application.java

    答案在此揭晓,此时调用了ProxyApplication类的方法attachBaseContext,注意此时还没有调用ProxyApplication类的方法onCreate。


    0x02

    知道了执行到ProxyApplication类的方法attachBaseContext之前的流程,我们接下来重点分析下这个方法。

protected void attachBaseContext(Context base) {super.attachBaseContext(base);try {......// 配置动态加载环境Object currentActivityThread = RefInvoke.invokeStaticMethod("android.app.ActivityThread", "currentActivityThread",new Class[] {}, new Object[] {});//获取主线程对象 http://blog.csdn.net/myarrow/article/details/14223493String packageName = this.getPackageName();//当前apk的包名//下面两句不是太理解HashMap mPackages = (HashMap) RefInvoke.getFieldOjbect("android.app.ActivityThread", currentActivityThread,"mPackages");WeakReference wr = (WeakReference) mPackages.get(packageName);//创建被加壳apk的DexClassLoader对象  加载apk内的类和本地代码(c/c++代码)DexClassLoader dLoader = new DexClassLoader(apkFileName, odexPath,libPath, (ClassLoader) RefInvoke.getFieldOjbect("android.app.LoadedApk", wr.get(), "mClassLoader"));//base.getClassLoader(); 是不是就等同于 (ClassLoader) RefInvoke.getFieldOjbect()? 有空验证下//?//把当前进程的DexClassLoader 设置成了被加壳apk的DexClassLoader  ----有点c++中进程环境的意思~~RefInvoke.setFieldOjbect("android.app.LoadedApk", "mClassLoader",wr.get(), dLoader);Log.i("demo","classloader:"+dLoader);......} catch (Exception e) {Log.i("demo", "error:"+Log.getStackTraceString(e));e.printStackTrace();}}
    代码位于Android中的Apk的加固(加壳)原理解析和实现。

    省略部分的代码还请大家参考Android中的Apk的加固(加壳)原理解析和实现。

    首先通过反射调用了ActivityThread类的currentActivityThread方法,该方法是静态的,返回当前的ActivityThread,代码如下:

    public static final ActivityThread currentActivityThread() {        return sThreadLocal.get();    }
    代码位于frameworks\base\core\java\android\app\ActivityThread.java。  

然后再获取ActivityThread的成员变量mPackages,mPackages也位于frameworks\base\core\java\android\app\ActivityThread.java中:

    final HashMap<String, WeakReference<LoadedApk>> mPackages            = new HashMap<String, WeakReference<LoadedApk>>();
    他是一个HashMap,键是包名,值是LoadedApk的软引用。然后通过当前的包名在HashMap中获取对应LoadedApk的软引用。

然后根据要加载的apk,也就是实际要执行的apk,生成DexClassLoader对象,其中parentClassLoader就是刚刚获取的LoadedApk对象中的mClassLoader变量。

大家可能会有个疑问,这里获取的LoadedApk对象和data.info对象是一样的么?答案是一样的,代码的关键在handleBindApplication中getPackageInfoNoCheck,代码如下:

    private final LoadedApk getPackageInfo(ApplicationInfo aInfo,            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())) {                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, 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;        }    }
    代码位于frameworks\base\core\java\android\app\ActivityThread.java。  

这里生成了一个LoadedApk对象,并以当前包名为键,LoadedApk对象为值存入了mPackages这个HashMap中,并且返回LoadedApk对象,并赋值给data.info。

data.info = getPackageInfoNoCheck(data.appInfo);

回到attachBaseContext中,最后把这个新生成DexClassLoader对象赋值给LoadedApk对象的mClassLoader变量,也就是更新了这个mClassLoader变量。


0x03

执行完mActivityThread.mInstrumentation.newApplication,返回到makeApplication,继续执行下面两句代码:

 mActivityThread.mAllApplications.add(app); mApplication = app;
执行完data.info.makeApplication,我们返回到handleBindApplication(代码请参考上面),继续执行下面一句代码:

mInitialApplication = app;
    这三行代码对于理解ProxyApplication类的onCreate方法有帮助,此时application是ProxyApplication,我们要把它替换为我们自己的application,本例中为MyApplication。


0x04

执行完data.info.makeApplication,我们返回到handleBindApplication(代码请参考上面),继续执行mInstrumentation.callApplicationOnCreate(app),此时ProxyApplication类的onCreate方法开始执行。

@Overridepublic void onCreate() {{//loadResources(apkFileName);Log.i("demo", "onCreate");// 如果源应用配置有Appliction对象,则替换为源应用Applicaiton,以便不影响源程序逻辑。String appClassName = null;try {ApplicationInfo ai = this.getPackageManager().getApplicationInfo(this.getPackageName(),PackageManager.GET_META_DATA);Bundle bundle = ai.metaData;if (bundle != null && bundle.containsKey("APPLICATION_CLASS_NAME")) {appClassName = bundle.getString("APPLICATION_CLASS_NAME");//className 是配置在xml文件中的。} else {Log.i("demo", "have no application class name");return;}} catch (NameNotFoundException e) {Log.i("demo", "error:"+Log.getStackTraceString(e));e.printStackTrace();}//有值的话调用该ApplicaitonObject currentActivityThread = RefInvoke.invokeStaticMethod("android.app.ActivityThread", "currentActivityThread",new Class[] {}, new Object[] {});Object mBoundApplication = RefInvoke.getFieldOjbect("android.app.ActivityThread", currentActivityThread,"mBoundApplication");Object loadedApkInfo = RefInvoke.getFieldOjbect("android.app.ActivityThread$AppBindData",mBoundApplication, "info");//把当前进程的mApplication 设置成了nullRefInvoke.setFieldOjbect("android.app.LoadedApk", "mApplication",loadedApkInfo, null);Object oldApplication = RefInvoke.getFieldOjbect("android.app.ActivityThread", currentActivityThread,"mInitialApplication");//http://www.codeceo.com/article/android-context.htmlArrayList<Application> mAllApplications = (ArrayList<Application>) RefInvoke.getFieldOjbect("android.app.ActivityThread",currentActivityThread, "mAllApplications");mAllApplications.remove(oldApplication);//删除oldApplicationApplicationInfo appinfo_In_LoadedApk = (ApplicationInfo) RefInvoke.getFieldOjbect("android.app.LoadedApk", loadedApkInfo,"mApplicationInfo");ApplicationInfo appinfo_In_AppBindData = (ApplicationInfo) RefInvoke.getFieldOjbect("android.app.ActivityThread$AppBindData",mBoundApplication, "appInfo");appinfo_In_LoadedApk.className = appClassName;appinfo_In_AppBindData.className = appClassName;Application app = (Application) RefInvoke.invokeMethod("android.app.LoadedApk", "makeApplication", loadedApkInfo,new Class[] { boolean.class, Instrumentation.class },new Object[] { false, null });//执行 makeApplication(false,null)RefInvoke.setFieldOjbect("android.app.ActivityThread","mInitialApplication", currentActivityThread, app);......app.onCreate();}}
    代码位于Android中的Apk的加固(加壳)原理解析和实现。

首先appClassName为MyApplication,然后依然是通过反射调用了ActivityThread类的currentActivityThread方法,该方法是静态的,返回当前的ActivityThread对象。

再通过反射获取当前ActivityThread对象的mBoundApplication变量,这个mBoundApplication对象还记得么?是在handleBindApplication被赋值的。

private final void handleBindApplication(AppBindData data) {mBoundApplication = data;......}
    然后再获取mBoundApplication对象里面的info,这个info实际上就是data.info,是LoadedApk对象。
data.info = getPackageInfoNoCheck(data.appInfo);......        Application app = data.info.makeApplication(data.restrictedBackupMode, null);
    继续执行,把LoadedApk对象中的mApplication变量设置为null,为什么要这么做呢?我们稍后解释。

继续执行到如下函数:

Object oldApplication = RefInvoke.getFieldOjbect("android.app.ActivityThread", currentActivityThread,"mInitialApplication");//http://www.codeceo.com/article/android-context.htmlArrayList<Application> mAllApplications = (ArrayList<Application>) RefInvoke.getFieldOjbect("android.app.ActivityThread",currentActivityThread, "mAllApplications");mAllApplications.remove(oldApplication);//删除oldApplication
    这几句函数实际上就对应0x03中函数,从原来ActivityThread对象中移除了原有的application对象。

回到onCreate中继续看,我们先过掉几行代码,直接看makeApplication生成新的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;......                return app;    }
    首先mApplication对象需为null,这就是为什么刚刚把LoadedApk对象中的mApplication变量设置为null的原因。

然后需要获取ApplicationInfo对象mApplicationInfo的成员变量className,因为现在我要启动是被加壳的apk中MyApplication,所以我们要把名字设置为MyApplication。这就是下面几行代码的作用。

ApplicationInfo appinfo_In_LoadedApk = (ApplicationInfo) RefInvoke.getFieldOjbect("android.app.LoadedApk", loadedApkInfo,"mApplicationInfo");ApplicationInfo appinfo_In_AppBindData = (ApplicationInfo) RefInvoke.getFieldOjbect("android.app.ActivityThread$AppBindData",mBoundApplication, "appInfo");appinfo_In_LoadedApk.className = appClassName;appinfo_In_AppBindData.className = appClassName;
    代码位于ProxyApplication类的onCreate方法中。

    最后返回到onCreate方法中,把新生成的application对象赋值给ActivityThread对象的mInitialApplication变量。

对了,还有一点忘记说了,在makeApplication时,getClassLoader获得的ClassLoader对象,已经被替换为DexClassLoader对象,这个对象加载的是被加壳的apk。


0x05

那么MyApplication类什么时候执行onCreate呢?答案在ProxyApplication类的onCreate方法最后,会调用app.onCreate()。


0x06

那么什么时候开启MainActivtiy呢?怎么样开启的呢?

我们再一次看图1,桌面启动一个应用Activity的启动过程,怎么开启的MainActivity呢?

 private final void handleLaunchActivity(ActivityClientRecord r, Intent customIntent) {        ......        Activity a = performLaunchActivity(r, customIntent);......}
private final Activity performLaunchActivity(ActivityClientRecord r, Intent customIntent) {        // System.out.println("##### [" + System.currentTimeMillis() + "] ActivityThread.performLaunchActivity(" + r + ")");        ActivityInfo aInfo = r.activityInfo;        if (r.packageInfo == null) {            r.packageInfo = getPackageInfo(aInfo.applicationInfo,                    Context.CONTEXT_INCLUDE_CODE);        }        ComponentName component = r.intent.getComponent();        if (component == null) {            component = r.intent.resolveActivity(                mInitialApplication.getPackageManager());            r.intent.setComponent(component);        }        if (r.activityInfo.targetActivity != null) {            component = new ComponentName(r.activityInfo.packageName,                    r.activityInfo.targetActivity);        }        Activity activity = null;        try {            java.lang.ClassLoader cl = r.packageInfo.getClassLoader();            activity = mInstrumentation.newActivity(                    cl, component.getClassName(), r.intent);            r.intent.setExtrasClassLoader(cl);            if (r.state != null) {                r.state.setClassLoader(cl);            }        } catch (Exception e) {            if (!mInstrumentation.onException(activity, e)) {                throw new RuntimeException(                    "Unable to instantiate activity " + component                    + ": " + e.toString(), e);            }        }        try {            ......                mInstrumentation.callActivityOnCreate(activity, r.state);} catch {}                }

    代码位于frameworks\base\core\java\android\app\ActivityThread.java。

    此时获取的Classloader对象已经被替换为DexClassLoader对象,这个对象加载的是被加壳的apk。

但是此时获取的activity信息为什么是MainActivity呢?答案在AndroidManifest.xml里面。

        <activity            android:name="com.example.forceapkobj.MainActivity"            android:label="@string/app_name" >            <intent-filter>                <action android:name="android.intent.action.MAIN" />                <category android:name="android.intent.category.LAUNCHER" />            </intent-filter>        </activity>                <activity            android:name="com.example.forceapkobj.SubActivity"></activity>
    MainActivity启动起来后,被加壳的apk就可以正常工作了。
0 0
原创粉丝点击