APK反逆向之二:四种基本加固方式

近些年来移动 APP 数量呈现爆炸式的增加,黑产也从原来的PC端移到了移动端,伴随而来的逆向攻击手段也愈来愈高明。本篇章主要介绍应用加固的最基础的四种方式:1.proguard 混淆 2.签名比对验证 3.ndk 编译 .so 动态库 4.代码动态加载php

原文地址:APK反逆向之二:四种基本加固方式html

0x00 简介

应该大多数开发者都不会关注应用会不逆向破解,并且如今有第三方厂商提供免费的加固方案,因此 apk 应用的安全性就所有依赖于第三方。可是若是第三方加固方案被破解那么 apk 就陷于被动,因此咱们也能够经过一些手段来加固应用自己逻辑,或者数据的加密。java

最多见的加固就是 proguard 混淆,经过混淆能够加大逻辑分析的难度。对于数据的加密经过 ndk 开发动态库基本上能够防止大部分的破解者,逆向 native 跟逆向 java 不是一个级别的。android

下面介绍的四种加固方式成本比较低,开发者能够很快开发完成,而且也不会影响应用的兼容性。git

0x01 proguard混淆

简介

ProGuard 是一个 SourceForge 上知名的开源项目。官网网址是:http://proguard.sourceforge.net/github

java 编译后的字节码很容易被反编译,基本上 java 编译器均可以好比 eclipse、idea,还有用的比较多的 JD-GUI。均可以将 java 字节码转化为 java 源码。ProGuard的主要做用就是混淆。固然它还能对字节码进行缩减体积、优化等,但那些对于咱们来讲都算是次要的功能。算法

ProGuard is a free Java class file shrinker, optimizer, obfuscator, and preverifier. It detects and removes unused classes, fields, methods, and attributes. It optimizes bytecode and removes unused instructions. It renames the remaining classes, fields, and methods using short meaningless names. The resulting applications and libraries are smaller, faster, and a bit better hardened against reverse engineering.数组

Android 配置

Android 2.3 之后 SDK 中已经默认支持 ProGuard 混淆,具体配置文件在 sdk-root/tools/proguard/proguard-android.txt缓存

SDK 内置 ProGuard 混淆文件:安全

# This is a configuration file for ProGuard.
# http://proguard.sourceforge.net/index.html#manual/usage.html

-dontusemixedcaseclassnames
-dontskipnonpubliclibraryclasses
-verbose

# Optimization is turned off by default. Dex does not like code run
# through the ProGuard optimize and preverify steps (and performs some
# of these optimizations on its own).
-dontoptimize
-dontpreverify
# Note that if you want to enable optimization, you cannot just
# include optimization flags in your own project configuration file;
# instead you will need to point to the
# "proguard-android-optimize.txt" file instead of this one from your
# project.properties file.

-keepattributes *Annotation*
-keep public class com.google.vending.licensing.ILicensingService
-keep public class com.android.vending.licensing.ILicensingService

# For native methods, see http://proguard.sourceforge.net/manual/examples.html#native
-keepclasseswithmembernames class * {
    native <methods>;
}

# keep setters in Views so that animations can still work.
# see http://proguard.sourceforge.net/manual/examples.html#beans
-keepclassmembers public class * extends android.view.View {
   void set*(***);
   *** get*();
}

# We want to keep methods in Activity that could be used in the XML attribute onClick
-keepclassmembers class * extends android.app.Activity {
   public void *(android.view.View);
}

# For enumeration classes, see http://proguard.sourceforge.net/manual/examples.html#enumerations
-keepclassmembers enum * {
    public static **[] values();
    public static ** valueOf(java.lang.String);
}

-keepclassmembers class * implements android.os.Parcelable {
  public static final android.os.Parcelable$Creator CREATOR;
}

-keepclassmembers class **.R$* {
    public static <fields>;
}

# The support library contains references to newer platform versions.
# Don't warn about those in case this app is linking against an older
# platform version.  We know about them, and they are safe.
-dontwarn android.support.**

# Understand the @Keep support annotation.
-keep class android.support.annotation.Keep

-keep @android.support.annotation.Keep class * {*;}

-keepclasseswithmembers class * {
    @android.support.annotation.Keep <methods>;
}

-keepclasseswithmembers class * {
    @android.support.annotation.Keep <fields>;
}

-keepclasseswithmembers class * {
    @android.support.annotation.Keep <init>(...);
}

