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(u16),
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) -> 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 #[must_use]
439 pub fn build_import_string(&self) -> String {
440 let mut imports_map: HashMap<String, HashSet<String>> = HashMap::new();
442
443 for import in &self.imports {
445 let mut lib = import.library.to_lowercase();
446 if lib.ends_with(".dll") {
447 lib = lib.replace(".dll", "");
448 } else if lib.ends_with(".sys") {
449 lib = lib.replace(".sys", "");
450 } else if let Some(idx) = lib.find(".so") {
451 lib.truncate(lib.len() - idx);
452 }
453
454 if !imports_map.contains_key(&lib) {
455 imports_map.insert(lib.clone(), HashSet::new());
456 }
457
458 if let Some(import_ref) = imports_map.get_mut(&lib) {
459 import_ref.insert(import.function.to_lowercase());
460 }
461 }
462
463 let mut libs: Vec<&String> = imports_map.keys().collect();
465 libs.sort();
466
467 let mut imports_string = Vec::new();
469 for lib in libs {
470 if let Some(functions) = imports_map.get(lib) {
472 let mut functions = Vec::from_iter(functions);
473 functions.sort();
474 for function in &functions {
475 imports_string.push(format!("{lib}.{function}"));
476 }
477 }
478 }
479
480 imports_string.join(",")
481 }
482
483 #[must_use]
485 pub fn hash(&self) -> Vec<u8> {
486 let mut hasher = Md5::new();
487 hasher.update(self.build_import_string());
488 let result = hasher.finalize();
489 result.to_vec()
490 }
491
492 #[must_use]
494 pub fn fuzzy_hash(&self) -> String {
495 let import_string = self.build_import_string();
496 let fuzzy = FuzzyHash::new(import_string.into_bytes());
497 fuzzy.to_string()
498 }
499}