当前位置:网站首页 > 编程语言 > 正文

dex解密字符串(dex 解密)



App通常都会做混淆防止别人反编译,即使反编译出来也是a、b、c这种,但是这种还是会被一些有心的人还原代码,这样我们需要给dex加密,这样别人就不容易反编译。

效果:

别人是没办法看见主工程的代码只能看见解密工程的java代码,因为解密是在C中实现的,所以密钥和解密方法都还是安全的。

APK本质就是一个zip压缩包,解压之后包含AndroidManifest.xml、class.dex、resources.arsc等等文件

要把Dex加密,但是系统是不认识我们加密后的Dex的,所以还需要一个解密,我们做一个解密工程生成主Dex(系统能识别的Dex,这个不加密),然后利用这个Dex解密我们加密过的原App的Dex。

整个工程分三个部分,工程结构:

不是利用Android Studio的自动打包,而是我们去生成一个apk

大致步骤:

1.主APP生成apk,解密工程生成aar,提供给后面使用

获取解密工程生成的aar中的class.jar包,并把它生成为classes.dex。

3.获得主APK里面的所有dex,并给所有dex加密成系统不能识别的dex。

把解密dex放入加密dex的目录,重新生成一个apk,并签名这个apk

替换回application(也就是AndroidManifest.xml中定义的application)。替换Application

1. 创建解密工程

首先在主APP工程中创建一个Module作为解密工程,别忘了在主工程的gradle中添加

implementation project(':proxy_guard_core')

一个app的入口都是Application,所以我们创建一个Application在这里实现解密,并在主APP的AndroidManifest.xml中使用这个Application。

准备工作完毕,下面撸解密代码

Application最先调用的方法是attachBaseContext(Context base)。

for (File file : files) {

String name = file.getName();

//文件名是 .dex结尾, 并且不是主dex 放入 dexDir 目录

if (name.endsWith('.dex') && !TextUtils.equals(name, 'classes.dex')) {

try {

//从文件中读取 byte数组 加密后的dex数据

byte[] bytes = Utils.getBytes(file);

//将dex 文件 解密 并且写入 原文件file目录

Utils.decrypt(bytes, file.getAbsolutePath());

dexFiles.add(file);

} catch (Exception e) {

e.printStackTrace();

}

}

}

系统在加载dex的时候首先加载的是classes.dex,后面我们需要把解密工程生成classes.dex,所以这里遍历得到所以的dex文件,classes.dex除外。Utils.decrypt()调用openssl实现解密

//加解密的 上下文

EVP_CIPHER_CTX *ctx = EVP_CIPHER_CTX_new();

int outlen;

unsigned char outbuf[1024];

//初始化上下文 设置解码参数

EVP_DecryptInit_ex(ctx, EVP_aes_128_ecb(), NULL, userkey, NULL);

//密文比明文长,所以肯定能保存下所有的明文

uint8_t *out = malloc(src_len);

//数据置空

memset(out, 0, src_len);

int len;

//解密 abcdefg z z

EVP_DecryptUpdate(ctx, out, &outlen, src, src_len);

len = outlen;

//解密剩余的所有数据 校验

EVP_DecryptFinal_ex(ctx, out + outlen, &outlen);

len += outlen;

EVP_CIPHER_CTX_free(ctx);

//写文件 以二进制形式写出

FILE *f = fopen(path, 'wb');

fwrite(out, len, 1, f);

fclose(f);

free(out);

openSSL的编译看最后面。

我们把加密的dex解密成普通的dex之后,这个时候系统已经运行完成了多dex自动加载过程,但是我们的dex并没有被加载,所以我们需要自己实现多dex的加载。多dex加载原理看后面,5.0之后开始支持多dex加载,源码MultiDex里也是这样实现的。(但是MultiDex适配19(4.4)以上的之调用下面的第一个if,并没有适配6.x,就能做到19以上系统都能支持多dex加载,具体原因是什么还不清楚,或者我看错了)

//1.1 获得classloader中的pathList => DexPathList

