总结
- 基本都是使用DexClassLoader进行加载,构造函数的参数分别是:apk/dex/jar路径、缓存oat的路径(已无效)、lib路径、父类加载器。只是加载dex和lib,其它资源不会加载。dex中全部java类载入。
- 加载时注意权限,api23以后权限写在清单里是没有效果的(如sd卡读写权,只是能开启但不会自动开启),开启需要写代码询问或者手动去设置中开启清单申请的。没有权限会直接抛出一般错误
- 查看错误原因时要逐层向父类找,比如找不到加载类错误的父类可能抛出的是未加载dex
壳加载方案:
因为需要原apk的各种manifest声明、资源、lib、以及权限问题,所以一般在原apk中进行修改,在manifest中加入最早运行的类application的自定义声明,并将原application名保存,将原apk的资源、dex加密。放入自己的dex(一般包含原dex)、so到apk中。在自己的dex-application中加载so。so内解密资源、解密dex、加载dex。之后替换运行原本application即可。
实践
java层加载dex
是不能直接从asset获取然后直接转换为File对象的,因为asset被存储为apk中,除非你解压Apk文件,一般是不能找到一个Path实例化一个File对象的,
eclipse写好类,导出JAR文件,任意名字
1234567891011package jarexport_android;public class Ijarclass implements Ijar {public Ijarclass() {}@Overridepublic String say(int a) {return "输入"+a;}}用SDK build-tools下的dx.jar转化jar为打包的dex
1dx --dex --output=dynamic_dex.jar dynamic.jar提出dex放于android一目录下:sd下的ls
- 动态加载:12345678910111213141516171819202122private void loadJar() {//这里应该用dex文件,打包成jar的dex不可以,sd下的/ls/classes.dexString a=Environment.getExternalStorageDirectory().toString() + File.separator + "ls"+ File.separator+"classes.dex";String b=getApplicationInfo().dataDir;//dex的位置,解压的路径,lib,classloaderDexClassLoader cl = new DexClassLoader(a, b, null, this.getClassLoader());Class libProviderClazz = null;try {// 反射可以调用,但newInstance转成接口就死活不对怀疑是android运行环境的问题libProviderClazz = cl.loadClass("jarexport_android.Ijarclass");Object loader = libProviderClazz.newInstance();Method m=libProviderClazz.getMethod("say", int.class);Toast.makeText(MainActivity.this, (String) m.invoke(loader,5), Toast.LENGTH_SHORT).show();} catch (Exception exception) {// Handle exception gracefully here.exception.printStackTrace();}}
java层加载apk
直接参考之前的apk保护文章。
- 写好打包用来加载的apk,置于指定目录下
加载运行apk的壳apk中要声明好原apk的activity,如果有application要写入名字用于替换,注意声明权限
12345678910111213141516171819202122232425262728293031<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE"></uses-permission><uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE"></uses-permission><applicationandroid:allowBackup="true"android:label="@string/app_name"android:name=".ProxyApplication"><meta-data android:name="APPLICATION_CLASS_NAME" android:value="com.example.forceapkobj.MyApplication"/><activityandroid:name=".MainActivity"android:label="@string/app_name" ></activity><activityandroid: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><activityandroid:name="com.example.forceapkobj.SubActivity"></activity></application>代码中application中进行加载dex替换application:
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216package com.example.imbaya.dynamic_for_apk;import java.io.BufferedInputStream;import java.io.ByteArrayInputStream;import java.io.ByteArrayOutputStream;import java.io.DataInputStream;import java.io.File;import java.io.FileInputStream;import java.io.FileOutputStream;import java.io.IOException;import java.lang.ref.WeakReference;import java.lang.reflect.Method;import java.util.ArrayList;import java.util.HashMap;import java.util.Iterator;import java.util.zip.ZipEntry;import java.util.zip.ZipInputStream;import android.app.Application;import android.app.Instrumentation;import android.content.Context;import android.content.pm.ApplicationInfo;import android.content.pm.PackageManager;import android.content.pm.PackageManager.NameNotFoundException;import android.content.res.AssetManager;import android.content.res.Resources;import android.content.res.Resources.Theme;import android.os.Build;import android.os.Bundle;import android.os.Environment;import android.support.annotation.RequiresApi;import android.util.ArrayMap;import android.util.Log;import dalvik.system.DexClassLoader;public class ProxyApplication extends Application{private static final String appkey = "APPLICATION_CLASS_NAME";private String apkFileName;private String odexPath;private String libPath;//这是context 赋值@RequiresApi(api = Build.VERSION_CODES.KITKAT)@Overrideprotected void attachBaseContext(Context base) {super.attachBaseContext(base);String a= Environment.getExternalStorageDirectory().toString() + File.separator + "ls"+ File.separator+"ForceApkObj.apk";try {//创建两个文件夹payload_odex,payload_lib 私有的,可写的文件目录File odex = this.getDir("payload_odex", MODE_PRIVATE);File libs = this.getDir("payload_lib", MODE_PRIVATE);odexPath = odex.getAbsolutePath();libPath = libs.getAbsolutePath();apkFileName = a;// 配置动态加载环境Object currentActivityThread = RefInvoke.invokeStaticMethod("android.app.ActivityThread", "currentActivityThread",new Class[] {}, new Object[] {});//获取主线程对象 http://blog.csdn.net/myarrow/article/details/14223493String packageName = this.getPackageName();//当前apk的包名//下面两句不是太理解ArrayMap mPackages = (ArrayMap) 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);try{Object actObj = dLoader.loadClass("com.example.forceapkobj.MainActivity");Log.i("demo", "actObj:"+actObj);}catch(Exception e){Log.i("demo", "activity:"+Log.getStackTraceString(e));}} catch (Exception e) {Log.i("demo", "error:"+Log.getStackTraceString(e));e.printStackTrace();}}@RequiresApi(api = Build.VERSION_CODES.KITKAT)@Overridepublic void onCreate() {super.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);ArrayMap mProviderMap = (ArrayMap) RefInvoke.getFieldOjbect("android.app.ActivityThread", currentActivityThread,"mProviderMap");Iterator it = mProviderMap.values().iterator();while (it.hasNext()) {Object providerClientRecord = it.next();Object localProvider = RefInvoke.getFieldOjbect("android.app.ActivityThread$ProviderClientRecord",providerClientRecord, "mLocalProvider");RefInvoke.setFieldOjbect("android.content.ContentProvider","mContext", localProvider, app);}Log.i("demo", "app:" + app);app.onCreate();}}//以下是加载资源protected AssetManager mAssetManager;//资源管理器protected Resources mResources;//资源protected Theme mTheme;//主题protected void loadResources(String dexPath) {try {AssetManager assetManager = AssetManager.class.newInstance();Method addAssetPath = assetManager.getClass().getMethod("addAssetPath", String.class);addAssetPath.invoke(assetManager, dexPath);mAssetManager = assetManager;} catch (Exception e) {Log.i("inject", "loadResource error:"+Log.getStackTraceString(e));e.printStackTrace();}Resources superRes = super.getResources();superRes.getDisplayMetrics();superRes.getConfiguration();mResources = new Resources(mAssetManager, superRes.getDisplayMetrics(),superRes.getConfiguration());mTheme = mResources.newTheme();mTheme.setTo(super.getTheme());}@Overridepublic AssetManager getAssets() {return mAssetManager == null ? super.getAssets() : mAssetManager;}@Overridepublic Resources getResources() {return mResources == null ? super.getResources() : mResources;}@Overridepublic Theme getTheme() {return mTheme == null ? super.getTheme() : mTheme;}}