1use std::collections::{HashMap, HashSet};
4use std::fmt::{Display, Formatter};
5
6use chrono::{DateTime, Utc};
7use fuzzyhash::FuzzyHash;
8use md5::{Digest, Md5};
9
10#[cfg(feature = "elf")]
12pub mod elf;
13
14#[cfg(feature = "macho")]
16pub mod macho;
17
18#[cfg(feature = "pe32")]
20pub mod pe32;
21
22#[cfg(feature = "pef")]
24pub mod pef;
25
26#[derive(Clone, Copy, Debug, Eq, PartialEq, Hash)]
28pub enum Architecture {
29 Alpha,
32
33 ARM,
36
37 ARMThumb,
40
41 ARM64,
44
45 HitachiSH3,
48
49 HitachiSH4,
52
53 HitachiSH5,
56
57 Hobbit,
60
61 Itanium,
64
65 LoongArch32,
68
69 LoongArch64,
72
73 M68k,
76
77 M88k,
80
81 MIPS,
84
85 MIPS64,
88
89 MIPSEL,
92
93 MIPSEL64,
96
97 PowerPC,
100
101 PowerPC64,
104
105 PowerPCLE,
108
109 PowerPC64LE,
112
113 RISCV,
116
117 RISCV64,
120
121 RISCV128,
124
125 Sparc,
128
129 Sparc64,
132
133 S390,
136
137 S390x,
140
141 X86,
144
145 X86_64,
148
149 Other(u32),
151
152 Unknown,
154}
155
156impl Architecture {
157 #[must_use]
159 pub fn as_str(&self) -> &'static str {
160 match self {
161 Architecture::Alpha => "DEC Alpha",
162 Architecture::ARM => "ARM",
163 Architecture::ARMThumb => "ARM Thumb",
164 Architecture::ARM64 => "ARM64",
165 Architecture::HitachiSH3 => "Hitachi SH3",
166 Architecture::HitachiSH4 => "Hitachi SH4",
167 Architecture::HitachiSH5 => "Hitachi SH5",
168 Architecture::Hobbit => "AT&T Hobbit",
169 Architecture::Itanium => "Intel Itanium",
170 Architecture::LoongArch32 => "LoongArch",
171 Architecture::LoongArch64 => "LoongArch64",
172 Architecture::M68k => "M68k",
173 Architecture::M88k => "M88k",
174 Architecture::MIPS => "MIPS",
175 Architecture::MIPS64 => "MIPS64",
176 Architecture::MIPSEL => "MIPSEL",
177 Architecture::MIPSEL64 => "MIPSEL64",
178 Architecture::PowerPC => "PowerPC",
179 Architecture::PowerPC64 => "PowerPC64",
180 Architecture::PowerPCLE => "PowerPCLE",
181 Architecture::PowerPC64LE => "PowerPC64LE",
182 Architecture::RISCV => "RISC-V",
183 Architecture::RISCV64 => "RISC-V 64",
184 Architecture::RISCV128 => "RISC-V 128",
185 Architecture::Sparc => "Sparc",
186 Architecture::Sparc64 => "Sparc64",
187 Architecture::S390 => "S390",
188 Architecture::S390x => "S390x",
189 Architecture::X86 => "x86",
190 Architecture::X86_64 => "x86_64",
191 Architecture::Other(_) => "Other",
192 Architecture::Unknown => "Unknown architecture, or architecture-independent",
193 }
194 }
195}
196
197impl Display for Architecture {
198 fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
199 match self {
200 Architecture::Other(other) => write!(f, "Other: 0x{other:02X}"),
201 a => write!(f, "{}", a.as_str()),
202 }
203 }
204}
205
206#[derive(Clone, Copy, Debug, Eq, PartialEq, Hash)]
208pub enum OperatingSystem {
209 AIX,
211
212 Linux,
214
215 FreeBSD,
217
218 OpenBSD,
220
221 NetBSD,
223
224 HPUX,
226
227 Irix,
229
230 Solaris,
232
233 UnknownUnixLike,
235
236 Haiku,
238
239 MacOS,
241
242 #[allow(non_camel_case_types)]
244 MacOS_Classic,
245
246 DOS,
248
249 Windows,
251
252 Other(u16),
254}
255
256impl OperatingSystem {
257 #[must_use]
259 pub fn as_str(&self) -> &'static str {
260 match self {
261 OperatingSystem::AIX => "AIX",
262 OperatingSystem::Linux => "Linux",
263 OperatingSystem::FreeBSD => "FreeBSD",
264 OperatingSystem::OpenBSD => "OpenBSD",
265 OperatingSystem::NetBSD => "NetBSD",
266 OperatingSystem::HPUX => "HP-UX",
267 OperatingSystem::Irix => "Irix",
268 OperatingSystem::Solaris => "Solaris",
269 OperatingSystem::UnknownUnixLike => "Unknown Unix or Unix-like",
270 OperatingSystem::Haiku => "Haiku",
271 OperatingSystem::MacOS => "Mac OS (or maybe iOS)",
272 OperatingSystem::MacOS_Classic => "Classic Mac OS (7.0 - 9.2)",
273 OperatingSystem::DOS => "MS-DOS or compatible",
274 OperatingSystem::Windows => "Windows",
275 OperatingSystem::Other(_) => "Other",
276 }
277 }
278}
279
280impl Display for OperatingSystem {
281 fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
282 match self {
283 OperatingSystem::Other(other) => write!(f, "Other: 0x{other:02X}"),
284 o => write!(f, "{}", o.as_str()),
285 }
286 }
287}
288
289#[derive(Copy, Clone, Debug, Eq, PartialEq)]
291pub enum ExecutableType {
292 Core,
294
295 Library,
297
298 Program,
300
301 Unknown(u16),
303}
304
305impl ExecutableType {
306 #[must_use]
308 pub fn as_str(&self) -> &'static str {
309 match self {
310 ExecutableType::Core => "Core file",
311 ExecutableType::Library => "Shared library",
312 ExecutableType::Program => "Program/Application",
313 ExecutableType::Unknown(_) => "Unknown",
314 }
315 }
316}
317
318impl Display for ExecutableType {
319 fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
320 match self {
321 ExecutableType::Unknown(other) => write!(f, "Unknown 0x{other:02X}"),
322 x => write!(f, "{}", x.as_str()),
323 }
324 }
325}
326
327pub trait ExecutableFile {
329 fn architecture(&self) -> Option<Architecture>;
331
332 fn pointer_size(&self) -> usize;
334
335 fn operating_system(&self) -> OperatingSystem;
337
338 fn compiled_timestamp(&self) -> Option<DateTime<Utc>>;
340
341 fn num_sections(&self) -> u32;
343
344 fn sections(&self) -> Option<&Sections>;
346
347 fn import_hash(&self) -> Option<String>;
349
350 fn fuzzy_imports(&self) -> Option<String>;
352}
353
354#[derive(Clone, Debug, PartialEq)]
356pub struct Section<'a> {
357 pub name: String,
359
360 pub is_executable: bool,
362
363 pub size: usize,
365
366 pub offset: usize,
368
369 pub virtual_address: u32,
371
372 pub virtual_size: u32,
374
375 pub entropy: f32,
377
378 pub data: Option<&'a [u8]>,
380}
381
382type Sections<'a> = Vec<Section<'a>>;
383
384impl Display for Section<'_> {
385 fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
386 write!(
387 f,
388 "{} at 0x{:02x}, size 0x{:02x}, entropy {:.2}",
389 self.name, self.offset, self.size, self.entropy
390 )?;
391 if self.virtual_address > 0 {
392 write!(f, ", v address: 0x{:02x}", self.virtual_address)?;
393 }
394 if self.is_executable {
395 write!(f, " - executable")?;
396 }
397 Ok(())
398 }
399}
400
401#[derive(Clone, Debug, Eq, PartialEq)]
403pub struct Import {
404 pub library: String,
406
407 pub function: String,
409}
410
411impl Display for Import {
412 fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
413 write!(f, "{}:{}", self.library, self.function)
414 }
415}
416
417#[derive(Clone, Debug, Default, Eq, PartialEq)]
419pub struct Imports {
420 pub imports: Vec<Import>,
422
423 pub expected_imports: u32,
425}
426
427impl Display for Imports {
428 fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
429 for import in &self.imports {
430 writeln!(f, "{import}")?;
431 }
432 Ok(())
433 }
434}
435
436impl Imports {
437 #[allow(clippy::case_sensitive_file_extension_comparisons)]
439 #[must_use]
440 pub fn build_import_string(&self) -> String {
441 let mut imports_map: HashMap<String, HashSet<String>> = HashMap::new();
443
444 for import in &self.imports {
446 let mut lib = import.library.to_lowercase();
447 if lib.ends_with(".dll") {
449 lib = lib.replace(".dll", "");
450 } else if lib.ends_with(".sys") {
451 lib = lib.replace(".sys", "");
452 } else if let Some(idx) = lib.find(".so") {
453 lib.truncate(lib.len() - idx);
454 }
455
456 if !imports_map.contains_key(&lib) {
457 imports_map.insert(lib.clone(), HashSet::new());
458 }
459
460 if let Some(import_ref) = imports_map.get_mut(&lib) {
461 import_ref.insert(import.function.to_lowercase());
462 }
463 }
464
465 let mut libs: Vec<&String> = imports_map.keys().collect();
467 libs.sort();
468
469 let mut imports_string = Vec::new();
471 for lib in libs {
472 if let Some(functions) = imports_map.get(lib) {
474 let mut functions = Vec::from_iter(functions);
475 functions.sort();
476 for function in &functions {
477 imports_string.push(format!("{lib}.{function}"));
478 }
479 }
480 }
481
482 imports_string.join(",")
483 }
484
485 #[must_use]
487 pub fn hash(&self) -> Vec<u8> {
488 let mut hasher = Md5::new();
489 hasher.update(self.build_import_string());
490 let result = hasher.finalize();
491 result.to_vec()
492 }
493
494 #[must_use]
496 pub fn fuzzy_hash(&self) -> String {
497 let import_string = self.build_import_string();
498 let fuzzy = FuzzyHash::new(import_string.into_bytes());
499 fuzzy.to_string()
500 }
501}