Android加壳原理分析

志不强者智不达,言不信者行不果。这篇文章主要讲述Android加壳原理分析相关的知识,希望能为你提供帮助。
    0x00
    阅读本文前,建议读者首先阅读android加壳原理,参考文章Android中的Apk的加固(加壳)原理解析和实现。如果没有看过这篇文章,本文理解起来比较困难。
    0x01
    下面我们来分析脱壳代码为什么要这样写,核心脱壳代码在ProxyApplication类里面,首先执行成员方法attachBaseContext,然后执行成员方法onCreate。
    那么attachBaseContext是什么时候被执行的呢,为什么先于onCreate执行呢?那就需要看Android的源码了,我们选用的是Android2.3源码。
    我们首先看一张图,这张图表述了从桌面启动一个应用Activity的启动过程。
   

Android加壳原理分析

文章图片


                        图   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 = https://www.songbingjia.com/android/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 = https://www.songbingjia.com/android/(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类图,帮大家理解。
Android加壳原理分析

文章图片


    那么我们讲了这么多,到底是什么时候执行的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/14223493 String 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方法开始执行。

@Override public 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(); } //有值的话调用该Applicaiton Object 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 设置成了null RefInvoke.setFieldOjbect(" android.app.LoadedApk" , " mApplication" , loadedApkInfo, null); Object oldApplication = RefInvoke.getFieldOjbect( " android.app.ActivityThread" , currentActivityThread, " mInitialApplication" ); //http://www.codeceo.com/article/android-context.html ArrayList< 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 = https://www.songbingjia.com/android/(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.html ArrayList< 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 = https://www.songbingjia.com/android/(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里面。
【Android加壳原理分析】
< 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就可以正常工作了。

    推荐阅读