工具|Entropia——RustShellcode/BOF编译器

admin 2026-07-01 05:54:47 网络安全文章 来源:ZONE.CI 全球网 0 阅读模式

文章总结: Entropia是一个用Rust编写的实验性编译器,可将.etpy语言编译为Windowsx64位置无关shellcode和CobaltStrikeBOF。项目约15800行代码,具备OPSEC混淆功能,审查发现其x64指令编码器质量高、错误诊断体系完善,但存在全仓库零单元测试、str.format缓冲区溢出等6个实质性bug。总体评价为工程素养出色但未达到1.0版本应有的工程纪律,建议优先补充测试和修复关键漏洞。 综合评分: 75 文章分类: 安全工具,红队,安全开发,二进制安全


cover_image

工具 | Entropia —— Rust Shellcode/BOF 编译器

原创

GLM-5.2 GLM-5.2

赛博生存指南

2026年6月29日 08:58 浙江

在小说阅读器读本章

去阅读

把 .etpy 源码一行命令编译成 Windows x64 shellcode 或 Cobalt Strike BOF,OPSEC 混淆全做成编译参数——这个叫 Entropia 的项目,代码到底写得怎么样?本文对它的 ~15800 行 Rust 做了一次完整审查。

仓库地址:https://github.com/entropykit/entropia

这是什么项目

Entropia 是一个实验性(pre-1.0)编译器,用 Rust 写成,把自创的 .etpy 语言编译成两种产物:

  • • Windows x86-64 位置无关 shellcode.bin
  • • Cobalt Strike Beacon Object File / COFF.x64.o

定位是红队 / 安全研究的 dual-use 工具(README 有明确的授权与责任使用声明,类比 nanodump、mimikatz 这类公开工具)。它最大的卖点是把传统”C 编译器 + 链接器 + sRDI/Donut 位置无关变换 + 手工 OPSEC”的多工具链,压成单个编译器,OPSEC 技术栈(指令多态、加密字符串、直接/间接系统调用、栈欺骗、sleep mask……)全部以 --opsec=... 编译参数的形式提供。

先看规模:

