#![allow(clippy::uninlined_format_args)]
pub mod analysis;
pub mod error;
pub mod formats;
pub mod types;
#[cfg(any(feature = "disasm-capstone", feature = "disasm-iced"))]
pub mod disasm;
pub mod utils;
#[cfg(any(feature = "disasm-capstone", feature = "disasm-iced"))]
pub use disasm::DisassemblyEngine;
pub use error::{BinaryError, Result};
pub use types::{
AnalysisResult, Architecture, BasicBlock, BinaryFormat, BinaryFormatParser, BinaryFormatTrait,
BinaryMetadata, CallGraph, CallGraphConfig, CallGraphEdge, CallGraphNode, CallGraphStatistics,
ComplexityMetrics, ControlFlowGraph, EnhancedControlFlowAnalysis, EntropyAnalysis, Export,
Function, HalsteadMetrics, Import, Instruction, Loop, LoopType, NodeType, Section,
SecurityIndicators, Symbol,
};
pub struct BinaryAnalyzer {
config: AnalysisConfig,
}
#[derive(Debug, Clone)]
pub struct AnalysisConfig {
pub enable_disassembly: bool,
#[cfg(any(feature = "disasm-capstone", feature = "disasm-iced"))]
pub disassembly_engine: DisassemblyEngine,
pub enable_control_flow: bool,
pub enable_call_graph: bool,
pub enable_cognitive_complexity: bool,
pub enable_advanced_loops: bool,
pub enable_entropy: bool,
pub enable_symbols: bool,
pub max_analysis_size: usize,
pub architecture_hint: Option<Architecture>,
pub call_graph_config: Option<CallGraphConfig>,
}
impl Default for AnalysisConfig {
fn default() -> Self {
Self {
enable_disassembly: true,
#[cfg(any(feature = "disasm-capstone", feature = "disasm-iced"))]
disassembly_engine: DisassemblyEngine::Auto,
enable_control_flow: true,
enable_call_graph: false,
enable_cognitive_complexity: true,
enable_advanced_loops: true,
enable_entropy: true,
enable_symbols: true,
max_analysis_size: 100 * 1024 * 1024, architecture_hint: None,
call_graph_config: None,
}
}
}
impl BinaryAnalyzer {
pub fn new() -> Self {
Self::with_config(AnalysisConfig::default())
}
pub fn with_config(config: AnalysisConfig) -> Self {
Self { config }
}
pub fn config(&self) -> &AnalysisConfig {
&self.config
}
pub fn analyze(&self, data: &[u8]) -> Result<AnalysisResult> {
let binary_file = BinaryFile::parse(data)?;
self.analyze_binary(&binary_file)
}
pub fn analyze_binary(&self, binary: &BinaryFile) -> Result<AnalysisResult> {
#[allow(unused_mut)] let mut result = AnalysisResult {
format: binary.format(),
architecture: binary.architecture(),
entry_point: binary.entry_point(),
sections: binary.sections().to_vec(),
symbols: binary.symbols().to_vec(),
imports: binary.imports().to_vec(),
exports: binary.exports().to_vec(),
metadata: binary.metadata().clone(),
..Default::default()
};
if self.config.enable_disassembly {
#[cfg(any(feature = "disasm-capstone", feature = "disasm-iced"))]
{
result.disassembly = Some(self.perform_disassembly(binary)?);
}
}
if self.config.enable_control_flow {
#[cfg(feature = "control-flow")]
{
result.control_flow = Some(self.perform_control_flow_analysis(binary)?);
}
}
if self.config.enable_call_graph {
#[cfg(feature = "control-flow")]
{
result.call_graph = Some(self.perform_call_graph_analysis(binary)?);
}
}
if self.config.enable_cognitive_complexity || self.config.enable_advanced_loops {
#[cfg(feature = "control-flow")]
{
result.enhanced_control_flow =
Some(self.perform_enhanced_control_flow_analysis(binary)?);
}
}
if self.config.enable_entropy {
#[cfg(feature = "entropy-analysis")]
{
result.entropy = Some(self.perform_entropy_analysis(binary)?);
}
}
#[cfg(feature = "symbol-resolution")]
{
if self.config.enable_symbols {
analysis::symbols::demangle_symbols(&mut result.symbols);
}
}
Ok(result)
}
#[cfg(any(feature = "disasm-capstone", feature = "disasm-iced"))]
fn perform_disassembly(&self, binary: &BinaryFile) -> Result<Vec<Instruction>> {
disasm::disassemble_binary(binary, &self.config)
}
#[cfg(feature = "control-flow")]
fn perform_control_flow_analysis(&self, binary: &BinaryFile) -> Result<Vec<ControlFlowGraph>> {
analysis::control_flow::analyze_binary(binary)
}
#[cfg(feature = "control-flow")]
fn perform_call_graph_analysis(&self, binary: &BinaryFile) -> Result<CallGraph> {
let config = self.config.call_graph_config.clone().unwrap_or_default();
analysis::call_graph::analyze_binary_with_config(binary, config)
}
#[cfg(feature = "control-flow")]
fn perform_enhanced_control_flow_analysis(
&self,
binary: &BinaryFile,
) -> Result<EnhancedControlFlowAnalysis> {
let control_flow_config = analysis::control_flow::AnalysisConfig {
max_instructions: 10000,
max_depth: 100,
detect_loops: true,
calculate_metrics: true,
enable_call_graph: false,
enable_cognitive_complexity: self.config.enable_cognitive_complexity,
enable_advanced_loops: self.config.enable_advanced_loops,
call_graph_config: None,
};
let analyzer = analysis::control_flow::ControlFlowAnalyzer::with_config(
binary.architecture(),
control_flow_config,
);
let control_flow_graphs = analyzer.analyze_binary(binary)?;
let mut total_cognitive_complexity = 0;
let mut max_cognitive_complexity = 0;
let mut most_complex_function = None;
let mut functions_analyzed = 0;
let mut total_loops = 0;
let mut natural_loops = 0;
let mut irreducible_loops = 0;
let mut nested_loops = 0;
let mut max_nesting_depth = 0;
let mut loops_by_type = std::collections::HashMap::new();
for cfg in &control_flow_graphs {
functions_analyzed += 1;
let cognitive = cfg.complexity.cognitive_complexity;
total_cognitive_complexity += cognitive;
if cognitive > max_cognitive_complexity {
max_cognitive_complexity = cognitive;
most_complex_function = Some(cfg.function.name.clone());
}
total_loops += cfg.loops.len();
for loop_info in &cfg.loops {
match loop_info.loop_type {
LoopType::Natural => natural_loops += 1,
LoopType::Irreducible => irreducible_loops += 1,
_ => {}
}
if loop_info.nesting_level > 1 {
nested_loops += 1;
}
if loop_info.nesting_level > max_nesting_depth {
max_nesting_depth = loop_info.nesting_level;
}
*loops_by_type
.entry(loop_info.loop_type.clone())
.or_insert(0) += 1;
}
}
let average_cognitive_complexity = if functions_analyzed > 0 {
total_cognitive_complexity as f64 / functions_analyzed as f64
} else {
0.0
};
let cognitive_complexity_summary = types::CognitiveComplexityStats {
total_cognitive_complexity,
average_cognitive_complexity,
max_cognitive_complexity,
most_complex_function,
functions_analyzed,
};
let loop_analysis_summary = types::LoopAnalysisStats {
total_loops,
natural_loops,
irreducible_loops,
nested_loops,
max_nesting_depth,
loops_by_type,
};
Ok(EnhancedControlFlowAnalysis {
control_flow_graphs,
cognitive_complexity_summary,
loop_analysis_summary,
})
}
#[cfg(feature = "entropy-analysis")]
fn perform_entropy_analysis(&self, binary: &BinaryFile) -> Result<EntropyAnalysis> {
analysis::entropy::analyze_binary(binary)
}
}
impl Default for BinaryAnalyzer {
fn default() -> Self {
Self::new()
}
}
pub struct BinaryFile {
data: Vec<u8>,
parsed: Box<dyn BinaryFormatTrait>,
}
impl BinaryFile {
pub fn parse(data: &[u8]) -> Result<Self> {
let format = formats::detect_format(data)?;
let parsed = formats::parse_binary(data, format)?;
Ok(Self {
data: data.to_vec(),
parsed,
})
}
pub fn format(&self) -> BinaryFormat {
self.parsed.format_type()
}
pub fn architecture(&self) -> Architecture {
self.parsed.architecture()
}
pub fn entry_point(&self) -> Option<u64> {
self.parsed.entry_point()
}
pub fn sections(&self) -> &[Section] {
self.parsed.sections()
}
pub fn symbols(&self) -> &[Symbol] {
self.parsed.symbols()
}
pub fn imports(&self) -> &[Import] {
self.parsed.imports()
}
pub fn exports(&self) -> &[Export] {
self.parsed.exports()
}
pub fn metadata(&self) -> &BinaryMetadata {
self.parsed.metadata()
}
pub fn data(&self) -> &[u8] {
&self.data
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_analyzer_creation() {
let analyzer = BinaryAnalyzer::new();
assert!(analyzer.config.enable_disassembly);
assert!(analyzer.config.enable_control_flow);
assert!(!analyzer.config.enable_call_graph);
assert!(analyzer.config.enable_cognitive_complexity);
assert!(analyzer.config.enable_advanced_loops);
assert!(analyzer.config.enable_entropy);
assert!(analyzer.config.enable_symbols);
}
#[test]
fn test_custom_config() {
let config = AnalysisConfig {
enable_disassembly: false,
#[cfg(any(feature = "disasm-capstone", feature = "disasm-iced"))]
disassembly_engine: DisassemblyEngine::Auto,
enable_control_flow: true,
enable_call_graph: true,
enable_cognitive_complexity: false,
enable_advanced_loops: true,
enable_entropy: false,
enable_symbols: true,
max_analysis_size: 1024,
architecture_hint: Some(Architecture::X86_64),
call_graph_config: Some(CallGraphConfig::default()),
};
let analyzer = BinaryAnalyzer::with_config(config);
assert!(!analyzer.config.enable_disassembly);
assert!(analyzer.config.enable_control_flow);
assert!(analyzer.config.enable_call_graph);
assert!(!analyzer.config.enable_cognitive_complexity);
assert!(analyzer.config.enable_advanced_loops);
assert!(!analyzer.config.enable_entropy);
assert!(analyzer.config.enable_symbols);
assert_eq!(analyzer.config.max_analysis_size, 1024);
assert!(analyzer.config.call_graph_config.is_some());
}
}