十一、Dex加解密

一、关于Applicationc对象创建过程后执行的第一个方法attachBaseContext的源码追踪

linux下的zygote进程fork出一个子进程,调用ActivityThread中的main方法,在main方法中初始化ActivityThread,并且调用ActivityThread的attach方法;在此方法汇总获得IActivityManager对象,并调用此对象的attachApplication方法,这里是个进程间通信,IActivityManager的实现类是ActivityManagerService,实现类的方法中调用了attachApplicationLocked方法,在此方法中调用了IApplicationThread的bindApplication方法,这个方法也是进程间通信,会回调的ThreadActivity类的bindApplication方法,此方法中发送一个message,调用handleBindApplication方法,在此方法中调用了

Application app = data.info.makeApplication(data.restrictedBackupMode, null);

进行初始化,data.info是LoadedApk对象,调用此对象方法中的

Application app = mActivityThread.mInstrumentation.newApplication(
cl, appClass, appContext);

Instrumentation对象的newApplication方法进行返回Application对象的,在此方法中又调用了

Application app = (Application)clazz.newInstance();
app.attach(context);

最终,调用到Application对象中的attach方法,在此方法中,调用了attachBaseContext()方法,这个方法也是Application生命周期第一个执行的方法,可以在此方法中做需要提前做得业务;然后再调用onCreate方法;

二、关于ClassLoader加载机制

在Android系统中,我们通过context.getClassLoader获得对象都是PathClassLoader,通过调用其父类BaseDexClassLoader中的findClass方法来加载需要加载的类;在此方法中又调用DexPathList中的findClass方法,在此方法中遍历Element[]数组,这个数组中保存着apk解压后的所有classes.dex文件,通过传进来的参数className来从classes.dex中找到需要加载的类;

三、关于热修复加载机制

从5.0以后,android系统内部对第一次安装的app进行了优化;我们会发现5.0以后安装的速度比之前版本慢了许多,这是典型的:时间换空间;这是因为在第一次安装时,系统会把安装的apk解压到data/user/包名/app_odex目录下;这样以后启动app时,直接从这个文件夹下读取dex文件进行findClass,而不是从安装的apk中解压读取,这样能大大提升app运行的速度;
由于Android自身的加载dex文件的系统漏洞:按照顺序加载dex文件(dex顺序加载机制),在加载需要加载类时,按照dex文件的顺序中去找,直到找到为止,找到后就停止不在继续找,即使后续的dex文件中也存在一个同样的类文件;热修复tinker正是利用这个系统漏洞,通过把我们修复的dex文件放到dex数组的前面进行截胡(加载)实现的;而阿里的Andfix使用的是jvm的特性进行热修复的;

四、build-tool命令的使用:
  • 1.将jar生成dex文件
    命令所在位置:sdk\build-tools,将classes.jar生成dex文件

生成命令:

dx --dex --output out.dex classes.jar


image.png
  • 2.对齐命令
    zipalign
    sdk\build-tools ,apk整理对齐工具。
    未压缩的数据开头均相对于文件开头部分执行特定的字节对齐,减少应用运行内存。
    https://developer.android.google.cn/studio/command-line/zipalign.html

zipalign [-v] [-f] 4 in.apk out.apk


image.png
  • 3.签名
    apksigner
    sdk\build-tools\24.0.3 以上,apk签名工具
    https://developer.android.google.cn/studio/command-line/apksigner.html#options-sign-general
    签名命令:

apksigner sign --ks jks文件地址 --ks-key-alias 别名 --ks-pass pass:jsk密码 --key-pass pass:别名密码 --out out.apk in.apk


image.png
  • 4.签名校验
    校验:

apksigner verify -v out.apk


image.png
五、合并Element数组示例
package com.zcbl.airport_assist.proxy_guard_core;

import android.content.Context;
import android.os.Environment;
import android.util.Log;
import android.widget.Toast;

import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.lang.reflect.Array;
import java.lang.reflect.Field;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.util.ArrayList;
import java.util.HashSet;
import java.util.List;

import dalvik.system.DexClassLoader;
import dalvik.system.PathClassLoader;

import static android.content.ContentValues.TAG;

/**
 * Created by serenitynanian on 2018/10/23.
 * 合并自己的Element数组和系统的Element数组
 */

public class CombineElement {

    public static final String DEX_DIR = "odex";

    public static HashSet loadedDex = new HashSet<>();

    static {
        loadedDex.clear();
    }

