BinFlip/nimrod

GitHub: BinFlip/nimrod

nimrod 是一个用 Rust 编写的 Nim 编译二进制文件解析库,专为恶意软件分析和逆向工程场景恢复 Nim 运行时遗留的取证工件。

Stars: 0 | Forks: 0

# nimrod 一个用于解析由 [Nim](https://nim-lang.org/) 编译的原生二进制文件并提取取证工件(artifact)的纯 Rust 解析器。专为**恶意软件分析**和**逆向工程**而构建。 Nim 会先编译为 C/C++/ObjC,然后再编译为标准的 **ELF、PE 或 Mach-O** 二进制文件——它不存在 Nim 专用的容器。此 crate 用于恢复 Nim 编译器遗留的运行时工件:入口垫片(entry shims)、模块初始化函数、RTTI 表、字符串字面量、堆栈跟踪元数据、构建主机归属路径以及异常抛出位置。 ## 它能提取什么 | 工件 | 描述 | |----------|-------------| | **检测** | 11 个独立的指纹探针 —— 即使在被 strip 过的 `-d:danger` 构建版本上也很可靠 | | **GC 模式 / 版本** | `refc` 与 `arc/orc` 的对比,附加尽力而为的 `Nim1xRefc` / `Nim2xArc` / `Nim2xOrc` 提示 | | **入口垫片** | `NimMain`、`PreMain`、`NimMainModule` 等及其地址 | | **初始化函数** | `*Init000` / `*DatInit000` 以及解码后的构建主机模块路径 | | **类型图** | 交叉链接的类型:成员、偏移量、大小、对齐方式、继承、枚举值、已解析的析构函数 | | **代码入口点** | 带有 VA 标记的流,包含垫片、初始化函数、proc、包含抛出异常的函数以及用于反汇编器标记的 RTTI proc | | **模块映射** | 编译进二进制文件的每个 Nim 模块,包含逐个函数的名称、地址和大小(ELF) | | **符号还原(demangle)** | 将 Nim 的 `___u` 修饰名还原回原始标识符 | | **RTTI** | `TNimTypeV2` 字段(大小、对齐、深度、析构函数)以及带有字段名恢复的 `TNimType` | | **字符串字面量** | V2(`NIM_STRLIT_FLAG`)和 V1(`NimStringDesc`)扫描 | | **堆栈跟踪** | Proc 名称和 `.nim` 文件路径 —— 绝对路径会泄露构建主机信息 | | **Nimble 路径** | 解析泄露的 `.nimble/pkgs`,获取包名、版本、哈希值和用户名 | | **异常类型** | 在 rodata 中找到的 `*Error` / `*Defect` cstrings | | **抛出位置** | 通过 x86_64/AArch64 指令分析恢复完整的 (类型、proc、文件、行号) 元组 | ## 快速开始 ``` use nimrod::NimBinary; let data = std::fs::read("sample.exe")?; let bin = NimBinary::from_bytes(&data)?; if !bin.is_nim() { println!("Not a Nim binary"); return Ok(()); } println!("Format: {:?}, GC: {:?}", bin.format(), bin.gc_mode()); ``` ## 类型图 `bin.types()` 从 RTTI(V1 `TNimType` 和 V2 `TNimTypeV2`)中将每个 Nim 类型恢复到一个交叉链接的图中:大小、对齐方式、继承深度、带有偏移量和已解析字段类型的成员字段、枚举值,以及已解析为函数符号的析构函数 proc。 ``` for t in bin.types() { let name = t.name.as_deref().or(t.type_fragment.as_deref()).unwrap_or("?"); println!("{} {} (size={}, align={})", t.version, name, t.size, t.align); if let Some(parent) = &t.parent { println!(" inherits: {}", parent.name.as_deref().unwrap_or("?")); } for f in &t.fields { let fty = f.type_ref.as_ref().and_then(|r| r.name.as_deref()).unwrap_or("?"); println!(" +{:<4} {}: {}", f.offset, f.name, fty); } for e in &t.enum_values { println!(" = {} ({})", e.name, e.ordinal); } if let Some(d) = &t.destructor { println!(" =destroy: {}", d.function.as_deref().unwrap_or("?")); } } ``` V2(ARC/ORC)对象布局也通过 `display` 类 token 数组展示了继承链。在 Mach-O 上,传统的 V1 全局变量存储在 `__DATA,__common` 中且没有文件 backing,因此它们会优雅地降级为仅包含名称的条目(`t.is_readable() == false`),并带有类型名称片段——绝不会引发 panic,也绝不会丢失类型。 ## 代码入口点 `bin.code_entrypoints()` 将每个被确信标记的代码地址——入口垫片、模块初始化函数、还原后的符号、包含抛出异常的函数以及 RTTI 析构函数 / 跟踪 proc——合并为一个去重并按 VA 排序的流,这样反汇编器前端就可以通过一次调用标记整个二进制文件: ``` for ep in bin.code_entrypoints() { println!("{:#x} {} {}", ep.va, ep.kind, ep.name); } ``` ## 模块映射 模块映射将初始化函数、还原后的符号和堆栈跟踪文件路径交叉引用到逐个模块的视图中。每个模块都列出了每个函数及其还原后的名称、虚拟地址和大小(ELF): ``` let mmap = bin.module_map(); for (name, info) in &mmap.modules { println!("{name}: {} functions", info.symbol_count()); if let Some(ref path) = info.init_path { println!(" source: {path}"); } for sym in &info.symbols { println!(" {:#x} {} ({} bytes)", sym.address, sym.name, sym.size); } } ``` ``` cgen: 650 functions source: cgen.nim 0x7b6100 cProcParams (439 bytes) 0x7b62c0 genProcPrototype (312 bytes) ... system: 224 functions source: system.nim 0x405e70 rawAlloc (1284 bytes) 0x406360 collectCyclesBacon (820 bytes) ... ``` 这为下游工具(Binary Ninja、Ghidra、IDA)提供了它们进行反汇编和分析所需的函数边界。 ## 抛出位置恢复 第二阶段的抛出位置恢复通过分析 `raiseExceptionEx` 调用周围的 x86_64 和 AArch64 指令,来提取完整的异常元组: ``` for rs in &bin.raise_sites() { println!( "{} in {} at {}:{} [fn: {}]", rs.exception_type.as_deref().unwrap_or("?"), rs.proc_name.as_deref().unwrap_or("?"), rs.file.as_deref().unwrap_or("?"), rs.line.map(|l| l.to_string()).unwrap_or("?".into()), rs.enclosing_function.as_deref().unwrap_or("?"), ); } ``` ``` ValueError in parseHexInt at strutils.nim:1242 [fn: nsuParseHexInt] IndexDefect in delete at system.nim:2196 [fn: delete__closureiters_u3150] MyError in inner at exceptions.nim:7 [fn: outer__exceptions_u129] ``` ## 构建主机归属 Debug 和标准发行版的 Nim 构建版本会通过堆栈跟踪元数据和 nimble 包路径泄露构建主机路径: ``` // Absolute .nim file paths (build-host leak) let harvest = bin.stack_trace(); for f in &harvest.file_paths { if f.is_absolute { println!("leaked: {}", f.path); } } // Nimble package paths (username + package intel) for p in &bin.nimble_paths() { println!("pkg: {}@{}", p.pkg_name.as_deref().unwrap_or("?"), p.pkg_version.as_deref().unwrap_or("?")); if let Some(ref user) = p.user_hint { println!(" user: {user}"); } } ``` ``` leaked: /opt/homebrew/Cellar/nim/2.2.8/nim/lib/system.nim pkg: nimSHA2@0.1.1 user: alex ``` ## dump 示例 包含的 `dump` 示例会打印出所有可恢复的工件: ``` cargo run --example dump -- sample.exe ``` ## 支持的格式 - **ELF**(Linux、BSD)—— 完整支持,包括来自 `st_size` 的函数大小 - **PE**(Windows)—— COFF 符号表 + 导出表;支持 MinGW 和 MSVC 链接 - **Mach-O**(macOS)—— 单架构和通用/fat 二进制文件 ## 依赖项 刻意保持最简: - [`goblin`](https://docs.rs/goblin) — ELF/PE/Mach-O 解析 - [`memchr`](https://docs.rs/memchr) — 快速的字节级 rodata 扫描 ## 许可证 Apache-2.0
标签:DAST, Rust, 二进制解析, 云安全监控, 云资产清单, 可视化界面, 恶意软件分析, 网络流量审计, 逆向工程, 通知系统, 静态分析