使用Frida在多设备中批量脱壳APP

一、Frida简介

Frida是一款基于python + javascript 的hook框架,适用于android/ios/linux/win/osx等平台。Frida的动态代码执行功能,主要是在它的核心引擎Gum中用C语言来实现的。
注入模式:大部分情况下,我们都是附加到一个已经运行到进程,或者是在程序启动到时候进行劫持,然后再在目标进程中运行我们的代码逻辑。这种方式是Frida最常用的使用方式。注入模式的大致实现思路是这样的,带有GumJS的Frida核心引擎被打包成一个动态连接库,然后把这个动态连接库注入到目标进程中,同时提供了一个双向通信通道,这样控制端就可以和注入的模块进行通信了,在不需要的时候,还可以在目标进程中把这个注入的模块给卸载掉。
嵌入模式:针对没root过的设备Frida提供了一个动态连接库组件 frida-gadget, 可以把这个动态库集成到程序里面来使用Frida的动态执行功能。一旦集成了gadget,就可以和程序使用Frida进行交互。
预加载模式:自动加载Js文件。
交互原理如下如所示:

图 1-1 frida通信原理图.png

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-1 查看手机属性.png

2.2.2 下载frida-server版本

frida-server 是一个守护进程,通过TCP和Frida核心引擎通信,默认的监听端口是27042
地址: https://github.com/frida/frida/releases
注意:版本和类型对应,框架和设备对应

图2-2 frida-server版本.png

根据上一步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
图2-3 image.png

-U 代表着 USB,并且让 Frida 检查 USB-Device真机,出现图上这样就是成功了。

三、Frida脱壳原理

3.1 壳分类概述

壳的种类非常多,根据其种类不同,使用的技术也不同,这里稍微简单分个类:
 一代整体型壳:采用Dex整体加密,动态加载运行的机制;
 二代函数抽取型壳:粒度更细,将方法单独抽取出来,加密保存,解密执行;
 三代VMP、Dex2C壳:独立虚拟机解释执行、语义等价语法迁移,强度最高。
先说最难的Dex2C目前是没有办法还原的,只能跟踪进行分析;VMP虚拟机解释执行保护的是映射表,只要心思细、功夫深,是可以将映射表还原的;二代壳函数抽取目前是可以从根本上进行还原的,dump出所有的运行时的方法体,填充到dump下来的dex中去的,这也是fart的核心原理;最后也就是目前我们推荐的几个内存中搜索和dumpdexFrida工具,在一些场景中可以满足大家的需求。

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-1 result.png

连热补丁都被搜出来了,在某些一代壳上效果不错。

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"

图 3-2 image.png

搜出来的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-1 连接多台设备.png

4.2 Frida 在多设备中进行批量脱壳

脱壳思路采用的是“3.2.3 暴力搜内存:DEXDump”。
批量加壳的APP在多设备中自动化脱壳流程如下图所示:


图 4-2 批量脱壳流程图.png

注:批量自动化脱壳引擎大致流程是,对外界输送过来的批量加固APP进行筛选,如果包完整就进行在空闲的测试机中进行安装,测试机安装前初次脱壳进行初始化操作,如果安装成功使用Frida运行待测试的APP,内存转储成功后的dex保存到PC端进行处理。
项目地址:后续更新。。。。。。

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 203,456评论 5 477
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 85,370评论 2 381
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 150,337评论 0 337
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,583评论 1 273
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,596评论 5 365
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,572评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,936评论 3 395
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,595评论 0 258
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,850评论 1 297
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,601评论 2 321
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,685评论 1 329
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,371评论 4 318
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,951评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,934评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,167评论 1 259
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 43,636评论 2 349
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,411评论 2 342