Skip to main content

rbat/core/
plugins.rs

1//! # Heuristic Analysis Plugins
2//!
3//! This module implements modular static analysis analyzers as individual plugins
4//! implementing the [`HeuristicPlugin`] trait. This enables parallelized analysis execution.
5//!
6//! # Architecture
7//! Every heuristic task (such as disassembly, entropy estimation, signature matches)
8//! is encapsulated in its own plugin structure. These plugins take a shared read-only
9//! references context ([`AnalysisContext`]) and run concurrently.
10//!
11//! # Example
12//! ```rust
13//! use std::path::Path;
14//! use goblin::Object;
15//! use rbat::core::{AnalysisContext, BinaryArch, BinaryOS, AnalysisProgress};
16//! use rbat::core::plugins::MetadataPlugin;
17//! use rbat::core::traits::HeuristicPlugin;
18//!
19//! # fn run() -> Result<(), Box<dyn std::error::Error>> {
20//! let buffer = vec![0x7f, 0x45, 0x4c, 0x46, 2, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 2, 0, 62, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 64, 0, 56, 0, 1, 0, 64, 0, 0, 0, 0, 0];
21//! let obj = Object::parse(&buffer)?;
22//! let section_ranges = vec![];
23//!
24//! let ctx = AnalysisContext {
25//!     buffer: &buffer,
26//!     binary_object: &obj,
27//!     section_ranges: &section_ranges,
28//!     os: BinaryOS::Linux,
29//!     arch: BinaryArch::X64,
30//!     text_bytes: &buffer,
31//!     entry_addr: 0x1000,
32//! };
33//!
34//! let progress = MetadataPlugin.run(&ctx)?;
35//! match progress {
36//!     AnalysisProgress::BinaryMetadata(meta) => {
37//!         println!("Detected: {}", meta.binary_type);
38//!     }
39//!     _ => {}
40//! }
41//! # Ok(())
42//! # }
43//! ```
44
45use crate::core::heuristics::disassemble_section;
46use crate::core::traits::HeuristicPlugin;
47use crate::core::{AnalysisContext, AnalysisProgress, Result, parser::Parser};
48use crate::core::{packer_sig_check, string_check};
49use crate::utils::get_metadata::get_binary_metadata;
50
51/// A plugin that performs instruction disassembly using Capstone.
52/// Detects code caves (NOP runs, zero/trap padding runs) and anti-analysis/evasion instruction mnemonics.
53pub struct DisassemblyPlugin;
54
55impl HeuristicPlugin for DisassemblyPlugin {
56    fn name(&self) -> &'static str {
57        "disassembly"
58    }
59
60    fn run(&self, ctx: &AnalysisContext) -> Result<AnalysisProgress> {
61        let (code_cave, blacklisted_mnemonics) =
62            disassemble_section(ctx.text_bytes, &ctx.entry_addr, &ctx.os, &ctx.arch)?;
63        Ok(AnalysisProgress::Disassembly((
64            code_cave,
65            blacklisted_mnemonics,
66        )))
67    }
68}
69
70/// A plugin that performs YARA scanning on strings in the binary.
71/// Flags common indicator rules or obfuscated content strings.
72pub struct StringCheckPlugin;
73
74impl HeuristicPlugin for StringCheckPlugin {
75    fn name(&self) -> &'static str {
76        "string_check"
77    }
78
79    fn run(&self, ctx: &AnalysisContext) -> Result<AnalysisProgress> {
80        let results = string_check(ctx.buffer, ctx.section_ranges)?;
81        Ok(AnalysisProgress::Strings(results))
82    }
83}
84
85/// A plugin that checks the binary's byte structures against packer, cryptor, or compiler signatures.
86pub struct PackerSigCheckPlugin;
87
88impl HeuristicPlugin for PackerSigCheckPlugin {
89    fn name(&self) -> &'static str {
90        "packer_sig_check"
91    }
92
93    fn run(&self, ctx: &AnalysisContext) -> Result<AnalysisProgress> {
94        let results = packer_sig_check(ctx.buffer, ctx.section_ranges)?;
95        Ok(AnalysisProgress::PackerSigs(results))
96    }
97}
98
99/// A plugin that computes entropy metrics across individual binary section ranges to detect packed, encrypted, or compressed payloads.
100pub struct EntropyPlugin;
101
102impl HeuristicPlugin for EntropyPlugin {
103    fn name(&self) -> &'static str {
104        "entropy"
105    }
106
107    fn run(&self, ctx: &AnalysisContext) -> Result<AnalysisProgress> {
108        let parser = Parser::new(ctx.buffer, ctx.binary_object);
109        let results = parser.evaluate_section_entropy()?;
110        Ok(AnalysisProgress::Entropy(results))
111    }
112}
113
114/// A plugin that checks imported functions and symbols to identify potential API hooking or function redirection logic.
115pub struct ApiHookingPlugin;
116
117impl HeuristicPlugin for ApiHookingPlugin {
118    fn name(&self) -> &'static str {
119        "api_hooking"
120    }
121
122    fn run(&self, ctx: &AnalysisContext) -> Result<AnalysisProgress> {
123        let parser = Parser::new(ctx.buffer, ctx.binary_object);
124        let results = parser.detect_api_hooking(ctx.section_ranges)?;
125        Ok(AnalysisProgress::ApiHooking(results))
126    }
127}
128
129/// A plugin that analyzes imported library symbols to detect common API patterns indicative of process injection or hollow injection.
130pub struct ProcessInjectionPlugin;
131
132impl HeuristicPlugin for ProcessInjectionPlugin {
133    fn name(&self) -> &'static str {
134        "process_injection"
135    }
136
137    fn run(&self, ctx: &AnalysisContext) -> Result<AnalysisProgress> {
138        let parser = Parser::new(ctx.buffer, ctx.binary_object);
139        let results = parser.check_process_injec()?;
140        Ok(AnalysisProgress::ProcessInjection(results))
141    }
142}
143
144/// A plugin that extracts basic format metadata from binary headers (e.g. entry points, machine CPU, type strings).
145pub struct MetadataPlugin;
146
147impl HeuristicPlugin for MetadataPlugin {
148    fn name(&self) -> &'static str {
149        "metadata"
150    }
151
152    fn run(&self, ctx: &AnalysisContext) -> Result<AnalysisProgress> {
153        let results = get_binary_metadata(ctx.binary_object)?;
154        Ok(AnalysisProgress::BinaryMetadata(results))
155    }
156}
157
158#[cfg(test)]
159mod tests {
160    use super::*;
161    use crate::core::{BinaryArch, BinaryOS};
162    use crate::utils::test_helpers::test_helpers;
163    use goblin::Object;
164    use std::fs;
165    use tempfile::tempdir;
166
167    #[test]
168    fn test_metadata_plugin() {
169        let dir = tempdir().unwrap();
170        let path = dir.path().join("mock_elf");
171        test_helpers::generate_elf(&path);
172
173        let buffer = fs::read(&path).unwrap();
174        let obj = Object::parse(&buffer).unwrap();
175        let section_ranges =
176            crate::utils::section_offset::build_section_map(&obj, &buffer).unwrap();
177
178        let ctx = AnalysisContext {
179            buffer: &buffer,
180            binary_object: &obj,
181            section_ranges: &section_ranges,
182            os: BinaryOS::Linux,
183            arch: BinaryArch::X64,
184            text_bytes: &[],
185            entry_addr: 0x1000,
186        };
187
188        let result = MetadataPlugin.run(&ctx).unwrap();
189        match result {
190            AnalysisProgress::BinaryMetadata(meta) => {
191                assert_eq!(meta.binary_type, "Linux ELF");
192            }
193            _ => panic!("Expected BinaryMetadata progress"),
194        }
195    }
196
197    #[test]
198    fn test_entropy_plugin() {
199        let dir = tempdir().unwrap();
200        let path = dir.path().join("mock_elf");
201        test_helpers::generate_elf(&path);
202
203        let buffer = fs::read(&path).unwrap();
204        let obj = Object::parse(&buffer).unwrap();
205        let section_ranges =
206            crate::utils::section_offset::build_section_map(&obj, &buffer).unwrap();
207
208        let ctx = AnalysisContext {
209            buffer: &buffer,
210            binary_object: &obj,
211            section_ranges: &section_ranges,
212            os: BinaryOS::Linux,
213            arch: BinaryArch::X64,
214            text_bytes: &[],
215            entry_addr: 0x1000,
216        };
217
218        let result = EntropyPlugin.run(&ctx).unwrap();
219        match result {
220            AnalysisProgress::Entropy(entropy) => {
221                assert!(!entropy.is_empty());
222            }
223            _ => panic!("Expected Entropy progress"),
224        }
225    }
226
227    #[test]
228    fn test_disassembly_plugin() {
229        let dir = tempdir().unwrap();
230        let path = dir.path().join("mock_elf");
231        test_helpers::generate_elf(&path);
232
233        let buffer = fs::read(&path).unwrap();
234        let obj = Object::parse(&buffer).unwrap();
235        let section_ranges =
236            crate::utils::section_offset::build_section_map(&obj, &buffer).unwrap();
237
238        let text_bytes = vec![0x90; 128];
239
240        let ctx = AnalysisContext {
241            buffer: &buffer,
242            binary_object: &obj,
243            section_ranges: &section_ranges,
244            os: BinaryOS::Linux,
245            arch: BinaryArch::X64,
246            text_bytes: &text_bytes,
247            entry_addr: 0x1000,
248        };
249
250        let result = DisassemblyPlugin.run(&ctx).unwrap();
251        match result {
252            AnalysisProgress::Disassembly((code_cave, _)) => {
253                assert!(code_cave.contains_key("nop_addr"));
254            }
255            _ => panic!("Expected Disassembly progress"),
256        }
257    }
258}