diff --git a/src-tauri/src/app_error.rs b/src-tauri/src/app_error.rs index 8c5eeea..5f1afcc 100644 --- a/src-tauri/src/app_error.rs +++ b/src-tauri/src/app_error.rs @@ -7,6 +7,9 @@ use crate::pe_parse::error::{ #[derive(Debug, thiserror::Error)] pub enum AppError { + #[error(transparent)] + Utf8Error(#[from] std::str::Utf8Error), + #[error(transparent)] Io(#[from] std::io::Error), diff --git a/src-tauri/src/app_state.rs b/src-tauri/src/app_state.rs index a628733..3b85be3 100644 --- a/src-tauri/src/app_state.rs +++ b/src-tauri/src/app_state.rs @@ -1,11 +1,15 @@ -use std::mem::{self}; - use crate::app_error::AppError; -use crate::pe_parse::header::{ImageSectionHeader, SectionCharacteristics}; +use crate::pe_parse::header::{ + ImageDirectoryEntry, ImageImportDescriptor, ImageSectionHeader, ImportLookupTable32, + ImportLookupTable64, SectionCharacteristics, +}; use crate::pe_parse::pe::MutablePE; use crate::services::file::expand_file_size; use crate::{pe_parse::pe::ReadOnlyPE, services::file}; use memmap2::Mmap; +use serde::Serialize; +use std::ffi::CStr; +use std::mem::{self}; /// 应用的状态结构体 #[derive(Default)] @@ -100,7 +104,10 @@ impl AppState { // 2. 以可读可写的方式重新映射文件 rw_mmap_option = Some(file::mmap_file_rw(file_path)?); // 拓展头部大小 拓展一个节的大小 - rw_mmap_option.as_mut().unwrap().expand_headers(file_alignment)?; + rw_mmap_option + .as_mut() + .unwrap() + .expand_headers(file_alignment)?; } // 2. 添加节 // 先拓展文件大小 @@ -111,7 +118,7 @@ impl AppState { } let rw_mmap = rw_mmap_option.as_mut().unwrap(); - let characteristics = SectionCharacteristics::from_bits(section_characteristics).unwrap(); + let characteristics = SectionCharacteristics::from_bits(section_characteristics).unwrap(); rw_mmap.add_section(§ion_name_bytes, section_size, characteristics)?; @@ -120,4 +127,141 @@ impl AppState { Ok(()) } -} \ No newline at end of file + + + pub fn get_import_tables(&self) -> Result, AppError> { + let mmap = self.get_mmap_ref()?; + let is_64_bit = self.is_64_bit.unwrap(); + // 1. 获取导入表 + let import_table = mmap.get_data_directory(ImageDirectoryEntry::Import)?; + // 2. 获取导入表的RVA + let import_table_rva = import_table.virtual_address; + // 3. RVA转FOA + let import_table_foa = mmap.rva_to_foa(import_table_rva)?; + // 从文件import_table_foa循环解析ImageImportDescriptor,当解析出的导入表==ImageImportDescriptor::default()时,结束循环 + let mut origin_import_module_tables: Vec<&ImageImportDescriptor> = Vec::new(); + let mut base_offset = import_table_foa as usize; + loop { + let import_module_item = unsafe { + let ptr = mmap.as_ptr().add(base_offset) as *const ImageImportDescriptor; + ptr.as_ref().unwrap() + }; + if import_module_item == &ImageImportDescriptor::default() { + break; + } + origin_import_module_tables.push(import_module_item); + base_offset += mem::size_of::(); + } + // 4. 解析导入表 主要是来格式化一些数据 先创建一个ImportModuleTable 的变长数组 + let mut import_module_tables: Vec = Vec::new(); + // 循环处理origin_import_module_tables + for origin_item in origin_import_module_tables.iter() { + let mut import_module = ImportModuleTable::default(); + // 1. 获取模块名称 + // 先获取模块名称的FOA + let module_name_foa = mmap.rva_to_foa(origin_item.name)?; + // 获取模块名称 需要unsafe读取 + let module_name = unsafe { + let ptr = mmap[module_name_foa as usize..].as_ptr() as *const i8; + CStr::from_ptr(ptr).to_str()?.to_string() + }; + import_module.module_name = module_name; + // 其他成员 + import_module.forwarder_chain = origin_item.forwarder_chain; + import_module.iat_rva = origin_item.first_thunk; + import_module.int_rva = origin_item.original_first_thunk; + import_module.module_name_rva = origin_item.name; + import_module.timestamp = origin_item.time_date_stamp; + // 2. 获取函数表 + let mut functions: Vec = Vec::new(); + // 获取函数表的FOA + let mut base_offset = mmap.rva_to_foa(origin_item.original_first_thunk)? as usize; + // 循环解析函数表 + loop { + // TODO: 这里需要判断是64位还是32位 + // 如果是64位,每次读取8字节 否则读取4字节 + let import_lookup_table = if is_64_bit { + let ptr = mmap[base_offset as usize..].as_ptr() as *const u64; + unsafe { ptr.read() as u64 } + } else { + let ptr = mmap[base_offset as usize..].as_ptr() as *const u32; + unsafe { ptr.read() as u64 } + }; + + // 如果import_lookup_table == 0 则表示结束 + if import_lookup_table == 0 { + break; + } + + // 1. 判断是序号还是命名函数 + let is_named = match is_64_bit { + true => { + import_lookup_table & ImportLookupTable64::IMPORT_BY_ORDINAL.bits() == 0 + } + false => { + import_lookup_table as u32 & ImportLookupTable32::IMPORT_BY_ORDINAL.bits() + == 0 + } + }; + let mut import_function_table = ImportFunctionTable::default(); + let hint_name_table_rva = import_lookup_table as u32 & 0x7FFFFFFF; + let hint_name_table_foa = mmap.rva_to_foa(hint_name_table_rva)?; + // 1. 读hint 两个字节 + let funtion_hint: u16 = unsafe { + let ptr = mmap[hint_name_table_foa as usize..].as_ptr() as *const u16; + ptr.read() + }; + import_function_table.function_hint = funtion_hint; + import_function_table.function_address = hint_name_table_rva; + match is_named { + true => { + // 命名函数 + let function_name = unsafe { + let ptr = + mmap[hint_name_table_foa as usize + 2..].as_ptr() as *const i8; + CStr::from_ptr(ptr).to_str()?.to_string() + }; + import_function_table.function_name = function_name; + functions.push(ImportFunctionTableItem::Named(import_function_table)); + } + false => { + functions.push(ImportFunctionTableItem::Ordinal(import_function_table)); + } + } + // 下一个 + base_offset += if is_64_bit { 8 } else { 4 }; + } + import_module.functions = functions; + import_module_tables.push(import_module); + } + Ok(import_module_tables) + } +} + +// 定义一个获取导入表后需要返回的结构 +#[derive(Debug, Default, Clone, Serialize)] +pub struct ImportModuleTable { + pub module_name: String, // 模块名称 + pub functions: Vec, // 这里要另一个结构体来描述 + pub timestamp: u32, // 时间戳 + pub forwarder_chain: u32, // 转发链 + pub module_name_rva: u32, // dll名称的RVA + pub iat_rva: u32, // IAT的RVA + pub int_rva: u32, // INT的RVA +} + +// 因为导入查找表可能是函数名、也可能是序号 所以用枚举包一下最好 +#[derive(Debug, Clone, Serialize)] +pub enum ImportFunctionTableItem { + // 命名的导入函数 + Named(ImportFunctionTable), + // 序号表示的 + Ordinal(ImportFunctionTable), +} + +#[derive(Debug, Default, Clone, Serialize)] +pub struct ImportFunctionTable { + pub function_name: String, // 函数名称 + pub function_address: u32, // 函数地址 IAT + pub function_hint: u16, // 函数提示 +} diff --git a/src-tauri/src/commands.rs b/src-tauri/src/commands.rs index d852ec4..ad252c1 100644 --- a/src-tauri/src/commands.rs +++ b/src-tauri/src/commands.rs @@ -97,6 +97,11 @@ pub fn command_get_file_pe_node_tree_data( key: "section_header".to_string(), children: vec![], }, + PeNodeTreeData { + title: "导入目录".to_string(), + key: "import_directory".to_string(), + children: vec![], + }, ], }; Ok(vec![result]) @@ -179,6 +184,19 @@ pub fn command_get_pe_data_section_headers( })) } +// 命令,获取导入表数据 +#[tauri::command(async)] +pub fn command_get_pe_data_import_directory( + app_state: State<'_, Mutex>, +) -> Result { + let app_state = app_state.lock().unwrap(); + let import_directory = app_state.get_import_tables()?; + Ok(json!({ + "fields": import_directory, + "base_offset": 0, + })) +} + // 命令,修改偏移处数据 #[tauri::command(async)] pub fn command_write_data( diff --git a/src-tauri/src/lib.rs b/src-tauri/src/lib.rs index 83d82b7..d7c89f2 100644 --- a/src-tauri/src/lib.rs +++ b/src-tauri/src/lib.rs @@ -24,6 +24,7 @@ pub fn run() { commands::command_get_pe_data_file_header, commands::command_get_pe_data_optional_header, commands::command_get_pe_data_section_headers, + commands::command_get_pe_data_import_directory, commands::command_write_data, commands::command_add_section, commands::command_test, diff --git a/src-tauri/src/pe_parse/error.rs b/src-tauri/src/pe_parse/error.rs index ca8b1b9..0cd3319 100644 --- a/src-tauri/src/pe_parse/error.rs +++ b/src-tauri/src/pe_parse/error.rs @@ -12,6 +12,12 @@ pub enum PEParseError { InvalidOptionalSize, #[error("解析超出了文件范围")] OutOfBounds, + #[error("错误的RVA: {0}")] + RvaToFoaError(u32), + #[error("RVA对应的是一个空节区数据!RVA: {0}")] + RvaToEmptySection(u32), + #[error("错误的数据目录索引")] + InvalidDataDirectoryIndex, } /// PE操作的错误 #[derive(Error, Debug)] diff --git a/src-tauri/src/pe_parse/header.rs b/src-tauri/src/pe_parse/header.rs index 613bef9..6a945ee 100644 --- a/src-tauri/src/pe_parse/header.rs +++ b/src-tauri/src/pe_parse/header.rs @@ -45,7 +45,7 @@ pub struct ImageNTHeader64 { #[repr(C)] #[derive(Serialize, Clone, Copy)] #[serde(untagged)] -pub enum ImageNTHeader{ +pub enum ImageNTHeader { NTHeader32(ImageNTHeader32), NTHeader64(ImageNTHeader64), } @@ -53,21 +53,24 @@ pub enum ImageNTHeader{ #[repr(C)] #[derive(Serialize)] #[serde(untagged)] -pub enum ImageNTHeaderMut<'a>{ +pub enum ImageNTHeaderMut<'a> { NTHeader32(&'a mut ImageNTHeader32), NTHeader64(&'a mut ImageNTHeader64), } -impl <'a> ImageNTHeaderMut<'a>{ - pub fn get_optional_header_mut(&mut self) -> ImageOptionalHeaderMut{ - match self{ - ImageNTHeaderMut::NTHeader32(header) => ImageOptionalHeaderMut::OptionalHeader32(&mut header.optional_header), - ImageNTHeaderMut::NTHeader64(header) => ImageOptionalHeaderMut::OptionalHeader64(&mut header.optional_header), +impl<'a> ImageNTHeaderMut<'a> { + pub fn get_optional_header_mut(&mut self) -> ImageOptionalHeaderMut { + match self { + ImageNTHeaderMut::NTHeader32(header) => { + ImageOptionalHeaderMut::OptionalHeader32(&mut header.optional_header) + } + ImageNTHeaderMut::NTHeader64(header) => { + ImageOptionalHeaderMut::OptionalHeader64(&mut header.optional_header) + } } } } - #[repr(C)] #[derive(Debug, Clone, Copy, Serialize)] pub struct ImageFileHeader { @@ -334,22 +337,22 @@ pub struct ImageDataDirectory { #[derive(Copy, Clone, Eq, PartialEq, Debug)] pub enum ImageDirectoryEntry { - Export = 0, - Import = 1, - Resource = 2, - Exception = 3, - Security = 4, - BaseReloc = 5, - Debug = 6, - Architecture = 7, - GlobalPTR = 8, - TLS = 9, - LoadConfig = 10, - BoundImport = 11, - IAT = 12, - DelayImport = 13, - COMDescriptor = 14, - Reserved = 15, + Export = 0, + Import = 1, + Resource = 2, + Exception = 3, + Security = 4, + BaseReloc = 5, + Debug = 6, + Architecture = 7, + GlobalPTR = 8, + TLS = 9, + LoadConfig = 10, + BoundImport = 11, + IAT = 12, + DelayImport = 13, + COMDescriptor = 14, + Reserved = 15, } #[repr(C)] @@ -374,9 +377,27 @@ impl ImageOptionalHeader { ImageOptionalHeader::OptionalHeader64(header) => header.size_of_image, } } + pub fn get_size_of_optional_header(&self) -> u32 { + match self { + ImageOptionalHeader::OptionalHeader32(_) => { + size_of::() as u32 + } + ImageOptionalHeader::OptionalHeader64(_) => { + size_of::() as u32 + } + } + } + /// 获取数据目录数量 + pub fn get_data_directory_count(&self) -> u32 { + // 如果数据目录数量大于16,则返回16 + match self { + ImageOptionalHeader::OptionalHeader32(header) => header.number_of_rva_and_sizes, + ImageOptionalHeader::OptionalHeader64(header) => header.number_of_rva_and_sizes, + } + .min(16) + } } - #[repr(C)] #[derive(Debug, Clone, Copy, Serialize)] pub struct ImageSectionHeader { @@ -391,3 +412,42 @@ pub struct ImageSectionHeader { pub number_of_linenumbers: u16, pub characteristics: SectionCharacteristics, } + +#[repr(C)] +#[derive(Copy, Clone, Eq, PartialEq, Debug, Default)] +pub struct ImageImportDescriptor { + pub original_first_thunk: u32, + pub time_date_stamp: u32, + pub forwarder_chain: u32, + pub name: u32, + pub first_thunk: u32, +} + +bitflags! { + /// 导入查找表 32位 + /// [Import Lookup Table](https://learn.microsoft.com/en-us/windows/win32/debug/pe-format#import-lookup-table) + #[derive(Debug, Clone, Copy, Serialize)] + pub struct ImportLookupTable32: u32 { + /// 第31位为1表示导入函数通过序号导入 + const IMPORT_BY_ORDINAL = 0x80000000; + /// 15-0 位表示导入函数的序号 + const IMPORT_BY_ORDINAL_MASK = 0x0000FFFF; + /// 30-0 位表示导入函数的RVA + const IMPORT_BY_RVA_MASK = 0x7FFFFFFF; + } + + /// 导入查找表 64位 + /// [Import Lookup Table](https://learn.microsoft.com/en-us/windows/win32/debug/pe-format#import-lookup-table) + #[derive(Debug, Clone, Copy, Serialize)] + pub struct ImportLookupTable64: u64 { + /// 第63位为1表示导入函数通过序号导入 + const IMPORT_BY_ORDINAL = 0x8000000000000000; + /// 15-0 位表示导入函数的序号 + const IMPORT_BY_ORDINAL_MASK = 0x0000FFFF; + /// 30-0 位表示导入函数的RVA + const IMPORT_BY_RVA_MASK = 0x7FFFFFFFFFFFFFFF; + /// 62-31 must be zero. + const RESERVED = 0x7FFFFFFF00000000; + } + +} \ No newline at end of file diff --git a/src-tauri/src/pe_parse/pe.rs b/src-tauri/src/pe_parse/pe.rs index 19b02d7..9a8fbff 100644 --- a/src-tauri/src/pe_parse/pe.rs +++ b/src-tauri/src/pe_parse/pe.rs @@ -26,11 +26,86 @@ pub trait ReadOnlyPE: Deref + Sized + AsRef<[u8]> { Ok(is_64_bit) } - // TODO: 需要一个RVA->FOA的转换函数 - // TODO: 需要一个FOA->RVA的转换函数 - // TODO: 获取数据目录、可变数据目录 - // TODO: 通过枚举获取某个数据目录的数据 + /// 获取数据目录 + fn get_data_directories(&self) -> Result<&[ImageDataDirectory], PEParseError> { + // 1. 获取数据目录偏移 + + let data_directories_offset = self.get_data_directories_offset()?; + // 2. 解析数据目录 + let data_directories = unsafe { + let ptr = self + .as_ptr() + .wrapping_offset(data_directories_offset as isize) + as *const ImageDataDirectory; + let data_directories_count = self.get_optional_header()?.get_data_directory_count(); + let result = std::slice::from_raw_parts(ptr, data_directories_count as usize); + result + }; + Ok(data_directories) + } + /// 通过枚举获取某项数据目录的成员 + /// data_directory: 数据目录枚举 + fn get_data_directory( + &self, + data_directory: ImageDirectoryEntry, + ) -> Result<&ImageDataDirectory, PEParseError> { + let data_directories = self.get_data_directories()?; + let data_directory = data_directories + .get(data_directory as usize) + .ok_or(PEParseError::InvalidDataDirectoryIndex)?; + Ok(data_directory) + } + + /// 获取数据目录的偏移 + fn get_data_directories_offset(&self) -> Result { + // 1. 获取可选头和可选头的偏移 + let optional_header: ImageOptionalHeader = self.get_optional_header()?; + let optional_header_offset = self.get_optional_header_offset()? as u32; + // 2. 数据目录的偏移=可选头的偏移+可选头的大小 + let data_directories_offset = + optional_header_offset + optional_header.get_size_of_optional_header(); + Ok(data_directories_offset) + } + + /// 将RVA转换为FOA + /// rva: 需要转换的RVA + fn rva_to_foa(&self, rva: u32) -> Result { + let sections = self.get_section_headers()?; + for section in sections.iter() { + let section_start = section.virtual_address; + let section_end = + section_start + self.align_size_with_file_alignment(section.size_of_raw_data)?; + if rva >= section_start && rva < section_end { + if section.size_of_raw_data == 0 { + // 如果节区的SizeOfRawData是0,那么这个节区是没有数据的 映射到这个节区的RVA一定是无效的 + return Err(PEParseError::RvaToEmptySection(rva)); + } + let foa = rva - section_start + section.pointer_to_raw_data; + return Ok(foa); + } + } + Err(PEParseError::RvaToFoaError(rva)) + } + + /// 将FOA转换为RVA + /// foa: 需要转换的FOA + fn foa_to_rva(&self, foa: u32) -> Result { + let sections = self.get_section_headers()?; + for section in sections.iter() { + // 如果section的PointerToRawData是0,那么这个节区是没有数据的,直接跳过 + if section.pointer_to_raw_data == 0 { + continue; + } + let section_start = section.pointer_to_raw_data; + let section_end = section_start + section.size_of_raw_data; + if foa >= section_start && foa < section_end { + let rva = foa - section_start + section.virtual_address; + return Ok(rva); + } + } + Err(PEParseError::RvaToFoaError(foa)) + } /// 将size与节对齐值进行对齐,返回对齐后的值 /// size: 需要对齐的值 @@ -275,6 +350,36 @@ pub trait MutablePE: ReadOnlyPE + DerefMut + AsMut<[u8]> { Ok(result) } + /// 获取可变的数据目录引用 + fn get_data_directories_mut(&mut self) -> Result<&mut [ImageDataDirectory], PEParseError> { + // 1. 获取数据目录偏移 + let data_directories_offset = self.get_data_directories_offset()?; + // 2. 解析数据目录 + let data_directories = unsafe { + let ptr = self + .as_mut_ptr() + .wrapping_offset(data_directories_offset as isize) + as *mut ImageDataDirectory; + let data_directories_count = self.get_optional_header()?.get_data_directory_count(); + let result = std::slice::from_raw_parts_mut(ptr, data_directories_count as usize); + result + }; + Ok(data_directories) + } + + /// 通过枚举获取某项数据目录的可变引用 + /// data_directory: 数据目录枚举 + fn get_data_directory_mut( + &mut self, + data_directory: ImageDirectoryEntry, + ) -> Result<&mut ImageDataDirectory, PEParseError> { + let data_directories = self.get_data_directories_mut()?; + let data_directory = data_directories + .get_mut(data_directory as usize) + .ok_or(PEParseError::InvalidDataDirectoryIndex)?; + Ok(data_directory) + } + /// 扩大头映射大小,调用者必须要保证当挪动所有节区数据时,不会超出文件范围 /// 也就是,调用者必须要在扩大头映射大小之前,先扩大文件大小 /// add_size: 需要扩大的大小,不需要进行对齐,会自动对齐 diff --git a/src/components/ImportDirectory/ImportDirectory.tsx b/src/components/ImportDirectory/ImportDirectory.tsx new file mode 100644 index 0000000..62a7dad --- /dev/null +++ b/src/components/ImportDirectory/ImportDirectory.tsx @@ -0,0 +1,19 @@ +import { invoke } from "@tauri-apps/api/core"; +import { useEffect } from "react"; +export default function ImportDirectory() { + const getData = () => { + invoke("command_get_pe_data_import_directory") + .then((res) => { + console.log("res", res); + }) + .catch((err) => { + console.log("err", err); + }); + }; + + useEffect(() => { + getData(); + }, []); + + return
ImportDirectory
; +} diff --git a/src/pages/MainPage.tsx b/src/pages/MainPage.tsx index 628cfb1..343ac51 100644 --- a/src/pages/MainPage.tsx +++ b/src/pages/MainPage.tsx @@ -12,6 +12,7 @@ import FileHeader from "../components/FileHeader/FileHeader"; import SectionHeaders from "../components/SectionHeaders/SectionHeaders"; import OptionalHeader from "../components/OptionalHeader/OptionalHeader"; import { open } from "@tauri-apps/plugin-dialog"; +import ImportDirectory from "../components/ImportDirectory/ImportDirectory"; const SelectNodeMap = { dos_header: , @@ -19,6 +20,7 @@ const SelectNodeMap = { file_header: , optional_header: , section_header: , + import_directory: , }; export default function MainPage() {