Field pathListField = Utils.findField(getClassLoader(), 'pathList');

Object pathList = pathListField.get(getClassLoader());

//1.2 获得pathList类中的 dexElements

Field dexElementsField = Utils.findField(pathList, 'dexElements');

Object[] dexElements = (Object[]) dexElementsField.get(pathList);

if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT && Build.VERSION.SDK_INT <

Build.VERSION_CODES.M) {

//5.x

makeDexElements = Utils.findMethod(pathList, 'makeDexElements', ArrayList.class,

File.class, ArrayList.class);

addElements = (Object[]) makeDexElements.invoke(pathList, dexFiles,

optimizedDirectory,

suppressedExceptions);

} else if(Build.VERSION.SDK_INT < Build.VERSION_CODES.N &&Build.VERSION.SDK_INT >= Build.VERSION_CODES.M){

//6.x

makeDexElements = Utils.findMethod(pathList, 'makePathElements', List.class,

File.class, List.class);

addElements = (Object[]) makeDexElements.invoke(pathList, dexFiles,

optimizedDirectory,

suppressedExceptions);

}else {

makeDexElements = Utils.findMethod(pathList, 'makeDexElements',

List.class, File.class, List.class,ClassLoader.class);

Field definingContextField = Utils.findField(pathList, 'definingContext');

ClassLoader definingContext = (ClassLoader) definingContextField.get(pathList);

addElements = (Object[]) makeDexElements.invoke(pathList,dexFiles, optimizedDirectory, suppressedExceptions,definingContext);

}

合并两个Element[]并替换原来的Element[]

//创建一个数组

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

2. 解密工程生成dex

生成dex我们需要借助androidSDKbuild-tools28.0.3下的dx.bat工具,这个工具可以把class/jar生成dex。

一条命令就能生成

dx --dex --output out.dex in.jar

把之前的解密工程生成aar,拿到其中的classes.jar生成dex

/

* 1、制作只包含解密代码的dex 文件

*/

//1.1 解压aar 获得classes.jar

File aarFile = new File('proxy-guard-core/build/outputs/aar/proxy-guard-core-debug.aar');

File aarTemp = new File('proxy-guard-tools/temp');

Zip.unZip(aarFile, aarTemp);

File classesJar = new File(aarTemp, 'classes.jar');

//1.2 执行dx命令 将jar变成dex文件

File classesDex = new File(aarTemp, 'classes.dex');

//执行命令 windows:cmd /c linux/mac不需要(cmd /c)

Process process = Runtime.getRuntime().exec('cmd /c dx --dex --output ' + classesDex

.getAbsolutePath() + ' ' +

classesJar.getAbsolutePath());

process.waitFor();

//失败

if (process.exitValue() != 0) {

throw new RuntimeException('dex error');

}

3. 拿到主工程所有的dex并加密

/

* 2、加密apk中所有dex文件

*/

//2.1 解压apk 获得所有的dex文件

File apkFile = new File('app/build/outputs/apk/debug/app-debug.apk');

File apkTemp = new File('app/build/outputs/apk/debug/temp');

Zip.unZip(apkFile, apkTemp);

//获得所有的dex

File[] dexFiles = apkTemp.listFiles(new FilenameFilter() {

@Override

public boolean accept(File file, String s) {

return s.endsWith('.dex');

}

});

//初始化aes

AES.init(AES.DEFAULT_PWD);

for (File dex : dexFiles) {

//读取文件数据

byte[] bytes = getBytes(dex);

//加密

byte[] encrypt = AES.encrypt(bytes);

//写到指定目录

FileOutputStream fos = new FileOutputStream(new File(apkTemp, 'secret-'

+ dex.getName()));

fos.write(encrypt);

fos.flush();

fos.close();

dex.delete();

}

4. 放入解密工程的dex,并签名

/

* 3、把classes.dex 放入 apk解压目录 在压缩成apk

*/

classesDex.renameTo(new File(apkTemp, 'classes.dex'));

File unSignedApk = new File('app/build/outputs/apk/debug/app-unsigned.apk');

