阅读更多

0顶
0踩

编程语言

转载新闻 Android 混淆那些事儿

2017-06-29 14:56 by 副主编 jihong10102006 评论(0) 有16633人浏览
本文主要讲述了代码混淆和资源混淆的原理,Studio默认的混淆方案,混淆的参数,以及如何对Apk进行代码混淆(自定义混淆文件)和资源混淆(结合微信混淆和美团混淆两种方案),避免Apk被逆向。

为什么要混淆

我们的apk在打包发布之前,都要进行混淆处理来避免源代码和资源文件被小白用户通过反编译拿到。未混淆代码的反编译操作非常简单,网上有很多教程, 也可以通过使用Android Studio自带的apk分析工具(Build—Analyze APK)直接看到未混淆Apk的源代码和原始的资源文件。对比图如下,从图中可以看到未混淆apk所有的代码都一目了然,随便改改资源和代码,就能变成一个新的apk。为了避免我们的劳动成果被窃取,也避免出现安全漏洞和隐患,此篇文章从混淆的原理到代码和资源文件的混淆实践做一下阐述。

混淆前:

混淆后:

混淆的原理

Java 是一种跨平台、解释型语言,Java 源代码编译成的class文件中有大量包含语义的变量名、方法名的信息,很容易被反编译为Java 源代码。为了防止这种现象,我们可以对Java字节码进行混淆。混淆不仅能将代码中的类名、字段、方法名变为无意义的名称,保护代码,也由于移除无用的类、方法,并使用简短名称对类、字段、方法进行重命名缩小了程序的size。

ProGuard由shrink、optimize、obfuscate和preverify四个步骤组成,每个步骤都是可选的,需要哪些步骤都可以在脚本中配置。 参见ProGuard官方介绍
  • 压缩(Shrink): 侦测并移除代码中无用的类、字段、方法、和特性(Attribute)。
  • 优化(Optimize): 分析和优化字节码。
  • 混淆(Obfuscate): 使用a、b、c、d这样简短而无意义的名称,对类、字段和方法进行重命名。
上面三个步骤使代码size更小,更高效,也更难被逆向工程。
  • 预检(Preveirfy): 在java平台上对处理后的代码进行预检。
混淆流程图如下:

Proguard读入input jars(or wars,zips or directories),经过四个步骤生成处理之后的jars(or wars,ears,zips or directories),Optimization步骤可选择多次进行。

为了确定哪些代码应该被保留,哪些代码应该被移除或混淆,需要确定一个或多个Entry Point。Entry Point经常是带有main methods,applets,midlets的classes,它们在混淆过程中会被保留。我们来看一下Proguard的几个步骤如何处理Entry Points。
  • 在压缩阶段,Proguard从上述Entry Points开始遍历搜索哪些类和类成员被使用。其他没有被使用的类和类成员会移除。
  • 在优化阶段,Proguard进一步设置非Entry Point的类和方法为private、static和final来进行优化,不使用的参数会被移除,某些方法会被标记被内联。
  • 在混淆阶段,Proguard重命名非Entry Points的类和类成员。
  • 预检阶段是唯一没有触及Entry Points的阶段。
Android Studio 默认的混淆方案及字段解读

开启混淆

参见google官方文档压缩代码和资源

要通过Proguard启动代码压缩,在build.gradle文件内相应的构建类型中添加minifyEnabled true。
android {
    buildTypes {
        release {
            minifyEnabled true
            proguardFiles getDefaultProguardFile('proguard-android.txt'),
                    'proguard-rules.pro'
        }
    }
    ...
}

除了 minifyEnabled 属性外,还有用于定义 ProGuard 规则的 proguardFiles 属性:
proguardFiles getDefaultProguardFile('proguard-android.txt'),
                    'proguard-rules.pro'

google的官方文档介绍:

getDefaultProguardFile(‘proguard-android.txt’) 方法可从 Android SDK tools/proguard/ 文件夹获取默认的 ProGuard 设置。要想做进一步的代码压缩,请尝试使用位于同一位置的 proguard-android-optimize.txt 文件。它包括相同的 ProGuard 规则,但还包括其他在字节码一级(方法内和方法间)执行分析的优化,以进一步减小 APK 大小和帮助提高其运行速度。

