#![allow(clippy::match_bool)]
use crate::check::{Analyze, GenericMap};
use crate::errors::{BinError, BinResult};
use crate::rules;
use goblin::mach::Mach;
use goblin::Object;
use yara::Compiler;
use byte_unit::Byte;
use chrono::prelude::*;
use serde_json::{json, Value};
use std::fs;
use std::path::PathBuf;
#[derive(serde::Serialize)]
pub struct Detector {
basic: GenericMap,
compilation: GenericMap,
mitigations: GenericMap,
instrumentation: Option<GenericMap>,
}
impl Detector {
pub fn run(binpath: PathBuf) -> BinResult<Self> {
let mut basic_map = GenericMap::new();
let _abspath: PathBuf = fs::canonicalize(&binpath)?;
let abspath = _abspath.to_str().unwrap().to_string();
basic_map.insert("Absolute Path".to_string(), json!(abspath));
let metadata: fs::Metadata = fs::metadata(&binpath)?;
let size: u128 = metadata.len() as u128;
let byte = Byte::from_bytes(size);
let filesize: String = byte.get_appropriate_unit(false).to_string();
basic_map.insert("File Size".to_string(), json!(filesize));
if let Ok(time) = metadata.accessed() {
let datetime: DateTime<Utc> = time.into();
let stamp: String = datetime.format("%Y-%m-%d %H:%M:%S").to_string();
basic_map.insert("Last Modified".to_string(), json!(stamp));
}
let data: Vec<u8> = std::fs::read(&binpath)?;
let instrumentation = Detector::detect_instrumentation(&data)?;
match Object::parse(&data)? {
Object::Elf(elf) => Ok(Self {
basic: {
use goblin::elf::header;
basic_map.insert("Binary Format".to_string(), json!("ELF"));
let arch: String = header::machine_to_str(elf.header.e_machine).to_string();
basic_map.insert("Architecture".to_string(), json!(arch));
let entry_point: String = format!("0x{:x}", elf.header.e_entry);
basic_map.insert("Entry Point Address".to_string(), json!(entry_point));
basic_map
},
compilation: elf.run_compilation_checks(&data)?,
mitigations: elf.run_mitigation_checks(),
instrumentation,
}),
Object::PE(pe) => Ok(Self {
basic: {
basic_map.insert("Binary Format".to_string(), json!("PE/EXE"));
let arch: String = if pe.is_64 {
String::from("PE32+")
} else {
String::from("PE32")
};
basic_map.insert("Architecture".to_string(), json!(arch));
let entry_point: String = format!("0x{:x}", pe.entry);
basic_map.insert("Entry Point Address".to_string(), json!(entry_point));
basic_map
},
compilation: pe.run_compilation_checks(&data)?,
mitigations: pe.run_mitigation_checks(),
instrumentation,
}),
Object::Mach(Mach::Binary(mach)) => Ok(Self {
basic: {
basic_map.insert("Binary Format".to_string(), json!("Mach-O"));
basic_map
},
compilation: mach.run_compilation_checks(&data)?,
mitigations: mach.run_mitigation_checks(),
instrumentation,
}),
_ => Err(BinError::new("unsupported filetype for analysis")),
}
}
#[inline]
fn detect_instrumentation(data: &[u8]) -> BinResult<Option<GenericMap>> {
use yara::MetadataValue;
let mut compiler = Compiler::new()?;
compiler.add_rules_str(rules::INSTRUMENTATION_RULES)?;
let rules = compiler.compile_rules()?;
let inst_matches = rules.scan_mem(&data, 5)?;
let mut instrumentation = GenericMap::new();
for rule in inst_matches.iter() {
if let MetadataValue::String(name) = rule.metadatas[0].value {
instrumentation.insert(String::from(name), json!(true));
}
}
if instrumentation.is_empty() {
Ok(None)
} else {
Ok(Some(instrumentation))
}
}
pub fn output(&self, json: Option<&str>) -> serde_json::Result<()> {
if let Some(_path) = json {
let output: &str = &serde_json::to_string_pretty(self)?;
if _path == "-" {
println!("{}", output);
return Ok(());
} else {
todo!()
}
}
Detector::table("BASIC", self.basic.clone());
Detector::table("COMPILATION", self.compilation.clone());
Detector::table("EXPLOIT MITIGATIONS", self.mitigations.clone());
if let Some(instrumentation) = &self.instrumentation {
Detector::table("INSTRUMENTATION", instrumentation.clone());
}
Ok(())
}
#[inline]
pub fn table(name: &str, mapping: GenericMap) {
println!("-----------------------------------------------");
println!("{}", name);
println!("-----------------------------------------------\n");
for (name, feature) in mapping {
let value: String = match feature {
Value::Bool(true) => String::from("\x1b[0;32m✔️\x1b[0m"),
Value::Bool(false) => String::from("\x1b[0;31m✖️\x1b[0m"),
Value::String(val) => val,
_ => unimplemented!(),
};
println!("{0: <45} {1}", name, value);
}
println!();
}
}