dex字符串解密_十一、Dex加解密

本文详细探讨了Android应用的 Dex 加解密过程,包括Application对象的attachBaseContext方法,ClassLoader的加载机制,以及热修复技术如Tinker的原理。此外,还介绍了如何使用build-tools中的dx命令生成dex文件,zipalign进行对齐操作,以及apksigner进行签名。最后,文章提到了反射在加载自定义dex和OpenSSL编译中的应用。
摘要由CSDN通过智能技术生成

一、关于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

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的使用与编译

具体使用步骤

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();

}

}

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值