在 Android Studio 中构建新项目时会在每一个 module 下生成 proguard-rules.pro 这里能够配置一些自定义的 ProGuard 规则。在 build.gradle 中配置是否启动混淆配置, 通常状况下编译 release 版本会启用混淆,debug 版本关闭:

buildTypes {
    release {
        signingConfig signingConfigs.tdSignConf
        minifyEnabled true
        proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
    }
    debug{
        signingConfig signingConfigs.tdSignConf
        minifyEnabled false
    }
}

生成文件

在Android Studio 中启用 ProGuard 混淆会在/app/build/outputs/mapping/release下生成下面四个文件:

  • mapping.txt —> 表示混淆先后代码的对照表

  • dump.txt —> 描述apk内全部class文件的内部结构

  • seeds.txt —> 列出了没有被混淆的类和成员

  • usage.txt —> 列出了源代码中被删除在apk中不存在的代码

mapping.txt 文件很是重要。若是你的代码混淆后会产生bug的话,log提示中是混淆后的代码,但愿定位到源代码的话就能够根据mapping.txt反推。每次发布都要保留它方便该版本出现问题时调出日志进行排查,它能够根据版本号或是发布时间命名来保存或是放进代码版本控制中。

若是是手动 ProGuard 混淆能够在配置文件中添加 -printmapping选项。

异常堆栈还原

混淆后代码变量、函数名、类名、都已经变成了随机的字母,crash信息也就看不错在什么地方。因此就须要上面提到 mapping.txt 保存了混淆先后的代码对照表。

例若有个错误日志:

Caused by: java.lang.NullPointerException
at cc.gnaixx.be.u(Unknown Source)
at cc.gnaixx.at.v(Unknown Source)
at cc.gnaixx.at.d(Unknown Source)
at cc.gnaixx.av.onReceive(Unknown Source)

这个日志中咱们根本没法排查错误在哪一行,可是 ProGuard 提供了一个还原工具,工具在proguard/bin/retrace.sh
执行:

#crash.txt 为错误栈文件
retrace.sh -verbose mapping.txt crash.txt

执行后的效果:

Caused by: java.lang.NullPointerException
at cc.gnaixx.UtilTelephony.boolean is800MhzNetwork()(Unknown Source)
at cc.gnaixx.ServiceDetectLte.void checkAndAlertUserIf800MhzConnected()(Unknown Source)
at cc.gnaixx.ServiceDetectLte.void startLocalBroadcastReceiver()(Unknown Source)
at cc.gnaixx.ServiceDetectLte$2.void onReceive(android.content.Context,android.content.Intent)(Unknown Source)

这里须要注意一点若是你想查单个错误信息,必须在错误信息前加 at, 否则没法找出错误位置。

配置规则

基本选项

-include {filename}                   从给定的文件中读取配置参数
-basedirectory {directoryname}        指定基础目录为之后相对的档案名称 
-injars {class_path}                  指定要处理的应用程序jar,war,ear和目录   
-outjars {class_path}                 指定处理完后要输出的jar,war,ear和目录的名称   
-libraryjars {classpath}              指定要处理的应用程序jar,war,ear和目录所须要的程序库文件   
-dontskipnonpubliclibraryclasses      指定不去忽略非公共的库类。   
-dontskipnonpubliclibraryclassmembers 指定不去忽略包可见的库类的成员。

保留选项

-keep {Modifier} {class_specification}                保护指定的类文件和类的成员   
-keepclassmembers {modifier} {class_specification}    保护指定类的成员,若是此类受到保护他们会保护的更好  
-keepclasseswithmembers {class_specification}         保护指定的类和类的成员,但条件是全部指定的类和类成员是要存在。   
-keepnames {class_specification}                      保护指定的类和类的成员的名称(若是他们不会压缩步骤中删除)   
-keepclassmembernames {class_specification}           保护指定的类的成员的名称(若是他们不会压缩步骤中删除)   
-keepclasseswithmembernames {class_specification}     保护指定的类和类的成员的名称,若是全部指定的类成员出席(在压缩步骤以后)   
-printseeds {filename}                                列出类和类的成员-keep选项的清单,标准输出到给定的文件

压缩

-dontshrink    不压缩输入的类文件   
-printusage {filename}   
-whyareyoukeeping {class_specification}

优化

-dontoptimize                                 不优化输入的类文件   
-assumenosideeffects {class_specification}    优化时假设指定的方法,没有任何反作用   
-allowaccessmodification                      优化时容许访问并修改有修饰符的类和类的成员

混淆