    /**
     * 执行此方法前,先执行loadExternalDexToOdex()方法,将外置卡里的dex转移到data/user/包名/app_odex文件夹下
     *
     * @param context
     */
    public static void loadDex(Context context) {
        if (context == null) {
            return;
        }
        File filesDir = context.getDir("odex", Context.MODE_PRIVATE);
        File[] listFiles = filesDir.listFiles();
        for (File file : listFiles) {
            if (file.getName().startsWith("classes") || file.getName().endsWith(".dex")) {
                Log.i("INFO", "dexName:" + file.getName());
                loadedDex.add(file);
            }
        }
        String optimizeDir = filesDir.getAbsolutePath() + File.separator + "opt_dex";
        File fopt = new File(optimizeDir);
        if (!fopt.exists()) {
            fopt.mkdirs();
        }

        for (File dex : loadedDex) {
            try {
                // -----------------------系统的ClassLoader------------------------------------
                PathClassLoader pathClassLoader = (PathClassLoader) context.getClassLoader();
                Class baseDexClazzLoader = Class.forName("dalvik.system.BaseDexClassLoader");
                Field pathListFiled = baseDexClazzLoader.getDeclaredField("pathList");
                pathListFiled.setAccessible(true);
                Object pathListObject = pathListFiled.get(pathClassLoader);

                Class systemPathClazz = pathListObject.getClass();
                Field systemElementsField = systemPathClazz.getDeclaredField("dexElements");
                systemElementsField.setAccessible(true);
                Object systemElements = systemElementsField.get(pathListObject);

                DexClassLoader classLoader = new DexClassLoader(dex.getAbsolutePath(), fopt.getAbsolutePath(), null, context.getClassLoader());

                // ------------------自己的ClassLoader--------------------------
                Class myDexClazzLoader = Class.forName("dalvik.system.BaseDexClassLoader");
                Field myPathListFiled = myDexClazzLoader.getDeclaredField("pathList");
                myPathListFiled.setAccessible(true);
                Object myPathListObject = myPathListFiled.get(classLoader);

                Class myPathClazz = myPathListObject.getClass();
                Field myElementsField = myPathClazz.getDeclaredField("dexElements");
                myElementsField.setAccessible(true);
                Object myElements = myElementsField.get(myPathListObject);

                //------------------------融合-----------------------------
                Class sigleElementClazz = systemElements.getClass().getComponentType();
                int systemLength = Array.getLength(systemElements);
                int myLength = Array.getLength(myElements);
                int newSystenLength = systemLength + myLength;
                //生成一个新的 数组   类型为Element类型
                Object newElementsArray = Array.newInstance(sigleElementClazz, newSystenLength);
                for (int i = 0; i < newSystenLength; i++) {
                    if (i < myLength) {
                        Array.set(newElementsArray, i, Array.get(myElements, i));
                    } else {
                        Array.set(newElementsArray, i, Array.get(systemElements, i - myLength));
                    }
                }
                //-------------------融合完毕   将新数组  放到系统的PathLoad内部---------------------------------
                Field elementsField = pathListObject.getClass().getDeclaredField("dexElements");

                elementsField.setAccessible(true);

                elementsField.set(pathListObject, newElementsArray);
            } catch (Exception e) {
                e.printStackTrace();
            }
        }
    }

    /**
     * 加载dex文件集合
     *
     * @param dexFiles
     */
    private void loadDex(Context context, List dexFiles, File optimizedDirectory) throws
            NoSuchFieldException, IllegalAccessException, NoSuchMethodException,
            InvocationTargetException {
        /**
         * 1.获得 系统 classloader中的dexElements数组
         */
        //1.1  获得classloader中的pathList => DexPathList
        Field pathListField = ReflectUtils.findField(context.getClassLoader(), "pathList");
        Object pathList = pathListField.get(context.getClassLoader());
        //1.2 获得pathList类中的 dexElements
        Field dexElementsField = ReflectUtils.findField(pathList, "dexElements");
        Object[] dexElements = (Object[]) dexElementsField.get(pathList);
        /**
         * 2.创建新的 element 数组 -- 解密后加载dex
         */
        //5.x 需要适配6.x 7.x
        Method makeDexElements;
//        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP && Build.VERSION.SDK_INT <
//                Build.VERSION_CODES.M) {
        makeDexElements = ReflectUtils.findMethod(pathList, "makeDexElements", ArrayList.class,
                File.class, ArrayList.class);
//        } else if(Build.VERSION.SDK_INT >= Build.VERSION_CODES.N){
//            makeDexElements = Utils.findMethod(pathList, "makePathElements", ArrayList.class,
//                    File.class, ArrayList.class);
//        }
        ArrayList suppressedExceptions = new ArrayList();
        Object[] addElements = (Object[]) makeDexElements.invoke(pathList, dexFiles,
                optimizedDirectory,
                suppressedExceptions);
        /**
         * 3.合并两个数组
         */
        //创建一个数组
        Object[] newElements = (Object[]) Array.newInstance(dexElements.getClass()
                .getComponentType(), dexElements.length +
                addElements.length);
        System.arraycopy(dexElements, 0, newElements, 0, dexElements.length);
        System.arraycopy(addElements, 0, newElements, dexElements.length, addElements.length);
        /**
         * 4.替换classloader中的 element数组
         */
        dexElementsField.set(pathList, newElements);
    }