| 范围 | 行数 | | — | — | | 核心编译器 src/*.rs(17 个模块) | ~11,000 | | 工具链 tools/*(BOF 加载器、本地运行器、DAP 调试器、Win32 头生成器) | ~4,800 | | 合计 | ~15,800 行 Rust |

编译管线很清晰:

lexer → parser → AST 变换 → typecheck → dce → codegen → 输出
                                                  ↑
                                            OPSEC 多态后处理

下面进入正题:代码质量到底如何。


总体评价:7 / 10

一句话结论:

工程素养出色的实验性编译器。手写 x64 编码器和 COFF/Beacon 语义还原度令人印象深刻,错误诊断带稳定错误码,核心编译在简单程序下是正确的;但全仓库零单元测试、几处实质性 bug、以及一个有意保持宽松的类型系统,是它的主要短板。

它的问题不是”写得很烂”,而是”还没到 1.0 该有的工程纪律”。

四个子领域的打分:

| 模块 | 评分 | 一句话 | | — | — | — | | 前端 lexer / parser | 7.5 | 优先级阶梯扎实、诊断好;零测试、转义静默降级 | | 语义分析 | 6.5 | DCE/GC/PEB-walk 设计正确;类型检查实质宽松(部分有意)+ 一处真实空分支 | | 后端 codegen / encoder / poly | 7.0 | encoder 编码质量高、reloc-aware;poly 指令边界隐患、str.format 溢出、COFF aux | | 工具链 | 8.0 | 分层清晰、Beacon/VEH 还原度高;coff.rs 缺边界检查、dap.rs 过大 |


先说亮点:这些地方写得确实好

1. x64 指令编码器质量很高

encoder.rs(1277 行)是整个项目正确性的命脉,它做对了几件手写编码器最容易翻车的事:

  • • rbp/r13 作基址寄存器时,mod=00 会被误解成 disp32,代码强制改成 mod=01 + disp8=0
  • • rsp/r12(低 3 位是 100)在所有寻址模式下都正确补了 SIB 字节 0x24
  • • spl/bpl/sil/dil 这类 8 位寄存器正确识别”即使没有扩展寄存器也需要 REX 前缀”,避免了把 mov al,[mem] 误编码成 ah 的经典 bug;
  • • 立即数 size-aware:能塞进 imm8 就用 83 形式,否则才用 81,省字节。

更难得的是:整个后端没有 unsafe、没有 transmute,是纯 safe Rust。

2. 错误诊断有产品意识

类型检查器用了一套稳定的错误码体系 T001–T023,错误信息带行列定位,还常带可操作的修复建议(比如 “Add an explicit cast (u32)arg“)。display_type 会把内部哨兵 ? 转成对用户友好的 <unknown>,不泄露实现细节——这是一个有人在认真做”开发者体验”的信号。

3. DCE / GC / PEB-walk 运行时设计正确

这几个最容易出隐蔽 bug 的地方,作者都做对了:

  • • **DCE(死代码消除)**正确保留了带 [Hook]/[Stage]/[Override] 属性的间接调用函数——这些函数没有被显式 call,但运行时会被入口 prologue 或钩子间接调起,漏保留就是”链接期丢函数”。作者没漏。
  • • 保守式 GC 的根扫描设计合理:入口处捕获 STACK_TOP、收集时保存全部 Win64 非易失寄存器、用”本趟新增标记数为 0 才停”的不动点算法处理多代引用链。
  • • PEB-walk 严格按 PE 导出目录的 Names → Ordinals → Functions 三级跳转解析 API,还专门处理了 ntdll 对栈对齐敏感(movaps)的坑。

4. OPSEC 多态变换是真的”重定位感知”

polymorphism.rs 在每个指令等价替换点都检查 overlaps_reloc 和 label_inside,避免改坏重定位字段。README 宣传的 “reloc-aware” 是有代码支撑的,不是营销话术。

5. 工具链分层复用做得干净

bof-loader 作为纯库被 CLI 运行器和 DAP 调试器共同依赖;Beacon API 的字节序 / 长度前缀与 Cobalt Strike 的 bof_pack 严格对称;DAP 的 readMemory 用 VirtualQuery 守卫避免野指针打爆调试器进程,反汇编视图还会叠加断点处的原始字节让你看到真实指令而非 0xCC——细节到位。


再说问题:按真实严重性分级

🔴 应该修的实质性 bug

1. 类型检查的一处”空操作”分支typecheck.rs

一个声明返回 int 但写了 ret;(不带返回值)的函数,会被静默放行。对应的 match 分支是空的,而隔壁 void 函数返回值却会报 T023 错误——这种不对称强烈暗示是写漏了一行 errs.push(...)

2. str.format 的栈缓冲区溢出codegen.rs

字符串格式化用的是微软已废弃的 wsprintfA,搭配一个固定 256 字节、无边界检查的缓冲区。用户可控的格式串展开超长时,会越界写 .bss 段相邻 slot。一个安全工具里不该有的内存安全漏洞。

3. BOF 的 COFF 符号格式不规范coff.rs

输出的函数符号标了 IMAGE_SYM_DTYPE_FUNCTION(type=0x20)但 NumberOfAuxSymbols=0,不符合 COFF 规范的严格读法(正常要么 type=0x00,要么配 .bf/.ef 辅助记录)。宽松的 BOF loader 能吃,但严格的自研 loader 可能拒绝加载或偏移错位——这直接威胁”BOF 能被加载”这个核心卖点。

4. BOF 加载器解析外部 COFF 缺边界检查tools/bof-loader/src/coff.rs

解析 .obj 文件时,节表 / 符号表的访问没有边界校验直接切片 + .unwrap()。一个字段值被伪造的畸形 .obj,会让加载器在启动阶段直接 panic,而不是给出可读的错误信息。作为一个”解析外部输入”的入口,这里应当全部改成带校验的 ? 返回。

5. BeaconFormatFree 的 Vec 重建契约脆弱tools/bof-loader/src/beacon.rs

用 Vec::from_raw_parts(ptr, 0, cap) 重建并 drop 一个 Vec 来释放内存,依赖”len 永远传 0″这个隐式假设——一旦有人在 append 路径改用 Vec::push 或记录真实 len,就会以错误的 len/cap 释放导致未定义行为。用 dealloc + Layout 更稳。

6. 字符串转义静默降级lexer.rs

未知的转义符(比如 \x、或者 Windows 路径 "C:\bad" 里漏写的反斜杠)会被静默降级成字面字符,没有任何警告。在 BOF 场景里这是高频且极隐蔽的错误来源。

🟡 设计弱点 / 隐患

指令多态的扫描没有”指令边界”概念polymorphism.rs

这是审查里最技术性的一处隐患。多态变换是线性扫描字节流、按 i += consumed 前进的,没有维护”指令起始偏移”集合。这意味着当某条指令的立即数或位移字节,恰好拼出 REX.W + 0x85/0x09/0x21 + 合法 ModRM 的样子时,扫描器会误以为这是一条 test/or/and 指令并改写它——把指令内部字节改坏了。重定位检查防不住这种(位移字段不在 reloc 表里)。

需要说明:它不是”随便一个程序就会崩”,触发需要特定的字节模式(立即数里恰好出现特定字节序列)。但它是一个真实的正确性隐患,且一旦触发极难调试。修法是让 codegen 在 emit 时记录每条指令的边界,多态扫描只在边界处尝试匹配。

try/catch 不支持嵌套

try/catch 的异常处理用了一组全局 BSS slot(__handler_pc/rsp/rbp),意味着嵌套 try 的内层会覆盖外层的 handler,内层退出后外层 raise 就丢了。这是 VEH-based 异常机制的固有局限,应当在文档里明示。

codegen 的栈对齐”侥幸正确”且无防护

Windows x64 ABI 要求调用瞬间栈 16 字节对齐。审查逐点复核后发现,目前多数调用点其实是对的——但全是靠”恰好此刻 rsp 对齐”侥幸成立,没有任何 assert 守护。任何对函数 prologue 的改动,都可能悄悄引入一个 8 字节错位、然后在某个深层调用处崩成无法追踪的 AV。建议加编译期栈深度跟踪或运行时断言。

C 头解析器与 README 承诺不符

cimport.rs 的顶层解析器拒绝函数声明、#include#pragmaenum、非整数 #define——遇到就直接报错终止。但 README 的示例写着 use_c "winuser.h",暗示能直接喂 SDK 头。真实 SDK 头第一行就是 #include,会直接解析失败。实际上它只能消化由配套工具 entc-win32gen 从 win32metadata 生成的”净化版”头。这是文档与实现的落差,对用户是真实陷阱。

🟢 可维护性 / 小问题

  • • 类型系统刻意宽松:Cast 是”逃生舱”(任何 (T)expr 直接通过类型检查)、整数与指针双向自由互通。代码注释明确写着”by design,未来会收紧”。对写底层 shellcode 这种场景是合理的取舍——这不是 bug,是已知限制——但意味着类型检查对指针密集的代码基本不起作用,文档应该说清楚。
  • • 巨型文件codegen.rs 3450 行(一个 struct 22 个字段)、dap.rs 1985 行(还内嵌了一个手写的表达式求值器),都到了该拆分的体量。
  • • 零散的 .unwrap() / unreachable!() 散落各处;内联汇编立即数的 hex/dec 转换里有一处”注释和实现恰好相反”的小矛盾。

最大的系统性短板:全仓库零单元测试

四个子审查一致确认:

src/*.rs 和 tools/*/src/*.rs 里没有任何 #[test],仓库也没有 tests/ 目录。 README 自己也写着 “Tests: To be implemented”。仅有 entc-debug/scripts/ 下 3 个 Python 冒烟测试。

对一个把源码编译成机器码的编译器来说,这是最要命的:

  • • 运算符优先级表、x64 指令编码、类型兼容规则、COFF 二进制格式、多态等价变换——全部没有回归网
  • • 任何重构都只能靠人肉跑 examples 验证,风险极高;
  • • 而像类型兼容 compat、类型推断 infer_type、C 头解析 parse_header、COFF 解析 ParsedCoff 这些,全是纯函数,加测试的成本极低、收益却最高

这是 ROI(投入产出比)最高的改进方向,没有之一。


如果要改进,优先级建议

  1. 1. 补单元测试(最高 ROI)——先覆盖 lexer 词法、parser 优先级阶梯、typecheck 的兼容性判断、C 头解析、COFF 解析(喂一个小 .obj fixture)。
  2. 2. 修掉上面 6 个 🔴 实质 bug——尤其 str.format 缓冲区溢出、类型检查空分支、BOF 加载器的 COFF 边界检查。
  3. 3. 给多态变换加指令边界跟踪,给 codegen 加栈对齐 assert——消除两类最隐蔽的崩溃源。
  4. 4. 拆分 codegen.rs / dap.rs 两个巨型文件
  5. 5. 对齐 C 头解析器与文档——要么容错跳过函数声明 / #include,要么 README 明示只支持净化头。

写在最后

审完整体感受是:这代码不是 AI 粗制滥造的产物,也不是新手练手作。 指令编码器、错误码体系、保守式 GC、PEB-walk 运行时——这些地方都见得到真实的工程功底和踩坑经验(注释里能看到”作者实际遇到过这个 AV 并修复了”的痕迹)。

它的核心编译能力在简单程序下是正确的,OPSEC 栈的设计也是认真的。

它的问题,本质上是一个实验性项目还没补上 1.0 该有的工程纪律

  • • 没有测试网,所以每次改动都在裸奔;
  • • 几处边界 bug 没补(尤其那个 str.format 溢出,在安全工具里特别讽刺);
  • • 类型系统还停在”渐进收紧”的早期阶段;
  • • 文档在某些地方(C 头支持)领先于实现。

以 experimental(实验性)的标准衡量,它是合格偏上的;但如果想正式宣称 “Stable”,至少需要:补测试 + 修掉 🔴 列表 + 加固多态与栈对齐。

对于正在学编译器、学 Rust、或对红队工具链内部实现好奇的读者,这是一个相当值得读的代码库——编码器和运行时部分尤其有学习价值。


本次为只读审查,未修改任何源码。评估基于当前 main 分支快照,项目仍在活跃开发中,部分问题可能在后续提交中已修复。


免责声明:

本文所载程序、技术方法仅面向合法合规的安全研究与教学场景,旨在提升网络安全防护能力,具有明确的技术研究属性。

任何单位或个人未经授权,将本文内容用于攻击、破坏等非法用途的,由此引发的全部法律责任、民事赔偿及连带责任,均由行为人独立承担,本站不承担任何连带责任。

本站内容均为技术交流与知识分享目的发布,若存在版权侵权或其他异议,请通过邮件联系处理,具体联系方式可点击页面上方的联系我

本文转载自:赛博生存指南 GLM-5.2 GLM-5.2《工具 | Entropia —— Rust Shellcode/BOF 编译器》

评论:0   参与:  0