-dontobfuscate                             不混淆输入的类文件   
-printmapping {filename}                   混淆先后代码的对照表
-applymapping {filename}                   重用映射增长混淆   
-obfuscationdictionary {filename}          使用给定文件中的关键字做为要混淆方法的名称   
-overloadaggressively                      混淆时应用侵入式重载   
-useuniqueclassmembernames                 肯定统一的混淆类的成员名称来增长混淆   
-flattenpackagehierarchy {package_name}    从新包装全部重命名的包并放在给定的单一包中   
-repackageclass {package_name}             从新包装全部重命名的类文件中放在给定的单一包中   
-dontusemixedcaseclassnames                混淆时不会产生形形色色的类名   
-keepattributes {attribute_name,...}       保护给定的可选属性,例如LineNumberTable, LocalVariableTable, SourceFile, Deprecated, Synthetic, Signature, InnerClasses.   
-renamesourcefileattribute {string}        设置源文件中给定的字符串常量

使用总结

下面这些规则主要是本身在使用过程当中遇到过的问题

# 忽略指点错误,减小打印日志,由于我是针对jar作混淆,打印一堆没用日志有点烦
-dontnote java.**, javax.**, org.**

# 日志打印类名从新定义为"ProGuard"字符串
-renamesourcefileattribute ProGuard

# 保留源文件名,保留行号,保留annotation
-keepattributes SourceFile, LineNumberTable, *Annotation*

# 保持枚举 enum 类不被混淆
-keepclassmembers enum * {                    
    public static **[] values();  
    public static ** valueOf(java.lang.String);  
} 

# 保存内部类不被混淆
-keep class cc.gnaixx.TestClass$* {*;}

0x02 签名比对验证

简介

Android APK的发布是须要签名的。签名机制在Android应用和框架中有着十分重要的做用。

例如,Android系统禁止更新安装签名不一致的APK;若是应用须要使用system权限,必须保证APK签名与Framework签名一致,等等。要破解一个APK,必然须要从新对APK进行签名。而这个签名,通常状况没法再与APK原先的签名保持一致。(除非APK原做者的私钥泄漏)简单地说,签名机制标明了APK的发行机构。

Android签名机制

为了说明APK签名比对对软件安全的有效性,咱们有必要了解一下Android APK的签名机制。为了更易于你们理解,咱们从Auto-Sign工具的一条批处理命令提及。

要签名一个没有签名过的APK,可使用一个叫做auto-sign的工具。auto-sign工具实现签名功能的命令主要是这一行命令:

java -jar signapk.jar testkey.x509.pem testkey.pk8 update.apk update_signed.apk

这条命令的意义是:经过signapk.jar这个可执行jar包,以“testkey.x509.pem”这个公钥文件和“testkey.pk8”这个私钥文件对“update.apk”进行签名,签名后的文件保存为“update_signed.apk”。

对于此处所使用的私钥和公钥的生成方式,这里就不作进一步介绍了。这方面的资料你们能够找到不少。咱们这里要讲的是signapk.jar到底作了什么。

signapk.jar是Android源码包中的一个签名工具。因为Android是个开源项目,因此,很高兴地,咱们能够直接找到signapk.jar的源码!路径为/build/tools/signapk/SignApk.java。

对比一个没有签名的APK和一个签名好的APK,咱们会发现,签名好的APK包中多了一个叫作META-INF的文件夹。里面有三个文件,分别名为MANIFEST.MF、CERT.SF和CERT.RSA。signapk.jar就是生成了这几个文件(其余文件没有任何改变。所以咱们能够很容易去掉原有签名信息)。

经过阅读signapk源码,咱们能够理清签名APK包的整个过程。

一、 生成MANIFEST.MF文件:

程序遍历update.apk包中的全部文件(entry),对非文件夹非签名文件的文件,逐个生成SHA1的数字签名信息,再用Base64进行编码。具体代码见这个方法:

private static Manifest addDigestsToManifest(JarFile jar)

//关键代码以下:
for (JarEntry entry: byName.values()) {
  String name = entry.getName();
  if (!entry.isDirectory() && !name.equals(JarFile.MANIFEST_NAME) &&
      !name.equals(CERT_SF_NAME) && !name.equals(CERT_RSA_NAME) &&
             (stripPattern == null ||!stripPattern.matcher(name).matches())) {
                
    InputStream data = jar.getInputStream(entry);
                while ((num = data.read(buffer)) > 0) {
                    md.update(buffer, 0, num);
                }
                Attributes attr = null;
                if (input != null) attr = input.getAttributes(name);
                attr = attr != null ? new Attributes(attr) : new Attributes();
                attr.putValue("SHA1-Digest", base64.encode(md.digest()));
                output.getEntries().put(name, attr);
    }
}