    /**
     * 将外置卡里面的dex文件转移到data/user/包名/app_odex文件下
     *
     * @param context
     */
    private void loadExternalDexToOdex(Context context) {
        File filesDir = context.getDir("odex", Context.MODE_PRIVATE);
        String name = "out.dex";
        String filePath = new File(filesDir, name).getAbsolutePath();
        File file = new File(filePath);
        if (file.exists()) {
            file.delete();
        }
        InputStream is = null;
        FileOutputStream os = null;
        try {
            Log.i(TAG, "fixBug: " + new File(Environment.getExternalStorageDirectory(), name).getAbsolutePath());
            is = new FileInputStream(new File(Environment.getExternalStorageDirectory(), name));
            os = new FileOutputStream(filePath);
            int len = 0;
            byte[] buffer = new byte[1024];
            while ((len = is.read(buffer)) != -1) {
                os.write(buffer, 0, len);
            }
            File f = new File(filePath);
            if (f.exists()) {
                Toast.makeText(context, "dex overwrite", Toast.LENGTH_SHORT).show();
            }
        } catch (IOException e) {
            // TODO Auto-generated catch block
            e.printStackTrace();
        } finally {
            try {
                os.close();
                is.close();
            } catch (IOException e) {
                // TODO Auto-generated catch block
                e.printStackTrace();
            }
        }
    }
}
四、反射得到指定对象的Method和Field
package com.zcbl.airport_assist.proxy_guard_core;

import java.lang.reflect.Field;
import java.lang.reflect.Method;
import java.util.Arrays;

/**
 * Created by serenitynanian on 2018/10/23.
 */

public class ReflectUtils {

    /**
     *  反射获得  指定对象(当前->父类—>父类..) 中的 成员属性
     * @param object
     * @param name
     * @return
     */
    public static Field findField(Object object, String name) throws NoSuchFieldException {

        Class aClass = object.getClass();
        while (null != aClass) {
            try {
                Field declaredField = aClass.getDeclaredField(name);
                //如果无法访问 设置可访问
                if (!declaredField.isAccessible()) {
                    declaredField.setAccessible(true);
                }
            } catch (NoSuchFieldException e) {
//                e.printStackTrace();
                //如果找不到 往父类中找
                aClass = aClass.getSuperclass();
            }
        }
        throw new NoSuchFieldException("Field "+name+" not found in "+object.getClass());
    }

    /**
     * 反射获得 指定 对象(当前->父类—>父类..) 中的 方法
     * @param object
     * @param name
     * @param parametersTypes
     * @return
     */
    public static Method findMethod(Object object, String name,Class... parametersTypes) throws NoSuchMethodException {
        Class aClass = object.getClass();
        while (null != aClass) {
            try {
                Method declaredMethod = aClass.getDeclaredMethod(name, parametersTypes);
                if (!declaredMethod.isAccessible()) {
                    declaredMethod.setAccessible(true);
                }
            } catch (NoSuchMethodException e) {
//                e.printStackTrace();
                //如果找不到 往父类中找
                aClass = aClass.getSuperclass();
            }
        }
        throw new NoSuchMethodException("Method "+name +" witd parameters "+ Arrays.asList(parametersTypes)+
                " not found in "+object.getClass());
    }
}
六、OpenSSL的使用与编译

密码库,囊括主要的密码算法、常用的密钥和证书封装管理功能及SSL协议;
https://www.openssl.org/source/
https://wiki.openssl.org/index.php/Android

具体使用步骤
  • 1、下载:使用linux下载命令:wget进行下载
    wget https://www.openssl.org/source/openssl-1.1.0g.tar.gz
  • 2、解压
    tar xvf openssl-1.1.0g.tar.gz
  • 3、编译脚本
    复制https://wiki.openssl.org/images/7/70/Setenv-android.sh中的内容,修改以 '_' 开头的变量( _ANDROID_NDK 不用管,《export ANDROID_NDK_ROOT=NDK目录》 即可)
    在末尾加上
    ./config shared no-ssl2 no-ssl3 no-comp no-hw no-engine --openssldir=pwd/android/x86/openssl --prefix=pwd/android/x86
    make depend
    make all
    sudo -E make install CC=ANDROID_TOOLCHAIN/arm-linux-androideabi-ranlib
    (arm-linux-androideabi- 如果是x86需要为i686-linux-android-)

备注:动态获取AndroidManifest.xml中的的meta-data值

 private void getAndroidManifestMetaData(){
        PackageManager packageManager = getPackageManager();
        try {
            ApplicationInfo applicationInfo = packageManager.getApplicationInfo(getPackageName(),
                    PackageManager.GET_META_DATA);
            Bundle metaData = applicationInfo.metaData;
            if (null != metaData) {
                if (metaData.containsKey("app_name")) {
                    String app_name = metaData.getString("app_name");
                }
            }
        } catch (PackageManager.NameNotFoundException e) {
            e.printStackTrace();
        }
    }

你可能感兴趣的:(十一、Dex加解密)