1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
use crate::{dir::*, error::*, header::*, mem::*, section::Section};
use alloc::vec::Vec;
use core::mem;
/// View of a PE32+ file
pub struct PeView<'a> {
dos_header: &'a DosHeader,
nt_header: &'a NtHeader,
sections: Vec<Section<'a>>,
}
impl<'a> PeView<'a> {
/// Creates a [`PeView`] of a PE32+ file by parsing and validating the
/// specified raw byte buffer representing it.
///
/// # Errors
///
/// This function will return an error if the byte buffer does not
/// represent a valid and complete PE32+ file.
pub fn parse(bytes: &'a [u8]) -> Result<Self> {
// Create an interface for easily reading the buffer
let mut data = ByteReader::new(bytes);
// Read and validate both the DOS- and NT-header
let dos_header = data.read::<DosHeader>()?.validate()?;
let nt_header = data
.skip(SkipPos::Rel(dos_header.e_lfanew as _))
.read::<NtHeader>()?
.validate()?;
// Jump to the RVA of the first section header
data.skip(SkipPos::Rel(
dos_header.e_lfanew as usize
+ mem::size_of::<u32>()
+ mem::size_of::<FileHeader>()
+ nt_header.file_header.size_of_optional_header as usize,
));
// Allocate a vector for holding the sections
let mut sections =
Vec::with_capacity(nt_header.file_header.num_of_sections as _);
// Iterate over each section header and save its section after validation
for _ in 0..nt_header.file_header.num_of_sections {
sections.push(Section::parse(
bytes,
data.read::<SectionHeader>()?
.validate(&nt_header.optional_header)?,
)?)
}
Ok(Self {
dos_header,
nt_header,
sections,
})
}
/// Returns a reference to the DOS-header of this [`PeView`].
pub fn dos_header(&self) -> &DosHeader {
self.dos_header
}
/// Returns a reference to the NT-header of this [`PeView`].
pub fn nt_header(&self) -> &NtHeader {
self.nt_header
}
/// Returns a reference to the sections of this [`PeView`].
pub fn sections(&self) -> &[Section] {
self.sections.as_ref()
}
/// Returns a reference to a single section of this [`PeView`],
/// who's raw data contains the specified RVA.
///
/// Returns [`None`] if no such section is found.
pub fn section_by_rva(&self, rva: u32) -> Option<&Section> {
self.sections
.iter()
.find(|s| !s.empty() && s.contains_rva(rva))
}
/// Returns a reference to a single section of this [`PeView`],
/// who's name is equal to the one specified.
///
/// Returns [`None`] if no such section is found.
pub fn section_by_name(&self, name: &str) -> Option<&Section> {
self.sections.iter().find(|s| s.name() == name)
}
/// Checks if specified flag is contained in the file headers characteristics.
pub fn has_flag(&self, flag: FileFlags) -> bool {
self.nt_header.file_header.characteristics & flag as u16 == 1
}
/// Returns a reference to the data directory of the specified type.
///
/// Returns [`None`] if the data directory is empty
pub fn directory(&self, typ: DataDirectoryType) -> Option<&DataDirectory> {
let directory =
&self.nt_header.optional_header.data_directories[typ as usize];
if directory.size > 0 {
Some(directory)
} else {
None
}
}
/// Returns the export table
///
/// # Errors
///
/// This function will return an error if:
/// - The [`DataDirectoryType::ExportTable`] data directory is empty ([`Error::DataDirectoryEmpty`])
/// - The .edata section is empty or not found ([`Error::SectionEmpty`])
/// - The export table is malformed
///
/// As soon as the iterator returned by this function returns an error,
/// it is in an unrecoverable invalid state and further usage will result
/// in potentially invalid results
pub fn exports(&self) -> Result<ExportTable> {
self.directory_table(DataDirectoryType::ExportTable)
}
/// Returns the import table
///
/// # Errors
///
/// This function will return an error if:
/// - The [`DataDirectoryType::ImportTable`] data directory is empty ([`Error::DataDirectoryEmpty`])
/// - The .idata section is empty or not found ([`Error::SectionEmpty`])
/// - The import table is malformed
///
/// As soon as the iterator returned by this function returns an error,
/// it is in an unrecoverable invalid state and further usage will result
/// in potentially invalid results
pub fn imports(&self) -> Result<ImportTable> {
self.directory_table(DataDirectoryType::ImportTable)
}
/// Returns the base relocation table
///
/// # Errors
///
/// This function will return an error if:
/// - The [`DataDirectoryType::RelocationTable`] data directory is empty ([`Error::DataDirectoryEmpty`])
/// - The .reloc section is empty or not found ([`Error::SectionEmpty`])
/// - The base relocation table is malformed
///
/// As soon as the iterator returned by this function returns an error,
/// it is in an unrecoverable invalid state and further usage will result
/// in potentially invalid results
pub fn relocations(&self) -> Result<RelocationTable> {
self.directory_table(DataDirectoryType::RelocationTable)
}
/// Internal method for abstracting over the process of getting
/// parsed tables for the raw data contained in the specified data directories
fn directory_table<T>(&'a self, typ: DataDirectoryType) -> Result<T>
where
T: DataDirectoryTable<'a>,
{
// Get the data directory
let directory = self.directory(typ).ok_or(Error::DataDirectoryEmpty)?;
// Get the raw data of the required section
let data = self
.section_by_rva(directory.rva)
.ok_or(Error::SectionEmpty)?
.data()
.as_ref()
.unwrap();
// Validate the length of the sections raw data
if data.bytes().len() <= directory.size as usize {
return Err(Error::InsufficientBuffer);
}
// Get a slice of the sections raw data which contains the required table
let bytes = match typ {
DataDirectoryType::ExportTable | DataDirectoryType::RelocationTable => {
&data.bytes_at(directory.rva as _)?[..directory.size as _]
}
DataDirectoryType::ImportTable => data.bytes_at(directory.rva as _)?,
_ => unimplemented!(),
};
// Return the actual table
Ok(T::new(bytes, directory))
}
}