Zip.zip(apkTemp, unSignedApk);

//4.1 对齐

// 26.0.2不认识-p参数 zipalign -v -p 4 my-app-unsigned.apk my-app-unsigned-aligned.apk

File alignedApk = new File('app/build/outputs/apk/debug/app-unsigned-aligned.apk');

process = Runtime.getRuntime().exec('cmd /c zipalign -f 4 ' + unSignedApk

.getAbsolutePath() + ' ' +

alignedApk.getAbsolutePath());

process.waitFor();

//失败

if (process.exitValue() != 0) {

throw new RuntimeException('zipalign error');

}

//4.2 签名

// apksigner sign --ks jks文件地址 --ks-key-alias 别名 --ks-pass pass:jsk密码 --key-pass

// pass:别名密码 --out out.apk in.apk

//官方文档没有 --ks-key-alias等参数 有点坑爹啊

File signedApk = new File('app/build/outputs/apk/debug/app-signed-aligned.apk');

File jks = new File('proxy-guard-tools/proxyDex.jks');

process = Runtime.getRuntime().exec('cmd /c apksigner sign --ks ' + jks.getAbsolutePath

() + ' --ks-key-alias hz --ks-pass pass: --key-pass pass: --out' +

' ' + signedApk.getAbsolutePath() + ' ' + alignedApk.getAbsolutePath());

process.waitFor();

//失败

if (process.exitValue() != 0) {

throw new RuntimeException('apksigner error');

}

多Dex加载原理

Dex的加载是通过ClassLoader来加载,源码目录:

libcoredalviksrcmainjavadalviksystem

加载一个类我们常常会调用

getClassLoader().loadClass('类名');

通过getClassLoader()我们实际获得是一个PathClassLoader对象,loadClass方法在抽象类ClassLoader中

protected Class> loadClass(String name, boolean resolve)

throws ClassNotFoundException

{

// First, check if the class has already been loaded

Class> c = findLoadedClass(name);

if (c == null) {

try {

if (parent != null) {

c = parent.loadClass(name, false);

} else {

c = findBootstrapClassOrNull(name);

}

} catch (ClassNotFoundException e) {

// ClassNotFoundException thrown if class not found

// from the non-null parent class loader

}

if (c == null) {

// If still not found, then invoke findClass in order

// to find the class.

c = findClass(name);

}

}

return c;

}

这里的findClass实际又是PathClassLoader的父类BaseDexClassLoader的一个方法

@Override