proguard-rules.pro 文件用于添加自定义 ProGuard 规则。默认情况下,该文件位于模块根目录(build.gradle 文件旁),内容为空。

通过试验,gradle 2.2之后,defaultProguardFile没有使用sdk目录下的proguard-android.txt,而是使用了gradle自带的proguard-android.txt,不同的gradle版本带有不同的默认混淆文件,在项目根目录的build/intermediates/proguard-files/proguard-android.txt-2.3.3(笔者用的gradle版本)即为gradle自带的混淆文件。在proguard-android.txt-2.3.3文件中也写有说明,gradle 2.2之后自带混淆文件:
引用
Starting with version 2.2 of the Android plugin for Gradle, this file is distributed together with
the plugin and unpacked at build-time. The files in $ANDROID_HOME are no longer maintained and
will be ignored by new version of the Android plugin for Gradle.

构建输出

构建时Proguard都会输出下列文件:

(1)dump.txt — 说明APK中所有类文件的内部结构
(2)mapping.txt — 提供原始与混淆过的类、方法和字段名称之间的转换
(3)seeds.txt — 列出未进行混淆的类和成员
(4)usage.txt — 列出从APK移除的代码

这些文件保存在/build/outputs/mapping/release目录下。

解码混淆过的堆叠追踪

使用混淆后,一定要保存好mapping文件,程序csh时通过脚本进行解码。
retrace工具位于/tools/proguard/目录中,解码命令为:
retrace.bat|retrace.sh [-verbose] mapping.txt [<stacktrace_file>] 

例如mac平台下:
retrace.sh -verbose mapping.txt obfuscated_trace.txt

默认的混淆方案及字段解读

下面结合默认混淆文件中的内容来解释混淆的参数: 参见Proguard官方字段解读
不使用大小写混写类名
-dontusemixedcaseclassnames

默认情况下混淆的类名可以包含大小写字符的混合。
不忽略公共类库
-dontskipnonpubliclibraryclasses

指定不去忽略非public的library classes。从Proguard 4.5开始,是默认的设置。
-dontoptimize
-dontpreverify

默认optimize和preverify选项是关闭的,因为Android的dex并不像Java虚拟机需要optimize(优化)和previrify(预检)两个步骤。
指定哪个属性不要混淆,可一次指定多个属性
-keepattributes [attribute_filter]

通常Exceptions, Signature, Deprecated, SourceFile, SourceDir, LineNumberTable, LocalVariableTable, LocalVariableTypeTable, Synthetic, EnclosingMethod, RuntimeVisibleAnnotations, RuntimeInvisibleAnnotations, RuntimeVisibleParameterAnnotations, RuntimeInvisibleParameterAnnotations, and AnnotationDefault属性需要被保留,根据项目具体使用情况保留。

这里需要特别注意的一点是,gradle默认的keepattributes属性不全,只保留了Annotation,Signature,InnerClasses,EnclosingMethod,为了混淆之后定位csh代码方便,我们需要在proguard_rules.pro中手动添加抛出异常时保留代码行号,并且重命名抛出异常时的文件名称,这样能方便定位问题:
抛出异常时保留代码行号
-keepattributes SourceFile,LineNumberTable

重命名抛出异常时的文件名称
-renamesourcefileattribute SourceFile

keep选项非常重要,keep指定了哪些类,哪些方法不被混淆,从而保证了程序的正常运行。官方的keep用法有6种:

左边不带names的选项为From being removed or renames,即不会被移除或重命名,即使类或类成员未被使用。带有names的选项为From being renamed,不会被重命名,如果是无用的类或类成员,会被移除。

(1)-keep(names)选项 指定类和类成员(变量和方法)不被混淆
-keep [,modifier,...] class_specification

eg.
指定类名不被改变
-keep public class com.google.vending.licensing.ILicensingService

指定使用了Keep注解的类和类成员都不被改变
-keep @android.support.annotation.Keep class * {*;}

关于Keep注解的解释参见文末参考链接

