sreeharipj/unhusk

GitHub: sreeharipj/unhusk

利用 Rust panic 元数据从剥离符号的 release 二进制文件中识别用户编写函数的逆向分析工具。

Stars: 0 | Forks: 0

# unhusk 利用 panic 元数据在剥离了符号的 Rust release 二进制文件中识别用户编写的函数 —— 无需反汇编,无需符号表,无需签名数据库。 ## 功能介绍 一个经过 strip 处理和 LTO 优化的 Rust release 二进制文件主要由标准库和 Cargo 依赖构成。作者编写的代码只占很小一部分。`unhusk` 通过利用 Rust panic 机制的结构特性来查找用户编写的函数。 **主要输出 —— `certain`(确定)函数:**指那些直接引用了 `.data.rel.ro` 中 `core::panic::Location` 结构体的函数,且这些结构体的源码路径指向用户代码(`src/`、`tests/`、`examples/`)。每一个这样的函数都包含用户路径的 panic 元数据。 **在 60 个真实的 Rust 二进制文件上测得的精确度(ripgrep, bat, fd, just, dust, broot, topgrade, …):** | 真值方法 | 精确率中位数 | 平均值 | 真正的 FP 率 | |---|---|---|---| | 符号名称(crate 归属) | **93.8%** | 88.1% | **6.2%** | | DWARF `decl_file` | 86.6% | 77.5% | — | 符号名称数据 (93.8%) 是更客观真实的测量结果。DWARF 的数据较低,是因为即使闭包体完全属于用户代码,DWARF 也会将 `FnOnce`/`FnMut` 闭包垫片归类到 `core/src/ops/function.rs`;这占据了约 67% 的 DWARF 误报。不可避免的那 6.2% 误差来自于使用用户类型单态化的 std/dep 泛型函数 —— 在剥离符号的二进制文件中,unhusk 无法将它们与真正的用户函数区分开来。 **测得的召回率(以 DWARF 为分母 —— 上限):**`certain` 在真实二进制文件中大约能捕获 15% 的用户函数(中位数 15.8%,范围 0.4%–45.5%)。在包含 13 个二进制文件的 realval 集中,总体召回率(certain+inferred,d=∞)**按 DWARF 分母计算为 46.2%**(上限),**按符号分母计算为 19.0%**(下限):DWARF 因为丢弃了 FnOnce/FnMut 闭包垫片和未映射的单态化而导致统计偏低;而符号统计则因为包含了该工具无法找到的 `::method` 样板代码而导致统计偏高。真实的用户逻辑召回率介于两者之间。推导过程请参见 `realval/BACKTRACE_SWEEP.md`。结构性上限将在下文的“局限性”中解释。 **调用闭包(非用户代码):**`inferred`(推断)函数可以通过调用边从用户代码到达。在真实二进制文件中 DWARF 精确度约为 5%(主要是从用户 panic 位置传递调用的 std/dep 胶水代码)。单独进行标记。 ## 工作原理 对于每个可达的 `panic!`、`assert!`、边界检查和 `.unwrap()` 调用位置,Rust 都会在 `.data.rel.ro` 中嵌入一个 `core::panic::Location` 结构体。每个结构体都持有一个指向 `.rodata` 中源码路径字符串的胖 `&'static str` 指针(通过 `.rela.dyn` 中的 PIE 重定位实现),外加行号和列号。 **阶段 1** —— 重构每个 Location 结构体并对其源码路径进行分类: ``` .rela.dyn R_X86_64_RELATIVE { offset, addend } │ offset → slot in .data.rel.ro (the file-ptr field of the Location) └── addend → string in .rodata ("src/main.rs", "/rustc/…", …) .data.rel.ro [ ptr(reloc) | len(u64) | line(u32) | col(u32) ] ``` 源码路径分类(确定性,无启发式算法): | 模式 | 归属 | |---|---| | `src/*.rs`, `tests/*.rs`, `examples/*.rs` | **用户代码** | | `/rustc/HASH/library/…` 或 `library/…` | std/core/alloc | | `/rust/deps/CRATE-VER/…` | 工具链内置依赖 | | `*/cargo/registry/src/*/CRATE-VER/…` | Cargo registry 依赖 | **阶段 2** —— 基于 `.eh_frame` FDE 的函数范围映射 + RIP 相对地址交叉引用扫描。任何包含对用户 Location 结构体引用的函数都会被标记为 `certain`。 ## 精确度验证 `--validate ` 参数会将 unhusk 的归属判定与来自配套未剥离二进制文件的 DWARF `.debug_info` 真值进行对比: ``` unhusk binary.stripped --validate binary.unstripped ``` 在三个小型/合成测试套件(其中用户函数是独立的、非内联的)上测得的结果: | 测试套件 | FDEs | DWARF 用户函数 | Certain 精确度 | Certain 召回率 | |---|---|---|---|---| | medium_debug (synthetic) | 541 | 1 | 100% (1/1) | 100% | | unhusk-on-unhusk | 1490 | 8 | 100% (3/3) | 37.5% | | scored_debug (designed) | 529 | 19 | 100% (6/6) | ~84% | 在真实的二进制文件上,精确度有所下降(按 DWARF 统计中位数为 86.2%,按符号统计为 94.5%),因为以闭包形式或库泛型实例化表达的代码会被 DWARF 归因于其在 core/std 中的定义位置。完整结果请参见 `REAL_BINARY_VALIDATION.md`。 ## 局限性 **闭包垫片和泛型单态化会降低精确度。** 当用户代码是通过 `Fn*` 调用的闭包,或者将用户类型传递给了 std/dep 泛型(sort、迭代适配器、延迟初始化)时,生成的单态化函数可能会包含用户路径的 panic Location,尽管该函数体的大部分是库逻辑。unhusk 会将这些标记为 `certain`;它们构成了真实二进制文件中大部分的误报。 **没有可达 panic 位置的函数**无法被发现。纯计算、getter 以及在开启 `lto = true` 编译的情况下,优化器证明所有 panic 均不可达的代码,在二进制文件中将不会存在任何 panic Location 结构体 —— 没有可供锚定的点。 **通过标准库机制或间接调度调用的用户代码**无法被发现。仅通过 trait 对象、函数指针或穿过 std/dep 调度层的回调进行调用的函数会被归类为 `library`。 **仅支持 x86-64 ELF。** 同时支持 PIE(`ET_DYN`,`cargo build --release` 的默认设置)和非 PIE(`ET_EXEC`)。 ## 构建 ``` cargo build --release ``` 需要 Rust 1.70+。没有 C 库依赖,运行时也不需要外部工具。 ## Docker 你可以使用 Docker 轻松构建和运行 `unhusk`,它提供了一个隔离的环境,而无需安装 Rust 工具链。 ### 构建镜像 ``` docker build -t unhusk . ``` ### 运行容器 通过 Docker 运行时,你需要将包含二进制文件的目录挂载为卷,以便容器能够访问它们。 ``` # 挂载当前目录并分析 binary docker run --rm -v "$(pwd)":/work -w /work unhusk # 针对 unstripped binary 进行验证 docker run --rm -v "$(pwd)":/work -w /work unhusk --validate ``` ## 使用方法 ``` # 在 stripped binary 中识别用户编写的函数 unhusk # 同时报告经 DWARF 验证的 precision/recall 数据 unhusk --validate # 显示完整的 call-closure 列表(可从用户访问,主要为 dep/std) unhusk --show-call-closure # 将 call-graph BFS 限制为 N 跳(在 13 个真实 binary 上测得): # depth 1:9.3% 推断 precision(+1.8x),-3.9pp recall — 高精度审计 # depth 2:6.4% 推断 precision(+1.3x),-1.1pp recall — 最佳平衡 unhusk --infer-depth 2 ``` ## 测试 ``` cargo test ``` 集成测试需要 `/tmp/unhusk-research/` 下的测试二进制文件(参见 `tests/integration.rs`)。字符串分类器的单元测试无需测试数据即可运行。 ## 许可证 本项目采用双重许可: 1. **GNU Affero General Public License v3.0 (AGPLv3)**:用于开源项目和一般用途。 2. **商业许可证**:用于 AGPLv3 限制不适用的专有或商业环境。 详情请参阅 `LICENSE` 文件。
标签:Rust, 二进制分析, 云安全监控, 云安全运维, 云资产清单, 代码分析, 元数据分析, 凭证管理, 可视化界面, 网络流量审计, 请求拦截, 逆向工程, 通知系统, 静态分析