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

最近大神同事带我一起做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 设置成了null
Reflect.on(loadedApkInfo).set("mApplication", null);
Object oldApplication = Reflect.on(currentActivityThread).get("mInitialApplication");

//http://www.codeceo.com/article/android-context.html
ArrayList<Application> mAllApplications = Reflect.on(currentActivityThread).field("mAllApplications").get();
mAllApplications.remove(oldApplication);//删除oldApplication
ApplicationInfo 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 设置成了null
Reflect.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;
@Override
protected void attachBaseContext(Context base) {
    super.attachBaseContext(base);
    ...
    ...
    app = Reflect.on(loadedApkInfo).call("makeApplication", new Object[]{false, null}).get();
}

@Override
public 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;
@Override
protected 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();
 }

}

@Override
public 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 的分享让我们有了入坑的资本。

  • 1
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 3
    评论
评论 3
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值