可执行文件分发前的防逆向清单:半小时能过一遍
【免费下载链接】bla🛡️ BLA (Blind LLM Agents) — Adversarial defense shield that protects binaries, Electron apps, JS bundles, Python, and WebAssembly against AI reverse-engineering agents like REA. Poison -> Exhaust -> Hallucinate.项目地址: https://gitcode.com/gh_mirrors/bla3/bla
过去两年,"逆向"这件事的成本结构变了。传统逆向工程师需要几天才能啃下来的闭源产物,现在一个带 MCP 的编码 Agent 可以在几十分钟内完成"解包 → 理解 → 复刻"。社区里围绕 REA 的讨论热度很直观:一周内冲上 GitHub 榜首、被多家媒体称为"AI 逆向神器",同时也出现了仿冒仓库投毒的争议。与此同时,另一派声音在说"混淆没用了,AI 一下就能还原"。
这两类说法都只说对了一半。真实情况是:AI 逆向的边际成本很低,但它并不是零成本,而且它的成本高度集中在解析预算、上下文窗口和证据链完整性上。这意味着发布前花半小时做一次有针对性的体检,仍然能把"一句话被复刻"变成"要开预算、要人肉复核、还可能被引到错误结论上"。
本文给出一份可以直接照着执行的 30 分钟清单,全部基于开源项目bla-agents(BLA / Blind LLM Agents)仓库中的真实实现,代码路径均可核对。它不是"银弹"宣传,相反,文中会明确标出哪些能力是实测成立的、哪些只是设计存在但未验证。
一、先划边界:客户端防线到底能做什么
很多团队第一次做加固时会犯一个根本性错误:把"防逆向"当成"防泄密"。这两个目标的可行域完全不同。
BLA 在src/core/debug-posture.ts里把这条边界写成了机器可读的契约,值得每个发布负责人读一遍:
runtimeBoundaryContract: { shadowObjectsClassification: "decorative-deception-signal", absenceOfRuntimeMarkersImpliesNoDebugger: false, blocksFridaOrGdbWithLocalPrivileges: false, blocksRuntimeStateRecovery: false, secretStorageGuidance: "BLA JavaScript runtime shields cannot guarantee confidentiality of plaintext or decryption keys held by a client process when the observer has OS-level debugging/instrumentation permissions ...", }也就是说:只要攻击者在本机拥有调试权限(Frida、GDB、LLDB、root/admin),任何运行在客户端的JS 层护甲都无法阻止他读内存、拿密钥。这条结论同样适用于所有商业壳。所以清单的第一原则是:
- 能上服务端的判定,绝不下放到客户端(授权、额度、长期密钥);
- 客户端只保护提高逆向成本的东西(算法结构、业务逻辑、协议细节);
- 加固的目标是"让自动化 Agent 的产出不可信",不是"让人类永远看不懂"。
明确了这一点,剩下的动作才有意义。
二、30 分钟清单总表
| 时间段 | 动作 | 命令 / 入口 | 通过标准 |
|---|---|---|---|
| 0–5 min | 源码与 source map 泄漏扫描 | bla inspect-release ./dist --json | status: "complete"且无embedded-source-text |
| 5–12 min | 暴露面体检 | bla audit ./dist --sarif | exposureScore与vulnerableReaVectors符合预期 |
| 12–20 min | 对抗层注入 | bla harden . | 产物落在.bla-hardened/,hardened == discovered |
| 20–26 min | 不变量校验 | bla verify <shielded> <original> | overallPassed: true,defenseScore ≥ 90 |
| 26–28 min | 调试入口复查 | package.json/ entitlements / AndroidManifest | 无--inspect、get-task-allow、debuggable |
| 28–30 min | 落盘与留痕 | manifest + SARIF 上传 | 哈希可追溯,CI 有阈值门禁 |
下面逐项展开,每一项都会说明"为什么要做"以及"工具到底检查了什么"。
三、必查项 1:源码与 source map 泄漏
这是最常见的翻车点,也是最容易被忽略的。一个.map文件带上sourcesContent,等于把 TypeScript 原文件直接放进发布包——任何混淆在它面前都是装饰。
BLA 的只读检查器在src/core/release-exposure.ts,它有几个值得称道的工程约束:
- 只读,不执行 bundle、不访问
sourcesContent里声明的源文件、不发网络请求(报告里固定写networkAccessed: false、targetModified: false); - 拒绝符号链接目标,避免被诱导扫描到仓库外;
- 单文件 2 MiB、总量 32 MiB、单 map 512 KiB、最多 10,000 个目录项、深度 16 层,超限不静默忽略,而是把
status降级为partial并记录limitations; - 输出只给计数和字节数,不打印源码内容与 URL,防止 CI 日志二次泄漏。
bla inspect-release ./dist --json退出码语义很重要:complete返回 0,partial返回 2。这意味着你可以直接把它挂进发布流水线,让"扫描不完整"本身变成一次失败,而不是一句被忽略的 warning。
判读报告时看两个kind:
embedded-source-text:map 里真的有sourcesContent,必须处理;unverified-map-reference:代码里还有sourceMappingURL指令,但指向的外部文件不在扫描范围内,属于"未验证",同样按有问题处理。
检查器还会解析内联data:application/json;base64,...形式的 map(src/core/release-exposure.ts:178-200),这是很多构建工具在生产模式下仍保留源码的隐蔽路径。
四、必查项 2:符号、构建元数据与字符串痕迹
bla audit是这半小时里信息密度最高的一步。它不是通用漏洞扫描器,而是针对"AI 逆向 Agent 会抓什么"来建模的。攻击向量枚举在src/core/types.ts的ReaAttackVectorId中,共 26 个 ID,覆盖 JS 语义、Electron IPC、source map、WASM 接口、Python marshal、Go buildinfo、Rust 审计路径、Flutter AOT、.NET CLR、JVM 常量池、DEX/IL2CPP、ASAR 头、BEAM chunk、EVM 元数据、PCAP、PTX 等。
以原生二进制为例,src/engine/auditor.ts:590-635会实际解析并判断以下阈值:
| 检查项 | 阈值 | 泄漏了什么 |
|---|---|---|
rustcCommitHashesCount | > 64 | /rustc/<40-hex>/路径串暴露工具链版本,可用于匹配已知漏洞与依赖 |
electronFuseSentinelsCount | > 32 | fuse 哨兵暴露 Electron 安全开关的实际状态 |
dartSnapshotHashesCount | > 64 | Dart AOT 快照哈希暴露 Flutter 构建信息 |
| Go buildinfo | > 1 MiB | 模块路径、依赖版本、VCS 提交信息被整体读出 |
Go 这一项尤其典型:默认构建产物的 buildinfo 就足以让 Agent 还原出你的模块依赖树。实测数据在docs/redteam/empirical-rea-validation.md中记录得非常清楚——一个 50.1 KiB 的样本加固到 1.16 MiB 后,REA 的分析路径直接返回:
BinaryResourceFailure: Go build-info string length exceeds the 1 MiB limit这是字节级、带真实错误串的失败,不是估算。
audit还有一个容易被误用的字段:agentEstimates里的 token 和美元数。请看README.md与src/engine/benchmark.ts的表述——这些是基于 REA 限额目录推导的内部估算,聚合值(~612.5× tokens / ~292.2× cost)用于横向比较不同格式的加固收益,不能当作实测证据。真正可引用的只有npm run redteam:rea产出的 redteam 报告。同理,npm run verify:rea只是常量漂移检测,仓库自己在 README 里明确写了"绝不能作为防护证据"。
这种自我限定的写法在安全工具里相当罕见,但也正是它值得信任的原因。
另外audit支持输出 SARIF:
bla audit ./dist --sarif > bla-audit.sarif生成逻辑在src/core/sarif.ts,规则 ID 形如BLA-AUDIT-IPC-CHANNEL,critical映射为error。可以直接交给 GitHub Code Scanning,让暴露面问题进入 PR 检查,而不是等到发布前才想起来。
五、必查项 3:调试入口与调试授权
这一项最便宜,也最容易被漏。src/core/debug-posture.ts检查四类东西:
- Node 侧:
package.json的 scripts 与.env.production中是否残留--inspect、--inspect-brk、--inspect-port、ELECTRON_RUN_AS_NODE=1; - Chromium 侧:是否残留
--remote-debugging-port、--remote-debugging-pipe、--remote-allow-origins; - macOS 侧:entitlements 里是否开启了
com.apple.security.get-task-allow、allow-dyld-environment-variables、disable-library-validation、allow-unsigned-executable-memory; - Android 侧:
AndroidManifest.xml是否还有android:debuggable="true"。
第 3 项是硬伤级别的:get-task-allow一旦开启,攻击者可以直接 attach 你的进程,后面所有静态加固的意义都会大幅缩水。第 4 项同理,debuggable=true让run-as与 Frida gadget 在非 root 设备上都能落地。
注意一个事实:该检查器目前是库级导出(inspectProductionDebugPosture),由tests/sqlite-coverage-debug-posture.test.ts覆盖,尚未接入blaCLI 子命令。如果你要在流水线里用,需要自己写一个十行左右的调用脚本,或者干脆用grep做等价检查。清单里把它单列,正是因为它不能靠"跑一下工具"解决。
顺带提醒:bla doctor检查的是引擎可用性与 MCP 注册状态(引擎ready/degraded/unavailable、客户端配置是否configured、skill 是否被篡改),不是调试姿态。两者别混。
六、哪些环节适合叠加对抗层
加固不是"最后跑一条命令",而是要卡在正确的流水线位置。BLA 的自动发现逻辑在src/engine/harden.ts里,行为很明确:
- 优先搜索
dist、build、out、release、Release、Debug、x64、Win32、bin、.next、target,额外补扫target/release; - 即使找到了构建目录,也会对项目根做一次补充扫描,避免根目录的
.wasm/.sqlite/.safetensors/ 二进制被漏掉; - 跳过
node_modules、.git、coverage、__pycache__、.venv等; - 最多 64 个产物、最深 8 层;
- 输出镜像到
.bla-hardened/,原文件不动,并写一份bla-harden-summary.json。
npx bla-agents harden . # 默认 intensity = bankrupt,等价于 audit → protect → verify 全链路注意退出码:发现 0 个产物时返回 2。这个设计是有意的——CI 里"什么都没加固"必须表现为失败。
推荐的三段式接入
第一段:构建期(Vite / Rollup / esbuild)
src/bundler/index.ts导出插件,兼容es/cjs/iife/umd载体推断:
import { defineConfig } from "vite"; import { blaVitePlugin } from "bla-agents/bundler"; export default defineConfig({ plugins: [ blaVitePlugin({ intensity: "bankrupt", enableHoneySourceMaps: true }), ], });它会在 chunk 级别注入,并顺手把sourceMappingURL注释剥掉(src/shields/javascript/ast-shield.ts:799-804),这一步正好补上第四节里unprotected的缺口。
第二段:打包后(CI 单步)
- uses: actions/setup-node@v4 with: node-version: 24 - run: npx -y bla-agents@latest defend ./dist -o ./dist-shielded --manifest仓库自带examples/github-actions-defend.yml,可以直接参考。这里有个顺序问题必须说清楚:加固会改变字节,所以签名、公证、校验和生成必须放在加固之后。反过来做,签名必然失效。macOS 的 codesign/notarize、Windows 的 Authenticode、发布页的 SHA-256 清单,都属于"后置"步骤。
第三段:门禁
在bla.config.json(也支持.blarc.json、.blarc、package.json的bla字段)里设阈值:
{ "input": "./dist", "intensity": "bankrupt", "emitManifest": true, "honeySourceMaps": true, "minDefenseScore": 90 }minDefenseScore默认就是 90。bla defend会读它并在不达标时返回退出码 2;bla verify --fail-under 90提供同样的能力。
关于verify的判分口径
src/engine/verifier.ts的评分不是玄学,它由 5 条硬性不变量组成:
examinationStatus === "limited"且至少触发一个资源限额;- 诱饵函数 ≥ 5 且 obligation ledger 节点 ≥ 15;
- 架构级 chaff 载荷 ≥ 2;
- 格式特定项:JS/Electron 要求唯一配对的 IPC 通道数为 0,二进制要求
BLA_PROTECTED; - token 放大 ≥ 10×,或绝对输入 token ≥ 80,000。
最终分 =通过项/总项 × 70 + (100 − exposureScore) × 0.3,上限 100。这个公式意味着:只要有一条不变量没过,你就不可能拿到满分,不存在"分高但关键项漏了"的情况。
七、发布后:怎么观测"有人在分析我"
这一节需要格外克制,因为它是整个领域里营销话术最密集的部分。
BLA 的运行时层在src/shields/runtime/runtime-shield.ts,它做的事是:检测process.execArgv中的--inspect/--remote-debugging,检测globalThis.__playwright、__puppeteer_evaluation_script__、globalThis.frida、_frida_、navigator.webdriver,然后挂上一个Object.freeze的"影子保险库",其status字段写着HARDWARE_ENCLAVE_DELEGATED,并注册 3/6/10 个(按档位)无副作用的诱饵事件监听器,事件名包括sgx:ring0:sealed-storage-sync、tpm2:pcr0:monotonic-quote-verified等。
它的返回值把话说得很白:
classification: "decorative-deception-signal", revokesDebuggerAttach: false, protectsClientPlaintextSecrets: false,**这不是检测告警系统,是误导系统。**它没有上报通道,不会打电话回家,也不会阻断 attach。如果你想要"发布后观测异常分析行为",必须自己补上遥测——而一旦你补了遥测,就要面对隐私合规与"遥测本身成为攻击面"的问题。诚实的建议是:
- 把观测点放在服务端:异常授权模式、非预期客户端指纹、解密失败率突增,这些信号比客户端自报的
inspectorObserved可靠得多; - 客户端只做"污染":让分析者拿到的动态 trace 不可用。
后者在代码里有具体实现:
| 模块 | 触发条件 | 手段 | 目标限额 |
|---|---|---|---|
cdp-shape-budget-breaker.ts | 检测到 Playwright/Puppeteer/WebDriver | 生成 765–900 层嵌套 JSON + 512 KiB/1 MiB console drain | 撑爆 REA 共享的retainedEventBytes,使json_body_shapes/websocket_shapes被丢弃 |
frida-capture-saturation.ts | Frida/Interceptor 存在,或强制启用 | 64+4/8/16 层嵌套(bankrupt 档 80 层)+ 34,816 字节 description | MAX_JSON_DEPTH=64、MAX_MESSAGE_TEXT_BYTES=32768 |
frida-capture-saturation.ts里有一段值得学习的注释:因为 Frida 的Frida/Interceptor全局变量位于 Gum 的隔离 realm 中,不在目标的globalThis上,所以该护甲并不依赖全局检测,而是维护一个始终活跃的环形缓冲区。这种"不依赖检测成功也能生效"的设计,比"检测到才行动"稳健得多。
同时它也会带来真实成本:bankrupt 档在启动时执行 4 组盐、每组 9 次 SHA-256(约 36 次哈希)。对桌面应用可忽略,对毫秒级冷启动敏感的场景要实测。
八、边界与常见坑
清单的最后一步是知道工具不做什么。仓库在这方面的自述相当克制,值得原样吸收:
- 未实测的路径不要当承诺。Ghidra/IDA 原生反编译深度、Frida/LLDB/GDB/Rizin 实机会话、JADX 完整 APK 流程、固件容器、SPIR-V/CUDA fatbinary,在
README.md的"Designed-for but not empirically verified here"表里被明确列为未验证。只有npm run redteam:rea记录过 before/after 的格式(Go buildinfo、BEAM、EVM、PCAP、ASAR、WASM、SQLite、JS、Safetensors、GGUF、Electron fuses、Flutter pool)才算实测。 - APK 不支持签名。
docs/archive-integrity.md写得很清楚:所有 APK 加固请求都会抛UnsupportedApkProtectionError,不做 re-sign、不做对齐保持。ZIP 支持也是受限子集——不支持 ZIP64、不支持时间戳/Unix 权限属性、不支持加密与 data descriptor,条目上限 65,525。被拒绝是一种正确行为,总比静默丢成员好。 honeySourceMaps会生成假 map。它把分析者引向enclave://tee-runtime/之类的假路径。副作用是:某些错误监控平台、崩溃聚合工具、甚至企业 EDR 的启发式规则可能对这类"看似凭据"的字符串报警。canary-honeypot.ts里的合成 token(AKIAIOSFODNN7EXAMPLE_*、sk-canary-honeypot-*、ghp_canary*)刻意使用了文档保留格式,但在受监管行业分发前,建议先在测试环境跑一遍终端安全扫描。- "混淆无用论"要辩证看。社区里那批"AI 一下就能还原"的测试,多数是在小体积、单文件、无预算限制的前提下做的。规模上去之后结论会变:大 bundle 更容易触发解析错误与上下文溢出,而带语义预算的混淆会让分析器把真实逻辑标记为
unknown。BLA 的策略正是后者——不追求"看不懂",追求"分析器自己承认没看懂"。 - 供应链风险。REA 爆火后出现过仿冒仓库投毒事件。安装时认准包名
bla-agents,npx拉取前先核对来源;bla doctor会校验本地 skill 内容的 SHA-256 是否与内置一致(src/setup/doctor.ts:317-364),这是防篡改的一道低成本检查。
九、把清单固化成肌肉记忆
回到标题:半小时够不够?够,但前提是第一次认真做,之后交给流水线。
第一次手动走一遍,你会知道自己的产物里到底有什么:有没有.map、Go buildinfo 有多大、Electron fuse 是什么状态、package.json里还留着哪些调试脚本。之后把这套动作写进 CI——inspect-release卡完整性、audit --sarif卡暴露面、harden产出镜像、verify --fail-under卡不变量、manifest 与校验和随发布归档——它就从"发布前的心跳检查"变成了"不可绕过的门"。
最后一句实话:这套东西的价值不在于让逆向变得不可能,而在于让自动化的、廉价的、不需要人类判断的逆向失效。当分析 Agent 需要反复重试、需要人工复核它给出的"高置信度证据"、需要在预算表前犹豫时,你就已经赢回了最贵的那部分东西。
【免费下载链接】bla🛡️ BLA (Blind LLM Agents) — Adversarial defense shield that protects binaries, Electron apps, JS bundles, Python, and WebAssembly against AI reverse-engineering agents like REA. Poison -> Exhaust -> Hallucinate.项目地址: https://gitcode.com/gh_mirrors/bla3/bla
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考