protected Class> findClass(String name) throws ClassNotFoundException {

List suppressedExceptions = new ArrayList();

Class c = pathList.findClass(name, suppressedExceptions);

if (c == null) {

ClassNotFoundException cnfe = new ClassNotFoundException(

'Didn't find class '' + name + '' on path: ' + pathList);

for (Throwable t : suppressedExceptions) {

cnfe.addSuppressed(t);

}

throw cnfe;

}

return c;

}

类的加载实际是调用pathList的findClass方法通过类名来找到一个类。而pathList是一个DexPathList 对象。

private final DexPathList pathList;

再来看看DexPathList 的findClass方法:

public Class> findClass(String name, List suppressed) {

for (Element element : dexElements) {

Class> clazz = element.findClass(name, definingContext, suppressed);

if (clazz != null) {

return clazz;

}

}

if (dexElementsSuppressedExceptions != null) {

suppressed.addAll(Arrays.asList(dexElementsSuppressedExceptions));

}

return null;

}

/

* Finds the named class in one of the dex files pointed at by

* this instance. This will find the one in the earliest listed

* path element. If the class is found but has not yet been

* defined, then this method will define it in the defining

* context that this instance was constructed with.

*/

public Class> findClass(String name, List suppressed) {

for (Element element : dexElements) {

Class> clazz = element.findClass(name, definingContext, suppressed);

if (clazz != null) {

return clazz;

}

}

if (dexElementsSuppressedExceptions != null) {

suppressed.addAll(Arrays.asList(dexElementsSuppressedExceptions));

}

return null;

}

按照注释的意思就是从一个dex文件中找到class。

通过遍历dexElements,由此可见一个Element 对应一个dex。

果然dexElements的注释也证明了。

/

* List of dex/resource (class path) elements.

* Should be called pathElements, but the Facebook app uses reflection

* to modify 'dexElements' (http://b/).

*/

private Element[] dexElements;

所以需要把我们生成的dex加入到dexElements这个数组中就能实现多dex的加载

先来看看怎么生成dexElements。

public DexPathList(ClassLoader definingContext, String dexPath,

String librarySearchPath, File optimizedDirectory) {

.......

this.dexElements = makeDexElements(splitDexPath(dexPath), optimizedDirectory,

suppressedExceptions, definingContext);

......

注意:这里是DexPathList的四个参数的构造方法,而不是两个参数的构造方法,两个参数的构造方法,是加载内存中的dexFile。7.0一下源码没有两个参数的构造方法。

所以我们可以通过反射调用makeDexElements来帮我们生成dexElements。7.0以上都是一样,但是4.4到5.x参数不一样,6.x源码这个方法名就不一样了。

6.x

public DexPathList(ClassLoader definingContext, String dexPath,

String libraryPath, File optimizedDirectory) {

......

this.dexElements = makePathElements(splitDexPath(dexPath), optimizedDirectory,

suppressedExceptions);

......

4.4-5.x

public DexPathList(ClassLoader definingContext, String dexPath,

String libraryPath, File optimizedDirectory) {

this.dexElements = makeDexElements(splitDexPath(dexPath), optimizedDirectory,

suppressedExceptions);

4.4-5.x虽然也是makeDexElements但是只有三个参数。

编译openSSL

加密需要openSSL,所以首先先编译一个openSSL。

wget https://wwwhttp://www.360doc.com/content/12/0121/07/source/openssl-1.1.1b.tar.gz

解压之后编译。官网没有编译教程,github上也没有编译教程

还好有个wiki

https://wikihttp://www.360doc.com/content/12/0121/07/index.php/Android

把setenv-android.sh下载下来,并全部复制。

openSSL源码目录下创建一个build.sh,复制setenv-android.sh中的代码。我的NDK 是r17b版本,然后修改其中的配置。

_ANDROID_EABI='arm-linux-androideabi-4.9'

源码中是4.8,根据ndk版本来设置,我的ndk中是4.9。

添加ndk根目录

export ANDROID_NDK_HOME=/ndk/android-ndk-r17b

export ANDROID_NDK_ROOT=/ndk/android-ndk-r17b

如果不设置ANDROID_NDK_HOME会报ANDROID_NDK_HOME未定义的错误。

设置了 ANDROID_NDK_ROOT之后

这个配置就不用管了。

这些都是配置,还需要编译模块

完整的编译脚本在demo中。Dex加密下

到此这篇dex解密字符串(dex 解密)的文章就介绍到这了,更多相关内容请继续浏览下面的相关推荐文章,希望大家都能在编程的领域有一番成就!

版权声明


相关文章:

  • 找代码的网站(找代码的免费网站)2026-02-04 15:36:04
  • bigboss源官网sileo(bigboss源怎么用)2026-02-04 15:36:04
  • 查域名ip地址查询(查域名ip地址查询命令)2026-02-04 15:36:04
  • github代理域名(http代理域名)2026-02-04 15:36:04
  • 文件权限755,对文件拥有者而言,该文件( )(文件权限755对文件拥有者而言何义)2026-02-04 15:36:04
  • esp32 天气时钟(esp32天气时钟)2026-02-04 15:36:04
  • 回环地址怎么配(回环地址的功能)2026-02-04 15:36:04
  • 网站制作代码草图(网站制作代码草图怎么做)2026-02-04 15:36:04
  • 宇宙十大本源法则(宇宙十大本源法则的由来)2026-02-04 15:36:04
  • 广度优先搜索树是唯一的吗(广度优先搜索的生成树)2026-02-04 15:36:04
  • 全屏图片