一、常见的壳
通常是看lib文件夹下so库特征,以下是市面上常见的不同厂商对APP的加固特征:
爱加密:libexec.so,libexecmain.so,ijiami.dat
梆梆: libsecexe.so,libsecmain.so , libDexHelper.so libSecShell.so
360:libprotectClass.so,libjiagu.so,libjiagu_art.so,libjiagu_x86.so
百度:libbaiduprotect.so
腾讯:libshellx-2.10.6.0.so,libBugly.so,libtup.so, libexec.so,libshell.so,stub_tengxun
网易易盾:libnesec.so
为什么要加固:
一定程度保护源代码
加固方式:
.dex加固 .so加固
二、加固原理
壳dex 读取源dex文件,加密后,写进一个新的dex文件
新APK运行
先加载壳APP—>壳APP读Dex文件末尾的源APKD大小—->在内存中壳APP解密出源APP—>运行源APP 壳APK有自己的Application对象
源APK有自己的Application对象
壳APK启动时 在AndroidMenifest.xml里找源APK的Application 执行它的oncreate方法 启动源APK
Dex文件格式: 任何类型的文件都有文件格式,对应的软件按照文件格式来就能解析出类型,.xml .json .jpeg
三、如何查看有没有壳
用apktool
对apk进行反编译,或者修改apk的后缀为zip,进行解压。查看解压后的文件下的lib目录,
可以看到这两个是用了腾讯的乐加固,或者我们不确定的话可以用jadx打开apk,也是可以发现的
都是壳的代码。不过即使怎么加壳,它都会去调源apk的Application,打开AndroidManifest.xml
文件,
可以看到,上面一个是壳的,下面是源apk的Application。
逆向/脱壳方法
反编译/Hook技术和动态调试
Hook:先取得要Hook函数/方法的控制权,不用破坏程序
动态调试:反调试,汇编,计算内存地址
四、Hook技术
改变程序执行流程的一种技术 在函数被调用前,通过HOOK技术,先得到该函数的控制权,实现该函数的逻辑改写
Hook可以在在Java层、Native层(.so库)
在代码层 寻找要Hook的地方 进行Hook 改下代码逻辑
五、脱壳工具
脱壳原理:
在壳APK解密源APK后,源APK被加载前,拦截这个过程中的系统函数 把内存种Dex dump出来。
手动脱壳:
通过动态调试,跟踪计算Dex源文件的内存偏移地址,从内存中Dump出Dex文件 ,难度大,寄存器,汇编,反调试,反读写 IDA。
工具脱壳:
HOOK技术/内存特征寻找 简单易操作
基于xposed 脱壳工具:
Fdex2:Hook ClassLoader loadClass方法
通用脱壳 dumpDex:https://github.com/WrBug/dumpDex
逆向框架: 筑好底层 提供开发接口
xposed(Java 编译,只能在java层)
frida(Python Javascript 代码注入,可以hook住java层、Native层)
六、开始脱壳(用FDex2脱壳)
首先我们打开xposed,把FDex2打开,并选择我们要的app
接下来就可以运行app,然后我们去/data/user/0/com.iCitySuzhou.suzhou001
该目录下查看,多出来两个dex文件
把这两个文件拉到电脑用kadx打开,第一个是壳
打开第二个很明显看到这才是源apk的源码,而且可以发现它对源码进行混淆了
所以到这里我们的脱壳就成功了。
七、开始脱壳(用Frida脱壳)
Frida脱壳
1 | function_address = Module.findExportByName(libname, function); |
加壳apk运行流程:app启动后–>壳dex先加载起来–>把源classes.dex读出来–>解密源classes.dex–>把源classes.dex给加载进内存–>源dex运行起来
下两篇文章都对dex进行了详解
Dex文件格式详解 https://www.jianshu.com/p/f7f0a712ddfe
ART 加载dex文件 https://www.jianshu.com/p/f81242ad8cb7
dex文件结构
数据名称 | 解释 |
---|---|
header | dex文件头部,记录整个dex文件的相关属性 |
string_ids | 字符串数据索引,记录了每个字符串在数据区的偏移量 |
type_ids | 类似数据索引,记录了每个类型的字符串索引 |
proto_ids | 原型数据索引,记录了方法声明的字符串,返回类型字符串,参数列表 |
field_ids | 字段数据索引,记录了所属类,类型以及方法名 |
method_ids | 类方法索引,记录方法所属类名,方法声明以及方法名等信息 |
class_defs | 类定义数据索引,记录指定类各类信息,包括接口,超类,类数据偏移量 |
data | 数据区,保存了各个类的真是数据 |
link_data | 连接数据区 |
header
简单记录了dex文件的一些基本信息,以及大致的数据分布。长度固定为0x70,其中每一项信息所占用的内存空间也是固定的,好处是虚拟机在处理dex时不用考虑dex文件的多样性
字段名称 | 偏移值 | 长度 | 说明 |
---|---|---|---|
magic | 0x0 | 8 | 魔数字段,值为”dex\ |
035\0”(固定的) | |||
checksum | 0x8 | 4 | 校验码 |
signature | 0xc | 20 | sha-1签名 |
file_size | 0x20 | 4 | dex文件总长度 |
…… | …… | ….. | …… |
字段太多就不都展示出来,可以看到file_size
这个就是我们要找的dex文件,因为源dex解密后会加载进内存,所以我们去Hook加载Dex的函数,把Dex从内存中dump出来。
下面这个函数就是把解密后的源dex加载进内存:
DexFile::OpenMemory(const uint8_t* base,
size_t size,
const std::string& location,
uint32_t location_checksum,
MemMap* mem_map,//nullptr const OatDexFile* oat_dex_file,
std::string* error_msg)
OpenMemory()是在安卓系统/system/lib/libart.so
里面,然后我们先把这个so文件拉到电脑用IDA打开
打开IDA选择静态调试,打开libart.so这个文件
这些就是so文件里面的函数,点击这个框,按ctrl+f进行搜索,输入OpenMemory这个函数,右键进行编辑,_ZN3art7DexFile10OpenMemoryEPKhjRKNSt3__112basic_stringIcNS3_11char_traitsIcEENS3_9allocatorIcEEEEjPNS_6MemMapEPKNS_10OatDexFileEPS9_
这个字符串就是OpenMemory函数在内存中对外的方法名,我们打开IDA就是为了找这个方法名。
现在来写脚本:
1 | import frida |
把frida-server运行起来
hook需要app运行,先把app打开,再运行脚本,
我们去手机/data/data/com.iCitySuzhou.suzhou001
目录下查看,会多了几个dex文件
把这些dex文件拉到电脑用jadx打开看下,一个个查看后,找到了源dex
到这里用frida脱壳也成功了。。。
补充
壳的种类⾮常多,根据其种类不同,使⽤的技术也不同,这⾥稍微简单分个类:
- ⼀代整体型壳:采⽤ Dex 整体加密,动态加载运⾏的机制;
- ⼆代函数抽取型壳:粒度更细,将⽅法单独抽取出来,加密保存,解密执⾏;
- 三代 VMP、Dex2C 壳:独⽴虚拟机解释执⾏、语义等价语法迁移,强度最⾼。
先说最难的 Dex2C ⽬前是没有办法还原的,只能跟踪进⾏分析; VMP 虚拟机解释执⾏保护的是映射表,只要⼼思细、功夫深,是可以将映射表还原的;
⼆代壳函数抽取⽬前是可以从根本上进⾏还原的, dump 出所有的运⾏时的⽅法体,填充到 dump 下来的 dex 中去的,这也是fart的核⼼原理。
frida-Dexdump
https://github.com/hluwa/FRIDA-DEXDump
利⽤ frida 的搜索内存,通过匹配 DEX ⽂件的特征,例如 DEX ⽂件的 文件头 中的 magic — dex.035 这个特征。 frida-Dexdump 便是这种脱壳⽅法的代表作。
- 对于于完整的 dex,采⽤暴⼒搜索 dex035 即可找到。
- ⽽对于抹头的 dex,通过匹配⼀些特征来找到,然后⾃动修复⽂件头。
抽取 -> invoke -> 还原 -> 再抽取