(2)-keepclassmembers(names) 指定类成员不被混淆,类名会被混淆
-keepclassmembers [,modifier,...] class_specification

eg.keep setters in views 使得animations仍然能够工作
-keepclassmembers public class * extends android.view.View {
    void set*(***);
    *** get*();
}

(3)-keepclasseswithmembers(names) 指定类和类成员都不被混淆
-keepclasseswithmembers [,modifier,...] class_specification

eg.包含native方法的类名和native方法都不能被混淆,如果native方法未被调用,则被移除。由于native方法与对应so库中的方法名称对应,方法名被混淆会导致调用出现问题,所以native方法不能被混淆。
-keepclasseswithmembernames class * {
    native <methods>;
}

通用Options:
(1)-verbose 打印混淆详细信息

(2)-dontnote选项:指定不去输出打印该类产生的错误或遗漏
-dontnote com.android.vending.licensing.ILicensingService

-dontnote android.support.**

(3)-dontwarn选项:指定不去warn unresolved references和其他重要的problem
-dontwarn android.support.**

如上面(2)(3)所示,android.support的libraries需要保留

至此,gradle自带的proguard-android.txt文件相关字段已解析完毕。下面将介绍我们自定义的proguard-rules.pro文件需要添加什么参数。

自定义混淆文件

一般而言,我们会定义我们自己的proguard-rules.pro,下面列出自定义的一个proguard-rules.pro供大家参考。在看自定义的混淆文件之前,先讲解一下Filters和assumenosideeffects,以便更好地理解下面的指令。

(1)Filters
?   matches any single character in a name.(匹配一个字符)
*   matches any part of a name not containing the directory separator.(匹配一个名字,除了目录分隔符外的任意部分)
**  matches any part of a name, possibly containing any number of directory separators.(匹配任意名,可能包含任意路径分隔符)
!  exclude
<field>     匹配类中的所有字段
<method>    匹配类中所有的方法
<init>      匹配类中所有的构造函数

eg.
-keep class com.lily.test.** 本包和所包含子包下的类名都保持
-keep class com.lily.test.* 保持该包下的类名
-keep class com.lily.test.** {*;} 保持包和子包的类名和里面的内容均不被混淆

(2)-assumenosideeffects 指令: 下文会用在android log的移除上
assumeosideeffects是Optimization过程中的选项,所以为保证指令的有效,需要开启optimization。这个指令的含义是Proguard会在optimization过程中删除对这些方法的调用,需要注意:Only use this option if you know what you’re doing!

下面是自定义混淆文件的一个范例,四大组件,native方法,反射用到的类,一些引入的第三方库等都不能进行混淆:
# 代码混淆压缩比,在0~7之间
-optimizationpasses 5

# 混合时不使用大小写混合,混合后的类名为小写
-dontusemixedcaseclassnames

# 指定不去忽略非公共库的类
-dontskipnonpubliclibraryclasses

# 不做预校验,preverify是proguard的四个步骤之一,Android不需要preverify,去掉这一步能够加快混淆速度。
-dontpreverify

-verbose

