最近抽空把之前写的一些文章梳理出来,力争把相关系列更新完成
前面我们十分清晰的讲解了一代壳的加壳原理,还使用开源代码制作了一代壳加壳器,并进行了对抗研究,后面我们针对一代壳的加壳原理,还进行有针对性的脱壳研究,研究了脱壳点和脱壳原理,并分享了六类一代壳脱壳的相关方法,并进行了具体的实现,以及上一篇文章提供给大家了脱壳工具集。本文将在基础上进一步研究不落地加载的基本原理,以及不落地加载和动态加载的区别,不落地加载开源代码解析,不落地加载加壳器对抗实验。
我们说动态加载被称为第一代加壳技术,其原理十分简单,主要是使用了动态加载的技术,并选择了合适的加壳时机,具体原理大家参考前文
这里简单总结一下流程:
这个中间需要注意的两点就是类加载器DexClassLoader的替换
和Application的修正
,当然成熟的方案也许需要考虑多dex的加载问题,这里我们就不具体的展开了。
类加载器修正原因:
Applicaition修正:
至于在壳代理类中需不需要完成原始dex的解密,这取决于原始的dex有无进行加密,但无论怎么处理,DexClassLoader加载了源dex后,都会将其释放在/data/data/...
文件夹或相关的文件夹下,这使得这种加壳技术的防护性较低,恶意攻击者可以直接对本地的源文件进行解密,拿到原始dex。
参考文章:
安卓加固方案从落地加载到类指令抽取编写报告
[Android加固实现,不落地加载(support 5.0-11)]
第二代不落地加载器具体实现
由于动态加载存在释放dex到本地的关键致命问题,为了解决这种问题,不落地加载的加壳技术就出现了。不落地加载技术可以实现直接将dex文件加载到内存中,而不需要先释放到本地的文件夹,这样就很好的对本地的释放的缺陷进行了防护。那怎么实现直接加载到dex文件到内存中呢?我们都知道Android中一般用DexClassLoader实现对dex文件的加载,而我们要想实现直接加载到内存中,无疑就需要重写DexClassLoader,重写DexClassLoader必然会涉及到defineClass
、findClass
、loadClass
,因为一个类在加载流程中必然会先后调用这三个函数,因此我们需要重写这三个函数。其实DexClassLoader在加载dex进入内存中也是通过字节加载的,因此我们只需要在Dalivk和Art选择在so文件中重写合适的加载函数,然后通过cookie来进行操作,即可
Dalvik:
Art:
而现在基本都是ART,难点在于8.0及以后有系统提供的内存加载dex接口,所以难点主要集中在8.0以前。8.0以前实现原理是加载虚dex然后替换成真实的cookie返回。这里如何替换是个难点,好在网上大神已经开源了方案,只是一番尝试下来只能在5.0和5.1上使用,并且也不支持多dex。网上查找相关资料,没有找到有用的结果,可能是搜索姿势不对。
前面讲述了那么多的原理,这里我们拿具体的项目来进行全面的分析,帮助大家进行理解
项目地址:https://github.com/Frezrik/Jiagu
分析项目前,我们先看看工具的效果
大佬开发的工具已经十分的完善,支持linux端和windows端,这里我们简单的使用下linux端的效果
未加壳的样本:
加壳后的样本:
可以发现除了代理类,我们基本看不到源样本的任何代码,这是不是比原来的第一代壳加壳器的防护要更高呢
项目的模块大概分为:
按照加壳程序的运行流程,我们首先分析pack这个目录
从结构我们可以看出这部分功能主要是制作一个加壳器,但加壳实现具体流程肯定不在这里面,我们从Main类开始分析
main()
parse()
函数主要作用是对我们输入参数进行过滤
(1)解压APK
主要功能:
(2)解压壳Dex
主要功能:
(3)将壳和原dex进行加密然后组合成一个dex
这里是很多制作加壳器的基本的过程,就是拼接壳和dex为一个dex,这里最关键对dex进行了加密,所以防护上上升了一级
(4)copy so
保存数据到libjiagu.so中
(5)重打包APK
将APK重新的打包
(6)重签名APK
对APK进行签名
这里我们可以发现该函数主要是我们加壳器的主要功能,接下来我们具体分析加壳的原理
前面我们将壳dex和原始dex拼接成一个dex,而且对原始dex进行了加密,修改了Application:name的值,根据程序的执行流程必然会先加载壳的dex,我们继续分析
与我们前面说的一致,壳的Application中含有attachBaseContext和onCreate函数,这也是我们使用加壳的具体实现所在
(1)attachBaseContext
copyJiagu:
完成对libjiagu.so的拷贝
attach:
这里我们可以发现attach在so层,说明这里就是我们前面分析的重写OpenMemory
函数的地方
可以发现attach函数进行了动态注册,注册到native_attach函数
native_attach:
首先从安装后的apk中获得dex,然后在内存加载dex,我们主要关注内存加载dex
loadDex:
对dex解密并保存到字节数据,然后获取application的名称
实现内存加载dex,这里对sdk的版本进行了判定,如果是Android8.0以下,就对openmemory函数进行hook重写,反之如果Android8.0以上则反射调用InMemoryDexClassLoader, 然后通过InMemoryDexClassLoader
来加载dex,并返回mCookie,就可以通过mCookie来操作dex
将dex中的对象保存字节数组,这是为了处理多dex的情况
然后对application进行hook
这里就是代理类实现的大致功能,8.0上是直接反射调用InMemoryDexClassLoader,这个函数可以直接加载dex到内存,我们主要关注8.0下的openmemory函数的重写
openmemory_load_dex:
同样是加载然后返回dexfile
这两个函数是对dex进行加载
load:
这里最后还是通过dlsym进行函数调用,然后对原始的OpenMemory进行hook,返回是一个DexFile,这里然后进行了重写,将dex传进去
针对7.0即以下上面加上了dex check的代码
7.0
我们再次查看8.0这里是如何处理,因为8.0可以直接加载dex到内存
所以这里给location赋值了一样的
到这里我们c层的代码也分析完了,整体框架的项目也就分析到这里
最后梳理执行流程:
工具处理:
壳dex处理:
我们为了进一步验证不落地加载的安全性,使用恶意应用检测来验证最好,大家可以设想,如果我们的加壳技术进行升级后,防护性更强是不是说明源码不易被检测和识别,那么使用加壳的恶意应用来检测就十分能说明这个问题,例如加壳防护级别越高的恶意应用越难以检测出恶意性,那么我们检测平台的分数应该也会越低,按照这个设想我们开展实验:
我们还是拿之前的恶意样本,我们先不对其加壳,查看恶意性:
可以发现VirusTotal上面有27家厂商识别出了恶意应用,说明这个恶意性还是很容易检测到的
我们使用之前的一代壳加载器进行加壳,查看其恶意性:
这里发现分数略微降低,但是还是很高,这是因为之前一代加壳器,我们未对dex进行加密,而且dex保存本地,所以十分容易检测出来
我们使用本文的不落地加载壳进行加载,查看其恶意性:
这里可以发现67家厂商中只有9家厂商可以识别出来,识别率大大降低,这更加反映了不落地加载防护技术的升级
本文讲述了不落地加载的基本原理,也总结了和前面的落地加载的区别,本文通过分析一个开源项目详细的分析了不落地加载器实现的基本原理,最后通过实验进一步证明了不落地加载的防护性进一步升级。本文的相关资料存放知识星球:安全后厨。
github地址:github
声明:本文所使用的工具和样本为研究学习使用,任何人不得使用其进行非法用途,其行为与本人无关
声明:本文所使用的工具和样本为研究学习使用,任何人不得使用其进行非法用途,其行为与本人无关
APP启动
-
-
-
-
-
>加载壳的代理类Application
-
-
-
-
-
-
>在壳代理类中的attchBaseContext和onCreate函数中对源程序dex进行加载和解密
-
-
-
-
-
>启动原程序dex的执行流程
APP启动
-
-
-
-
-
>加载壳的代理类Application
-
-
-
-
-
-
>在壳代理类中的attchBaseContext和onCreate函数中对源程序dex进行加载和解密
-
-
-
-
-
>启动原程序dex的执行流程
我们使用自定义的DexClassLoader加载,使得其不具备Android加载的生命周期,导致后面会出现崩溃,所以我们需要解决,相关的方案前面我也讲解了,一般可以替换为系统类加载器mClassLoader
我们使用自定义的DexClassLoader加载,使得其不具备Android加载的生命周期,导致后面会出现崩溃,所以我们需要解决,相关的方案前面我也讲解了,一般可以替换为系统类加载器mClassLoader
我们使用代理类的Application,但是可能我们的源Apk里面也包含自身的代理类Application,因此在我们针对这种情况,完成壳的加载后,还需要对Application进行修正
我们使用代理类的Application,但是可能我们的源Apk里面也包含自身的代理类Application,因此在我们针对这种情况,完成壳的加载后,还需要对Application进行修正
hook重写的函数:libdvm.so中的openDexFile
hook重写的函数:libdvm.so中的openDexFile
Android8.
0
以下版本采用call libart的OpenMemory函数实现内存加载dex
Android8.
0
及以上采用系统提供的InMemoryDexClassLoader实现内存加载dex
Android8.
0
以下版本采用call libart的OpenMemory函数实现内存加载dex
Android8.
0
及以上采用系统提供的InMemoryDexClassLoader实现内存加载dex
[注意]传递专业知识、拓宽行业人脉——看雪讲师团队等你加入!