最近大神同事带我一起做gradle加固,我当然满怀信心的想和他一起做,毕竟一直感觉这是个高大上的事,下面说说具体方案:
注:壳app==壳 加固app == app
- 我们准备了一个加固的壳,这个壳是用来做替换的,也就是说我们安装后打开的是壳,然后会在壳里面解析出真正的app。
- 自定义gradle插件,在编译的时候我们去捕获dex的生成的过程,在app生成dex的最后,我们先将app的dex剪切(没错就是剪切,可以理解为copy,删除)到asset目录下并进行加密,然后我们把壳的dex拷贝到app生成的dex目录下,这样壳的dex就成了我们app安装后会去寻找并执行的dex。
- 打开程序后会执行壳app的application,当然了壳dex里面也只有自定义的application一个类,用来替换成真实的application
- 然后用自定义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 的分享让我们有了入坑的资本。