4.1应用加固概述

文章目录
  1. 1. 理论介绍
  2. 2. 应用加壳
  3. 3. 代码混淆
  4. 4. Android应用加固技术
    1. 4.0.1. 第一代 动态加载技术
    2. 4.0.2. 第二代 内存不落地加载
    3. 4.0.3. 第三代 指令抽取
    4. 4.0.4. 第四代 指令转换
    5. 4.0.5. 第五代 虚拟机源码保护

理论介绍

应用加固的种类按照是否获知源码,可以分为白盒加固和黑盒加固。白盒加固指的是在具有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

加壳原理图

加壳需要按照以下的步骤进行:

  1. 反编译Target.apk获得Target文件夹
  2. 检测manifest文件是否有自定义的Application
  3. 如果源APK有自定义的Application,修改TuokeApk工程中ProxyApplication中的applicationName变量为源APK的Application的名称,接下来执行第5步,否则执行第4步
  4. 复制smali文件夹,跟反编译后的app下的smali合并,给app添加一个自定义的Application,接下来执行第5步
  5. 修改manifest文件,将自定义Application设定"org.hackcode.ProxyApplication"
  6. 重打包Target为APK
  7. 将重打包的APK命名为Target.modified.apk,并提取重打包后的apk中的classes.dex文件,并压缩为TargetApk.zip文件
  8. 合并TuokeApk/.../classes.dexTargetApk.zip,生成classes.dex
  9. 将合并生成的新classes.dex文件与Target.modified.apk中的classes.dex替换
  10. 复制TuokeApk/libs目录下的所有文件和文件夹到目标app中
  11. 破坏SO文件的ELF头部(删除 ELF header)
  12. 将修改后的app重压缩成zip文件
  13. 重新签名

我们采用Python编写了一个自动化加固的脚本,伪代码如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
Target = apktool_decompile(Target.apk)

flag = check_is_defined_application("Target/AndroidManifest.xml")

if(flag):
self_defined_application = get_application_name("Target/AndroidManifest.xml")
change_application_name("TuokeApk/app/src/.../ProxyApplication.java")
gradle_rebuild(TuokeApk)
else:
copy(smali, "Target/smali")

change_application_name("Target/AndroidManifest.xml", 'org.hackcode.ProxyApplication')

Target.apk = apktool_build(Target)

rename("Target.apk", "Target.modified.apk")
Target.modified = unzip("Target.modified.apk")
classes.dex = extract_dex("Target.modified")
zip("classes.dex", "TargetApk.zip")

new_classes.dex = make_new_dex("JiaguApk.jar, TuokeApk/.../classes.dex" AND "TargetApk.zip")

replace_dex("new_classes.dex", "Target.modified/classes.dex")
copy("TuokeApk/libs/*", "Target.modified/")

delete_elf_head(Target.modified/.../*.so)
zip(Target.modified, 'Target.modified.2.apk')
Target-signed.apk = resign(Target.modified.2.apk)

代码混淆

我们会在4.4节介绍混淆的相关技术,以及开源项目O-LLVM是怎样实现它们的。我们会在4.2和4.3将会先介绍一个目前流行的编译器架构LLVM,它相对于传统的Clang有许多革新的地方。

Android应用加固技术

目前,软件保护技术已经是一个相对独立的研究领域,采用的方式主要分为两类:基于硬件的加密保护技术基于软件的加密保护技术,硬件加密技术破解难度较大,同时开发难度也大,成本较高。

基于软件的保护技术主要包括注册验证,代码混淆,防篡改,加壳技术等。软件加密技术较为灵活,成本较低,同时安全性有限。随着应用市场的高速发展,对应用的安全性提出了较高的要求。在综合考虑了安全性、便利性和相对成本等因素后,我们采用了基于软件加固的思想,此处我们将其称之为Android应用加固,它要实现的功能是对任意的应用进行加固,具有很好的通用性和安全性,有效防止恶意攻击者的威胁。

 Android应用加固技术的变迁

APP加固技术从理论到应用的迁移早在2013~2014年就开始了,经历了五代的变更(图),保护级别每一代都有提升。前两代能够在网络上找到相当多的资料,以及分析和实现的示例,但是从第三代开始,相应的技术基本都是专利或者商业机密,尤其是第五代应用于安全要求非常高的金融机构等国家重点领域。

我们来看一下五代加固技术的实现原理。

第一代 动态加载技术

开发过程是将程序分为两部分: LoaderKey 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、腾讯、阿里等巨头则是利用自身流量的优势,强制开发者必须在在自家的应用发布平台上使用自己的加固技术。

在技术层面,加固技术目前无移动安全界的权威评价标准,未来将趋向于向轻量化、高可靠性、高兼容性、高保护性的趋势发展,且安全性的保障将更多依赖于系统内核以及内建机制的安全,而非单纯依靠软件自身。