简介
基本概念
APK结构
Dex结构
APK打包过程
APK加载过程
Android JNI机制
常用破解手段
静态破解
反编译
静态分析
动态破解
二次打包
本地数据窃取
通信抓取
加固途径
防静态破解
防反编译
防静态分析
资源文件保护
防二次打包保护
防动态破解
SO文件保护
本地存储数据保护
ART模式下的加固
常用第三方加固平台
腾讯云-乐固
简介
加固项目
DEX文件加固
资源文件保护
防二次打包保护
防调试器保护
内存防dump保护
高级内存保护
so文件保护
使用客户
收费情况
加固过程
加固APK反编译测试
SO
DEX反编译
安全键盘
接入效果
安全数据库
兼容性测试
加固平台开发
简介
随着Android应用破解的愈发猖獗,Android应用安全越来越受到重视, Apk加固作为一种防止Apk被反编译的一种强力手段,越来越受到apk开发者的青睐。
说道APK加固,需要和混淆有所区别,由于Java代码在APK没有做过混淆处理时,很容易通过反编译得到源码的类,变量,函数名,数据结构等信息。Android应用的混淆是通过将Java代码中的类,变量,函数名用a,b,c等无意义的字母代替。这样逆向得到的代码变得不可读。从一定程度上保护源码
基本概念
APK结构
apk文件实际是一个压缩包,使用解压软件即可解压缩,解压后的文件一般如下表所示:
路径 |
作用 |
assets目录 |
存放一些配置文件,原assets目录 |
lib目录 |
armeabi子目录下存放so库 |
META-INF目录 |
签名信息和证书 |
res目录 |
资源文件 |
AndroidManifest |
应用配置文件 |
classes.dex (classes2.dex) |
dex执行文件(dalvik vm executes) |
resources.arsc |
记录资源文件和资源ID之间的映射关系 |
Dex结构
Dex文件结构如下:
其中header是dex文件的头部,包含了dex文件的检查签名,和数据结构偏移地址结构如下
typedef struct DexHeader {
u1 magic[8]; /* includes version number */
u4 checksum; /* adler32 checksum */
u1 signature[kSHA1DigestLen]; /* SHA-1 hash */
u4 fileSize; /* length of entire file */
u4 headerSize; /* offset to start of next section */
u4 endianTag; /*字节序标号*/
u4 linkSize;
u4 linkOff;
u4 mapOff;
u4 stringIdsSize;
u4 stringIdsOff;
u4 typeIdsSize;
u4 typeIdsOff;
u4 protoIdsSize;
u4 protoIdsOff;
u4 fieldIdsSize;
u4 fieldIdsOff;
u4 methodIdsSize;
u4 methodIdsOff;
u4 classDefsSize;
u4 classDefsOff;
u4 dataSize;
u4 dataOff;
} DexHeader;
String table -Class Def table是偏移索引的区域。
Data区域是真正的数据保存位置。
这里面需要特殊说明的是Header的前几个量:
之后说的dex加壳之后重新打包的时候就需要将这几个值修改了。
APK打包过程
dex文件是供dalvik虚拟机运行的可执行文件,在编译的时候将java源码编译成标准的java字节码文件,然后通过DX工具转为dex文件,最后通过aapt(Android Asset Packaging Tool)工具将dex和其他资源文件打包为apk文件。
APK加载过程
Android系统zygote进程,通过fork新建一个Dalvik虚拟机实例,来执行apk,Dalvik虚拟机运行流程如下:
Android JNI机制
和其他Java虚拟机一样,Android也可以通过JNI机制调用native层代码,用来达到提高效率,复用代码,提高安全性等目的。
JNI代码的实现有两种方式:
通过第二种方法,可以实现运行时动态调整本地函数和Java函数直接的映射关系。
常用破解手段
静态破解
反编译
通过反编译工具将dex文件反编译为smali代码和java源码,然后通过阅读源码获取原应用的实现。
常用工具:
静态分析
静态分析就是直接对反编译的到的smali代码和java源码进行分析。
当然so库也可以进行反编译和汇编分析,不过其难度要比java层的分析要困难的多
动态破解
通过挂在debug调试器到运行的apk,来获取运行时的数据,
使用的工具比如:zjdroid,IDA,smalidea,Andbug
通过使用IDA,甚至可以在apk加载到内存后,从dump出内存中的dex文件
毕竟不管上层怎么加固加密,最终加载到内存的dex肯定不是加固的,通过IDA来动态调试libdvm.so中的dvmDexFileOpenPartial函数就获取内存中的dex内容。而这个dex文件就是完全没有加壳的源程序dex。
二次打包
修改APK中的资源文件,修改smali中的代码,插入自己的恶意代码或者导流代码,重新打包后发布。
本地数据窃取
窃取APK中的SharePreference和缓存文件,数据库数据。
通信抓取
通过代理抓包等方式,抓取apk运行过程中的通信数据
加固途径
防静态破解
防反编译
防静态反编译一般是根据反编译工具的流程找起漏洞
常用方式其中一种就是在使用花指令,比如利用jd-gui反编译时的原理,添加一些特殊代码,让反编译工具出错。
在类中加入如下字段:
private static final char[] wJ = "0123456789abcdef".toCharArray();
public static String imsi = "204046330839890";
public static String p = "0";
public static String keyword = "电话";public static String tranlateKeyword = "%E7%94%B5%E8%AF%9D";
就会让jd-gui反编译出错(最新版jd-gui已修复该问题)
另外一种方式就是对DEX加壳保护,让反编译得到的源码只是一堆无用的代码,真实的代码是加密的。
加壳原理:
加固过程中涉及3个对象:
流程:
通过上面的流程得到的最终apk由于实际dex已经被加密,反编译的得到的只是壳apk 的内容。
不过反编译之后,他人可以得到壳apk中是如何对源apk进行解密的代码。所以实际上只是加大了反编译难度而已。为了进一步增加反编译难度。一般的壳程序中都会把解密部分放在native代码中。
防静态分析
代码混淆--代码混淆是Android常用的一种代码加密方式,混淆过后的代码中类名方法名等都会变成abc等无意义的字符。让阅读者没法直接从名称中直接分析出源码目的。增加静态分析难度。
资源文件保护
把资源放在assets目录下,必要情况对图片等资源的文件名和内容进行加密。
防二次打包保护
保护应用在被非法二次打包后不能正常运行:
但是实际上不论java层面或者ndk层面的签名校验,都可以通过修改smali或者native源码来绕过。
防动态破解
原理:当检测到apk被调试器连接时,终止apk的运行。
方式有如下几种:
1、多进程ptrace反调试---创建子进程。在子进程中获取父进程pid,并通过ptrace() 方法附着父进程。如果附着失败,说明进程已经被调试
2、获取/proc/$pid/status中TracerPid行,能够显示调试程序的pid, 可以写一个方法检查下这个值, 如果!=0就退出程序。参考Android Native反调试,用JNI实现APK的反调试。
3、检查代码执行的间隔时间。
4、检测手机上的一些硬件信息,判断是否在模拟器中,如果是模拟器则无法运行。
SO文件保护
对so文件进行优化压缩、源码加密隐藏、防止调试器逆向分析
某种so加密结构:
应用加载过程:
本地存储数据保护
本地存储数据的保护一般是对本地数据内容进行加密,读取时进行解密,数据库和SP存储都有一些开源方案。
数据库保护:SQLCipher
SharePreference:Secure-Preferences
文件:对称加密,或者底层对文件操作加一层,对所有文件操作都进行统一加解密
ART模式下的加固
上面提到过的apk加固方案实际是针对dex的,但是在Android4.4之后,Android引入了新的运行模式ART,ART模式相比Dalvik,引入了AOT(ahead of time)编译,改进了垃圾回收机制,对取样分析的支持等。在安装时,ART模式使用dex2oat将apk的dex文件重新编译成oat文件。oat文件是一种可执行文件(ELF)。这样ART模式就可以在应用安装时,将apk转为机器码的可执行文件保存在本地,在每次启动时加快启动速度。
Android5.0之后,ART模式就是默认的运行模式。其流程
这就导致了原有的加固方案只能准对Dalvik虚拟机的运行模式,无法再ART模式下使用。
Art模式下的加固,需要用到Hook技术,所谓hook就是通过一些手段改变一个函数的执行逻辑,比如在函数调用前更改一下参数或者在调用后修改返回值,甚至直接返回,不执行原函数。
在ART中类方法的执行要比在Dalvik中要复杂得多,Dalvik如果除去JIT部分,可以理解为是一个解析执行的虚拟机,而ART则同时包含本地指令执行和解析执行两种模式,同时所生成的oat文件也包含两种类型,分别是portable和quick。portable和quick的主要区别是对于方法的加载机制不相同,quick大量使用了Lazy Load机制,因此应用的启动速度更快,但加载流程更复杂。其中quick是作为默认选项,这里涉及的技术分析都是基于quick类型的。
由于ART存在本地指令执行和解析执行两种模式,因此类方法之间并不是能直接跳转的,而是通过一些预先定义的bridge函数进行状态和上下文的切换,如下图:
Hook方法涉及到ART底层的C代码和汇编代码,这里不列举分析了,其基本原理是:
修改ArtMethod中的entrypoint,通过中转处理,让ART中间执行一段我们的自定义代码,然后再继续执行原代码。而自定义代码部分可以做一些加密解密等操作。
常用第三方加固平台
市面上的第三方加固平台很多,加固用到的手段基本上已经涵盖了上文提到的所有手段,并且还有一些其他保密的加固方式,毕竟对于破解者来说,如果知道了其加固或者加密的方法,就更加容易找到相应的破解方法。
市面上比较常见的加固平台:
时间关系,暂时我并没有针对性的使用各个加固平台进行一个对比测试,下面放一个其他人做的几个平台的对比测试:
|
加固前 |
360 |
阿里 |
腾讯 |
梆梆 |
加固时间 |
|
1'20" |
35" |
1'1" |
2'14" |
apk体积 |
16M |
16M |
15M |
17M |
16.5M |
启动速度 |
2.52s |
1.77s |
5.02 |
3.48 |
5.57 |
兼容性 |
100% |
99% |
100% |
100% |
99% |
腾讯云-乐固
简介
应用乐固(Cloud Reinforcement)是移动应用一站式安全服务平台,涵盖应用加固、安全评测、渠道监控、安全SDK、适配分析、质量跟踪等服务。可防止应用被盗版破解、及时发现应用漏洞、监控应用正盗版分发等,有效捍卫应用开发者利益。
加固项目
DEX文件加固
对DEX文件进行专业加壳加花保护,防止利用调试器对应用进行逆向破解。
资源文件保护
资源文件被非法篡改、删除后,程序将无法正常运行。
防二次打包保护
应用内任意文件被修改、替换后,都将无法正常运行。
防调试器保护
防止使用各类静、动态调试工具影响应用运行。
内存防dump保护
防止通过动态调试、dump形式获取应用部分代码。
高级内存保护
可对内存数据进行高强度防护,有效防止内存调试、内存dump等方式窃取源码。
so文件保护
可对指定so文件进行安全防护,防止被逆向工具破解,暴露核心敏感逻辑。
使用客户
大众点评,驾考宝典,饿了么,四维图新
收费情况
目前乐固在线提供的基础服务免费,定制化需求需要和客服沟通
加固过程
两种方式:
1,在线上传应用加固
上传目标apk进行加固,加固完成后,只需要将加固后的apk,使用乐固提供的签名工具,使用原应用的签名重新进行一次签名即可。同时支持多渠道打包功能。
2,下载使用PC加固工具
使用加固PC工具能够一站式的加固应用,本地首先需要配置好签名信息。然后选择应用上传进行加固。
文件上传成功并加固完成后,会自己根据配置中的签名,进行签名并输出一个加固后的包
加固APK反编译测试
SO
对网游开发平台中下载的网游sdk demo进行加固测试,测试前后的apk进行解压后对比可以发现:
加固后的lib库中,增加了几个so文件
其中libbugly.so应该腾讯的bugly异常上报库,用来监控应用的crash,anr等错误。
liblegudb.so和libshella.so则
其中liblegudb.so看起来是数据库加密的native库
生成的数据库如下图所示:(注意,该数据库和应用本身的数据库并不相同,原应用数据库内容依旧是明文的)
进入某一个具体的数据表,看到的数据也是加密后的。
libshella.so则是解dex壳的native库,使用ida反编译时,ida报错无法反编译。
DEX反编译
加固后的apk的dex文件相比原dex文件大小略有增加,应该是增加了壳dex导致的。
对加固后的dex进行反编译后得到的只是壳程序的smali代码,而没有原应用dex文件的内容,这也符合上文中的加壳保护的现象。
大部分的代码进行了混淆操作,对以上的smali文件内容进行初步分析可知:
TxAppEntry:继承了Application类,其实现对乐固框架的初始化,我们从Manifest也可以看到,application已经改成了TxAppEntry。其attachBaseContext中做了如下事情:
protected void attachBaseContext(Context paramContext)
{
super.attachBaseContext(paramContext);
Log.d("SecShell", "attach start");
if (!b(this)) {//将libshella,libshellb,libshellc三个so库的路径初始化给静态全局变量
return;
}
LeguDB.init(this, null, null);//加载liblegudb.so
d(paramContext);//加载nfix和ufix SO库,修复本地资源文件和Unity资源文件
this.h = new Handler(getMainLooper());
paramContext = new CrashReport.UserStrategy(this);
paramContext.setAppVersion("2.10.1");
CrashReport.setSdkExtraData(this, "900015015", "2.10.1");
CrashReport.initCrashReport(this, "900015015", false, paramContext);
new Thread(new b(this)).start();
a(this);
paramContext = new IntentFilter(TxReceiver.TX_RECIEVER);
registerReceiver(new TxReceiver(), paramContext);
Log.d("SecShell", "attach end");
}
LeguModuleInitializer:通用的so加载器
LeguDB:数据库so加载初始化,调用LeguModuleInitializer进行本地库加载
正常的一个反编译流程,应该是获知shell程序是如何从加壳后的dex文件中,获取原始dex的解密流程。然后写一个小程序用同样的方法,解析出原dex。
不同于上文“防反编译”章节中对dex整体进行简单整体加密,目前很多的加密手段是对原dex进行解析,并针对每个方法都进行不同内容的插入,从而增加解壳部分的复杂度。
同时也会在解壳代码的加载过程,增加其复杂度,从静态分析中很难看到其解壳代码。
由于并不擅长反编译相关的手段和知识,对乐固加固后的应用和原应用的对比也仅限于此。
安全键盘
一般来说,通过Android系统自带的键盘可以通过以下几种方式窃取用户信息:
乐固本身提供了一个安全键盘的sdk,该sdk使用如下方式来保证输入安全:
接入效果
通过接入该sdk,在需要输入加密内容的部分使用SafeEditText代替EditText,在输入内容时,SafeEditText并不会显示你输入的内容(连闪一下也没有),规避截屏获取密码的可能。下方的键盘是自定义的密码键盘。
不过看起来这个键盘有个小bug:调起键盘界面时,下面的导航栏变成透明,并且和键盘下面的部分按键冲突。
安全数据库
目前乐固还没有上线安全数据库的sdk,并且乐固加固过程本身也是不会对原应用的数据库进行什么加密操作。所以这里不存在替换加固方案后,数据库升级问题。
单就数据库来说:
由于sqlite数据库是明文存储,root用户很容易拿到数据库的db文件,并查看其中保存的数据内容:
下图是查看乐固加固后的应用的数据库的数据:
其数据内容和原数据没有区别,并且都是明文的。
所以本地存储安全--包括数据库安全,sharepreerence安全和本地缓存文件安全需要应用本身去实现。这块可以使用已有的解决方案如:
数据库保护:SQLCipher---一方面对数据通过用户提供的key进行加密,另一方面其加密后的数据库无法打开无法看到其中存储的已加密数据
SharePreference:Secure-Preferences---其对保存的SP数据对,通过AES进行加密,加密的密钥是一个生成的伪随机数。加密后的数据
当然也可以自己选择加密算法,在写入本地存储前进行加密,读取时进行解密。
兼容性测试
从乐固加固升级到 |
覆盖安装 |
启动 |
数据库读写 |
SP读写 |
原应用 |
正常 |
正常 |
正常 |
正常 |
360加固 |
正常 |
正常 |
正常 |
正常 |
某开源加固方案 |
正常 |
正常 |
正常 |
正常 |
不过覆盖安装后,原有的乐固加固本身创建的bugly的数据依旧存在,因为升级安装本身并不会删除原因的data目录下文件,只会新增或者升级。
同样的,从乐固升级到360加固保。则应用的data目录下,就会存在两个平台产生的文件(360并没有新的数据库,但是有新的sp文件)
覆盖安装后原有的lib目录会被删除并覆盖,不会遗留原有的so文件,但是data下本应用的根目录还残留有乐固加固使用的文件(mix.dex,mix.so,tx_shell目录)。不过由于升级后的应用使用其他加固平台或者自由加固,这些文件只是会增加包体积而已,并不会从代码层面产生影响。
上表中的开源加固方案,使用ApkToolPlus集成的
。比第三方加固方式要简单得多。仅为测试加固兼容性需要。
综上可以看出:
各种加固方式基本是针对apk本身做的反编译,反调试等做的加固。从应用启动过程来看,覆盖安装后,启动过程就由新的应用所控制,原加固手段中在Application中所作的解密,hook等操作都已无效。只要新覆盖安装的apk的启动过程中需要的文件不存在异常即可。
加固本身并没有干涉应用本身内部使用的缓存文件,数据库,sp等(实际上如果不植入加固平台的sdk的话,加固平台是没法干涉这些代码层的操作的)。
加固平台开发
安全领域一句比较有名的观点是---“未知攻焉知防”。
对Android加固来说,为了其加固效果,开发者首先需要对常用的破解工具和破解手段比较熟悉。这也是为何大多数的安全专家本身就具有很高的软件破解能力的原因。
在对各种破解手段有一定的实践基础上,再针对性的对每一种破解手段进行防护,就是一个加固平台需要完成的任务。下表总结了针对市面上常规的破解方式的几种加固方式及其开发的难点。
项目 |
方式 |
难点 |
dex文件加固 |
加壳 |
ART模式下的兼容,各Android版本的适配,解壳算法的隐藏 |
|
加花指令 |
花指令的获取收集 |
资源文件保护 |
资源文件加密 |
|
|
资源文件篡改识别 |
|
反调试 |
防止调试器挂载调试 |
针对各种调试器的反调试实践 |
|
防止内存dump |
调试器dump时如何触发应用退出 |
防二次打包 |
java层签名校验 |
|
|
native层签名校验 |
|
|
利用二次打包工具缺陷 |
|
|
服务器校验 |
|
so保护 |
针对某so文件进行防护 |
加密后so文件的加载时机 |
本地数据保护 |
数据库保护 |
数据库整体的防加载 |
|
缓存文件保护 |
|
|
SP内容保护 |
|