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_attr(docsrs, doc(cfg(feature = "elf")))]
12#[cfg(feature = "elf")]
13pub mod elf;
14
15#[cfg_attr(docsrs, doc(cfg(feature = "macho")))]
17#[cfg(feature = "macho")]
18pub mod macho;
19
20#[cfg_attr(docsrs, doc(cfg(feature = "pe32")))]
22#[cfg(feature = "pe32")]
23pub mod pe32;
24
25#[cfg_attr(docsrs, doc(cfg(feature = "pef")))]
27#[cfg(feature = "pef")]
28pub mod pef;
29
30#[derive(Clone, Copy, Debug, Eq, PartialEq, Hash)]
32pub enum Architecture {
33 Alpha,
36
37 Alpha64,
39
40 ARM,
43
44 ARMThumb,
47
48 ARM64,
51
52 HitachiSH3,
55
56 HitachiSH4,
59
60 HitachiSH5,
63
64 Hobbit,
67
68 Itanium,
71
72 LoongArch32,
75
76 LoongArch64,
79
80 M68k,
83
84 M88k,
87
88 MIPS,
91
92 MIPS64,
95
96 MIPSEL,
99
100 MIPSEL64,
103
104 PowerPC,
107
108 PowerPC64,
111
112 PowerPCLE,
115
116 PowerPC64LE,
119
120 RISCV,
123
124 RISCV64,
127
128 RISCV128,
131
132 Sparc,
135
136 Sparc64,
139
140 S390,
143
144 S390x,
147
148 X86,
151
152 X86_64,
155
156 Other(u32),
158
159 Unknown,
161}
162
163impl Architecture {
164 #[must_use]
166 pub fn as_str(&self) -> &'static str {
167 match self {
168 Architecture::Alpha => "DEC Alpha",
169 Architecture::Alpha64 => "DEC Alpha64",
170 Architecture::ARM => "ARM",
171 Architecture::ARMThumb => "ARM Thumb",
172 Architecture::ARM64 => "ARM64",
173 Architecture::HitachiSH3 => "Hitachi SH3",
174 Architecture::HitachiSH4 => "Hitachi SH4",
175 Architecture::HitachiSH5 => "Hitachi SH5",
176 Architecture::Hobbit => "AT&T Hobbit",
177 Architecture::Itanium => "Intel Itanium",
178 Architecture::LoongArch32 => "LoongArch",
179 Architecture::LoongArch64 => "LoongArch64",
180 Architecture::M68k => "M68k",
181 Architecture::M88k => "M88k",
182 Architecture::MIPS => "MIPS",
183 Architecture::MIPS64 => "MIPS64",
184 Architecture::MIPSEL => "MIPSEL",
185 Architecture::MIPSEL64 => "MIPSEL64",
186 Architecture::PowerPC => "PowerPC",
187 Architecture::PowerPC64 => "PowerPC64",
188 Architecture::PowerPCLE => "PowerPCLE",
189 Architecture::PowerPC64LE => "PowerPC64LE",
190 Architecture::RISCV => "RISC-V",
191 Architecture::RISCV64 => "RISC-V 64",
192 Architecture::RISCV128 => "RISC-V 128",
193 Architecture::Sparc => "Sparc",
194 Architecture::Sparc64 => "Sparc64",
195 Architecture::S390 => "S390",
196 Architecture::S390x => "S390x",
197 Architecture::X86 => "x86",
198 Architecture::X86_64 => "x86_64",
199 Architecture::Other(_) => "Other",
200 Architecture::Unknown => "Unknown architecture, or architecture-independent",
201 }
202 }
203}
204
205impl Display for Architecture {
206 fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
207 match self {
208 Architecture::Other(other) => write!(f, "Other: 0x{other:02X}"),
209 a => write!(f, "{}", a.as_str()),
210 }
211 }
212}
213
214#[derive(Clone, Copy, Debug, Eq, PartialEq, Hash)]
216pub enum OperatingSystem {
217 AIX,
219
220 Linux,
222
223 FreeBSD,
225
226 OpenBSD,
228
229 NetBSD,
231
232 HPUX,
234
235 Irix,
237
238 Solaris,
240
241 UnknownUnixLike,
243
244 Haiku,
246
247 MacOS,
249
250 #[allow(non_camel_case_types)]
252 MacOS_Classic,
253
254 DOS,
256
257 Windows,
259
260 Other(u16),
262}
263
264impl OperatingSystem {
265 #[must_use]
267 pub fn as_str(&self) -> &'static str {
268 match self {
269 OperatingSystem::AIX => "AIX",
270 OperatingSystem::Linux => "Linux",
271 OperatingSystem::FreeBSD => "FreeBSD",
272 OperatingSystem::OpenBSD => "OpenBSD",
273 OperatingSystem::NetBSD => "NetBSD",
274 OperatingSystem::HPUX => "HP-UX",
275 OperatingSystem::Irix => "Irix",
276 OperatingSystem::Solaris => "Solaris",
277 OperatingSystem::UnknownUnixLike => "Unknown Unix or Unix-like",
278 OperatingSystem::Haiku => "Haiku",
279 OperatingSystem::MacOS => "Mac OS (or maybe iOS)",
280 OperatingSystem::MacOS_Classic => "Classic Mac OS (7.0 - 9.2)",
281 OperatingSystem::DOS => "MS-DOS or compatible",
282 OperatingSystem::Windows => "Windows",
283 OperatingSystem::Other(_) => "Other",
284 }
285 }
286}
287
288impl Display for OperatingSystem {
289 fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
290 match self {
291 OperatingSystem::Other(other) => write!(f, "Other: 0x{other:02X}"),
292 o => write!(f, "{}", o.as_str()),
293 }
294 }
295}
296
297#[derive(Copy, Clone, Debug, Eq, PartialEq)]
299pub enum ExecutableType {
300 Core,
302
303 Library,
305
306 Program,
308
309 Unknown(u16),
311}
312
313impl ExecutableType {
314 #[must_use]
316 pub fn as_str(&self) -> &'static str {
317 match self {
318 ExecutableType::Core => "Core file",
319 ExecutableType::Library => "Shared library",
320 ExecutableType::Program => "Program/Application",
321 ExecutableType::Unknown(_) => "Unknown",
322 }
323 }
324}
325
326impl Display for ExecutableType {
327 fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
328 match self {
329 ExecutableType::Unknown(other) => write!(f, "Unknown 0x{other:02X}"),
330 x => write!(f, "{}", x.as_str()),
331 }
332 }
333}
334
335pub trait ExecutableFile {
337 fn architecture(&self) -> Option<Architecture>;
339
340 fn pointer_size(&self) -> usize;
342
343 fn operating_system(&self) -> OperatingSystem;
345
346 fn compiled_timestamp(&self) -> Option<DateTime<Utc>>;
348
349 fn num_sections(&self) -> u32;
351
352 fn sections(&self) -> Option<&Sections<'_>>;
354
355 fn import_hash(&self) -> Option<String>;
357
358 fn fuzzy_imports(&self) -> Option<String>;
360}
361
362#[derive(Clone, Debug, PartialEq)]
364pub struct Section<'a> {
365 pub name: String,
367
368 pub is_executable: bool,
370
371 pub size: usize,
373
374 pub offset: usize,
376
377 pub virtual_address: u32,
379
380 pub virtual_size: u32,
382
383 pub entropy: f32,
385
386 pub data: Option<&'a [u8]>,
388}
389
390type Sections<'a> = Vec<Section<'a>>;
391
392impl Display for Section<'_> {
393 fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
394 write!(
395 f,
396 "{} at 0x{:02x}, size 0x{:02x}, entropy {:.2}",
397 self.name, self.offset, self.size, self.entropy
398 )?;
399 if self.virtual_address > 0 {
400 write!(f, ", v address: 0x{:02x}", self.virtual_address)?;
401 }
402 if self.is_executable {
403 write!(f, " - executable")?;
404 }
405 Ok(())
406 }
407}
408
409#[derive(Clone, Debug, Eq, PartialEq)]
411pub struct Import {
412 pub library: String,
414
415 pub function: String,
417}
418
419impl Display for Import {
420 fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
421 write!(f, "{}:{}", self.library, self.function)
422 }
423}
424
425#[derive(Clone, Debug, Default, Eq, PartialEq)]
427pub struct Imports {
428 pub imports: Vec<Import>,
430
431 pub expected_imports: u32,
433}
434
435impl Display for Imports {
436 fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
437 for import in &self.imports {
438 writeln!(f, "{import}")?;
439 }
440 Ok(())
441 }
442}
443
444impl Imports {
445 #[allow(clippy::case_sensitive_file_extension_comparisons)]
447 #[must_use]
448 pub fn build_import_string(&self) -> String {
449 let mut imports_map: HashMap<String, HashSet<String>> = HashMap::new();
451
452 for import in &self.imports {
454 let mut lib = import.library.to_lowercase();
455 if lib.ends_with(".dll") {
457 lib = lib.replace(".dll", "");
458 } else if lib.ends_with(".sys") {
459 lib = lib.replace(".sys", "");
460 } else if let Some(idx) = lib.find(".so") {
461 lib.truncate(lib.len() - idx);
462 }
463
464 if !imports_map.contains_key(&lib) {
465 imports_map.insert(lib.clone(), HashSet::new());
466 }
467
468 if let Some(import_ref) = imports_map.get_mut(&lib) {
469 import_ref.insert(import.function.to_lowercase());
470 }
471 }
472
473 let mut libs: Vec<&String> = imports_map.keys().collect();
475 libs.sort();
476
477 let mut imports_string = Vec::new();
479 for lib in libs {
480 if let Some(functions) = imports_map.get(lib) {
482 let mut functions = Vec::from_iter(functions);
483 functions.sort();
484 for function in &functions {
485 imports_string.push(format!("{lib}.{function}"));
486 }
487 }
488 }
489
490 imports_string.join(",")
491 }
492
493 #[must_use]
495 pub fn hash(&self) -> Vec<u8> {
496 let mut hasher = Md5::new();
497 hasher.update(self.build_import_string());
498 let result = hasher.finalize();
499 result.to_vec()
500 }
501
502 #[must_use]
504 pub fn fuzzy_hash(&self) -> String {
505 let import_string = self.build_import_string();
506 let fuzzy = FuzzyHash::new(import_string.into_bytes());
507 fuzzy.to_string()
508 }
509}