exe/
lib.rs

1//! [exe-rs](https://github.com/frank2/exe-rs) is a library for handling PE files, whether it be building them or analyzing them!
2//!
3//! Getting started is easy:
4//! ```rust
5//! use exe::pe::{PE, VecPE};
6//! use exe::types::{ImportDirectory, ImportData, CCharString};
7//!
8//! let image = VecPE::from_disk_file("test/compiled.exe").unwrap();
9//! let import_directory = ImportDirectory::parse(&image).unwrap();
10//!
11//! for descriptor in import_directory.descriptors {
12//!    println!("Module: {}", descriptor.get_name(&image).unwrap().as_str().unwrap());
13//!    println!("Imports:");
14//!
15//!    for import in descriptor.get_imports(&image).unwrap() {
16//!       match import {
17//!          ImportData::Ordinal(x) => println!("   #{}", x),
18//!          ImportData::ImportByName(s) => println!("   {}", s)
19//!       }
20//!    }
21//! }
22//! ```
23//!
24//! Standard PE headers and other types can be found in the [headers](headers/) module, while
25//! helper types can be found in the [types](types/) module. Low-level functionality for handling
26//! PE data, such as collecting pointers and managing pointers as well as pulling out data, is
27//! handled by the [pkbuffer](pkbuffer) module and the [`Buffer`](pkbuffer::Buffer) trait.
28//! Further usage examples can be found in the [test file](https://github.com/frank2/exe-rs/blob/main/src/tests.rs).
29
30pub mod headers;
31pub mod imphash;
32pub mod pe;
33pub mod types;
34
35#[cfg(feature="win32")]
36pub mod valloc;
37
38pub use crate::headers::*;
39pub use crate::imphash::*;
40pub use crate::pe::*;
41pub use crate::types::*;
42
43#[cfg(feature="win32")]
44pub use crate::valloc::*;
45
46#[cfg(test)]
47mod tests;
48
49use md5::{Md5, Digest};
50use sha1::Sha1;
51use sha2::Sha256;
52
53use num_traits;
54
55use pkbuffer::Error as PKError;
56
57use std::collections::HashMap;
58use std::io::Error as IoError;
59use std::str::Utf8Error;
60use widestring::error::Utf16Error;
61
62/// Aligns a given `value` to the boundary specified by `boundary`.
63///
64/// `value` and `boundary` must be an unsigned integer type.
65///
66/// # Example
67///
68/// ```rust
69/// use exe::align;
70///
71/// let value = 0x1200usize;
72/// let alignment = 0x1000usize;
73///
74/// assert_eq!(0x2000, align(value, alignment));
75/// ```
76pub fn align<V: num_traits::Num + num_traits::Unsigned + num_traits::Zero + core::ops::Rem + Copy>(value: V, boundary: V) -> V {
77    if value % boundary == (num_traits::zero::<V>()) {
78        value
79    }
80    else {
81        value + (boundary - (value % boundary))
82    }
83}
84
85/// Find all embedded images within the given [`PE`](PE) file, rendering them as the given [`PEType`](PEType).
86pub fn find_embedded_images<P: PE>(pe: &P, pe_type: PEType) -> Result<Option<Vec<PtrPE>>, Error> {
87    let mut results = Vec::<PtrPE>::new();
88    let mut index = 2usize; // skip the initial MZ header
89
90    while index < pe.len() {
91        if index > (u32::MAX as usize) { break; }
92
93        let mz = match pe.get_ref::<u16>(index) {
94            Ok(u) => u,
95            Err(_) => { index += 1; continue; },
96        };
97        if *mz != DOS_SIGNATURE { index += 1; continue; }
98
99        let dos_header = match pe.get_ref::<ImageDOSHeader>(index) {
100            Ok(h) => h,
101            Err(_) => { index += 1; continue; },
102        };
103
104        let e_lfanew: usize = index + dos_header.e_lfanew.0 as usize;
105
106        let nt_signature = match pe.get_ref::<u32>(e_lfanew) {
107            Ok(s) => s,
108            Err(_) => { index += 1; continue; },
109        };
110
111        if *nt_signature != NT_SIGNATURE { index += 1; continue; }
112
113        // we now have some kind of PE image. whether it's a valid PE image
114        // is yet to be determined. so read to the end of the buffer as a
115        // temporary image to start parsing out the proper image.
116        let eof = pe.len() - index;
117        let pe_ptr = match pe.offset_to_ptr(index) {
118            Ok(p) => p,
119            Err(_) => { index += 1; continue; },
120        };
121        let temp_pe = PtrPE::new(pe_type, pe_ptr, eof);
122
123        let image_size = match pe_type {
124            PEType::Disk => match temp_pe.calculate_disk_size() {
125                Ok(s) => s,
126                Err(_) => { index += 1; continue; },
127            },
128            PEType::Memory => match temp_pe.calculate_memory_size() {
129                Ok(s) => s,
130                Err(_) => { index += 1; continue; },
131            },
132        };
133
134        let validate_size = index + image_size;
135        if validate_size > pe.len() { index += 1; continue; }
136
137        let real_pe = PtrPE::new(pe_type, pe_ptr, image_size);
138
139        results.push(real_pe);
140        index += image_size;
141    }
142
143    if results.len() == 0 { Ok(None) }
144    else { Ok(Some(results)) }
145}
146
147/// Errors produced by the library.
148#[derive(Debug)]
149pub enum Error {
150    /// The error originated in `std::io`.
151    IoError(IoError),
152    /// The error was a UTF8 error.
153    Utf8Error(Utf8Error),
154    /// The error was a UTF16 error.
155    Utf16Error(Utf16Error),
156    /// The error originated in the [pkbuffer](pkbuffer) library.
157    PKBufferError(PKError),
158    /// The error occurred while parsing a number.
159    ParseIntError(std::num::ParseIntError),
160    /// The operation went out of bounds of something in the PE file.
161    ///
162    /// Arg0 is the expected boundary, arg1 is the offending boundary.
163    OutOfBounds(usize,usize),
164    /// The PE file has an invalid DOS signature.
165    ///
166    /// Arg0 is the offending signature.
167    InvalidDOSSignature(u16),
168    /// The header is not aligned correctly.
169    BadAlignment,
170    /// The PE file has an invalid PE signature.
171    ///
172    /// Arg0 is the offending signature.
173    InvalidPESignature(u32),
174    /// The PE file has an invalid NT signature.
175    ///
176    /// Arg0 is the offending signature.
177    InvalidNTSignature(u16),
178    /// The offset provided or generated resulted in an invalid offset value.
179    ///
180    /// Arg0 is the offending offset.
181    InvalidOffset(Offset),
182    /// The RVA provided or generated resulted in an invalid RVA value.
183    ///
184    /// Arg0 is the offending RVA.
185    InvalidRVA(RVA),
186    /// The VA provided or generated resulted in an invalid VA value.
187    ///
188    /// Arg0 is the offending VA.
189    InvalidVA(VA),
190    /// The PE section was not found given the search criteria (e.g., an RVA value)
191    SectionNotFound,
192    /// The pointer provided or generated did not fit in the range of the buffer.
193    ///
194    /// Arg0 is the offending pointer.
195    BadPointer(*const u8),
196    /// The data directory requested is currently unsupported.
197    ///
198    /// Arg0 is the unsupported directory entry.
199    UnsupportedDirectory(ImageDirectoryEntry),
200    /// The relocation entry is invalid.
201    InvalidRelocation,
202    /// The provided directory is not available.
203    ///
204    /// Arg0 is the unavailable directory entry.
205    BadDirectory(ImageDirectoryEntry),
206    /// The data directory is corrupt and cannot be parsed.
207    CorruptDataDirectory,
208    /// The architecture of the Rust binary and the given PE file do not match.
209    ///
210    /// Arg0 represents the expected arch, arg1 represents the offending arch.
211    ArchMismatch(Arch, Arch),
212    /// The resource was not found with the given arguments.
213    ResourceNotFound,
214    /// Only available on Windows. The function returned a Win32 error.
215    ///
216    /// Arg0 represents the Win32 error.
217    #[cfg(feature="win32")]
218    Win32Error(u32),
219    /// Only shows up on Windows. The section table was found to be not contiguous.
220    #[cfg(feature="win32")]
221    SectionsNotContiguous,
222    /// Only shows up on Windows. The section characteristics were bad. This is because there was a bad combination
223    /// of read, write and execute characteristics in the section.
224    #[cfg(feature="win32")]
225    BadSectionCharacteristics(SectionCharacteristics),
226    /// Only shows up on Windows. The memory region pointed to by the [`VallocBuffer`](VallocBuffer) is no longer available.
227    #[cfg(feature="win32")]
228    BufferNotAvailable,
229}
230impl std::fmt::Display for Error {
231    fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
232        match *self {
233            Error::IoError(ref io_error) =>
234                write!(f, "i/o error: {}", io_error.to_string()),
235            Error::Utf8Error(ref utf8_error) =>
236                write!(f, "UTF8 error: {}", utf8_error.to_string()),
237            Error::Utf16Error(ref utf16_error) =>
238                write!(f, "UTF16 error: {}", utf16_error.to_string()),
239            Error::PKBufferError(ref pk_error) =>
240                write!(f, "PKBuffer error: {}", pk_error.to_string()),
241            Error::ParseIntError(ref int_error) =>
242                write!(f, "Int parsing error: {}", int_error.to_string()),
243            Error::OutOfBounds(expected, got) =>
244                write!(f, "The PE buffer was too small to complete the operation. Buffer length is {}, got {}.", expected, got),
245            Error::InvalidDOSSignature(sig) =>
246                write!(f, "The PE file has an invalid DOS signature: {:#x}", sig),
247            Error::BadAlignment =>
248                write!(f, "The header is not aligned correctly."),
249            Error::InvalidPESignature(sig) =>
250                write!(f, "The PE file has an invalid PE signature: {:#x}", sig),
251            Error::InvalidNTSignature(sig) =>
252                write!(f, "The PE file has an invalid NT signature: {:#x}", sig),
253            Error::InvalidOffset(offset) =>
254                write!(f, "The offset provided or generated resulted in an invalid offset value: {:#x}", offset.0),
255            Error::InvalidRVA(rva) =>
256                write!(f, "The RVA provided or generated resulted in an invalid RVA value: {:#x}", rva.0),
257            Error::InvalidVA(va) => {
258                let va_value = match va {
259                    VA::VA32(va32) => va32.0 as u64,
260                    VA::VA64(va64) => va64.0,
261                };
262
263                write!(f, "The VA provided or generated resulted in an invalid VA value: {:#x}", va_value)
264            },
265            Error::SectionNotFound =>
266                write!(f, "The PE section was not found given the search criteria."),
267            Error::BadPointer(ptr) =>
268                write!(f, "The pointer provided or generated did not fit in the range of the buffer: {:p}", ptr),
269            Error::UnsupportedDirectory(data_dir) =>
270                write!(f, "The data directory requested is currently unsupported: {:?}", data_dir),
271            Error::InvalidRelocation =>
272                write!(f, "The relocation entry is invalid."),
273            Error::BadDirectory(data_dir) =>
274                write!(f, "The provided directory is not available in the PE: {:?}", data_dir),
275            Error::CorruptDataDirectory =>
276                write!(f, "The data directory is corrupt and cannot be parsed."),
277            Error::ArchMismatch(expected, got) =>
278                write!(f, "The architecture of the Rust binary and the given PE file do not match: expected {:?}, got {:?}", expected, got),
279            Error::ResourceNotFound =>
280                write!(f, "The resource was not found by the provided parameters."),
281            #[cfg(feature="win32")]
282            Error::Win32Error(err) => write!(f, "The function returned a Win32 error: {:#x}", err),
283            #[cfg(feature="win32")]
284            Error::SectionsNotContiguous => write!(f, "The sections in the PE file were not contiguous"),
285            #[cfg(feature="win32")]
286            Error::BadSectionCharacteristics(chars) => write!(f, "Bad section characteristics: {:#x}", chars.bits()),
287            #[cfg(feature="win32")]
288            Error::BufferNotAvailable => write!(f, "The buffer is no longer available"),
289        }
290    }
291}
292impl std::error::Error for Error {
293    fn source(&self) -> Option<&(dyn std::error::Error + 'static)> {
294        match self {
295            Self::IoError(ref e) => Some(e),
296            Self::PKBufferError(ref e) => Some(e),
297            _ => None,
298        }
299    }
300}
301impl std::convert::From<IoError> for Error {
302    fn from(io_error: IoError) -> Self {
303        Self::IoError(io_error)
304    }
305}
306impl std::convert::From<Utf8Error> for Error {
307    fn from(utf8_error: Utf8Error) -> Self {
308        Self::Utf8Error(utf8_error)
309    }
310}
311impl std::convert::From<Utf16Error> for Error {
312    fn from(utf16_error: Utf16Error) -> Self {
313        Self::Utf16Error(utf16_error)
314    }
315}
316impl std::convert::From<PKError> for Error {
317    fn from(pk_error: PKError) -> Self {
318        Self::PKBufferError(pk_error)
319    }
320}
321impl std::convert::From<std::num::ParseIntError> for Error {
322    fn from(int_error: std::num::ParseIntError) -> Self {
323        Self::ParseIntError(int_error)
324    }
325}
326unsafe impl Send for Error {}
327unsafe impl Sync for Error {}
328
329/// Syntactic sugar for producing various hashes of data. Typically applied to ```[u8]``` slices.
330///
331/// # Example
332///
333/// ```rust
334/// use hex;
335///
336/// use exe::{HashData, PE, VecPE};
337/// use exe::types::CCharString;
338///
339/// let pefile = VecPE::from_disk_file("test/compiled.exe").unwrap();
340/// let section_table = pefile.get_section_table().unwrap();
341///
342/// println!("=Section Hashes=");
343///
344/// for section in section_table {
345///    println!("[{}]", section.name.as_str().unwrap());
346///
347///    let section_data = section.read(&pefile).unwrap();
348///
349///    println!("MD5:    {}", hex::encode(section_data.md5()));
350///    println!("SHA1:   {}", hex::encode(section_data.sha1()));
351///    println!("SHA256: {}\n", hex::encode(section_data.sha256()));
352/// }
353pub trait HashData {
354    /// Produce an MD5 hash.
355    fn md5(&self) -> Vec<u8>;
356    /// Produce a SHA1 hash.
357    fn sha1(&self) -> Vec<u8>;
358    /// Produce a SHA256 hash.
359    fn sha256(&self) -> Vec<u8>;
360}
361impl HashData for [u8] {
362    fn md5(&self) -> Vec<u8> {
363        let mut hash = Md5::new();
364        hash.update(self);
365        hash.finalize()
366            .as_slice()
367            .iter()
368            .cloned()
369            .collect()
370    }
371    fn sha1(&self) -> Vec<u8> {
372        let mut hash = Sha1::new();
373        hash.update(self);
374        hash.finalize()
375            .as_slice()
376            .iter()
377            .cloned()
378            .collect()
379    }
380    fn sha256(&self) -> Vec<u8> {
381        let mut hash = Sha256::new();
382        hash.update(self);
383        hash.finalize()
384            .as_slice()
385            .iter()
386            .cloned()
387            .collect()
388    }
389}
390impl<T> HashData for T
391where
392    T: PE
393{
394    fn md5(&self) -> Vec<u8> { self.as_slice().md5() }
395    fn sha1(&self) -> Vec<u8> { self.as_slice().sha1() }
396    fn sha256(&self) -> Vec<u8> { self.as_slice().sha256() }
397}
398
399/// Syntactic sugar to calculate entropy on a given object.
400pub trait Entropy {
401    /// Calculates the entropy of a given object. Returns a value between 0.0 (low entropy) and 8.0 (high entropy).
402    fn entropy(&self) -> f64;
403}
404impl Entropy for [u8] {
405    // algorithm once again borrowed from Ero Carrera's legacy-leaving pefile
406    fn entropy(&self) -> f64 {
407        if self.len() == 0 { return 0.0_f64; }
408        
409        let mut occurences: HashMap<u8, usize> = (0..=255).map(|x| (x, 0)).collect();
410        for c in self { occurences.insert(*c, occurences.get(c).unwrap()+1); }
411
412        let mut entropy = 0.0_f64;
413
414        for (_, weight) in occurences {
415            let p_x = (weight as f64) / (self.len() as f64);
416
417            if p_x == 0.0 { continue; }
418            
419            entropy -= p_x * p_x.log2();
420        }
421
422        entropy.abs()
423    }
424}
425impl<T> Entropy for T
426where
427    T: PE
428{
429    fn entropy(&self) -> f64 { self.as_slice().entropy() }
430}
431