1 壳的分类
对于Android上的壳简单的分类可以分为动态加载型的第一代壳,后续出现的代码抽取型第二代壳,以及基于LLVM的代码混淆壳,也称之为第三代壳。由于个人水平有限,暂时只讨论前面两种类型的壳。
1.1 动态加载型壳
这种壳主要是对本地的dex文件,so文件,以及一些资源文件进行加密,然后在运行时进行动态还原。
对于壳的保护主要是通过反调试来防止逆向人员在调试时选择合适的时机将还原后的未加密源文件从内存中dump出来。
1.2 代码抽取型壳
除了具有第一代壳的特征之外,还会提取DEX文件中所有的方法实现,并在本地进行加密保存,在方法执行时,才会调用软件壳的native方法对dex文件中的方法进行还原。特征主要是,在dex方法执行前解密,执行后加密。这样我们即使dump出来了dex文件,但是由于dex中的方法都是经过加密的,因此还是无法还原源文件。
对于这种壳的保护通常会采取多进程保护,API hook等反调试与反dump技术。
1.3 代码混淆壳
典型案例OLLVM,基于LLVM pass实现的开源代码混淆壳。包括以下几种:
- 指令替换(Instructions Substitution, IS)
- 控制流平坦化(Control Flow Flattening, CFF)
- 伪造控制流(Bogus Control Flow, BCF)
在学习编译原理的时候,知道LLVM的编译器前端处理中,会将源语言翻译为AST,然后再翻译为IR,之后再经过编译器后端的处理,而再AST和IR中,都会有一系列的操作来对语言进行混淆。这部分暂时放一下,等到学习一定时间之后再来回顾
2 动态加载型壳的原理特征以及通用脱壳方法
2.1 壳的原理
以下是壳之所以能实现的理论基础,主要阐述了以下为什么壳可以实现,以及个人在理解的时候的一些QA
根据app的启动流程,应用程序通过Binder方式向system_server发起请求,而system_server接收到之后通过Socket向zygote发送请求,zygote收到请求之后fork出一个子进程作为app的进程。
而在这个函数中,完成初始化的擦啊欧总之后,会执行handleBindApplication
函数,执行了3个动作:
- 启动一个application对象,
- 调用application的
attchBaseContext()
函数 - 调用application的
onCreate()
函数
壳之所以能实现,也是得得益于这个步骤,以及类加载的相关原理,如下:
壳在完成解密之后,使用自定义的classloader
来加载解密之后的dex文件,并利用反射来修复一系列的变量。
在这里有一个问题:
- 为什么要使用反射定义一个新的自定义加载器而非使用系统自带的类加载器
由于Java的双亲委派机制(其实是 parent delegate而非parents delegate),会递归使用父类加载器执行类加载操作,而这个主要是为了避免用户自己实现库函数(eg:自己创建一个类为java.lang.System
,由于parent delegate机制存在,用户自己写到类不会被加载)。
而在外部类加载的时候,如果没有实现自定义类加载器,那么在加载外部类(源程序类相对于壳来说)的时候,会报ClassNotFound
异常,所以需要自定义类加载器完成外部类的加载工作。
到这里,基本上捋清楚了第一代壳的保护机制,那么,这种壳该如何去掉呢?
2.2 通用dex内存dump方法
APP脱壳的本质是对运行时内存中处于解密状态的dex的dump。
因此最重要的是,确定dex在内存中的起始位置以及大小,确定了这两点之后就能获取到dex文件。而如果在解密之后再执行dump操作,那么我们就能得到未加密的明文dex文件。
而在ART中是以
DexFile
类形式存在,在Davlik中,是以dexfile
结构体形式存在。所以在多数的Davlik虚拟机脱壳文章中都是在
dvmDexFileOpenPartial
起始处下断点,因为这个函数的参数中包含了dexfile的起始内存以及大小所以在目前的环境下,一样考虑的是hook那些使用了dexfile相关信息作为参数的函数
2.2.1 LoadClassMembers
1 | void ClassLinker::LoadClassMembers(Thread* self, const DexFile& dex_file, |
函数的第二个参数const DexFile& dex_file
包含了对当前处理的dex的dexfile对象引用
所以在这个位置可以进行脱壳
2.2.2 LoadMethod
1 | void ClassLinker::LoadMethod(Thread* self, |
同样,第二个参数包含了引用,也是一个脱壳点
2.2.3 OpenMemory or OpenCommon
1 | std::unique_ptr<const DexFile> DexFile::OpenMemory(const std::string& location, |
OpenMemory函数中包含了内存中dex的起始位置和大小。
1 | art::DexFile::OpenCommon(unsigned char const*, |
openCommon同样也有
3 查壳
3.1 壳的指纹特征
- application名称
- 固有的so名称
- so里面固有的字符
1 | 娜迦:libchaosvmp.so , libddog.solibfdog.so |