use once_cell::unsync::OnceCell;
use vmi_core::{
Architecture, Va, VmiError, VmiState, VmiVa,
driver::VmiRead,
os::{NoOS, VmiOsImage, VmiOsImageArchitecture, VmiOsImageSymbol},
};
use crate::{
ArchAdapter, WindowsError, WindowsOs,
pe::{
ExportTarget, IMAGE_DIRECTORY_ENTRY_DEBUG, IMAGE_DIRECTORY_ENTRY_EXCEPTION,
IMAGE_DIRECTORY_ENTRY_EXPORT, ImageDataDirectory, ImageDosHeader, ImageNtHeaders,
ImageOptionalHeader, PeDebugDirectory, PeExceptionDirectory, PeExportDirectory, PeHeader,
PeImage,
},
};
pub struct WindowsImage<'a, Driver>
where
Driver: VmiRead,
Driver::Architecture: ArchAdapter<Driver>,
{
pub(crate) vmi: VmiState<'a, NoOS<Driver>>,
va: Va,
pe_header: OnceCell<PeHeader>,
}
impl<Driver> VmiVa for WindowsImage<'_, Driver>
where
Driver: VmiRead,
Driver::Architecture: ArchAdapter<Driver>,
{
fn va(&self) -> Va {
self.va
}
}
impl<'a, Driver> WindowsImage<'a, Driver>
where
Driver: VmiRead,
Driver::Architecture: ArchAdapter<Driver>,
{
const MAX_DATA_DIRECTORY_SIZE: u32 = 1024 * 1024;
pub fn new(vmi: VmiState<'a, WindowsOs<Driver>>, va: Va) -> Self {
Self::new_without_os(vmi.without_os(), va)
}
pub(crate) fn new_without_os(vmi: VmiState<'a, NoOS<Driver>>, va: Va) -> Self {
Self {
vmi,
va,
pe_header: OnceCell::new(),
}
}
fn find_data_directory(&self, index: usize) -> Result<Option<ImageDataDirectory>, VmiError> {
let entry = match self.pe()?.data_directories().get(index).copied() {
Some(entry) => entry,
None => return Ok(None),
};
if entry.virtual_address == 0
|| entry.size == 0
|| entry.size > Self::MAX_DATA_DIRECTORY_SIZE
{
return Ok(None);
}
Ok(Some(entry))
}
fn read_data_directory(&self, entry: &ImageDataDirectory) -> Result<Vec<u8>, VmiError> {
let mut data = vec![0; entry.size as usize];
self.vmi
.read(self.va + entry.virtual_address as u64, &mut data)?;
Ok(data)
}
fn pe(&self) -> Result<&PeHeader, VmiError> {
self.pe_header.get_or_try_init(|| {
let mut data = vec![0; Driver::Architecture::PAGE_SIZE as usize];
self.vmi.read(self.va, &mut data)?;
Ok(PeHeader::parse(&data).map_err(WindowsError::from)?)
})
}
}
impl<'a, Driver> PeImage for WindowsImage<'a, Driver>
where
Driver: VmiRead,
Driver::Architecture: ArchAdapter<Driver>,
{
fn read_at_rva(&self, rva: u32, buf: &mut [u8]) -> Result<(), VmiError> {
self.vmi.read(self.va + rva as u64, buf)
}
fn dos_header(&self) -> Result<&ImageDosHeader, VmiError> {
Ok(self.pe()?.dos_header())
}
fn nt_headers(&self) -> Result<&ImageNtHeaders, VmiError> {
Ok(self.pe()?.nt_headers())
}
fn export_directory(&self) -> Result<Option<PeExportDirectory<'_, Self>>, VmiError> {
let entry = match self.find_data_directory(IMAGE_DIRECTORY_ENTRY_EXPORT)? {
Some(entry) => entry,
None => return Ok(None),
};
let data = self.read_data_directory(&entry)?;
Ok(Some(PeExportDirectory::new(self, entry, data)))
}
fn exception_directory(&self) -> Result<Option<PeExceptionDirectory<'_, Self>>, VmiError> {
let entry = match self.find_data_directory(IMAGE_DIRECTORY_ENTRY_EXCEPTION)? {
Some(entry) => entry,
None => return Ok(None),
};
let data = self.read_data_directory(&entry)?;
Ok(Some(PeExceptionDirectory::new(self, data)))
}
fn debug_directory(&self) -> Result<Option<PeDebugDirectory<'_, Self>>, VmiError> {
let entry = match self.find_data_directory(IMAGE_DIRECTORY_ENTRY_DEBUG)? {
Some(entry) => entry,
None => return Ok(None),
};
let data = self.read_data_directory(&entry)?;
Ok(Some(PeDebugDirectory::new(self, data)))
}
}
impl<'a, Driver> VmiOsImage<'a, Driver> for WindowsImage<'a, Driver>
where
Driver: VmiRead,
Driver::Architecture: ArchAdapter<Driver>,
{
type Os = WindowsOs<Driver>;
fn base_address(&self) -> Va {
self.va
}
fn architecture(&self) -> Result<Option<VmiOsImageArchitecture>, VmiError> {
let nt_headers = self.pe()?.nt_headers();
match &nt_headers.optional_header {
ImageOptionalHeader::ImageOptionalHeader32(_) => Ok(Some(VmiOsImageArchitecture::X86)),
ImageOptionalHeader::ImageOptionalHeader64(_) => {
Ok(Some(VmiOsImageArchitecture::Amd64))
}
}
}
fn exports(&self) -> Result<Vec<VmiOsImageSymbol>, VmiError> {
let directory = match self.export_directory()? {
Some(directory) => directory,
None => return Ok(Vec::new()),
};
let exports = directory.exports().map_err(WindowsError::from)?;
Ok(exports
.into_iter()
.filter_map(|export| match export.target {
ExportTarget::Address(address) => Some(VmiOsImageSymbol {
name: String::from_utf8_lossy(export.name?).to_string(),
address: self.va + address as u64,
}),
_ => None,
})
.collect())
}
}