#google推荐算法
-optimizations !code/simplification/arithmetic,!code/simplification/cast,!field/*,!class/merging/*

# 避免混淆Annotation、内部类、泛型、匿名类
-keepattributes *Annotation*,InnerClasses,Signature,EnclosingMethod

# 重命名抛出异常时的文件名称
-renamesourcefileattribute SourceFile

# 抛出异常时保留代码行号
-keepattributes SourceFile,LineNumberTable

# 处理support包
-dontnote android.support.**
-dontwarn android.support.**

# 保留四大组件,自定义的Application等这些类不被混淆
-keep public class * extends android.app.Activity
-keep public class * extends android.app.Appliction
-keep public class * extends android.app.Service
-keep public class * extends android.content.BroadcastReceiver
-keep public class * extends android.content.ContentProvider
-keep public class * extends android.preference.Preference
-keep public class com.android.vending.licensing.ILicensingService

# 保留本地native方法不被混淆
-keepclasseswithmembernames class * {
    native <methods>;
}

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

# 保留Parcelable序列化类不被混淆
-keep class * implements android.os.Parcelable {
    public static final android.os.Parcelable$Creator *;
}

#第三方jar包不被混淆
-keep class com.github.test.** {*;}

#保留自定义的Test类和类成员不被混淆
-keep class com.lily.Test {*;}
#保留自定义的xlog文件夹下面的类、类成员和方法不被混淆
-keep class com.test.xlog.** {
    <fields>;
    <methods>;
}

#assume no side effects:删除android.util.Log输出的日志
-assumenosideeffects class android.util.Log {
    public static *** v(...);
    public static *** d(...);
    public static *** i(...);
    public static *** w(...);
    public static *** e(...);
}

#保留Keep注解的类名和方法
-keep,allowobfuscation @interface android.support.annotation.Keep
-keep @android.support.annotation.Keep class *
-keepclassmembers class * {
    @android.support.annotation.Keep *;
}

资源文件的混淆:

上面讲述了如何进行代码混淆,再来讲讲如何对资源文件进行混淆。对资源文件进行混淆操作本质上是通过修改resources.arsc(参见文末链接详见resources.arsc作用及文件格式)。现针对两种资源混淆方案进行简要说明。第一种是微信的资源混淆方案,第二种是美团的资源混淆方案,两篇文章中都对原理进行了详细的阐述。
(1)微信的资源混淆方案:
微信的资源混淆是自己做了一个安装包解压并且用7z极限压缩打包器,修改的内容也是resources.arsc,优点是可以最大地混淆,不依赖源码与编译过程,无需在编译过程中修改源文件(java、xml、资源文件),无需改变Android打包流程。整体的流程如下:

使用微信的资源混淆方案有两种方法,第一种方式为修改gradle,第二种方式为直接使用命令行。下图为使用命令行最简单的方法生成资源混淆的apk,下载github工程后,进入tool_output文件夹,试验的apk为test.apk
java -jar AndResGuard-cli-1.2.3.jar test.apk

混淆过程中会输出log,混淆后会出现和apk同名的文件夹,里面包含了混淆后mapping的对应文件,新签名打包的apk和混淆后的资源文件目录。如下图所示:

混淆前资源文件:

混淆后资源文件:

可以看到资源文件的路径以及文件名都被混淆了。
(2)美团的资源混淆方案:
采用更改AAPT(Android Asset Packaging Tool)(参见文末链接详细解读AAPT)源码的方式,参考了Proguard Obfuscator,对APK中资源文件名使用简短无意义名称进行替换,如下面代码所示,在AAPT生成resources.arsc和*.ap*时把资源文件的名称进行替换。下面是美团修改后的Resource.cpp文件
static status_t makeFileResources(Bundle* bundle, const sp<AaptAssets>& assets,
                                      ResourceTable* table,
                                      const sp<ResourceTypeSet>& set,
                                      const char* resType)
    {
        String8 type8(resType);
        String16 type16(resType);

        bool hasErrors = false;

        ResourceDirIterator it(set, String8(resType));
        ssize_t res;
        while ((res=it.next()) == NO_ERROR) {
            if (bundle->getVerbose()) {
                printf("    (new resource id %s from %s)\n",
                       it.getBaseName().string(), it.getFile()->getPrintableSource().string());
            }
            String16 baseName(it.getBaseName());
            const char16_t* str = baseName.string();
            const char16_t* const end = str + baseName.size();
            while (str < end) {
                if (!((*str >= 'a' && *str <= 'z')
                        || (*str >= '0' && *str <= '9')
                        || *str == '_' || *str == '.')) {
                    fprintf(stderr, "%s: Invalid file name: must contain only [a-z0-9_.]\n",
                            it.getPath().string());
                    hasErrors = true;
                }
                str++;
            }
            String8 resPath = it.getPath();
            resPath.convertToResPath();

            String8 obfuscationName;
            String8 obfuscationPath = getObfuscationName(resPath, obfuscationName);

            table->addEntry(SourcePos(it.getPath(), 0), String16(assets->getPackage()),
                            type16,
                            baseName, // String16(obfuscationName),
                            String16(obfuscationPath), // resPath
                            NULL,
                            &it.getParams());
            assets->addResource(it.getLeafName(), obfuscationPath/*resPath*/, it.getFile(), type8);
        }

        return hasErrors ? UNKNOWN_ERROR : NO_ERROR;
    }

