Android 12集成Fart12脱壳机
zsk Lv4

环境:Ubuntu 20.04 + AOSP 12 + Pixel 4 XL (coral)
fart12 源码下载:fart12-lite.zip(提取码: vgcp)

编译 AOSP 12 准备

系统环境初始化

1
2
3
4
5
6
7
8
9
# 更新并安装 AOSP 编译必须的依赖包
sudo apt-get update
sudo apt-get install -y git-core gnupg flex bison build-essential zip curl zlib1g-dev \
gcc-multilib g++-multilib libc6-dev-i386 libncurses5 lib32ncurses5-dev \
x11proto-core-dev libx11-dev lib32z1-dev libgl1-mesa-dev libxml2-utils \
xsltproc unzip fontconfig bc kmod cpio libncurses5 python3

# 安装 OpenJDK 11 (Android 12 必须使用 JDK 11)
sudo apt-get install openjdk-11-jdk

配置 Repo 工具

1
2
3
4
5
6
7
8
mkdir ~/bin
curl https://mirrors.tuna.tsinghua.edu.cn/git/git-repo > ~/bin/repo
chmod a+x ~/bin/repo
export PATH=~/bin:$PATH

# 配置 Git 身份(必须配置,否则无法初始化)
git config --global user.email "your_email@example.com"
git config --global user.name "your_name"

初始化并同步源码

在拉取 AOSP 源码之前,由于服务器位于国外,直接同步通常会失败。使用 proxychains4 配合代理软件(如 Clash/V2Ray)来保障同步成功。

安装与配置 Proxychains

1
2
3
4
5
6
7
8
9
# 安装工具
sudo apt-get install proxychains4 -y

# 修改配置文件
sudo nano /etc/proxychains4.conf

# 在文件末尾,将默认的代理改为你本地代理软件的端口(通常是 7890 或 1080):
# 示例:假设你的代理在本地 7890 端口
socks5 127.0.0.1 7890

针对 Pixel 4 XL,我们选择一个稳定的 Android 12 分支:

1
2
3
4
5
6
7
8
9
10
# 创建工作目录
mkdir ~/aosp12 && cd ~/aosp12

# 初始化仓库
# 分支选择:android-12.0.0_r28
# 参数 --depth=1:浅克隆,大幅缩减下载体积
proxychains repo init -u https://aosp.tuna.tsinghua.edu.cn/platform/manifest -b android-12.0.0_r28 --depth=1

# 同步代码 (j8 表示 8 线程)
proxychains repo sync -j8 -c

导入 Pixel 4 XL 官方驱动

在下载驱动之前,请务必核对以下“三位一体”的关系:

  • 机型:Pixel 4 XL (coral)
  • 源码分支:android-12.0.0_r28
  • 驱动 Build ID:SQ1A.220205.002

对应关系查看 https://source.android.com/docs/setup/reference/build-numbers?hl=zh-cn

下载驱动放在aosp12的根目录:

  1. Google Drivers 下载对应机型的驱动。
  2. 通过 tar -zxvf** **解压得到两个 .sh 脚本,放入源码根目录。
  3. 执行脚本并**滑到最后输入 “I ACCEPT”**:
1
2
./extract-google_devices-coral.sh
./extract-qcom-coral.sh

替换fart12代码

将本仓库中的文件按路径替换到 AOSP 源码对应位置。建议手动打开文件对比后再替换,避免 AOSP 小版本之间的差异导致问题,不要直接整个文件覆盖。

需要将其加入 boot classpath 白名单,否则编译会报错。在 build/soong/scripts/check_boot_jars/package_allowed_list.txt 最后一行添加:

1
cn\.zskkk

准备编译

编译模式选择

默认使用 aosp_coral-userdebug 即可正常脱壳,因为 fart 代码运行在 framework 层,有权限读取 /data/local/tmp/fart.config

如果需要 eng 模式(adb shell 默认 root、关闭部分优化便于调试),可在 device/google/coral/AndroidProducts.mk 中添加:

1
2
3
4
COMMON_LUNCH_CHOICES := \
aosp_coral-eng \
aosp_coral-userdebug \
aosp_flame-userdebug \
1
2
3
4
5
6
7
8
9
10
11
12
# 启用编译缓存提高二次编译速度
export USE_CCACHE=1
export CCACHE_EXEC=/usr/bin/ccache

# 初始化编译环境
source build/envsetup.sh

# 选择 Pixel 4 XL 编译目标
lunch aosp_coral-userdebug

# 开始编译,nproc 会自动获取你虚拟机的核心数
make -j$(nproc)

如果遇到编译报错内存不够的话:

image

free -h 的输出,物理内存只有 9.7Gi,Swap 也只有 2Gi,总共加起来也就 11.7Gi 左右。

1
2
3
4
zsk@ubuntu:~/aosp12$ free -h
total used free shared buff/cache available
Mem: 9.7Gi 563Mi 8.9Gi 1.0Mi 207Mi 8.9Gi
Swap: 2.0Gi 404Mi 1.6Gi

增加物理内存或者增加交换分区

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
# 1. 禁用当前的交换文件(如果有的话,通常是 /swap.img 或类似的)
sudo swapoff -a

