java层动态加载dex-apk实践

总结

  1. 基本都是使用DexClassLoader进行加载,构造函数的参数分别是:apk/dex/jar路径、缓存oat的路径(已无效)、lib路径、父类加载器。只是加载dex和lib,其它资源不会加载。dex中全部java类载入。
  2. 加载时注意权限,api23以后权限写在清单里是没有效果的(如sd卡读写权,只是能开启但不会自动开启),开启需要写代码询问或者手动去设置中开启清单申请的。没有权限会直接抛出一般错误
  3. 查看错误原因时要逐层向父类找,比如找不到加载类错误的父类可能抛出的是未加载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对象的,

  1. eclipse写好类,导出JAR文件,任意名字

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    package jarexport_android;
    public class Ijarclass implements Ijar {
    public Ijarclass() {
    }
    @Override
    public String say(int a) {
    return "输入"+a;
    }
    }
  2. 用SDK build-tools下的dx.jar转化jar为打包的dex

    1
    dx --dex --output=dynamic_dex.jar dynamic.jar
  3. 提出dex放于android一目录下:sd下的ls

  4. 动态加载:
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    private void loadJar() {
    //这里应该用dex文件,打包成jar的dex不可以,sd下的/ls/classes.dex
    String a=Environment.getExternalStorageDirectory().toString() + File.separator + "ls"+ File.separator+"classes.dex";
    String b=getApplicationInfo().dataDir;
    //dex的位置,解压的路径,lib,classloader
    DexClassLoader 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保护文章。

  1. 写好打包用来加载的apk,置于指定目录下
  2. 加载运行apk的壳apk中要声明好原apk的activity,如果有application要写入名字用于替换,注意声明权限

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    <uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE"></uses-permission>
    <uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE"></uses-permission>
    <application
    android:allowBackup="true"
    android:label="@string/app_name"
    android:name=".ProxyApplication">
    <meta-data android:name="APPLICATION_CLASS_NAME" android:value="com.example.forceapkobj.MyApplication"/>
    <activity
    android:name=".MainActivity"
    android:label="@string/app_name" >
    </activity>
    <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>
    </application>
  3. 代码中application中进行加载dex替换application:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    52
    53
    54
    55
    56
    57
    58
    59
    60
    61
    62
    63
    64
    65
    66
    67
    68
    69
    70
    71
    72
    73
    74
    75
    76
    77
    78
    79
    80
    81
    82
    83
    84
    85
    86
    87
    88
    89
    90
    91
    92
    93
    94
    95
    96
    97
    98
    99
    100
    101
    102
    103
    104
    105
    106
    107
    108
    109
    110
    111
    112
    113
    114
    115
    116
    117
    118
    119
    120
    121
    122
    123
    124
    125
    126
    127
    128
    129
    130
    131
    132
    133
    134
    135
    136
    137
    138
    139
    140
    141
    142
    143
    144
    145
    146
    147
    148
    149
    150
    151
    152
    153
    154
    155
    156
    157
    158
    159
    160
    161
    162
    163
    164
    165
    166
    167
    168
    169
    170
    171
    172
    173
    174
    175
    176
    177
    178
    179
    180
    181
    182
    183
    184
    185
    186
    187
    188
    189
    190
    191
    192
    193
    194
    195
    196
    197
    198
    199
    200
    201
    202
    203
    204
    205
    206
    207
    208
    209
    210
    211
    212
    213
    214
    215
    216
    package 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)
    @Override
    protected 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/14223493
    String 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)
    @Override
    public 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();
    }
    //有值的话调用该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);//删除oldApplication
    ApplicationInfo 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());
    }
    @Override
    public AssetManager getAssets() {
    return mAssetManager == null ? super.getAssets() : mAssetManager;
    }
    @Override
    public Resources getResources() {
    return mResources == null ? super.getResources() : mResources;
    }
    @Override
    public Theme getTheme() {
    return mTheme == null ? super.getTheme() : mTheme;
    }
    }