当前位置: 首页 > news >正文

FART 精准脱壳:通过配置文件控制脱壳节奏与范围

版权归作者所有,如有转发,请注明文章出处:https://cyrus-studio.github.io/blog/

前言

由于 FART 默认会对所有 app 进行脱壳,每次 app 启动都会自动脱壳,而且会对 app 中所有类发起主动调用,这样效率比较慢,遇到 FART 对抗类也不能选择性跳过。

如何通过一份简单的配置文件,实现对 FART 脱壳过程的精准控制:包括是否启用脱壳、延迟时间、需要主动调用的类列表、排除类规则等。提高脱壳效率,也可以避开一些垃圾类(FART 对抗类)的调用。

关于 FART 的详细介绍参考下面的文章:

  • FART 自动化脱壳框架简介与脱壳点的选择

  • FART 主动调用组件设计和源码分析

  • 移植 FART 到 Android 10 实现自动化脱壳

  • FART 自动化脱壳框架一些 bug 修复记录

  • 使用 Frida 增强 FART:实现更强大的 Android 脱壳能力

  • 攻防 FART 脱壳:特征检测识别 + 对抗绕过全解析

通过配置文件控制脱壳节奏与范围

例如,配置项如下:

# 是否开启脱壳功能(true 开启,false 关闭)
dump=true# 启动后延迟多少毫秒再进行脱壳(单位:毫秒),避免应用初始化未完成
sleep=60000# 明确指定哪些类名或包路径需要主动调用以触发加载(支持通配符 *)
# 示例:ff.l0.* 表示 ff.l0 包下所有类
force=ff.l0.*# 忽略哪些类或包路径(支持通配符 *)
# 通常用于排除系统类、常见库类、FART对抗类等
ignore=androidx.*,android.*,com.google.android.*,org.jetbrains.*,kotlinx.*,kotlin.*,com.alibaba.android.arouter.*,org.intellij.*

效果说明:

  • force=:指定你想确保加载的类

  • ignore=:忽略系统包或你不想触发加载的类

  • 二者同时存在时,force 优先生效

  • 支持使用 * 匹配多个类名

1. 配置解析类实现

增加一个 Cyrus 类 用于读取和解析脱壳配置文件,并提供按类名判断是否应被主动调用的能力,配合 FART 脱壳框架实现精细化控制脱壳流程。