# 2. 创建一个 20G 的巨大交换文件(在 aosp 根目录或者根目录下都行)
# 使用 dd 命令分配空间(更稳定)
sudo dd if=/dev/zero of=/swapfile bs=1G count=20

# 3. 设置权限
sudo chmod 600 /swapfile

# 4. 格式化为交换分区
sudo mkswap /swapfile

# 5. 启用它
sudo swapon /swapfile

# 6. 确认一下
free -h

继续编译

看到 build completed successfully 就可以了

后续修改完成后如何只编译 ART 部分? 无需每次都全量 make。如果你只改了 art/ 目录,可以执行: m -j$(nproc) com.android.art 然后再执行 make snod 重新打包镜像,这样能极大节省调试时间。

镜像刷入

进入 Bootloader 模式

1
adb reboot bootloader

确认 Fastboot 状态: 手机进入黑底红字的界面后,输入:

1
fastboot devices

如果这里依然显示 no permissions,请再次执行第二步的 _sudo udevadm trigger_ 即可。

开始全量刷机: 确保你处于 ~/aosp12 目录下,且之前已经执行过 lunch

1
2
# -w 代表清空所有数据(必须要,否则容易无限重启)
fastboot flashall -w

使用

/data/local/tmp/下创建 fart.config文件,内容为 JSON 格式,一次只能指定一个目标应用:

1
{"enabled": true, "packageName": "com.xxxxxx", "appName": "xxxxxx", "isTuoke": true, "isDeep": true}

字段说明:

字段 说明
enabled 是否启用,填 true
packageName 目标 APP 的包名
appName 备注名,随意填写,勿用中文
isTuoke 是否执行脱壳
isDeep 是否深层主动调用(针对二代抽取壳)

配置好后,通过 adb 监控日志:

1
adb logcat -s "zskkk"

然后打开目标应用,等待 60 秒后自动开始脱壳。当 logcat 中输出 run over 时代表脱壳完毕:

image

结束

image

脱壳后的 dex 和 bin 文件位于 /data/data/<packageName>/zskkk/ 目录下。

白名单功能

Fart12延续了Fart10的白名单功能,即在/data/local/tmp/文件中写入需要主动调用的全类名,一行一个,两种写法的类名都可以,示例如下:

1
2
3
La/a/a;
b.b.b
Lc/c/c;

黑名单功能

/data/local/tmp/<packageName>_blacklist 文件中指定不参与主动调用的类前缀,一行一个:

1
2
3
4
5
6
7
androidx.
android.
java.
javax.
kotlin
okhttp
org.

黑名单的作用有两个:

  1. 应对反脱壳检测 — 部分加固方案会在 APP 中植入”垃圾类”,该类正常运行时不会被触发,一旦被主动调用就说明正在脱壳,随即杀死进程。通过监控脱壳流程定位到这类”垃圾类”,将其加入黑名单即可绕过。
  2. 提高效率 — androidandroidxjava 等系统类无需主动调用,排除后可加快脱壳速度。

合并dex和bin文件

脱壳完成后,/data/data/<packageName>/zskkk/ 目录下会产生大量 dex 和 bin 文件,需要将 bin 中的方法指令回填到对应的 dex 中。使用dexfixer.jar 工具完成合并。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
import os
import sys

# 脱壳过程中可能会闪退,这个时候要加一些类到黑名单再重新开始脱壳,因此需要获取数字最大的那个bin
def getMaxBin(bin_list: list) -> int:
l = [int(i.replace(".bin", "").split("_")[-1]) for i in bin_list]
return max(l)

script_dir = os.path.dirname(os.path.abspath(__file__))

input_dir = sys.argv[1] if len(sys.argv) > 1 else os.path.join(script_dir, "zskkk")
out_dir = os.path.join(input_dir, "fix_dex")
os.makedirs(out_dir, exist_ok=True)

unique_bin_id = {}

for file in os.listdir(input_dir):
if file.endswith(".bin"):
bin_id = file.split("_")[0]
if bin_id in unique_bin_id:
unique_bin_id[bin_id].append(file)
else:
unique_bin_id[bin_id] = [file]

for bin_id in unique_bin_id:
bin_list = unique_bin_id[bin_id]
max_bin = getMaxBin(bin_list)

input_dex_file = os.path.join(input_dir, bin_id + "_deep_dexfile.dex")
input_bin_file = os.path.join(input_dir, bin_id + "_deep_ins_" + str(max_bin) + ".bin")
out_dex_file = os.path.join(out_dir, bin_id + "_deep_fix.dex")

# 通过dexfixer.jar合并
command = 'java -jar dexfixer.jar "%s" "%s" "%s"' % (input_dex_file, input_bin_file, out_dex_file)
print(command)
os.system(command)

用法:

1
2
# 将 dexfixer.jar 和脚本放在同一目录,zskkk 文件夹为从手机 pull 下来的脱壳产物
python3 merge_dex.py ./zskkk

合并后的 dex 文件输出在 zskkk/fix_dex/ 目录下,可直接用 jadx 打开分析。

 评论