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