Skip to main content

nimrod/
lib.rs

1//! Parse and inspect Nim-compiled native binaries.
2//!
3//! This crate provides a pure-Rust parser and forensic-artifact extractor for
4//! binaries produced by the Nim compiler via its C, C++, or Objective-C
5//! backends. A Nim-compiled program is a normal ELF, PE, or Mach-O executable
6//! — there is no Nim-specific container. The crate recovers Nim **runtime
7//! artifacts** left inside an otherwise-ordinary native binary.
8//!
9//! # What it extracts
10//!
11//! | Artifact | Method | Notes |
12//! |----------|--------|-------|
13//! | Detection verdict | [`NimBinary::is_nim`] | 11 independent probes |
14//! | Format / arch / GC mode | [`NimBinary::format`], [`NimBinary::gc_mode`] | ELF, PE, Mach-O; refc vs ARC/ORC |
15//! | Entry shims | [`NimBinary::entry_shims`] | `NimMain`, `PreMain`, etc. |
16//! | Init functions | [`NimBinary::init_functions`] | With decoded module paths |
17//! | Module map | [`NimBinary::module_map`] | Per-module: symbols, sizes, init VAs, leaked paths |
18//! | RTTI globals | [`NimBinary::rtti_symbols`] | V1 (`TNimType`) and V2 (`TNimTypeV2`) with parsed fields |
19//! | Type graph | [`NimBinary::types`] | Cross-linked types: members, offsets, sizes, layout, inheritance, enum values, destructors |
20//! | Code entrypoints | [`NimBinary::code_entrypoints`] | One VA-tagged stream of shims, inits, procs, raise-enclosing fns, RTTI procs |
21//! | String literals | [`NimBinary::string_literals_v2`] | V2 `NIM_STRLIT_FLAG` scan |
22//! | Stack-trace metadata | [`NimBinary::stack_trace`] | Proc names + `.nim` file paths (build-host leaks) |
23//! | Nimble path leaks | [`NimBinary::nimble_paths`] | Package name, version, hash, username, OS |
24//! | Exception types | [`NimBinary::exception_types`] | `*Error`, `*Defect` cstrings in rodata |
25//! | Raise sites | [`NimBinary::raise_sites`] | Full (type, proc, file, line) tuples via instruction analysis |
26//! | Demangled symbols | [`demangle::symbol::parse`] | Identifier, module, item ID |
27//!
28//! # Quick start
29//!
30//! ```no_run
31//! use nimrod::NimBinary;
32//!
33//! let data = std::fs::read("sample").unwrap();
34//! let bin = NimBinary::from_bytes(&data).unwrap();
35//!
36//! if !bin.is_nim() {
37//!     eprintln!("not a Nim binary");
38//!     return;
39//! }
40//!
41//! // Detection and classification
42//! println!("format: {:?}, gc: {:?}", bin.format(), bin.gc_mode());
43//!
44//! // Module map: which Nim modules are compiled in, with every function
45//! let mmap = bin.module_map();
46//! for (name, info) in &mmap.modules {
47//!     println!("{name}: {} functions", info.symbol_count());
48//!     for sym in &info.symbols {
49//!         // sym.name    = demangled Nim identifier
50//!         // sym.address = VA (start disassembling here)
51//!         // sym.size    = byte count (ELF; 0 on Mach-O/PE)
52//!         let _ = (sym.name.as_str(), sym.address, sym.size);
53//!     }
54//! }
55//!
56//! // Raise sites: exception type + enclosing function + source location
57//! for rs in bin.raise_sites() {
58//!     let _ = (
59//!         rs.exception_type.as_deref(),   // "ValueError"
60//!         rs.enclosing_function.as_deref(), // "parseHexInt__strutils_u1234"
61//!         rs.file.as_deref(),              // "strutils.nim"
62//!         rs.line,                         // Some(1242)
63//!     );
64//! }
65//! ```
66//!
67//! # Design
68//!
69//! - **Pure Rust**, `#![deny(unsafe_code)]`.
70//! - **Cross-format**: ELF, PE, Mach-O via [`goblin`](https://docs.rs/goblin).
71//! - **Zero-copy** where possible — borrows from the input byte slice.
72//! - **Forensic-oriented**: prioritises attribution-grade artifacts (build-host
73//!   paths, package refs, exception locations) over pretty-printing.
74//!
75//! The format-level research backing every probe and struct layout is
76//! documented in `RESEARCH.md` at the crate root.
77//!
78//! # Robustness contract
79//!
80//! nimrod parses adversarial input, so every public artifact iterator obeys a
81//! uniform contract:
82//!
83//! - **Never panics** on malformed input. The crate denies `unwrap`, `expect`,
84//!   `panic`, unchecked indexing, and unchecked arithmetic in library code
85//!   (see the `[lints]` table in `Cargo.toml`).
86//! - **Returns empty on missing data.** A walker whose artifact is absent (no
87//!   V2 strings in a refc build, no raise sites in a binary that never raises)
88//!   returns an empty slice, not an error.
89//! - **Skips malformed records individually.** A single undecodable record
90//!   (e.g. an RTTI global whose struct bytes are not file-backed) is dropped or
91//!   degraded to a name-only entry; the rest of the scan still completes.
92//!   Name-only RTTI entries are flagged by [`NimType::is_readable`].
93//!
94//! # Address space
95//!
96//! Every address-bearing field returned by this crate is a **virtual address**
97//! in the input image's load space, never a file offset. Convert to a
98//! disassembler-relative RVA with the `*_rva` helpers on [`NimBinary`], with
99//! [`Container::va_to_rva`], or with [`va_to_i64`] for signed-integer storage.
100//!
101//! # Thread safety
102//!
103//! [`NimBinary`] is `Send + Sync` (enforced by a compile-time assertion), so it
104//! can be shared across threads or held across `.await` points. Its scan caches
105//! use [`std::sync::OnceLock`].
106
107// `missing_docs`, `unsafe_code`, plus the clippy panic-prevention set
108// (`unwrap_used`, `expect_used`, `panic`, `arithmetic_side_effects`,
109// `indexing_slicing`) are declared in `Cargo.toml` under `[lints]` so
110// they enforce on every build regardless of the consuming workspace.
111// nimrod is used in malware-analysis pipelines where every input byte
112// is adversarial and the parser must not panic.
113#![cfg_attr(
114    test,
115    allow(
116        clippy::unwrap_used,
117        clippy::expect_used,
118        clippy::panic,
119        clippy::arithmetic_side_effects,
120        clippy::indexing_slicing
121    )
122)]
123
124pub mod addr;
125pub mod container;
126pub mod demangle;
127pub mod detect;
128pub mod entrypoints;
129pub mod error;
130pub mod inits;
131pub mod metadata;
132pub mod modules;
133pub mod paths;
134pub mod raises;
135pub mod rtti;
136pub mod shims;
137pub mod sites;
138pub mod stacktrace;
139pub mod strings;
140pub mod types;
141
142mod binary;
143mod util;
144
145pub use addr::va_to_i64;
146pub use binary::NimBinary;
147pub use container::{Arch, Container, Format, Section, SectionKind, Symbol, SymbolKind};
148pub use detect::{DetectionMatches, DetectionReport};
149pub use entrypoints::{CodeEntrypoint, EntrypointKind};
150pub use error::{Error, Result};
151pub use inits::{InitFunction, InitKind};
152pub use metadata::{GcMode, NimVersionHint};
153pub use modules::{ModuleInfo, ModuleMap, ModuleSymbol};
154pub use paths::{NimblePath, PathOs};
155pub use raises::ExceptionRef;
156pub use rtti::symbols::{RttiSymbol, RttiVersion};
157pub use rtti::v1::{NimKind, NimTypeFlag};
158pub use shims::{EntryShim, ShimKind};
159pub use sites::RaiseSite;
160pub use stacktrace::{FilePath, StackTraceHarvest};
161pub use strings::v1::StringLiteralV1;
162pub use strings::v2::StringLiteral;
163pub use types::{CodeRef, EnumValue, NimType, TypeField, TypeFlags, TypeRef, TypeShape};