理论介绍
应用加固的种类按照是否获知源码,可以分为白盒加固和黑盒加固。白盒加固指的是在具有Android应用的源代码的情况下进行保护,这种情况下,加固操作在程序开发的阶段就能够介入,可以对源代码进行增添、删除或者变形,最后生成加固后的APK;黑盒加固指的是只提供发布阶段提供的APK文件的条件下进行保护,在该条件下往往不能获得完整的Java源码,需要针对DEX或者Smli文件进行处理。
而按照功能划分,加固技术可分为代码混淆、签名保护、反调试检测、应用加壳等等。本论文设计的平台主要采用代码混淆和应用加壳两种方式来对Android应用进行加固,代码混淆是源码级别的保护,对应于白盒加固,采用了基于OLLVM开发的工具链,参与到Android应用的编译和链接的过程中,对Native层的C/C++代码进行混淆;应用加壳技术对应于黑盒加固,依据APK的DEX的文件格式,采用壳程序负责加密和解密原始程序。
应用加壳
在加壳的过程中需要三个对象:
Target.apk
:源APK- JiaguApk工程 =>
JiaguApk.jar
:构建JiaguApk工程后可打包成jar包,用于用于合并TuokeApk/.../classes.dex
和加密了的目标目标TargetApk.zip
- TuokeApk:用于加密加固了的
classes.dex
文件 - smali文件夹:当目标app没有自定义Application时,给该app添加一个自定义Application
加壳需要按照以下的步骤进行:
- 反编译
Target.apk
获得Target
文件夹 - 检测manifest文件是否有自定义的Application
- 如果源APK有自定义的Application,修改TuokeApk工程中ProxyApplication中的applicationName变量为源APK的Application的名称,接下来执行第5步,否则执行第4步
- 复制smali文件夹,跟反编译后的app下的smali合并,给app添加一个自定义的Application,接下来执行第5步
- 修改manifest文件,将自定义Application设定
"org.hackcode.ProxyApplication"
- 重打包Target为APK
- 将重打包的APK命名为
Target.modified.apk
,并提取重打包后的apk中的classes.dex
文件,并压缩为TargetApk.zip
文件 - 合并
TuokeApk/.../classes.dex
和TargetApk.zip
,生成classes.dex
- 将合并生成的新
classes.dex
文件与Target.modified.apk
中的classes.dex
替换 - 复制
TuokeApk/libs
目录下的所有文件和文件夹到目标app中 - 破坏SO文件的ELF头部(删除 ELF header)
- 将修改后的app重压缩成zip文件
- 重新签名
我们采用Python编写了一个自动化加固的脚本,伪代码如下:
1 | Target = apktool_decompile(Target.apk) |
代码混淆
我们会在4.4节介绍混淆的相关技术,以及开源项目O-LLVM是怎样实现它们的。我们会在4.2和4.3将会先介绍一个目前流行的编译器架构LLVM,它相对于传统的Clang有许多革新的地方。
Android应用加固技术
目前,软件保护技术已经是一个相对独立的研究领域,采用的方式主要分为两类:基于硬件的加密保护技术和基于软件的加密保护技术,硬件加密技术破解难度较大,同时开发难度也大,成本较高。
基于软件的保护技术主要包括注册验证,代码混淆,防篡改,加壳技术等。软件加密技术较为灵活,成本较低,同时安全性有限。随着应用市场的高速发展,对应用的安全性提出了较高的要求。在综合考虑了安全性、便利性和相对成本等因素后,我们采用了基于软件加固的思想,此处我们将其称之为Android应用加固,它要实现的功能是对任意的应用进行加固,具有很好的通用性和安全性,有效防止恶意攻击者的威胁。
APP加固技术从理论到应用的迁移早在2013~2014年就开始了,经历了五代的变更(图),保护级别每一代都有提升。前两代能够在网络上找到相当多的资料,以及分析和实现的示例,但是从第三代开始,相应的技术基本都是专利或者商业机密,尤其是第五代应用于安全要求非常高的金融机构等国家重点领域。
我们来看一下五代加固技术的实现原理。
第一代 动态加载技术
开发过程是将程序分为两部分: Loader
和Key Logic Payload
,并单独打包,然后执行阶段让Loader
先运行。接着释放Payload,通过Java的动态加载技术加载它,并移交控制。
由于Payload部分必须解压及释放在文件系统中,所以非常容易通过文件定位,甚至通过自定义虚拟机截获关键函数,目前大部分已被破解。
第二代 内存不落地加载
与第一代技术的区别在于,在加载Loader
时,需要初始化StubApplication
,解密和加载Payload,初始化原始Application
,用原始Application
替代StubApplication
,最后正常加载其他组件。其执行过程需要拦截系统IO相关的函数,比如读函数(Read)和写函数(write),在这些函数中提供透明的加解密,然后直接调用虚拟机提供的函数进行不落地的加载。
这是当前市面上最常见的Android应用加固服务,通常作为一项基础性的免费服务向用户提供。但是由于启动时要进行大量的加解密操作,会降低应用的流畅度,Payload虽然不会释放在文件系统中,但是必定会在内存中展开,使用内存Dump即可获取源码,目前在吾爱破解、看雪论坛等已出现一些手工脱壳的方法。
第三代 指令抽取
第三代的保护的细粒度得到了提高,保护级别降到函数级别。首先将原始DEX内的函数内容清除,单独转移到一个文件中,运行阶段将函数内容重新恢复到对应的函数体。具体的做法如下,加载之后恢复函数内容到DEX壳所在的内存区域;加载之后将函数内容恢复到虚拟机内部的结构体上:虚拟机读取DEX文件后内部对应每一个函数有一个结构体,这个结构体上有一个指针指向函数内容,可以通过修改这个指针修改对应的函数内容;拦截虚拟机内与查找执行代码相关的函数,返回函数内容。
指令抽取与JIT性能相冲突,达不到最优的性能,而且由于使用了自定义指令集的虚拟机内部结构,加上纷繁复杂的Android厂商的定制的系统,会造成各种兼容性问题。由于第三代的技术破解难度较大,各大论坛上有分析的帖子,但是破解的数量相对于第二代而言少很多。
第四代 指令转换
第一种方案称为纯指令转换。DEX内的指令(函数)被抽离到独立的SO库中,并通过JNI和Android系统进行交互。第二种方案是VMP加固。同样将DEX文件内的函数被标记为native,但是不同的是,内容被抽离并转换成自定义的指令格式,该格式使用自定义接收器执行,不过和第一种方式相同的是,一样需要使用JNI和Android系统进行调用。
该技术仍然需要通过虚拟机提供的JNI接口与虚拟机进行交互,因此攻击者可以将加固方案视为黑盒子。 通过自定义JNI接口对象检测,记录和分析黑盒子的内部,以获得完整的DEX程序。 此外,这一代的加固技术通常与第三代加固技术结合使用,因此第三代的所有兼容性问题也存在于第四代。
使用第四代加固技术,基本上已经少有攻击者能够实施完整的破解了。
第五代 虚拟机源码保护
基于第四代方案的第一种方案(Java/Kotlin → C/C++),采用LLVM编译工具链(同时支持C/C++, Swift, Object-C),通过对IR进行指令转换,生成自定义指令集(IR → VM),APP内部隔离出独立的执行环境,将该核心代码放置在此独立的执行环境中运行。
虽然虚拟机源码保护技术,作为最新的加固技术,但它仍无法摆脱在JNI的依赖,因此依然存在第四代加固技术的缺陷,存在被记录修复的可能性,同时,由Java转换为等价的C/C++,这将导致系统的代码呈线性增加并降低性能。但是不论如何,由Java转C/C++后的代码,由于虚拟机的保护,逆向难度会上升一个数量级;对于C/C++部分逻辑,只能大量投入时间去破译虚拟机的指令集的含义,这无疑会极大地增加破解的代价。至少到目前而言,大部分仍然未被破解。
顶象、海云安率先提出了无壳加固并完成了加固技术的迭代,爱加密和梆梆加固作为最先进入移动应用安全领域,其加固方案注重兼容性、实用性,而360、腾讯、阿里等巨头则是利用自身流量的优势,强制开发者必须在在自家的应用发布平台上使用自己的加固技术。
在技术层面,加固技术目前无移动安全界的权威评价标准,未来将趋向于向轻量化、高可靠性、高兼容性、高保护性的趋势发展,且安全性的保障将更多依赖于系统内核以及内建机制的安全,而非单纯依靠软件自身。