一、Frida简介
Frida是一款基于python + javascript 的hook框架,适用于android/ios/linux/win/osx等平台。Frida的动态代码执行功能,主要是在它的核心引擎Gum中用C语言来实现的。
注入模式:大部分情况下,我们都是附加到一个已经运行到进程,或者是在程序启动到时候进行劫持,然后再在目标进程中运行我们的代码逻辑。这种方式是Frida最常用的使用方式。注入模式的大致实现思路是这样的,带有GumJS的Frida核心引擎被打包成一个动态连接库,然后把这个动态连接库注入到目标进程中,同时提供了一个双向通信通道,这样控制端就可以和注入的模块进行通信了,在不需要的时候,还可以在目标进程中把这个注入的模块给卸载掉。
嵌入模式:针对没root过的设备Frida提供了一个动态连接库组件 frida-gadget, 可以把这个动态库集成到程序里面来使用Frida的动态执行功能。一旦集成了gadget,就可以和程序使用Frida进行交互。
预加载模式:自动加载Js文件。
交互原理如下如所示:
ps:frida官方github网址:https://github.com/frida
二、脱壳环境搭建
1.脱壳所需要的工具
名称 | 说明 |
---|---|
apk | 已加固 |
apk运行环境 | root过真机/模拟器 |
python环境 | 版本要求3.6及以上 |
Frida | 最新版本 |
2.Frida环境搭建
2.1 安装Frida客户端
首先,需要安装python3并配置好环境变量。Frida安装命令如下所示:
pip install frida
其次,Frida安装完成后,再安装frida-tools,安装命令如下所示:
pip install frida-tools
最后,输入“frida-version”,查看Frida版本。
2.2 安装Frida服务端
2.2.1 获取测试机的CPU架构
通过如下命令查看测试手机或者模拟器的属性。
命令:adb -s [device_id] shell getprop ro.product.cpu.abi
2.2.2 下载frida-server版本
frida-server 是一个守护进程,通过TCP和Frida核心引擎通信,默认的监听端口是27042
地址: https://github.com/frida/frida/releases
注意:版本和类型对应,框架和设备对应
根据上一步getprop命令得知测试机是基于x86架构的,那么我们就在该页面上选择对应的文件。笔者下载时的最新版本是 frida-server-12.11.16-android-x86.xz文件。
2.2.3 将frida-server压缩包解压并push到设备中
下载frida-server-12.11.16-android-x86.xz文件后,对其进行解压。使用adb push命令将frida-server-12.11.16-android-x86服务器端文件保存到测试机的任意文件路径下,如:/data/local/tmp。
adb -s [device_id] push /Users/用户名/Desktop/frida-server-12.11.16-android-x86 data/local/tmp/
2.2.4 运行frida-server
adb -s [设备id] shell
su
cd /data/local/tmp
chmod 777 frida-server
./frida-server
2.2.5 检查是否正常
adb forward tcp:27042 tcp:27043
adb forward tcp:27043 tcp:27043
Frida-ps -U
-U 代表着 USB,并且让 Frida 检查 USB-Device真机,出现图上这样就是成功了。
三、Frida脱壳原理
3.1 壳分类概述
壳的种类非常多,根据其种类不同,使用的技术也不同,这里稍微简单分个类:
一代整体型壳:采用Dex整体加密,动态加载运行的机制;
二代函数抽取型壳:粒度更细,将方法单独抽取出来,加密保存,解密执行;
三代VMP、Dex2C壳:独立虚拟机解释执行、语义等价语法迁移,强度最高。
先说最难的Dex2C
目前是没有办法还原的,只能跟踪进行分析;VMP
虚拟机解释执行保护的是映射表,只要心思细、功夫深,是可以将映射表还原的;二代壳函数抽取目前是可以从根本上进行还原的,dump
出所有的运行时的方法体,填充到dump
下来的dex
中去的,这也是fart
的核心原理;最后也就是目前我们推荐的几个内存中搜索和dump
出dex
的Frida
工具,在一些场景中可以满足大家的需求。
3.2 脱壳类型及原理分析
3.2.1 文件头搜dex
github地址是:https://github.com/r0ysue/frida_dump
# frida -U --no-pause -f com.xxxxxx.xxxxxx -l dump_dex.js
____
/ _ | Frida 12.8.9 - A world-class dynamic instrumentation toolkit
| (_| |
> _ | Commands:
/_/ |_| help -> Displays the help system
. . . . object? -> Display information about 'object'
. . . . exit/quit -> Exit
. . . .
. . . . More info at https://www.frida.re/docs/home/
Spawned `com.xxxxx.xxxxx`. Resuming main thread!
[Google Pixel::com.xxxxx.xxxxx]-> [dlopen:] libart.so
_ZN3art11ClassLinker11DefineClassEPNS_6ThreadEPKcmNS_6HandleINS_6mirror11ClassLoaderEEERKNS_7DexFileERKNS9_8ClassDefE 0x7adcac4f74
[DefineClass:] 0x7adcac4f74
[find dex]: /data/data/com.xxxxx.xxxxx/files/7abfc00000_8341c4.dex
[dump dex]: /data/data/com.xxxxx.xxxxx/files/7abfc00000_8341c4.dex
[find dex]: /data/data/com.xxxxx.xxxxx/files/7ac4096000_6e6c8.dex
[dump dex]: /data/data/com.xxxxx.xxxxx/files/7ac4096000_6e6c8.dex
[find dex]: /data/data/com.xxxxx.xxxxx/files/7ac37c4028_8781c4.dex
[dump dex]: /data/data/com.xxxxx.xxxxx/files/7ac37c4028_8781c4.dex
其核心逻辑原理就是下面一句话magic.indexOf("dex") == 0,只要文件头中含有魔数dex,就把它dump下来。
if (dex_maps[base] == undefined) {
dex_maps[base] = size;
var magic = ptr(base).readCString();
if (magic.indexOf("dex") == 0) {
var process_name = get_self_process_name();
if (process_name != "-1") {
var dex_path = "/data/data/" + process_name + "/files/" + base.toString(16) + "_" + size.toString(16) + ".dex";
console.log("[find dex]:", dex_path);
var fd = new File(dex_path, "wb");
if (fd && fd != null) {
var dex_buffer = ptr(base).readByteArray(size);
fd.write(dex_buffer);
fd.flush();
fd.close();
console.log("[dump dex]:", dex_path);
}
}
}
}
3.2.2 DexClassLoader:objection
安卓只能使用继承自BaseDexClassLoader的两种ClassLoader,一种是PathClassLoader,用于加载系统中已经安装的apk;一种就是DexClassLoader,加载未安装的jar包或apk。
可以用objcetion直接在堆上暴力搜索所有的dalvik.system.DexClassLoader实例,效果见下图:
# android heap search instances dalvik.system.DexClassLoader
连热补丁都被搜出来了,在某些一代壳上效果不错。
3.2.3 暴力搜内存:DEXDump
github地址:https://github.com/hluwa/FRIDA-DEXDump
对于完整的dex
,采用暴力搜索dex035
即可找到。
而对于抹头的dex
,通过匹配一些特征来找到,然后自动修复文件头。
效果非常好:
root@roysuekali:~/Desktop/FRIDA-DEXDump# python main.py
[DEXDump]: found target [7628] com.xxxxx.xxxxx
[DEXDump]: DexSize=0x8341c4, SavePath=./com.xxxxx.xxxxx/0x7abfc00000.dex
[DEXDump]: DexSize=0x8341c4, SavePath=./com.xxxxx.xxxxx/0x7ac0600000.dex
root@roysuekali:~/Desktop/FRIDA-DEXDump# du -h com.xxxxx.xxxxx/*
8.3M com.xxxxx.xxxxx/0x7abfc00000.dex
8.3M com.xxxxx.xxxxx/0x7ac0600000.dex
root@roysuekali:~/Desktop/FRIDA-DEXDump# file com.xxxxx.xxxxx/*
com.xxxxx.xxxxx/0x7abfc00000.dex: Dalvik dex file version 035
com.xxxxx.xxxxx/0x7ac0600000.dex: Dalvik dex file version 035
打开dump下来的dex,非常完整,可以用jeb直接解析。用010打开可以看到完整的文件头——dexn035,其实现代码也是简单粗暴,直接搜索:64 65 78 0a 30 33 35 00:
Memory.scanSync(range.base, range.size, "64 65 78 0a 30 33 35 00").forEach(function (match) {
var range = Process.findRangeByAddress(match.address);
if (range != null && range.size < match.address.toInt32() + 0x24 - range.base.toInt32()) {
return;
}
var dex_size = match.address.add("0x20").readInt();
if (range != null) {
if (range.file && range.file.path
&& (range.file.path.startsWith("/data/app/")
|| range.file.path.startsWith("/data/dalvik-cache/")
|| range.file.path.startsWith("/system/"))) {
return;
}
if (match.address.toInt32() + dex_size > range.base.toInt32() + range.size) {
return;
}
}
还有一部分想要特征匹配的功能还在实现中:
// @TODO improve fuzz
if (
range.size >= 0x60
&& range.base.readCString(4) != "dexn"
&& range.base.add(0x20).readInt() <= range.size //file_size
// && range.base.add(0x24).readInt() == 112 //header_size
&& range.base.add(0x34).readInt() < range.size
&& range.base.add(0x3C).readInt() == 112 //string_id_off
) {
result.push({
"addr": range.base,
"size": range.base.add(0x20).readInt()
});
}
3.2.4 暴力搜内存:objection
既然直接使用Frida的API可以暴力搜索内存,那么别忘了我们上面介绍过的objection也可以暴力搜内存。
# memory search "64 65 78 0a 30 33 35 00"
搜出来的offset是:0x79efc00000,大小是c4 41 83 00,也就是0x8341c4,转化成十进制就是8602052,最后dump下来的内容与FRIDA-DEXDump脱下来的一模一样,拖到jdax里可以直接解析。
四、Frida用于自动化
在Frida出现之前,没有任何一款工具,可以在语言级别支持直接在电脑上调用app中的方法。像Xposed是纯Java,根本就没有电脑上运行的版本;各种Native框架也是一样,都是由C/C++/asm实现,根本与电脑毫无关系。
而Frida主要是一款在电脑上操作的工具,其本身就决定了其“高并发”、“多联通”、“自动化”等特性:
“高并发”:同时操作多台手机,同时调用多个手机上的多个app中的算法;
“多联通”:电脑与手机互联互通,手机上处理不了的在电脑上处理、反之亦然;
“自动化”:手机电脑互相协同,实现横跨桌面、移动平台协同自动化利器。
4.1 连接多台设备
Python bindings在安装好frida-tools的时候已经默认安装在我们的电脑上了,可以直接使用。
连接多台设备非常简单,如果是USB口直接连接的,只要确保adb已经连接上,如果是网络调试的,也要用adb connect连接上,并且都开启frida server,键入adb devices或者frida-ls-devices命令时多台设备的id都会出现,最终可以使用frida.get_device(id)的API来选择设备,如下图所示。
4.2 Frida 在多设备中进行批量脱壳
脱壳思路采用的是“3.2.3 暴力搜内存:DEXDump”。
批量加壳的APP在多设备中自动化脱壳流程如下图所示:
注:批量自动化脱壳引擎大致流程是,对外界输送过来的批量加固APP进行筛选,如果包完整就进行在空闲的测试机中进行安装,测试机安装前初次脱壳进行初始化操作,如果安装成功使用Frida运行待测试的APP,内存转储成功后的dex保存到PC端进行处理。
项目地址:后续更新。。。。。。