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, 二进制解析, 云安全监控, 云资产清单, 可视化界面, 恶意软件分析, 网络流量审计, 逆向工程, 通知系统, 静态分析