关于android加固的简单实现------Application替换

来源:互联网 发布:盐枪 淘宝 编辑:程序博客网 时间:2024/06/06 18:45

最近大神同事带我一起做gradle加固,我当然满怀信心的想和他一起做,毕竟一直感觉这是个高大上的事,下面说说具体方案:
注:壳app==壳 加固app == app

  1. 我们准备了一个加固的壳,这个壳是用来做替换的,也就是说我们安装后打开的是壳,然后会在壳里面解析出真正的app。
  2. 自定义gradle插件,在编译的时候我们去捕获dex的生成的过程,在app生成dex的最后,我们先将app的dex剪切(没错就是剪切,可以理解为copy,删除)到asset目录下并进行加密,然后我们把壳的dex拷贝到app生成的dex目录下,这样壳的dex就成了我们app安装后会去寻找并执行的dex。
  3. 打开程序后会执行壳app的application,当然了壳dex里面也只有自定义的application一个类,用来替换成真实的application
  4. 然后用自定义classLoader将之前存放到asset下的app的dex(可能有多个)的path加到classLoader中,这样classloader就具有了加载真实app的dex的能力了,然后再把classloader设置到loadedApk中去,因为像我们context.getClassLoader()的获取方法最终都是到loadedApk中获取的,代码如下:
 public static ClassLoader getClassLoader(ClassLoader loader, File dexDir, List<File> files) {        StringBuilder sb = new StringBuilder();        for (File file : files) {            Log.e("ggg", "ggg shell dexpath = " + file.getAbsolutePath());            sb.append(file.getAbsolutePath())                    .append(File.pathSeparator);        }        DexClassLoader classLoader = new DexClassLoader(sb.toString(), baseContext.getDir("out", Context.MODE_PRIVATE).getAbsolutePath(), baseContext.getApplicationInfo().nativeLibraryDir, loader);        return classLoader;    }Reflect.on(loadedApk()).set("mClassLoader", classLoader);

壳和app的dex路径都在如下目录下可以找到

build\intermediates\transforms\dex\debug\folders\1000\1f\main\classes.dex

那么问题来了,这次先说application替换吧,前面说到app打开后运行的是壳中的application的生命周期,所以我们要做的当然是去运行app中application的生命周期啦,application中必要执行两个生命周期方法应该是attach和oncreate吧,所以我就想怎么样才能执行这两个方法呢,所以我们去看了ActivityThread中的handleBindApplication,了解一点framework的同学就会知道最后会在loadedApk执行makeApplication()方法,生成一个application并调用他的attach方法,然后我们就像我们能不能也调用这个方法去生成呢,事实证明是可以的,为了保证生命周期,于是我们在壳application中attach方法中执行如下代码

Object currentActivityThread = currentActivityThread();Object mBoundApplication = Reflect.on(currentActivityThread).get("mBoundApplication");Object loadedApk = Reflect.on(mBoundApplication).get("info");//把当前进程的mApplication 设置成了nullReflect.on(loadedApkInfo).set("mApplication", null);Object oldApplication = Reflect.on(currentActivityThread).get("mInitialApplication");//http://www.codeceo.com/article/android-context.htmlArrayList<Application> mAllApplications = Reflect.on(currentActivityThread).field("mAllApplications").get();mAllApplications.remove(oldApplication);//删除oldApplicationApplicationInfo loadedApk = Reflect.on(loadedApkInfo).get("mApplicationInfo");ApplicationInfo appBindData = Reflect.on(mBoundApplication).get("appInfo");loadedApk.className = appClassName;appBindData.className = appClassName;//执行 makeApplication(false,null)Application app = Reflect.on(loadedApkInfo).call("makeApplication", new Object[]{false, null}).get();

上面代码可以看到会把loadapk中application这个对象的className替换成真实app的application,然后设置进行替换

//把当前进程的mApplication 设置成了nullReflect.on(loadedApkInfo).set("mApplication", app);      Reflect.on(currentActivityThread).set("mInitialApplication", app);

满心欢喜的去执行代码,安装好app后,你会发现获取到的application依然是壳application,这就很坑了,为了找出原因,于是去查看源码loadedApk,发现在makeAppliaction中执行会发生如下情况

  try {            java.lang.ClassLoader cl = getClassLoader();            if (!mPackageName.equals("android")) {                initializeJavaContextClassLoader();            }            ContextImpl appContext = ContextImpl.createAppContext(mActivityThread, this);            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;

壳application中的attach方法是在mActivityThread.mInstrumentation.newApplication这个方法中走的,也就是说我们在执行完替换后,下面的方法还会继续走,那么上面代码最后的两行依然会走

mActivityThread.mAllApplications.add(app);mApplication = app;

这两行代码就会导致,mApplication依然被赋值成了壳的application,哇,真的是好坑,为了解决这个问题,我和同事讨论,我们想把替换工作的放到壳application的onCreate()方法中执行,但是makeApplication方法依然放在attach中执行:

Application app;@Overrideprotected void attachBaseContext(Context base) {    super.attachBaseContext(base);    ...    ...    app = Reflect.on(loadedApkInfo).call("makeApplication", new Object[]{false, null}).get();}@Overridepublic void onCreate() {    super.onCreate();    Object currentActivityThread = currentActivityThread();    Object mBoundApplication = Reflect.on(currentActivityThread).get("mBoundApplication");    Object loadedApkInfo = Reflect.on(mBoundApplication).get("info"); Reflect.on(loadedApkInfo).set("mApplication", app);           Reflect.on(currentActivityThread).set("mInitialApplication", app);}

这样试了一下,果然错误不出现了,但是又有了一个新坑,那就是provider的问题

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

这个是ActivityThread中的安装provider的源码,问题出就出在安装provider的时候loadedApk中的mApplication还没有被替换掉,用的还是壳application,继续在填坑的路上不断前进,经过多次的方案推翻又重来,我们最后的做法,是用app的application手动去调用installProvider方法,然后把data.providers置空,避免后来壳application再次调用这个方法,试了一下,终于可行。可能说的比较乱,就将有着看吧,后面再进行改进,把其他实现也贴出来,完成代码:

Application app;@Overrideprotected void attachBaseContext(Context base) {    super.attachBaseContext(base);    ...    ...    app = Reflect.on(loadedApkInfo).call("makeApplication", new Object[]{false, null}).get();     List<ProviderInfo> providers = Reflect.on(getBoundApplication()).field("providers").get();Log.e("ggg shell", "ggg providers = " + providers);if (providers != null) {     Reflect.on(currentActivityThread).call("installContentProviders", app, providers);     providers.clear(); }}@Overridepublic void onCreate() {    super.onCreate();    Object currentActivityThread = currentActivityThread();    Object mBoundApplication = Reflect.on(currentActivityThread).get("mBoundApplication");    Object loadedApkInfo = Reflect.on(mBoundApplication).get("info"); Reflect.on(loadedApkInfo).set("mApplication", app);              Reflect.on(currentActivityThread).set("mInitialApplication", app);}

最后感谢 https://github.com/godlikewangjun/dexknife-wj.git 的分享让我们有了入坑的资本。