dex解密字符串(dex 解密)

dex解密字符串(dex 解密)div style width 656px margin 0 padding 0 height 0 div lt

大家好,我是讯享网,很高兴认识大家。




讯享网

 <tbody> <tr> <td id="artContent" style="max-width: 656px;"> <div style="width: 656px; margin: 0; padding: 0; height: 0;"></div> <p>App通常都会做混淆防止别人反编译,即使反编译出来也是a、b、c这种,但是这种还是会被一些有心的人还原代码,这样我们需要给dex加密,这样别人就不容易反编译。</p> <p>效果:</p> <p align='center'></p> <p>别人是没办法看见主工程的代码只能看见解密工程的java代码,因为解密是在C中实现的,所以密钥和解密方法都还是安全的。</p> <p>APK本质就是一个zip压缩包,解压之后包含AndroidManifest.xml、class.dex、resources.arsc等等文件</p> <p align='center'></p> <p>要把Dex加密,但是系统是不认识我们加密后的Dex的,所以还需要一个解密,我们做一个解密工程生成主Dex(系统能识别的Dex,这个不加密),然后利用这个Dex解密我们加密过的原App的Dex。</p> <p align='center'></p> <p>整个工程分三个部分,工程结构:</p> <p align='center'></p> <p>不是利用Android Studio的自动打包,而是我们去生成一个apk</p> <p>大致步骤:</p> <p>1.主APP生成apk,解密工程生成aar,提供给后面使用</p> <p>获取解密工程生成的aar中的class.jar包,并把它生成为classes.dex。</p> <p>3.获得主APK里面的所有dex,并给所有dex加密成系统不能识别的dex。</p> <p>把解密dex放入加密dex的目录,重新生成一个apk,并签名这个apk</p> <p>替换回application(也就是AndroidManifest.xml中定义的application)。替换Application</p> <p>1. 创建解密工程</p> <p>首先在主APP工程中创建一个Module作为解密工程,别忘了在主工程的gradle中添加</p> <p>implementation project(':proxy_guard_core')</p> <p>一个app的入口都是Application,所以我们创建一个Application在这里实现解密,并在主APP的AndroidManifest.xml中使用这个Application。</p> <p align='center'></p> <p>准备工作完毕,下面撸解密代码</p> <p>Application最先调用的方法是attachBaseContext(Context base)。</p> <p>for (File file : files) {</p> <p>String name = file.getName();</p> <p>//文件名是 .dex结尾, 并且不是主dex 放入 dexDir 目录</p> <p>if (name.endsWith('.dex') &amp;&amp; !TextUtils.equals(name, 'classes.dex')) {</p> <p>try {</p> <p>//从文件中读取 byte数组 加密后的dex数据</p> <p>byte[] bytes = Utils.getBytes(file);</p> <p>//将dex 文件 解密 并且写入 原文件file目录</p> <p>Utils.decrypt(bytes, file.getAbsolutePath());</p> <p>dexFiles.add(file);</p> <p>} catch (Exception e) {</p> <p>e.printStackTrace();</p> <p>}</p> <p>}</p> <p>}</p> <p>系统在加载dex的时候首先加载的是classes.dex,后面我们需要把解密工程生成classes.dex,所以这里遍历得到所以的dex文件,classes.dex除外。Utils.decrypt()调用openssl实现解密</p> <p>//加解密的 上下文</p> <p>EVP_CIPHER_CTX *ctx = EVP_CIPHER_CTX_new();</p> <p>int outlen;</p> <p>unsigned char outbuf[1024];</p> <p>//初始化上下文 设置解码参数</p> <p>EVP_DecryptInit_ex(ctx, EVP_aes_128_ecb(), NULL, userkey, NULL);</p> <p>//密文比明文长,所以肯定能保存下所有的明文</p> <p>uint8_t *out = malloc(src_len);</p> <p>//数据置空</p> <p>memset(out, 0, src_len);</p> <p>int len;</p> <p>//解密 abcdefg z z</p> <p>EVP_DecryptUpdate(ctx, out, &amp;outlen, src, src_len);</p> <p>len = outlen;</p> <p>//解密剩余的所有数据 校验</p> <p>EVP_DecryptFinal_ex(ctx, out + outlen, &amp;outlen);</p> <p>len += outlen;</p> <p>EVP_CIPHER_CTX_free(ctx);</p> <p>//写文件 以二进制形式写出</p> <p>FILE *f = fopen(path, 'wb');</p> <p>fwrite(out, len, 1, f);</p> <p>fclose(f);</p> <p>free(out);</p> <p>openSSL的编译看最后面。</p> <p>我们把加密的dex解密成普通的dex之后,这个时候系统已经运行完成了多dex自动加载过程,但是我们的dex并没有被加载,所以我们需要自己实现多dex的加载。多dex加载原理看后面,5.0之后开始支持多dex加载,源码MultiDex里也是这样实现的。(但是MultiDex适配19(4.4)以上的之调用下面的第一个if,并没有适配6.x,就能做到19以上系统都能支持多dex加载,具体原因是什么还不清楚,或者我看错了)</p> <p>//1.1 获得classloader中的pathList =&gt; DexPathList</p> <p>Field pathListField = Utils.findField(getClassLoader(), 'pathList');</p> <p>Object pathList = pathListField.get(getClassLoader());</p> <p>//1.2 获得pathList类中的 dexElements</p> <p>Field dexElementsField = Utils.findField(pathList, 'dexElements');</p> <p>Object[] dexElements = (Object[]) dexElementsField.get(pathList);</p> <p>if (Build.VERSION.SDK_INT &gt;= Build.VERSION_CODES.KITKAT &amp;&amp; Build.VERSION.SDK_INT &lt;</p> <p>Build.VERSION_CODES.M) {</p> <p>//5.x</p> <p>makeDexElements = Utils.findMethod(pathList, 'makeDexElements', ArrayList.class,</p> <p>File.class, ArrayList.class);</p> <p>addElements = (Object[]) makeDexElements.invoke(pathList, dexFiles,</p> <p>optimizedDirectory,</p> <p>suppressedExceptions);</p> <p>} else if(Build.VERSION.SDK_INT &lt; Build.VERSION_CODES.N &amp;&amp;Build.VERSION.SDK_INT &gt;= Build.VERSION_CODES.M){</p> <p>//6.x</p> <p>makeDexElements = Utils.findMethod(pathList, 'makePathElements', List.class,</p> <p>File.class, List.class);</p> <p>addElements = (Object[]) makeDexElements.invoke(pathList, dexFiles,</p> <p>optimizedDirectory,</p> <p>suppressedExceptions);</p> <p>}else {</p> <p>makeDexElements = Utils.findMethod(pathList, 'makeDexElements',</p> <p>List.class, File.class, List.class,ClassLoader.class);</p> <p>Field definingContextField = Utils.findField(pathList, 'definingContext');</p> <p>ClassLoader definingContext = (ClassLoader) definingContextField.get(pathList);</p> <p>addElements = (Object[]) makeDexElements.invoke(pathList,dexFiles, optimizedDirectory, suppressedExceptions,definingContext);</p> <p>}</p> <p>合并两个Element[]并替换原来的Element[]</p> <p>//创建一个数组</p> <p>Object[] newElements = (Object[]) Array.newInstance(dexElements.getClass()</p> <p>.getComponentType(), dexElements.length +</p> <p>addElements.length);</p> <p>System.arraycopy(dexElements, 0, newElements, 0, dexElements.length);</p> <p>System.arraycopy(addElements, 0, newElements, dexElements.length, addElements.length);</p> <p>/</p> <p>* 4.替换classloader中的 element数组</p> <p>*/</p> <p>dexElementsField.set(pathList, newElements);</p> <p>2. 解密工程生成dex</p> <p>生成dex我们需要借助androidSDKbuild-tools28.0.3下的dx.bat工具,这个工具可以把class/jar生成dex。</p> <p>一条命令就能生成</p> <p>dx --dex --output out.dex in.jar</p> <p>把之前的解密工程生成aar,拿到其中的classes.jar生成dex</p> <p>/</p> <p>* 1、制作只包含解密代码的dex 文件</p> <p>*/</p> <p>//1.1 解压aar 获得classes.jar</p> <p>File aarFile = new File('proxy-guard-core/build/outputs/aar/proxy-guard-core-debug.aar');</p> <p>File aarTemp = new File('proxy-guard-tools/temp');</p> <p>Zip.unZip(aarFile, aarTemp);</p> <p>File classesJar = new File(aarTemp, 'classes.jar');</p> <p>//1.2 执行dx命令 将jar变成dex文件</p> <p>File classesDex = new File(aarTemp, 'classes.dex');</p> <p>//执行命令 windows:cmd /c linux/mac不需要(cmd /c)</p> <p>Process process = Runtime.getRuntime().exec('cmd /c dx --dex --output ' + classesDex</p> <p>.getAbsolutePath() + ' ' +</p> <p>classesJar.getAbsolutePath());</p> <p>process.waitFor();</p> <p>//失败</p> <p>if (process.exitValue() != 0) {</p> <p>throw new RuntimeException('dex error');</p> <p>}</p> <p>3. 拿到主工程所有的dex并加密</p> <p>/</p> <p>* 2、加密apk中所有dex文件</p> <p>*/</p> <p>//2.1 解压apk 获得所有的dex文件</p> <p>File apkFile = new File('app/build/outputs/apk/debug/app-debug.apk');</p> <p>File apkTemp = new File('app/build/outputs/apk/debug/temp');</p> <p>Zip.unZip(apkFile, apkTemp);</p> <p>//获得所有的dex</p> <p>File[] dexFiles = apkTemp.listFiles(new FilenameFilter() {</p> <p>@Override</p> <p>public boolean accept(File file, String s) {</p> <p>return s.endsWith('.dex');</p> <p>}</p> <p>});</p> <p>//初始化aes</p> <p>AES.init(AES.DEFAULT_PWD);</p> <p>for (File dex : dexFiles) {</p> <p>//读取文件数据</p> <p>byte[] bytes = getBytes(dex);</p> <p>//加密</p> <p>byte[] encrypt = AES.encrypt(bytes);</p> <p>//写到指定目录</p> <p>FileOutputStream fos = new FileOutputStream(new File(apkTemp, 'secret-'</p> <p>+ dex.getName()));</p> <p>fos.write(encrypt);</p> <p>fos.flush();</p> <p>fos.close();</p> <p>dex.delete();</p> <p>}</p> <p>4. 放入解密工程的dex,并签名</p> <p>/</p> <p>* 3、把classes.dex 放入 apk解压目录 在压缩成apk</p> <p>*/</p> <p>classesDex.renameTo(new File(apkTemp, 'classes.dex'));</p> <p>File unSignedApk = new File('app/build/outputs/apk/debug/app-unsigned.apk');</p> <p>Zip.zip(apkTemp, unSignedApk);</p> <p>//4.1 对齐</p> <p>// 26.0.2不认识-p参数 zipalign -v -p 4 my-app-unsigned.apk my-app-unsigned-aligned.apk</p> <p>File alignedApk = new File('app/build/outputs/apk/debug/app-unsigned-aligned.apk');</p> <p>process = Runtime.getRuntime().exec('cmd /c zipalign -f 4 ' + unSignedApk</p> <p>.getAbsolutePath() + ' ' +</p> <p>alignedApk.getAbsolutePath());</p> <p>process.waitFor();</p> <p>//失败</p> <p>if (process.exitValue() != 0) {</p> <p>throw new RuntimeException('zipalign error');</p> <p>}</p> <p>//4.2 签名</p> <p>// apksigner sign --ks jks文件地址 --ks-key-alias 别名 --ks-pass pass:jsk密码 --key-pass</p> <p>// pass:别名密码 --out out.apk in.apk</p> <p>//官方文档没有 --ks-key-alias等参数 有点坑爹啊</p> <p>File signedApk = new File('app/build/outputs/apk/debug/app-signed-aligned.apk');</p> <p>File jks = new File('proxy-guard-tools/proxyDex.jks');</p> <p>process = Runtime.getRuntime().exec('cmd /c apksigner sign --ks ' + jks.getAbsolutePath</p> <p>() + ' --ks-key-alias hz --ks-pass pass: --key-pass pass: --out' +</p> <p>' ' + signedApk.getAbsolutePath() + ' ' + alignedApk.getAbsolutePath());</p> <p>process.waitFor();</p> <p>//失败</p> <p>if (process.exitValue() != 0) {</p> <p>throw new RuntimeException('apksigner error');</p> <p>}</p> <p>多Dex加载原理</p> <p>Dex的加载是通过ClassLoader来加载,源码目录:</p> <p>libcoredalviksrcmainjavadalviksystem</p> <p align='center'></p> <p>加载一个类我们常常会调用</p> <p>getClassLoader().loadClass('类名');</p> <p>通过getClassLoader()我们实际获得是一个PathClassLoader对象,loadClass方法在抽象类ClassLoader中</p> <p>protected Class&gt; loadClass(String name, boolean resolve)</p> <p>throws ClassNotFoundException</p> <p>{</p> <p>// First, check if the class has already been loaded</p> <p>Class&gt; c = findLoadedClass(name);</p> <p>if (c == null) {</p> <p>try {</p> <p>if (parent != null) {</p> <p>c = parent.loadClass(name, false);</p> <p>} else {</p> <p>c = findBootstrapClassOrNull(name);</p> <p>}</p> <p>} catch (ClassNotFoundException e) {</p> <p>// ClassNotFoundException thrown if class not found</p> <p>// from the non-null parent class loader</p> <p>}</p> <p>if (c == null) {</p> <p>// If still not found, then invoke findClass in order</p> <p>// to find the class.</p> <p>c = findClass(name);</p> <p>}</p> <p>}</p> <p>return c;</p> <p>}</p> <p>这里的findClass实际又是PathClassLoader的父类BaseDexClassLoader的一个方法</p> <p>@Override</p> <p>protected Class&gt; findClass(String name) throws ClassNotFoundException {</p> <p>List suppressedExceptions = new ArrayList();</p> <p>Class c = pathList.findClass(name, suppressedExceptions);</p> <p>if (c == null) {</p> <p>ClassNotFoundException cnfe = new ClassNotFoundException(</p> <p>'Didn't find class '' + name + '' on path: ' + pathList);</p> <p>for (Throwable t : suppressedExceptions) {</p> <p>cnfe.addSuppressed(t);</p> <p>}</p> <p>throw cnfe;</p> <p>}</p> <p>return c;</p> <p>}</p> <p>类的加载实际是调用pathList的findClass方法通过类名来找到一个类。而pathList是一个DexPathList 对象。</p> <p>private final DexPathList pathList;</p> <p>再来看看DexPathList 的findClass方法:</p> <p>public Class&gt; findClass(String name, List suppressed) {</p> <p>for (Element element : dexElements) {</p> <p>Class&gt; clazz = element.findClass(name, definingContext, suppressed);</p> <p>if (clazz != null) {</p> <p>return clazz;</p> <p>}</p> <p>}</p> <p>if (dexElementsSuppressedExceptions != null) {</p> <p>suppressed.addAll(Arrays.asList(dexElementsSuppressedExceptions));</p> <p>}</p> <p>return null;</p> <p>}</p> <p>/</p> <p>* Finds the named class in one of the dex files pointed at by</p> <p>* this instance. This will find the one in the earliest listed</p> <p>* path element. If the class is found but has not yet been</p> <p>* defined, then this method will define it in the defining</p> <p>* context that this instance was constructed with.</p> <p>*/</p> <p>public Class&gt; findClass(String name, List suppressed) {</p> <p>for (Element element : dexElements) {</p> <p>Class&gt; clazz = element.findClass(name, definingContext, suppressed);</p> <p>if (clazz != null) {</p> <p>return clazz;</p> <p>}</p> <p>}</p> <p>if (dexElementsSuppressedExceptions != null) {</p> <p>suppressed.addAll(Arrays.asList(dexElementsSuppressedExceptions));</p> <p>}</p> <p>return null;</p> <p>}</p> <p>按照注释的意思就是从一个dex文件中找到class。</p> <p>通过遍历dexElements,由此可见一个Element 对应一个dex。</p> <p>果然dexElements的注释也证明了。</p> <p>/</p> <p>* List of dex/resource (class path) elements.</p> <p>* Should be called pathElements, but the Facebook app uses reflection</p> <p>* to modify 'dexElements' (http://b/).</p> <p>*/</p> <p>private Element[] dexElements;</p> <p>所以需要把我们生成的dex加入到dexElements这个数组中就能实现多dex的加载</p> <p>先来看看怎么生成dexElements。</p> <p>public DexPathList(ClassLoader definingContext, String dexPath,</p> <p>String librarySearchPath, File optimizedDirectory) {</p> <p>.......</p> <p>this.dexElements = makeDexElements(splitDexPath(dexPath), optimizedDirectory,</p> <p>suppressedExceptions, definingContext);</p> <p>......</p> <p>注意:这里是DexPathList的四个参数的构造方法,而不是两个参数的构造方法,两个参数的构造方法,是加载内存中的dexFile。7.0一下源码没有两个参数的构造方法。</p> <p>所以我们可以通过反射调用makeDexElements来帮我们生成dexElements。7.0以上都是一样,但是4.4到5.x参数不一样,6.x源码这个方法名就不一样了。</p> <p>6.x</p> <p>public DexPathList(ClassLoader definingContext, String dexPath,</p> <p>String libraryPath, File optimizedDirectory) {</p> <p>......</p> <p>this.dexElements = makePathElements(splitDexPath(dexPath), optimizedDirectory,</p> <p>suppressedExceptions);</p> <p>......</p> <p>4.4-5.x</p> <p>public DexPathList(ClassLoader definingContext, String dexPath,</p> <p>String libraryPath, File optimizedDirectory) {</p> <p>this.dexElements = makeDexElements(splitDexPath(dexPath), optimizedDirectory,</p> <p>suppressedExceptions);</p> <p>4.4-5.x虽然也是makeDexElements但是只有三个参数。</p> <p>编译openSSL</p> <p>加密需要openSSL,所以首先先编译一个openSSL。</p> <p>wget https://wwwhttp://www.360doc.com/content/12/0121/07/source/openssl-1.1.1b.tar.gz</p> <p>解压之后编译。官网没有编译教程,github上也没有编译教程</p> <p>还好有个wiki</p> <p>https://wikihttp://www.360doc.com/content/12/0121/07/index.php/Android</p> <p>把setenv-android.sh下载下来,并全部复制。</p> <p>openSSL源码目录下创建一个build.sh,复制setenv-android.sh中的代码。我的NDK 是r17b版本,然后修改其中的配置。</p> <p>_ANDROID_EABI='arm-linux-androideabi-4.9'</p> <p>源码中是4.8,根据ndk版本来设置,我的ndk中是4.9。</p> <p>添加ndk根目录</p> <p>export ANDROID_NDK_HOME=/ndk/android-ndk-r17b</p> <p>export ANDROID_NDK_ROOT=/ndk/android-ndk-r17b</p> <p>如果不设置ANDROID_NDK_HOME会报ANDROID_NDK_HOME未定义的错误。</p> <p>设置了 ANDROID_NDK_ROOT之后</p> <p align='center'></p> <p>这个配置就不用管了。</p> <p>这些都是配置,还需要编译模块</p> <p align='center'></p> <p>完整的编译脚本在demo中。Dex加密下</p> </td> </tr> </tbody> 

讯享网
小讯
上一篇 2025-05-04 17:40
下一篇 2025-06-07 07:55

相关推荐

版权声明:本文内容由互联网用户自发贡献,该文观点仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容,请联系我们,一经查实,本站将立刻删除。
如需转载请保留出处:https://51itzy.com/kjqy/143594.html