malwaredb_types/exec/pef/
mod.rs1use crate::exec::{Architecture, ExecutableFile, OperatingSystem, Section, Sections};
4use crate::utils::{
5 bytes_offset_match, i32_from_offset, string_from_offset, u16_from_offset, u32_from_offset,
6 EntropyCalc,
7};
8use crate::{Ordering, SpecimenFile};
9
10use std::fmt::{Display, Formatter};
11use std::mem::size_of;
12
13use anyhow::{bail, Result};
14use chrono::{DateTime, NaiveDateTime, TimeDelta, Utc};
15use tracing::instrument;
16
17const MAGIC: [u8; 8] = [0x4a, 0x6f, 0x79, 0x21, 0x70, 0x65, 0x66, 0x66]; const PWPC: [u8; 4] = [0x70, 0x77, 0x70, 0x63];
22const M68K: [u8; 4] = [0x6d, 0x36, 0x38, 0x6b];
23
24const HEADER_SIZE: usize = 40;
25const SECTION_HEADER_SIZE: usize = 28;
26
27#[derive(Clone, Debug, PartialEq)]
32pub struct Pef<'a> {
33 pub arch: Architecture,
35
36 pub ordering: Ordering,
38
39 pub os: OperatingSystem,
41
42 pub sections: Option<Sections<'a>>,
44
45 pub timestamp: u32,
47
48 pub contents: &'a [u8],
50}
51
52#[derive(Copy, Clone, Debug, Eq, PartialEq)]
54pub struct SectionHeader {
55 pub name_offset: i32,
57
58 pub default_address: u32,
60
61 pub total_size: u32,
63
64 pub unpacked_size: u32,
66
67 pub packed_size: u32,
69
70 pub container_offset: u32,
72
73 pub section_kind: u8,
75
76 pub share_kind: u8,
78
79 pub alignment: u8,
81
82 pub reserved: u8,
84}
85
86impl AsRef<[u8; size_of::<Self>()]> for SectionHeader {
87 fn as_ref(&self) -> &[u8; size_of::<Self>()] {
88 unsafe { std::mem::transmute::<_, &[u8; size_of::<Self>()]>(self) }
89 }
90}
91
92impl SectionHeader {
93 #[must_use]
95 pub fn from(contents: &[u8]) -> Self {
96 Self {
97 name_offset: i32_from_offset(contents, 0, Ordering::BigEndian),
98 default_address: u32_from_offset(contents, 4, Ordering::BigEndian),
99 total_size: u32_from_offset(contents, 8, Ordering::BigEndian),
100 unpacked_size: u32_from_offset(contents, 12, Ordering::BigEndian),
101 packed_size: u32_from_offset(contents, 16, Ordering::BigEndian),
102 container_offset: u32_from_offset(contents, 20, Ordering::BigEndian),
103 section_kind: contents[24],
104 share_kind: contents[25],
105 alignment: contents[26],
106 reserved: contents[27],
107 }
108 }
109}
110
111impl<'a> Pef<'a> {
112 #[instrument(name = "PEF parser", skip(contents))]
114 pub fn from(contents: &'a [u8]) -> Result<Self> {
115 if !bytes_offset_match(contents, 0, &MAGIC) {
116 bail!("Not a PEF file");
117 }
118
119 let arch = {
120 if bytes_offset_match(contents, 8, &PWPC) {
121 Architecture::PowerPC
122 } else if bytes_offset_match(contents, 8, &M68K) {
123 Architecture::M68k
124 } else {
125 Architecture::Unknown
126 }
127 };
128
129 let section_count = u16_from_offset(contents, 32, Ordering::BigEndian);
130 let inst_section_count = u16_from_offset(contents, 34, Ordering::BigEndian);
131
132 let mut sections = Sections::default();
133 for section_index in 0..(section_count + inst_section_count) as usize {
134 let offset_this_section = HEADER_SIZE + section_index * SECTION_HEADER_SIZE;
138 if offset_this_section > contents.len() {
139 break;
140 }
141 let this_section = SectionHeader::from(
142 &contents[offset_this_section..offset_this_section + HEADER_SIZE],
143 );
144
145 let section_name = {
146 if this_section.name_offset > 0 {
147 string_from_offset(contents, this_section.name_offset as usize)
148 } else {
149 format!("Unnamed section {section_index}")
150 }
151 };
152
153 sections.push(Section {
154 name: section_name,
155 is_executable: this_section.section_kind == 0 || this_section.section_kind == 8,
156 size: this_section.packed_size as usize,
157 offset: this_section.container_offset as usize,
158 virtual_size: 0,
159 virtual_address: 0,
160 data: None,
161 entropy: 0.0,
162 });
163 }
164
165 Ok(Self {
166 arch,
167 ordering: Ordering::BigEndian,
168 os: OperatingSystem::MacOS_Classic,
169 sections: Some(sections),
170 timestamp: u32_from_offset(contents, 16, Ordering::BigEndian),
171 contents,
172 })
173 }
174
175 #[must_use]
181 pub fn compiled_date(&self) -> DateTime<Utc> {
182 let janone1940 = DateTime::from_naive_utc_and_offset(
183 NaiveDateTime::parse_from_str("1904-01-01 00:00:00", "%Y-%m-%d %H:%M:%S").unwrap(),
184 Utc,
185 );
186 janone1940 + TimeDelta::try_seconds(i64::from(self.timestamp)).unwrap()
187 }
188}
189
190impl ExecutableFile for Pef<'_> {
191 fn architecture(&self) -> Architecture {
192 self.arch
193 }
194
195 fn pointer_size(&self) -> usize {
196 32
197 }
198
199 fn operating_system(&self) -> OperatingSystem {
200 self.os
201 }
202
203 fn compiled_timestamp(&self) -> Option<DateTime<Utc>> {
204 Some(self.compiled_date())
205 }
206
207 fn num_sections(&self) -> u32 {
208 self.sections.as_ref().unwrap_or(&Sections::default()).len() as u32
209 }
210
211 fn sections(&self) -> Option<&Sections> {
212 self.sections.as_ref()
213 }
214
215 fn import_hash(&self) -> Option<String> {
216 None
217 }
218
219 fn fuzzy_imports(&self) -> Option<String> {
220 None
221 }
222}
223
224impl SpecimenFile for Pef<'_> {
225 const MAGIC: &'static [&'static [u8]] = &[&MAGIC];
226
227 fn type_name(&self) -> &'static str {
228 "PEF"
229 }
230}
231
232impl Display for Pef<'_> {
233 fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
234 writeln!(f, "PEF file:")?;
235 writeln!(f, "\tOS: {}", self.os)?;
236 writeln!(f, "\tArchitecture: {}", self.arch)?;
237 writeln!(f, "\tOrdering: {}", self.ordering)?;
238 if let Some(sections) = &self.sections {
239 writeln!(f, "\t{} sections:", sections.len())?;
240 for section in sections {
241 writeln!(f, "\t\t{section}")?;
242 }
243 }
244 writeln!(
245 f,
246 "\tCompiled: {:?}",
247 self.compiled_date().format("%Y-%m-%d %H:%M:%S").to_string()
248 )?;
249 writeln!(f, "\tSize: {}", self.contents.len())?;
250 writeln!(f, "\tEntropy: {:.4}", self.contents.entropy())
251 }
252}
253
254#[cfg(test)]
255mod tests {
256 use super::*;
257 use rstest::rstest;
258
259 #[test]
260 fn beos() {
261 const BYTES: &[u8] = include_bytes!("../../../testdata/pef/BeApp");
262
263 let pef = Pef::from(BYTES).unwrap();
264 eprintln!("BeOS:\n{pef}");
265 assert_eq!(pef.arch, Architecture::PowerPC);
266 }
267
268 #[rstest]
269 #[case(include_bytes!("../../../testdata/pef/MacOS_1"))]
270 #[case(include_bytes!("../../../testdata/pef/MacOS_2"))]
271 fn macos(#[case] bytes: &[u8]) {
272 let pef = Pef::from(bytes).unwrap();
273 eprintln!("Mac OS:\n{pef}");
274 assert_eq!(pef.arch, Architecture::PowerPC);
275 }
276}