修改的部分在:
String8 obfuscationName;
String8 obfuscationPath = getObfuscationName(resPath, obfuscationName);

assets->addResource(it.getLeafName(), obfuscationPath/*resPath*/, it.getFile(), type8);

混淆时常见的问题解决

参见官方问题解答

参考文献:
1、Android 项目的代码混淆,Android proguard 使用说明
2、google 混淆官方文档
3、混淆官方网址
4、Android混淆快速配置之@Keep
5、[http://www.jianshu.com/p/60ce4bf20f72url=""]Android resources.arsc文件格式及逆向修改res路径思路[/url]
6、Android应用程序资源的编译和打包过程分析(AAPT)
  • 大小: 43.5 KB
  • 大小: 84.8 KB
  • 大小: 39 KB
  • 大小: 7.5 KB
  • 大小: 70.3 KB
  • 大小: 85.7 KB
  • 大小: 98.6 KB
  • 大小: 31.3 KB
0
0
评论 共 0 条 请登录后发表评论

发表评论

您还没有登录,请您登录后再发表评论

相关推荐

  • 关于Android Studio封装SDK的那些事儿

    所以在Androidstudio中如果需要生成jar或者aar,就需要将module变成library。 1、AndroidStudio生成library 在这里介绍AndroidStudio两种生成library的方式。 1.1、两种生成library的方式 新建library module。 ...

  • Android混淆那些事儿

    介绍proguard基础语法和逆混淆工具的使用

  • android proguard 代码混淆有什么用,Android ProGuard 代码混淆那些事儿

    Android 开发中为了代码安全一般都会使用 ProGuard 进行代码...这是许多开发者对代码混淆的认识,但是 ProGuard 更深入的内容呢,如何配置混淆规则呢,下面我就分享下 Android 中 ProGuard 那些事。先敲敲黑板,划下...

  • Android混淆处理

    Android Studio自身集成Java语言的ProGuard作为压缩,优化、混淆和预检工具,配合Gradle构建工具使用很简单,只需要在工程应用目录的gradle文件中设置minifyEnabled为true即可。然后我们就可以到proguard-rules.pro...

  • Android混淆

    在 Android 日常开发过程中,混淆是我们开发 App 的一项必不可少的技能。只要是我们亲身经历过 App 打包上线的过程,或多或少都需要了解一些代码混淆的基本操作。那么,混淆到底是什么?它的好处有哪些?具体效果...

  • 详解Android中的混淆规则

    相信不少开发在发布时被代码混淆弄得一头雾水,大多都是百度一下,看看别人的混淆规则,复制粘贴拿来试一试,直到最后弄成了,也不知道为什么混淆规则要这么写,以及混淆都对自己的代码做了什么?不要问我为什么这么...

  • Android 混淆详解

    Android 混淆详解 转载请标明出处:http://blog.csdn.net/zhaoyanjun6/article/details/69388246 本文出自【赵彦军的博客】 混淆的基本概念 什么是混淆? 代码混淆亦称花指令,是将计算机程序的代码,转换成一种...

  • 谜题:Gradle插件-混淆器字符串加密(AndroidJava)

    Gradle插件-混淆器字符串加密(Android / Java) 这个项目是一个简单的Gradle插件,可帮助您在编译时加密Android Java代码的所有String值。 重要提示:如果您的项目不受git或SVN之类的SCM工具管理,则Enigma插件将...

  • android 怎么混淆代码,Android如何混淆代码

    android 引入了Proguard,Proguard 是Android tools包中提供用来对代码进行压缩,优化和进行混淆的工具,它能够移除无用的代码,对类名,方法名,字段名进行重命名,但不改变代码的结构,从而达到对代码混淆的作用。...

  • 一篇文章带你领略Android混淆的魅力

    在 Android 日常开发过程中,混淆是我们开发 App 的一项必不可少的技能。只要是我们亲身经历过 App 打包上线的过程,或多或少都需要了解一些代码混淆的基本操作。那么,混淆到底是什么?它的好处有哪些?具体效果...

  • Android代码混淆

    代码混淆 如有错误可以QQ邮箱联系,745661590@qq.com github不支持脚注 代码混淆概念1代码混淆(Obfuscated code)亦称花指令,是将计算机程序的代码,转换成一种功能上等价,但是难于阅读和理解的形式的行为。代码...

  • 马丁路德金博士的三段演讲录音

    1.Our God Is Marching On! - 25 March 1965 在线收听 Let us therefore continue our triumphant march (Uh huh) to the realization of the American dream. (Yes, sir) Let us march on segregated housing (Yes, si

  • 马丁路德金博士的演讲录音(二)

    上一次贴了马丁路德金博士的三段演讲, 今天再贴一些,另外大家可以去http://www.stanford.edu/group/King/popular_requests/ 找到更多。 1. Ive Been To The Mountaintop - 3 April 1968 (Kings last speech)Dr. Martin Luther King, Jr. delivered

  • “请帮助另外10个人吧”——一个德国小孩的故事

    刚才被这个故事感动得热泪盈眶: “请帮助另外10个人吧”———一个德国小孩的故事------------------------------------------   这是发生在德国的一个真实感人的故事。2003年母亲节,节日的温馨气氛再次燃起了伊特洛孤儿院孤儿德比对母亲的思念。电视机内一个6岁的小男孩在帮父母修剪草坪,德比对修

  • 如何做一个负责任的博客

    今天看到博客中国有这样一篇文章《老博客现身说法:如何做一个负责任的博客》,其中提到了做一个负责任的博客应该做到下面5点: 1.坚持时时更新2.要尊重版权3.不要散播谣言4.博客要尊重社会公德5.博客要互相尊重保持基本的礼节 对照自己,最大的困难是第一点,写些什么?写不写的出来?这个比较难。 另外,关于版权,也是一个问题。自己不能写的话,就会趋向于转载别人的文章,这里就有个版权的问题。 这篇文章的作

  • 几部想看而还没有看过的电影

    1. 陈果《去年烟花特别多》和《细路祥》2. 《秋天的童话》3. 张国荣《流星语》4.一个字头的诞生5《天浴》 6.三、正午 这是一个告诉男人正义和责任是什么的影片。它是美国六十年代(好象是)拍的一个西部片,是黑白画面的。主人公是一个小镇上的警察,他为了维护小镇居民的安全,将一个坏蛋绳之以法,现在这个坏蛋被释放出狱,在正午十二点前到达小镇来找他复仇。他需要小镇上的人帮助他来制服

  • 真话和假话

    今天在天涯看到押沙龙先生的一篇文章中说:"在一个只有撒谎者才能生存的环境里,生存下来的都是些撒谎者,这是一个很简单的逻辑。"面对这种环境,哈维尔认为“假如社会的支柱是在谎言中生活,那么在真话中生活必然是对它最根本的威胁。正因为如此,这种罪行受到的惩罚比任何其他罪行更严厉。”他说:“说真话,就是按照人的本性或良心说话行事。”精辟呀。

  • 有感于"3000万光棍找不到老婆"

    "3000万光棍找不到老婆" 最近常看到报道讲我国出生性别比偏高的问题,今天又看到一篇《性别失衡,2005年起中国男子娶妻难》 首先这个"3000万光棍找不到老婆",比较有趣,不知道“找到老婆的”还叫不叫光棍? 其次,假如这个说法成立,那绝对是一个big business! 看看水源就知道这是一个怎样巨大的问题啦。 与昨天有关打击网络色情的报道一比较,相映成趣、意味深长。 3000万啊!3000万

  • 向赵老师学习

    赵老师,你是我的偶像! 赵老师,你业务突出,几十年如一日,把《动物世界》演绎得炉火纯青,让小弟我崇拜地五体投地! 从今天起,我要向你学习,做一个“高尚”的人,一个纯粹的“人”,一个有“道德”的人,一个 “脱离”了低级趣味的人,一个“有益”于人民的人! 立此存照!

Global site tag (gtag.js) - Google Analytics