use crate::exec::{Architecture, ExecutableFile, OperatingSystem, Section, Sections};
use crate::utils::{
bytes_offset_match, i32_from_offset, string_from_offset, u16_from_offset, u32_from_offset,
EntropyCalc,
};
use crate::{Ordering, SpecimenFile};
use std::fmt::{Display, Formatter};
use std::mem::size_of;
use anyhow::{bail, Result};
use chrono::{DateTime, NaiveDateTime, TimeDelta, Utc};
use tracing::instrument;
use uuid::Uuid;
const MAGIC: [u8; 8] = [0x4a, 0x6f, 0x79, 0x21, 0x70, 0x65, 0x66, 0x66]; const PWPC: [u8; 4] = [0x70, 0x77, 0x70, 0x63];
const M68K: [u8; 4] = [0x6d, 0x36, 0x38, 0x6b];
const HEADER_SIZE: usize = 40;
const SECTION_HEADER_SIZE: usize = 28;
#[derive(Clone, Debug, PartialEq)]
pub struct Pef<'a> {
pub arch: Option<Architecture>,
pub ordering: Ordering,
pub os: OperatingSystem,
pub sections: Option<Sections<'a>>,
pub timestamp: u32,
pub contents: &'a [u8],
}
#[derive(Copy, Clone, Debug, Eq, PartialEq)]
pub struct SectionHeader {
pub name_offset: Option<usize>,
pub default_address: u32,
pub total_size: u32,
pub unpacked_size: u32,
pub packed_size: u32,
pub container_offset: u32,
pub section_kind: u8,
pub share_kind: u8,
pub alignment: u8,
pub reserved: u8,
}
impl AsRef<[u8; size_of::<Self>()]> for SectionHeader {
#[allow(clippy::transmute_ptr_to_ptr)]
fn as_ref(&self) -> &[u8; size_of::<Self>()] {
unsafe { std::mem::transmute::<_, &[u8; size_of::<Self>()]>(self) }
}
}
impl SectionHeader {
#[must_use]
pub fn from(contents: &[u8]) -> Self {
Self {
name_offset: {
let val = i32_from_offset(contents, 0, Ordering::BigEndian).unwrap_or_default();
if val > 0 {
#[allow(clippy::cast_sign_loss)]
Some(val as usize)
} else {
None
}
},
default_address: u32_from_offset(contents, 4, Ordering::BigEndian).unwrap_or_default(),
total_size: u32_from_offset(contents, 8, Ordering::BigEndian).unwrap_or_default(),
unpacked_size: u32_from_offset(contents, 12, Ordering::BigEndian).unwrap_or_default(),
packed_size: u32_from_offset(contents, 16, Ordering::BigEndian).unwrap_or_default(),
container_offset: u32_from_offset(contents, 20, Ordering::BigEndian)
.unwrap_or_default(),
section_kind: contents[24],
share_kind: contents[25],
alignment: contents[26],
reserved: contents[27],
}
}
}
impl<'a> Pef<'a> {
#[instrument(name = "PEF parser", skip(contents))]
pub fn from(contents: &'a [u8]) -> Result<Self> {
if !bytes_offset_match(contents, 0, &MAGIC) {
bail!("Not a PEF file");
}
let arch = {
if bytes_offset_match(contents, 8, &PWPC) {
Some(Architecture::PowerPC)
} else if bytes_offset_match(contents, 8, &M68K) {
Some(Architecture::M68k)
} else {
None
}
};
let section_count = u16_from_offset(contents, 32, Ordering::BigEndian).unwrap_or_default();
let inst_section_count =
u16_from_offset(contents, 34, Ordering::BigEndian).unwrap_or_default();
let mut sections = Sections::default();
for section_index in 0..(section_count + inst_section_count) as usize {
let offset_this_section = HEADER_SIZE + section_index * SECTION_HEADER_SIZE;
if offset_this_section > contents.len() {
break;
}
let this_section = SectionHeader::from(
&contents[offset_this_section..offset_this_section + HEADER_SIZE],
);
let section_name = {
let default = format!("Unnamed section {section_index}");
if let Some(offset) = this_section.name_offset {
string_from_offset(contents, offset).unwrap_or(default)
} else {
default
}
};
sections.push(Section {
name: section_name,
is_executable: this_section.section_kind == 0 || this_section.section_kind == 8,
size: this_section.packed_size as usize,
offset: this_section.container_offset as usize,
virtual_size: 0,
virtual_address: 0,
data: None,
entropy: 0.0,
});
}
Ok(Self {
arch,
ordering: Ordering::BigEndian,
os: OperatingSystem::MacOS_Classic,
sections: Some(sections),
timestamp: u32_from_offset(contents, 16, Ordering::BigEndian).unwrap_or_default(),
contents,
})
}
#[must_use]
pub fn compiled_date(&self) -> DateTime<Utc> {
let janone1940 = DateTime::from_naive_utc_and_offset(
NaiveDateTime::parse_from_str("1904-01-01 00:00:00", "%Y-%m-%d %H:%M:%S").unwrap(),
Utc,
);
janone1940 + TimeDelta::try_seconds(i64::from(self.timestamp)).unwrap()
}
}
impl ExecutableFile for Pef<'_> {
fn architecture(&self) -> Option<Architecture> {
self.arch
}
fn pointer_size(&self) -> usize {
32
}
fn operating_system(&self) -> OperatingSystem {
self.os
}
fn compiled_timestamp(&self) -> Option<DateTime<Utc>> {
Some(self.compiled_date())
}
#[allow(clippy::cast_possible_truncation)]
fn num_sections(&self) -> u32 {
self.sections.as_ref().unwrap_or(&Sections::default()).len() as u32
}
fn sections(&self) -> Option<&Sections<'_>> {
self.sections.as_ref()
}
fn import_hash(&self) -> Option<Uuid> {
None
}
fn fuzzy_imports(&self) -> Option<String> {
None
}
}
impl SpecimenFile for Pef<'_> {
const MAGIC: &'static [&'static [u8]] = &[&MAGIC];
fn type_name(&self) -> &'static str {
"PEF"
}
}
impl Display for Pef<'_> {
fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
writeln!(f, "PEF file:")?;
writeln!(f, "\tOS: {}", self.os)?;
if let Some(arch) = self.arch {
writeln!(f, "\tArchitecture: {arch}")?;
}
writeln!(f, "\tOrdering: {}", self.ordering)?;
if let Some(sections) = &self.sections {
writeln!(f, "\t{} sections:", sections.len())?;
for section in sections {
writeln!(f, "\t\t{section}")?;
}
}
writeln!(
f,
"\tCompiled: {:?}",
self.compiled_date().format("%Y-%m-%d %H:%M:%S").to_string()
)?;
writeln!(f, "\tSize: {}", self.contents.len())?;
writeln!(f, "\tEntropy: {:.4}", self.contents.entropy())
}
}
#[cfg(test)]
mod tests {
use super::*;
use rstest::rstest;
#[test]
fn beos() {
const BYTES: &[u8] = include_bytes!("../../../testdata/pef/BeApp");
let pef = Pef::from(BYTES).unwrap();
eprintln!("BeOS:\n{pef}");
assert_eq!(pef.arch, Some(Architecture::PowerPC));
}
#[rstest]
#[case(include_bytes!("../../../testdata/pef/MacOS_1"))]
#[case(include_bytes!("../../../testdata/pef/MacOS_2"))]
fn macos(#[case] bytes: &[u8]) {
let pef = Pef::from(bytes).unwrap();
eprintln!("Mac OS:\n{pef}");
assert_eq!(pef.arch, Some(Architecture::PowerPC));
}
}