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
193
194
195
196
197
198
199
200
201
202
203
204
205
206
use crate::{dir::*, error::*, header::*, mem::*, section::Section};
use alloc::vec::Vec;
use core::mem;
/// Address that represents a position within a [`PeView`]
#[derive(Clone, Copy)]
pub enum PeAddr {
/// Relative virtual address
Rva(u32),
/// Absolute offset within the file
FilePtr(u32),
}
/// View of a PE32+ file
pub struct PeView<'a> {
dos_header: &'a DosHeader,
nt_header: &'a NtHeader,
sections: Vec<Section<'a>>,
data: ByteReader<'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_to(Pos::Abs(dos_header.e_lfanew as _))
.read::<NtHeader>()?
.validate()?;
// Jump to the RVA of the first section header
data.skip_to(Pos::Abs(
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,
data,
})
}
/// 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 address.
///
/// Returns [`None`] if no such section is found.
pub fn section_by_addr(&self, addr: PeAddr) -> Option<&Section> {
self.sections
.iter()
.find(|s| !s.empty() && s.contains_addr(addr))
}
/// 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 a fallible iterator over 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
pub fn exports(&self) -> Result<ExportTable> {
self.directory_table(DataDirectoryType::ExportTable)
}
/// Returns a fallible iterator over 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
pub fn imports(&self) -> Result<ImportTable> {
self.directory_table(DataDirectoryType::ImportTable)
}
/// Returns a fallible iterator over 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
pub fn relocations(&self) -> Result<RelocationTable> {
self.directory_table(DataDirectoryType::RelocationTable)
}
/// Returns a fallible iterator over the certificate table
///
/// # Errors
///
/// This function will return an error if:
/// - The [`DataDirectoryType::CertificateTable`] data directory is empty ([`Error::DataDirectoryEmpty`])
/// - The certificate table is malformed
pub fn certificates(&self) -> Result<CertificateTable> {
self.directory_table(DataDirectoryType::CertificateTable)
}
/// 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 and raw data of table
let directory = self.directory(typ).ok_or(Error::DataDirectoryEmpty)?;
let data = match typ {
DataDirectoryType::CertificateTable => &self.data,
_ => self
.section_by_addr(PeAddr::Rva(directory.addr))
.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
| DataDirectoryType::CertificateTable => {
&data.bytes_at(directory.addr as _)?[..directory.size as _]
}
DataDirectoryType::ImportTable => data.bytes_at(directory.addr as _)?,
_ => unimplemented!(),
};
// Return the actual table
Ok(T::new(bytes, directory))
}
}