package android.app;import android.util.Log;
import java.io.*;
import java.util.*;
import java.util.regex.Pattern;public class Cyrus {private static final String TAG = "Cyrus";private static boolean initialized = false;private static boolean dumpEnabled = false;private static int sleepTimeMs = 0;private static List<Pattern> forceCallClassPatterns = new ArrayList<>();private static List<Pattern> ignoredClassPatterns = new ArrayList<>();/*** 初始化 Cyrus 配置* 从 /data/data/{packageName}/cyrus.config 读取配置项:* dump, sleep, force, ignore** @param packageName 应用包名*/public static void init(String packageName) {if (initialized) return;File configFile = new File("/data/data/" + packageName + "/cyrus.config");if (!configFile.exists()) {Log.w(TAG, "Config file not found: " + configFile.getPath());initialized = true;return;}try (BufferedReader reader = new BufferedReader(new FileReader(configFile))) {String line;while ((line = reader.readLine()) != null) {line = line.trim();if (line.startsWith("dump=")) {dumpEnabled = line.substring(5).equalsIgnoreCase("true");} else if (line.startsWith("sleep=")) {sleepTimeMs = Integer.parseInt(line.substring(6));} else if (line.startsWith("force=")) {String[] parts = line.substring(6).split(",");for (String part : parts) {forceCallClassPatterns.add(Pattern.compile(convertToRegex(part)));}} else if (line.startsWith("ignore=")) {String[] parts = line.substring(7).split(",");for (String part : parts) {ignoredClassPatterns.add(Pattern.compile(convertToRegex(part)));}}}} catch (Exception e) {Log.e(TAG, "Failed to read config: " + e.getMessage(), e);}initialized = true;}/*** 是否启用脱壳功能* @return true 表示启用*/public static boolean isDumpEnabled() {return dumpEnabled;}/*** 获取脱壳前的延迟休眠时间(毫秒)* @return 休眠时间(单位:毫秒)*/public static int getSleepTimeMs() {return sleepTimeMs;}/*** 获取匹配主动调用类的正则规则列表* @return 正则 Pattern 列表*/public static List<Pattern> getForceCallClassPatterns() {return forceCallClassPatterns;}/*** 获取忽略主动调用类的正则规则列表* @return 正则 Pattern 列表*/public static List<Pattern> getIgnoredClassPatterns() {return ignoredClassPatterns;}/*** 判断一个类是否需要在脱壳线程启动时被主动调用。* <p>* 判断逻辑如下:* 1. 如果配置中设置了 force 规则(forceCallClassPatterns 非空):*    - 只有匹配 force 列表中的类会返回 true,其余类返回 false。* 2. 如果未设置 force,但配置了 ignore 规则(ignoredClassPatterns 非空):*    - 匹配 ignore 列表的类返回 false,其余返回 true。* 3. 如果 force 和 ignore 都为空:*    - 默认所有类都返回 true。* 4. 如果同时配置了 force 和 ignore,则优先判断 force*/public static boolean shouldForceCall(String className) {if (!forceCallClassPatterns.isEmpty()) {for (Pattern force : forceCallClassPatterns) {if (force.matcher(className).matches()) {return true;}}return false;}if (!ignoredClassPatterns.isEmpty()) {for (Pattern ignored : ignoredClassPatterns) {if (ignored.matcher(className).matches()) {return false;}}}return true;}/*** 将配置文件中的通配符路径转为正则表达式* 例如 ff.l0.* → ff\.l0\..** @param pattern 原始配置字符串* @return 正则表达式字符串*/private static String convertToRegex(String pattern) {// exact match or wildcard * supportif (!pattern.contains("*")) {return Pattern.quote(pattern);}return pattern.replace(".", "\\.").replace("*", ".*");}
}

2. 脱壳线程实现修改

在 launchInspectorThread 方法里:

  • 调用 init 初始化配置

  • 通过 Cyrus.isDumpEnabled() 判断当前 app 是否需要脱壳

  • 通过 Cyrus.getSleepTimeMs() 方法获取配置的休眠时间

public static void launchInspectorThread(Context context) {new Thread(new Runnable() {@Overridepublic void run() {// 初始化配置Cyrus.init(context.getPackageName());// 判断是否需要脱壳if (Cyrus.isDumpEnabled()) {// 休眠try {Log.e("ActivityThread", "start sleep......" + Cyrus.getSleepTimeMs());Thread.sleep(Cyrus.getSleepTimeMs());} catch (InterruptedException e) {e.printStackTrace();}// 开始脱壳Log.e("ActivityThread", "sleep over and start startCodeInspection");startCodeInspection();Log.e("ActivityThread", "startCodeInspection run over");}}}).start();
}

另外把 launchInspectorThread 的调用放到 handleBindApplication 里,因为 performLaunchActivity 中有可能发生多次调用。

private void handleBindApplication(AppBindData data) {...//addlaunchInspectorThread(appContext);
}

3. 主动调用范围过滤

在 dispatchClassTask 中通过 Cyrus.shouldForceCall(eachclassname) 判断是否需要加载并调用当前类

public static void dispatchClassTask(ClassLoader appClassloader, String eachclassname, Method dumpMethodCode_method) {boolean shouldForceCall = Cyrus.shouldForceCall(eachclassname);Log.i("ActivityThread", (shouldForceCall ? "[load]" : "[skip]") + " dispatchClassTask: " + eachclassname);if (!shouldForceCall) {return;}...
}

重新编译系统

把修改后的 FART 代码替换到 Android 系统里面,重新编译。

# 初始化编译环境
source build/envsetup.sh# 设置编译目标
breakfast wayne# 回到 Android 源码树的根目录
croot# 开始编译
brunch wayne

如何编译 FART ROM 参考这篇文章:移植 FART 到 Android 10 实现自动化脱壳

生成 OTA 包

./sign_ota_wayne.sh

编译完成

word/media/image1.png

刷机

由于我这里是在 WSL 中编译,先把 ota 文件 copy 到 windwos 目录下

cp ./signed-ota_update.zip /mnt/e/lineageos/xiaomi6x_wayne_lineageos-17.1_signed-ota_update_fart_cyrus.zip

设备进入 recovery 模式(或者同时按住【音量+】和【开机键】)

adb reboot recovery

【Apply update】【Apply from adb】开启 adb sideload

word/media/image2.png

开始刷机

adb sideload E:\lineageos\xiaomi6x_wayne_lineageos-17.1_signed-ota_update_fart_cyrus.zip

成功刷入后重启手机。

脱壳配置

1. 获取 app 包名

你可以使用下面的 adb 命令来获取当前前台 app 的包名

Mac/Linux:

adb shell dumpsys window | grep -E 'mCurrentFocus|mFocusedApp'

Windows:

 adb shell dumpsys window | Select-String 'mCurrentFocus|mFocusedApp'

示例输出:

  mCurrentFocus=Window{b3fdf6e u0 com.shizhuang.duapp/com.shizhuang.duapp.du_login.optimize.LoginContainerActivityV2}mFocusedApp=AppWindowToken{c3cf4d4 token=Token{a76da27 ActivityRecord{fcc84e6 u0 com.shizhuang.duapp/.du_login.optimize.LoginContainerActivit
yV2 t55}}}mFocusedApp=Token{a76da27 ActivityRecord{fcc84e6 u0 com.shizhuang.duapp/.du_login.optimize.LoginContainerActivityV2 t55}}

提取其中的包名部分(如 com.shizhuang.duapp)。

2. 配置文件

通过下面命令把配置文件推送到 /data/data/<packageName>/cyrus.config 路径下:

假设只脱壳 ff 包下的类

adb shell 'cat > /data/data/com.shizhuang.duapp/cyrus.config <<EOF
dump=true
sleep=60000
force=ff.*
EOF'
  • cat > 表示覆盖写入

  • cat >> 表示追加写入

假设忽略 androidx.,android.,com.google.android.*… 中的类

adb shell 'cat > /data/data/com.shizhuang.duapp/cyrus.config <<EOF
dump=true
sleep=60000
ignore=androidx.*,android.*,com.google.android.*,org.jetbrains.*,kotlinx.*,kotlin.*,com.alibaba.android.arouter.*,org.intellij.*
EOF'

注意:如果 force 和 ignore 参数同时存在优先 force。

开始脱壳

清空日志缓存

adb logcat -c

输出日志到文件

adb logcat -v time > logcat.txt

打开 app 等待 60 秒开始自动脱壳(比如:只脱壳 ff 包下的类)。

word/media/image3.png

等输出 run over 就是脱壳完成。

word/media/image4.png

脱壳完成

FART 脱壳结束得到的文件列表(分 Execute 与 主动调用两类):

  1. Execute 脱壳点得到的 dex (*_dex_file_execute.dex)和 dex 中的所有类列表( txt 文件)

  2. 主动调用时 dump 得到的 dex (*_dex_file.dex)和此时 dex 中的所有类列表,以及该 dex 中所有函数的 CodeItem( bin 文件)

word/media/image5.png

完整源码

开源地址:https://github.com/CYRUS-STUDIO/FART

相关文章:

  • AI,如何重构理解、匹配与决策?
  • Oracle数据库笔记
  • [C]extern声明变量报错:undefined reference终极解决方案
  • 第五期书生大模型实战营-《L1G1-玩转书生大模型 API 之 Browser-Use 实践》
  • 若依Ruoyi中优先从本地文件加载静态资源
  • 理解网络协议
  • 3D动画在微信小程序的实现方法
  • el-amap-bezier-curve运用及线弧度设置
  • Vue前端篇——项目目录结构介绍
  • 学习笔记(23): 机器学习之数据预处理Pandas和转换成张量格式[1]
  • socket是什么
  • Java - 数组
  • 技术文章大纲:SpringBoot自动化部署实战
  • 【echarts】堆叠柱形图
  • 6.4 C++作业
  • Learning a Discriminative Prior for Blind Image Deblurring论文阅读
  • [C]深入解析条件式日志宏的设计原理
  • machine_env_loader must have been assigned before creating ssh child instance
  • CMake入门:3、变量操作 set 和 list
  • [蓝桥杯]碱基
  • 南通网站建设教程/网站建站系统
  • 怎么样子做网站/企业网络推广技巧
  • 杭州哪里做网站好/小程序设计
  • 安徽安庆邮编/网站如何优化排名
  • 校园网站建设提升/论坛推广网站
  • 教育网站集群建设方案/网络舆情分析