在 macOS 上编译去特征版 frida-server(Florida + frida 17.16.4 实战)
本文基于 autorun.top 的 frida-hide 教程 思路,在 macOS 上用 frida 17.16.4 + Florida-17.16.4 跑通全流程。
现状
网上 frida 去特征编译教程不少,但大多数有两个问题:
- 版本旧:还在讲 16.x 甚至更早,新版 frida 源码已经动过,照抄的 patch 会失败
- 环境单一:默认 Linux,macOS 用户照做会撞
sed、lief 一堆问题,而且报错信息无法明确指示
本文记录的用 macOS arm64 + frida 17.16.4 + Florida-17.16.4 实际编译通过的流程,把每个报错和修法都写清楚。换到更新的版本,核心思路不变,按文末"升级检查清单"调整即可。
背景:Florida 是什么
Florida 是一个自动跟踪 frida 上游、打 patch、构建去特征版 frida-server 的项目。它的 release 只发 patch 文件(不是完整源码),你拿官方 frida 源码 + Florida 的 patch,自己打补丁编译。
环境准备
# macOS arm64,frida 官方仓库,checkout 到 17.16.4 tag
# 需要:git、python3、ninja(fride 自带在 deps/ 里)、meson(fride configure 会拉)
# 额外:lief(去特征脚本依赖)
第一步:拉源码
# 官方 frida(Florida 就跟踪这个,不是 fork)
git clone --recurse-submodules https://github.com/frida/frida
cd frida
git checkout 17.16.4
git submodule update --init --recursive # 确保子模块对齐到这个 tag
# Florida release(只有 patch)
# 去 https://github.com/Ylarod/Florida/releases 下 17.16.4 的 source.zip,解压到隔壁
# 解压后结构:Florida-17.16.4/patches/frida-core/*.patch + patches/frida-gum/*.patch
关键:Florida 的 patch 是针对它发布那天官方 frida 默认分支 tip 写的(看它的 .github/workflows/build.yml 就是 git clone --recurse-submodules 不 pin commit)。所以你 checkout 到对应 tag 一般能对上,但 release 后上游再改过同一文件,patch 的精确行号就会漂移。
frida 主仓库根目录跑 configure(android-arm64):
./configure --host=android-arm64
# 或者你已有的 build/ 目录已经配好,跳过
这步会下载 frida 的 toolchain(deps/toolchain-macos-arm64/)、生成 build/build.ninja、build/frida-android-arm64.txt。首次比较慢。
第三步:打 patch(核心)
用 git apply --reject 批量应用。它能容错,能落地的落地,冲突的产生 .rej 文件,不会整体中止。
cd /path/to/frida/subprojects/frida-core
# 先清干净
git reset --hard HEAD && git clean -fd
# 批量 apply 10 个 patch
for p in $(ls /path/to/Florida-17.16.4/patches/frida-core/*.patch | sort); do
git apply --reject --whitespace=fix "$p"
done
判断 patch 链是否自洽,看 patch 里的 new file mode 标记,不要靠单测:
grep -E "new file mode" Florida-17.16.4/patches/frida-core/*.patch
# 会看到 0003 里有: new file mode 100644 (src/anti-anti-frida.py)
跑完 git apply --reject 后,预期产生 2 个 .rej:
lib/base/rpc.vala.rej —— 来自 0001,需手解(坑 3)
src/windows/windows-host-session.vala.rej —— 来自 0003,直接忽略(坑 4)
0001 patch 改 rpc.vala 3 处:插入 getRpcStr() 方法 + 2 处调用 + 1 处比较。其中第 3 个 hunk 能 apply 成功(把 type != "frida:rpc" 改成 type != getRpcStr(false)),但前 2 个 reject——结果就是第 128 行引用了 getRpcStr 但方法没定义,编译必报错。
失败原因是上游在 17.16.4 release 后又改过 rpc.vala,把 Object (peer: peer) 从第 11 行挪到了第 22 行,行号漂移导致 hunk 对不上。但搜索目标字符串是稳定的,用 python 行级编辑补上:
cd /path/to/frida/subprojects/frida-core
python3 - <<'EOF'
import io
path = "lib/base/rpc.vala"
with io.open(path, "r", encoding="utf-8", newline="") as f: src = f.read()
# 在构造器后插入 getRpcStr 方法
anchor = "\t\t\tObject (peer: peer);\n\t\t}\n"
assert src.count(anchor) == 1, "anchor 没找到,frida-core 改过这块了,看 0001 patch 重新定位"
method = ("\t\t\tpublic string getRpcStr(bool quote){\n"
"\t\t\t\tstring result = (string) GLib.Base64.decode((string) GLib.Base64.decode(\"Wm5KcFpHRTZjbkJq\"));\n"
"\t\t\t\tif(quote){\n\t\t\t\t\treturn \"\\\"\" + result + \"\\\"\";\n"
"\t\t\t\t}else{\n\t\t\t\t\treturn result;\n\t\t\t\t}\n\t\t\t\t}\n")
src = src.replace(anchor, anchor + "\n" + method, 1)
# 替换两处调用
src = src.replace('\t\t\t\t.add_string_value ("frida:rpc")\n',
'\t\t\t\t.add_string_value (getRpcStr(false))\n', 1)
src = src.replace('\t\t\tif (json.index_of ("\\"frida:rpc\\"") == -1)\n',
'\t\t\tif (json.index_of (getRpcStr(true)) == -1)\n', 1)
with io.open(path, "w", encoding="utf-8", newline="") as f: f.write(src)
print("rpc.vala patched")
EOF
# 验证:4 处 getRpcStr(1 定义 + 3 调用),无 frida:rpc 残留
grep -c getRpcStr lib/base/rpc.vala # 4
grep '"frida:rpc"' lib/base/rpc.vala # 应无输出
为什么不用 Edit 工具/手敲:vala 文件是 tab 缩进,Edit 的字符串匹配容易因 tab 数量不对失败。python 行级编辑 + 锚点字符串最稳。别写死行号,升级后行号会变,但搜索字符串稳定。
坑 4:windows-host-session.vala reject —— 直接忽略
0003 把 9 个文件的 frida_agent_main 改 main,其中 Windows 那个因行号漂移 reject。但你只编 android-arm64,根本不编译 Windows 目标。验证:
ls build/subprojects/frida-core/ | grep -i windows # 无输出 = 不编 Windows
直接删掉这个 .rej 即可,其它 8 个文件(darwin/freebsd/linux/qnx host-session + agent-container + 2 个 test)都已 cleanly apply。
坑 5:embed-agent.py 里路径写错(原教程提过但没说清)
0010 patch 在 src/embed-agent.py 里写死路径调用去特征脚本:
custom_script=str(output_dir)+"/../../../../frida/subprojects/frida-core/src/anti-anti-frida.py"
从 output_dir(build/subprojects/frida-core/src)往上 4 级到 frida 仓库根,再 frida/subprojects/... 多了一级 frida。编译时会报 can't open file ... anti-anti-frida.py(原教程就警告这个,但没说怎么算对)。
正确做法:去掉多余的 frida/,用 python 重新算相对路径:
python3 - <<'EOF'
import io, os
from pathlib import Path
path = "src/embed-agent.py"
with io.open(path, "r", encoding="utf-8", newline="") as f: src = f.read()
# output_dir 的实际值从 build.ninja 里 embed-agent.py 那行 COMMAND 取
# 一般是 <frida>/build/subprojects/frida-core/src
output_dir = Path("/path/to/frida/build/subprojects/frida-core/src")
real_file = Path("/path/to/frida/subprojects/frida-core/src/anti-anti-frida.py")
rel = os.path.relpath(real_file, output_dir) # 算出 ../../../../subprojects/frida-core/src/anti-anti-frida.py
lines = src.split("\n")
for i, l in enumerate(lines):
if l.startswith(' custom_script=str(output_dir)+"/'):
lines[i] = f' custom_script=str(output_dir)+"/{rel}"'
break
with io.open(path, "w", encoding="utf-8", newline="") as f: f.write("\n".join(lines))
print(f"path fixed -> {rel}")
EOF
坑 6:anti-anti-frida.py 的 sed -b 在 macOS 失效
脚本用 os.system(f"sed -b -i s/gum-js-loop/{random_name}/g {input_file}") 改 3 个线程名(gum-js-loop/gmain/gdbus)。macOS 的 BSD sed 不认 -b(GNU sed 选项),3 处全报 sed: illegal option -- b,线程名没被替换——但脚本照样打印 Patch gum-js-loop to xxx,这个假象让你以为成功了,实际产物里线程名原封不动。
正确做法:把 3 处 sed 改成 Python 原生字节替换,跨平台可靠:
python3 - <<'EOF'
import io
path = "src/anti-anti-frida.py"
with io.open(path, "r", encoding="utf-8", newline="") as f: src = f.read()
# 加辅助函数
anchor = "import sys\nimport random\nimport os\n"
assert src.count(anchor) == 1
helper = anchor + "\n\ndef _patch_bytes(path, old: bytes, new: bytes):\n with open(path, \"rb\") as f:\n data = f.read()\n data = data.replace(old, new)\n with open(path, \"wb\") as f:\n f.write(data)\n"
src = src.replace(anchor, helper, 1)
# 替换 3 处 sed
for name in ["gum-js-loop", "gmain", "gdbus"]:
old = f' os.system(f"sed -b -i s/{name}/{{random_name}}/g {{input_file}}")\n'
new = f' _patch_bytes(input_file, b"{name}", random_name.encode())\n'
assert src.count(old) == 1, f"{name} 那行没找到,脚本改过了?"
src = src.replace(old, new, 1)
with io.open(path, "w", encoding="utf-8", newline="") as f: f.write(src)
print("sed -> python done")
EOF
坑 7:frida-gum 的 ggbond 是已知特征
frida-gum 的 patch 把进程名改成 "ggbond"——这串本身已是公开已知特征(原教程作者特意提醒过)。apply 后再改成随机串:
cd /path/to/frida/subprojects/frida-gum
git apply --reject /path/to/Florida-17.16.4/patches/frida-gum/0001-Florida-pool-frida.patch
# 生成随机串改掉 ggbond
NEWNAME=$(LC_ALL=C tr -dc 'a-zA-Z' < /dev/urandom | head -c 7)
python3 - "$NEWNAME" <<'EOF'
import sys, io
path = "gum/gum.c"
with io.open(path, "r", encoding="utf-8", newline="") as f: src = f.read()
old = ' g_set_prgname ("ggbond");\n'
new = f' g_set_prgname ("{sys.argv[1]}");\n'
assert src.count(old) == 1
with io.open(path, "w", encoding="utf-8", newline="") as f: f.write(src.replace(old, new, 1))
print(f"ggbond -> {sys.argv[1]}")
EOF
注意这个进程名是编译时固定的,不是运行时随机。每次编译换一个新的。
清理 .rej,准备编译
cd /path/to/frida/subprojects/frida-core
find . -name "*.rej" -delete # rpc.vala.rej 已手解,windows 的可忽略
第四步:装 lief(去特征脚本依赖)
这是 macOS 上最容易漏的一步。anti-anti-frida.py 用 lief 解析 ELF 改符号,但编译用的 python 没装的话,脚本 4 次报 No module named 'lief',去特征等于没做,产物带着原始 frida 符号——而且编译不会因此失败(脚本失败不影响 ninja 继续链接),你不查根本发现不了。
先确认编译用的是哪个 python(从 build.ninja 看),再给那个 python 装:
# 找编译用的 python
PYBUILD=$(grep -o "/[^ ]*/python3" /path/to/frida/build/build.ninja | head -1)
echo "编译用 python: $PYBUILD"
# 装 lief
$PYBUILD -m pip install lief
# 验证
$PYBUILD -c "import lief; print(lief.__version__)"
别装到系统默认 python 上——ninja 调的是 build.ninja 里写死的那个 python,装错地方等于没装。
第五步:编译
cd /path/to/frida
make # 首次约 5-15 分钟,编 vala/C/Go
坑 8(重编译场景):ninja 不会自动重跑去特征
如果你之前已经编译过(产物已存在),改了 patch 重来时,光跑 make ninja 会说 "no work to do"——它不知道 anti-anti-frida.py 改过,也不会重跑 embed-agent 处理已有的 agent so。要强制重生成:
cd /path/to/frida/build
# 清掉 embed-agent 的产物,强制重跑
ninja -t clean subprojects/frida-core/src/frida-data-agent-blob.S
ninja subprojects/frida-core/src/frida-data-agent-blob.S
# 重新链接
ninja subprojects/frida-core/server/frida-server subprojects/frida-core/inject/frida-inject
跑这步时控制台应该看到去特征脚本的彩色输出:
Patch frida-agent: .../frida-agent-64.so
Patch `frida` to `XuKbq`
Patching section name=.rodata offset=0x... orig:FridaScriptEngine new:enignEtpircSadirF
Patch `gum-js-loop` to `ZdsHJXzQnlC`
Patch `gmain` to `eAmEY`
Patch `gdbus` to `lOkch`
Patch Finish
看到 4 个 anti-anti-frida finished 才算成功(64/32 是有效 ELF 会处理;arm64/arm 在只编 64 位 host 时是空文件,会打印 Not elf, exit,正常)。
第六步:对 frida-server 本体也跑一遍去特征
ninja 的 embed-agent 只处理 agent so(嵌进 blob 的那个)。但最终的 frida-server 本体链接了 glib,自带 gmain/gdbus 等线程名,这部分没被处理。要做到彻底,手动对 frida-server 再跑一次:
cd /path/to/frida
$PYBUILD subprojects/frida-core/src/anti-anti-frida.py build/subprojects/frida-core/server/frida-server
这步不在 ninja 构建图里,每次重新编译后都要手动跑一次,否则 frida-server 会被覆盖回带特征版本。
第七步:验证产物
SRV=build/subprojects/frida-core/server/frida-server
# 文件类型:ELF ARM aarch64, stripped
file "$SRV"
# 无 frida 导出符号
nm -D "$SRV" | grep -i frida # 应无输出
# 无三个线程名
strings "$SRV" | grep -x "gum-js-loop\|gmain\|gdbus" # 应无输出
# 无关键特征串(被反转了)
strings "$SRV" | grep -E "FridaScriptEngine|GumScript|frida_agent_main" # 应无输出
全部无输出 = 去特征生效。
产物怎么用
# push 到手机
adb push build/subprojects/frida-core/server/frida-server /data/local/tmp/frida-server
adb shell "chmod 755 /data/local/tmp/frida-server"
adb shell "su -c '/data/local/tmp/frida-server &'"
# 电脑端连(用同名改名后的 frida 工具,或直接 frida -U)
frida -U -n com.target.app -s hook.js
frida-inject 是一次性注入工具(不常驻),适合自动化场景:
adb push build/subprojects/frida-core/inject/frida-inject /data/local/tmp/
adb shell "/data/local/tmp/frida-inject -n com.target.app -s hook.js"
踩坑总结表
| # |
坑 |
现象 |
根因 |
修法 |
| 1 |
目录错 |
git am 报 not a git repo |
教程写的 build/subprojects/ 是产物目录 |
用 subprojects/(git 子模块) |
| 2 |
git am 不容错 |
行号漂移就整体失败 |
git am/--3way 对 base sha1 缺失零容忍 |
用 git apply --reject |
| — |
假象 |
以为 anti-anti-frida.py 缺失 |
--check 单测制造 |
看 new file mode 标记 |
| 3 |
rpc.vala reject |
编译报 getRpcStr 未定义 |
0001 前 2 个 hunk 行号漂移 |
python 行级编辑补 3 处 |
| 4 |
windows reject |
0003 1 个 hunk 失败 |
行号漂移 |
跳过(不编 Windows) |
| 5 |
路径错 |
编译报找不到 anti-anti-frida.py |
0010 多写一级 frida/ |
重新算相对路径 |
| 6 |
sed -b 失效 |
线程名没换(但脚本假装成功) |
macOS BSD sed 不认 -b |
改 Python 字节替换 |
| 7 |
ggbond 是特征 |
进程名是已知串 |
patch 写死 |
改随机串 |
| 8 |
lief 没装 |
去特征脚本全失败但编译照过 |
编译 python 没装 lief |
pip install lief 到编译 python |
| — |
重编译不跑去特征 |
ninja "no work to do" |
构建图不含去特征 |
ninja -t clean 强制重生成 |
升级到新版本时的检查清单
- frida 版本对齐:主仓库 checkout 到新 tag,
git submodule status 确认子模块对齐
- Florida 版本对齐:下对应版本 release,确认 patch 数量/名称没变
- 坑 3 复发风险高:
rpc.vala 是高频改动文件,重新看 0001 patch 的 hunk,确认搜索字符串(Object (peer: peer)、.add_string_value ("frida:rpc") 等)还在,不在就重新定位
- 坑 5 复发:目录结构变了重新算
output_dir → anti-anti-frida.py 的相对路径
- 坑 8 复发:确认编译用 python 没换,重装 lief
- 新 patch:Florida 可能加新 patch,重新跑
git apply --reject 看 reject 情况
- lief API 兼容:如果
anti-anti-frida.py 跑报 AttributeError,检查 lief 版本 API 是否又改了(0.x → 1.0 改过一次)
参考链接
本文基于 frida 17.16.4 + Florida-17.16.4 在 macOS arm64 实测通过。