以后将生成的签名写入MANIFEST.MF文件。关键代码以下:

Manifest manifest = addDigestsToManifest(inputJar);
je = new JarEntry(JarFile.MANIFEST_NAME);
je.setTime(timestamp);
outputJar.putNextEntry(je);
manifest.write(outputJar);

这里简单介绍下SHA1数字签名。简单地说,它就是一种安全哈希算法,相似于MD5算法。它把任意长度的输入,经过散列算法变成固定长度的输出(这里咱们称做“摘要信息”)。你不能仅经过这个摘要信息复原原来的信息。另外,它保证不一样信息的摘要信息彼此不一样。所以,若是你改变了apk包中的文件,那么在apk安装校验时,改变后的文件摘要信息与MANIFEST.MF的检验信息不一样,因而程序就不能成功安装。

二、生成CERT.SF文件:

对前一步生成的Manifest,使用SHA1-RSA算法,用私钥进行签名。关键代码以下:

Signature signature = Signature.getInstance("SHA1withRSA");
signature.initSign(privateKey);
je = new JarEntry(CERT_SF_NAME);
je.setTime(timestamp);
outputJar.putNextEntry(je);
writeSignatureFile(manifest,
new SignatureOutputStream(outputJar, signature));

RSA是一种非对称加密算法。用私钥经过RSA算法对摘要信息进行加密。在安装时只能使用公钥才能解密它。解密以后,将它与未加密的摘要信息进行对比,若是相符,则代表内容没有被异常修改。

三、生成CERT.RSA文件:

生成MANIFEST.MF没有使用密钥信息,生成CERT.SF文件使用了私钥文件。那么咱们能够很容易猜想到,CERT.RSA文件的生成确定和公钥相关。

CERT.RSA文件中保存了公钥、所采用的加密算法等信息。核心代码以下:

je = new JarEntry(CERT_RSA_NAME);
je.setTime(timestamp);
outputJar.putNextEntry(je);
writeSignatureBlock(signature, publicKey, outputJar);

//其中writeSignatureBlock的代码以下:
private static void writeSignatureBlock(
      Signature signature, X509Certificate publicKey, OutputStream out)
          throws IOException, GeneralSecurityException {
            SignerInfo signerInfo = new SignerInfo(
                new X500Name(publicKey.getIssuerX500Principal().getName()),
                publicKey.getSerialNumber(),
                AlgorithmId.get("SHA1"),
                AlgorithmId.get("RSA"),
                signature.sign());

        PKCS7 pkcs7 = new PKCS7(
          new AlgorithmId[] { AlgorithmId.get("SHA1") },
            new ContentInfo(ContentInfo.DATA_OID, null),
            new X509Certificate[] { publicKey },
            new SignerInfo[] { signerInfo });

        pkcs7.encodeSignedData(out);
}

分析完APK包的签名流程,咱们能够清楚地意识到:

  1. Android签名机制实际上是对APK包完整性和发布机构惟一性的一种校验机制。

  2. Android签名机制不能阻止APK包被修改,但修改后的再签名没法与原先的签名保持一致。(拥有私钥的状况除外)。

  3. APK包加密的公钥就打包在APK包内,且不一样的私钥对应不一样的公钥。换句话言之,不一样的私钥签名的APK公钥也必不相同。因此咱们能够根据公钥的对比,来判断私钥是否一致。

APK签名比对的实现方式

具体的实现方式在以前的一篇博客中有进行了介绍,实现方式其实很简单:

APK自我保护 - DEX/APK/证书校验

0x03 ndk 编译.so 动态库

简介

逆向 ndk 跟逆向 java 代码难度真不是在一个等级上的,ndk 开发除了安全性相对 java 开发高不少,并且在运行效率也高不少。

因此 NDK 开发相对于 JAVA 开发的优点:

1.代码的保护。因为apk的java层代码很容易被反编译,而C/C++库反汇难度较大。

2.能够方便地使用现存的开源库。大部分现存的开源库都是用C/C++代码编写的。

3.提升程序的执行效率。将要求高性能的应用逻辑使用C开发,从而提升应用程序的执行效率。

4.便于移植。用C/C++写得库能够方便在其余的嵌入式平台上再次使用。

可是 NDK 的兼容性相对 JAVA 差不少,因此在进行 NDK 开发的时候兼容性是一个很大的问题。

