use std::collections::{HashMap, HashSet};
use std::fmt::{Display, Formatter};
use std::ops::Deref;
use anyhow::Result;
use chrono::{DateTime, Utc};
use fuzzyhash::FuzzyHash;
use md5::{Digest, Md5};
#[cfg(feature = "elf")]
pub mod elf;
#[cfg(feature = "macho")]
pub mod macho;
#[cfg(feature = "pe32")]
pub mod pe32;
#[cfg(feature = "pef")]
pub mod pef;
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
pub enum Architecture {
Alpha,
ARM,
ARMThumb,
ARM64,
HitachiSH3,
HitachiSH4,
HitachiSH5,
Itanium,
LoongArch32,
LoongArch64,
M68k,
M88k,
MIPS,
MIPS64,
MIPSEL,
MIPSEL64,
PowerPC,
PowerPC64,
PowerPCLE,
PowerPC64LE,
RISCV,
RISCV64,
RISCV128,
Sparc,
Sparc64,
S390,
S390x,
X86,
X86_64,
Other(u16),
Unknown,
}
impl Display for Architecture {
fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
match self {
Architecture::Alpha => write!(f, "DEC Alpha"),
Architecture::ARM => write!(f, "ARM"),
Architecture::ARMThumb => write!(f, "ARM Thumb"),
Architecture::ARM64 => write!(f, "ARM64"),
Architecture::HitachiSH3 => write!(f, "Hitachi SH3"),
Architecture::HitachiSH4 => write!(f, "Hitachi SH4"),
Architecture::HitachiSH5 => write!(f, "Hitachi SH5"),
Architecture::Itanium => write!(f, "Intel Itanium"),
Architecture::LoongArch32 => write!(f, "LoongArch"),
Architecture::LoongArch64 => write!(f, "LoongArch64"),
Architecture::M68k => write!(f, "M68k"),
Architecture::M88k => write!(f, "M88k"),
Architecture::MIPS => write!(f, "MIPS"),
Architecture::MIPS64 => write!(f, "MIPS64"),
Architecture::MIPSEL => write!(f, "MIPSEL"),
Architecture::MIPSEL64 => write!(f, "MIPSEL64"),
Architecture::PowerPC => write!(f, "PowerPC"),
Architecture::PowerPC64 => write!(f, "PowerPC64"),
Architecture::PowerPCLE => write!(f, "PowerPCLE"),
Architecture::PowerPC64LE => write!(f, "PowerPC64LE"),
Architecture::RISCV => write!(f, "RISC-V"),
Architecture::RISCV64 => write!(f, "RISC-V 64"),
Architecture::RISCV128 => write!(f, "RISC-V 128"),
Architecture::Sparc => write!(f, "Sparc"),
Architecture::Sparc64 => write!(f, "Sparc64"),
Architecture::S390 => write!(f, "S390"),
Architecture::S390x => write!(f, "S390x"),
Architecture::X86 => write!(f, "x86"),
Architecture::X86_64 => write!(f, "x86_64"),
Architecture::Other(other) => write!(f, "Other: 0x{other:02X}"),
Architecture::Unknown => write!(f, "Unknown architecture, or architecture-independent"),
}
}
}
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
pub enum OperatingSystem {
AIX,
Linux,
FreeBSD,
OpenBSD,
NetBSD,
HPUX,
Irix,
Solaris,
UnknownUnixLike,
Haiku,
MacOS,
#[allow(non_camel_case_types)]
MacOS_Classic,
DOS,
Windows,
Other(u16),
}
impl Display for OperatingSystem {
fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
match self {
OperatingSystem::AIX => write!(f, "AIX"),
OperatingSystem::Linux => write!(f, "Linux"),
OperatingSystem::FreeBSD => write!(f, "FreeBSD"),
OperatingSystem::OpenBSD => write!(f, "OpenBSD"),
OperatingSystem::NetBSD => write!(f, "NetBSD"),
OperatingSystem::HPUX => write!(f, "HP-UX"),
OperatingSystem::Irix => write!(f, "Irix"),
OperatingSystem::Solaris => write!(f, "Solaris"),
OperatingSystem::UnknownUnixLike => write!(f, "Unknown Unix or Unix-like"),
OperatingSystem::Haiku => write!(f, "Haiku"),
OperatingSystem::MacOS => write!(f, "Mac OS (or maybe iOS)"),
OperatingSystem::MacOS_Classic => write!(f, "Classic Mac OS (7.0 - 9.2)"),
OperatingSystem::DOS => write!(f, "MS-DOS or compatible"),
OperatingSystem::Windows => write!(f, "Windows"),
OperatingSystem::Other(other) => write!(f, "Other: 0x{other:02X}"),
}
}
}
#[derive(Copy, Clone, Debug, Eq, PartialEq)]
pub enum Ordering {
BigEndian,
LittleEndian,
BiEndian,
}
impl Display for Ordering {
fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
match self {
Ordering::BigEndian => write!(f, "Big Endian"),
Ordering::LittleEndian => write!(f, "Little Endian"),
Ordering::BiEndian => write!(f, "Bi-Endian"),
}
}
}
#[derive(Copy, Clone, Debug, Eq, PartialEq)]
pub enum ExecutableType {
Core,
Library,
Program,
Unknown(u16),
}
impl Display for ExecutableType {
fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
match self {
ExecutableType::Core => write!(f, "Core file"),
ExecutableType::Library => write!(f, "Shared library"),
ExecutableType::Program => write!(f, "Program/Application"),
ExecutableType::Unknown(other) => write!(f, "Unknown 0x{other:02X}"),
}
}
}
pub trait ExecutableFile {
fn type_name(&self) -> String;
fn architecture(&self) -> &Architecture;
fn pointer_size(&self) -> usize;
fn operating_system(&self) -> &OperatingSystem;
fn compiled_timestamp(&self) -> Option<DateTime<Utc>>;
fn num_sections(&self) -> u32;
fn sections(&self) -> Result<&Sections>;
}
#[derive(Clone, Debug, PartialEq)]
pub struct Section<'a> {
pub name: String,
pub is_executable: bool,
pub size: usize,
pub offset: usize,
pub virtual_address: u32,
pub virtual_size: u32,
pub entropy: f32,
pub data: Option<&'a [u8]>,
}
#[derive(Clone, Debug, Default, PartialEq)]
pub struct Sections<'a>(Vec<Section<'a>>);
impl<'a> Display for Section<'a> {
fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
write!(
f,
"{} at 0x{:02x}, size 0x{:02x}",
self.name, self.offset, self.size
)?;
if self.virtual_address > 0 {
write!(f, ", v address: 0x{:02x}", self.virtual_address)?;
}
if self.is_executable {
write!(f, " - executable")?;
}
Ok(())
}
}
impl<'a> Display for Sections<'a> {
fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
for section in self.0.iter() {
writeln!(f, "{section}")?;
}
Ok(())
}
}
impl<'a> Deref for Sections<'a> {
type Target = Sections<'a>;
fn deref(&self) -> &Self::Target {
self
}
}
#[derive(Clone, Debug, Eq, PartialEq)]
pub struct Import {
pub library: String,
pub function: String,
}
impl Display for Import {
fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
write!(f, "{}:{}", self.library, self.function)
}
}
#[derive(Clone, Debug, Default, Eq, PartialEq)]
pub struct Imports {
pub imports: Vec<Import>,
pub expected_imports: u32,
}
impl Display for Imports {
fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
for import in self.imports.iter() {
writeln!(f, "{import}")?;
}
Ok(())
}
}
impl Imports {
pub fn build_import_string(&self) -> String {
let mut imports_map: HashMap<String, HashSet<String>> = HashMap::new();
for import in self.imports.iter() {
let mut lib = import.library.clone().to_lowercase();
if lib.ends_with(".dll") {
lib = lib.replace(".dll", "");
} else if lib.ends_with(".sys") {
lib = lib.replace(".sys", "");
} else if let Some(idx) = lib.find(".so") {
lib.truncate(lib.len() - idx);
}
if !imports_map.contains_key(&lib) {
imports_map.insert(lib.clone(), HashSet::new());
}
imports_map
.get_mut(&lib)
.unwrap()
.insert(import.function.to_lowercase());
}
let mut libs: Vec<&String> = imports_map.keys().collect();
libs.sort();
let mut imports_string = Vec::new();
for lib in libs {
let functions = imports_map.get(lib).unwrap();
let mut functions = Vec::from_iter(functions);
functions.sort();
for function in functions.iter() {
imports_string.push(format!("{lib}.{function}"));
}
}
imports_string.join(",")
}
pub fn hash(&self) -> Vec<u8> {
let mut hasher = Md5::new();
hasher.update(self.build_import_string());
let result = hasher.finalize();
result.to_vec()
}
pub fn fuzzy_hash(&self) -> String {
let import_string = self.build_import_string();
let fuzzy = FuzzyHash::new(import_string.into_bytes());
fuzzy.to_string()
}
}