教程

以前写了几篇关于 NDK 开发的文章基本覆盖了 NDK 开发的全部内容了。

这些教程都是基于 experimental 版本的 gradle 进行开发,可是这种版本的配置相对于普通版本的 gradle 有不少不一样。而且建立一个新的 module 都须要更新 build.gradle 配置很是麻烦。

最近 Android Studio 更新到了2.2 版本,能够支持两种方式的 NDK开发:

1. ndk-build 开发

这种方式很是简单只须要在 build.gradle 添加 ndk 编译配置:

externalNativeBuild {
     ndkBuild {
         path 'src/main/jni/Android.mk'
     }
 }

2. cmake 开发

先贴出官方文档,之后再介绍。

官网文档:
Add C and C++ Code to Your Project

0x04 代码动态加载

简介

这篇文章是在看雪上看到的对动态加载的原理介绍的比较清楚,大多数应用也是采用这种方式对 dex 文件进行加密处理。

熟悉Java技术的朋友,可能意识到,咱们须要使用类加载器灵活的加载执行的类。这在Java里已经算是一项比较成熟的技术了,可是在Android中,咱们大多数人都还很是陌生。

类加载机制

Dalvik虚拟机如同其余Java虚拟机同样,在运行程序时首先须要将对应的类加载到内存中。而在Java标准的虚拟机中,类加载能够从class文件中读取,也能够是其余形式的二进制流。所以,咱们经常利用这一点,在程序运行时手动加载Class,从而达到代码动态加载执行的目的。

然而Dalvik虚拟机毕竟不算是标准的Java虚拟机,所以在类加载机制上,它们有相同的地方,也有不一样之处。咱们必须区别对待。

例如,在使用标准Java虚拟机时,咱们常常自定义继承自ClassLoader的类加载器。而后经过defineClass方法来从一个二进制流中加载Class。然而,这在Android里是行不通的,你们就不必走弯路了。参看源码咱们知道,Android中ClassLoader的defineClass方法具体是调用VMClassLoader的defineClass本地静态方法。而这个本地方法除了抛出一个“UnsupportedOperationException”以外,什么都没作,甚至连返回值都为空。

static void Dalvik_java_lang_VMClassLoader_defineClass(const u4* args, JValue* pResult)
{

    Object* loader = (Object*) args[0];
    StringObject* nameObj = (StringObject*) args[1];
    const u1* data = (const u1*) args[2];
    int offset = args[3];
    int len = args[4];

    Object* pd = (Object*) args[5];
    char* name = NULL;

    name = dvmCreateCstrFromString(nameObj);
    LOGE("ERROR: defineClass(%p, %s, %p, %d, %d, %p)\n", loader, name, data, offset, len, pd);

    dvmThrowException("Ljava/lang/UnsupportedOperationException;", "can't load this type of class file");

    free(name);
    RETURN_VOID();
}

Dalvik虚拟机类加载机制

那若是在Dalvik虚拟机里,ClassLoader很差使,咱们如何实现动态加载类呢?Android为咱们从ClassLoader派生出了两个类:DexClassLoader和PathClassLoader。其中须要特别说明的是PathClassLoader中一段被注释掉的代码:

/**//* --this doesn't work in current version of Dalvik--

    if (data != null) {

        System.out.println("--- Found class " + name + " in zip[" + i + "] '" + mZips[i].getName() + "'");

        int dotIndex = name.lastIndexOf('.');
        if (dotIndex != -1) {

            String packageName = name.substring(0, dotIndex);

            synchronized (this) {

                Package packageObj = getPackage(packageName);

                if (packageObj == null) {
                    definePackage(packageName, null, null, null, null, null, null, null);
                }
            }
        }

        return defineClass(name, data, 0, data.length);
    }
*/

这从另外一方面佐证了defineClass函数在Dalvik虚拟机里确实是被阉割了。而在这两个继承自ClassLoader的类加载器,本质上是重载了ClassLoader的findClass方法。在执行loadClass时,咱们能够参看ClassLoader部分源码:

protected Class<?> loadClass(String className, boolean resolve) throws ClassNotFoundException {

    Class<?> clazz = findLoadedClass(className);

    if (clazz == null) {

        try {
            clazz = parent.loadClass(className, false);
        } catch (ClassNotFoundException e) {
            // Don't want to see this.
        }

        if (clazz == null) {
            clazz = findClass(className);
        }
    }

    return clazz;
}

所以DexClassLoader和PathClassLoader都属于符合双亲委派模型的类加载器(由于它们没有重载loadClass方法)。也就是说,它们在加载一个类以前,回去检查本身以及本身以上的类加载器是否已经加载了这个类。若是已经加载过了,就会直接将之返回,而不会重复加载。

DexClassLoader和PathClassLoader其实都是经过DexFile这个类来实现类加载的。这里须要顺便提一下的是,Dalvik虚拟机识别的是dex文件,而不是class文件。所以,咱们供类加载的文件也只能是dex文件,或者包含有dex文件的.apk或.jar文件。

也许有人想到,既然DexFile能够直接加载类,那么咱们为何还要使用ClassLoader的子类呢?DexFile在加载类时,具体是调用成员方法loadClass或者loadClassBinaryName。其中loadClassBinaryName须要将包含包名的类名中的”.”转换为”/”。咱们看一下loadClass代码就清楚了:

public Class loadClass(String name, ClassLoader loader) {

        String slashName = name.replace('.', '/');
        return loadClassBinaryName(slashName, loader);
}

在这段代码前有一段注释,截取关键一部分就是说:If you are not calling this from a class loader, this is most likely not going to do what you want. Use {@link Class#forName(String)} instead. 这就是咱们须要使用ClassLoader子类的缘由。至于它是如何验证是不是在ClassLoader中调用此方法的,我没有研究,你们若是有兴趣能够继续深刻下去。

有一个细节,可能你们不容易注意到。PathClassLoader是经过构造函数new DexFile(path)来产生DexFile对象的;而DexClassLoader则是经过其静态方法loadDex(path, outpath, 0)获得DexFile对象。这二者的区别在于DexClassLoader须要提供一个可写的outpath路径,用来释放.apk包或者.jar包中的dex文件。换个说法来讲,就是PathClassLoader不能主动从zip包中释放出dex,所以只支持直接操做dex格式文件,或者已经安装的apk(由于已经安装的apk在cache中存在缓存的dex文件)。而DexClassLoader能够支持.apk、.jar和.dex文件,而且会在指定的outpath路径释放出dex文件。

另外,PathClassLoader在加载类时调用的是DexFile的loadClassBinaryName,而DexClassLoader调用的是loadClass。所以,在使用PathClassLoader时类全名须要用”/”替换”.”。

实际操做

这一部分比较简单,所以我就不赘言了。只是简单的说下。

可能使用到的工具都比较常规:javac、dx、eclipse等。其中dx工具最好是指明--no-strict,由于class文件的路径可能不匹配。

加载好类后,一般咱们能够经过Java反射机制来使用这个类。可是这样效率相对不高,并且老用反射代码也比较复杂凌乱。更好的作法是定义一个interface,并将这个interface写进容器端。待加载的类,继承自这个interface,而且有一个参数为空的构造函数,以使咱们可以经过Class的newInstance方法产生对象。而后将对象强制转换为interface对象,因而就能够直接调用成员方法了。

关于代码加密的一些设想

最初设想将dex文件加密,而后经过JNI将解密代码写在Native层。解密以后直接传上二进制流,再经过defineClass将类加载到内存中。

如今也能够这样作,可是因为不能直接使用defineClass,而必须传文件路径给dalvik虚拟机内核,所以解密后的文件须要写到磁盘上,增长了被破解的风险。

Dalvik虚拟机内核仅支持从dex文件加载类的方式是不灵活的,因为没有很是深刻的研究内核,我不能肯定是Dalvik虚拟机自己不支持仍是Android在移植时将其阉割了。不过相信Dalvik或者是Android开源项目都正在向可以支持raw数据定义类方向努力。

咱们能够在文档中看到Google说:Jar or APK file with "classes.dex". (May expand this to include "raw DEX" in the future.);在Android的Dalvik源码中咱们也能看到RawDexFile的身影(不过没有具体实现)。

在RawDexFile出来以前,咱们都只能使用这种存在必定风险的加密方式。须要注意释放的dex文件路径及权限管理,另外,在加载完毕类以后,除非出于其余目的不然应该立刻删除临时的解密文件

总结

目前的方式若是被识别后破解仍是相对比较简单的,即便加密后的 dex 文件也须要在 load 前进行解密操做,而且须要把文件缓存在本地。因此只须要在合适的地方下个断点就能够把加密的 dex 拷贝下来。

可是咱们能够经过其余方式对 dex 进行加密,好比隐藏函数。这样即便拿到了 dex 也没法经过逆向工具查看源码。

参考文章:

相关文章
相关标签/搜索