From 7346f40592a9c6432d85b1a5cb383502cf1d08d4 Mon Sep 17 00:00:00 2001 From: "381848900@qq.com" Date: Thu, 12 Dec 2024 20:58:47 +0800 Subject: [PATCH] =?UTF-8?q?feat:=20=E5=A2=9E=E5=8A=A0=E8=8A=82=E5=8C=BA?= =?UTF-8?q?=E3=80=81=E5=85=B3=E9=97=AD=E6=96=87=E4=BB=B6?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src-tauri/src/app_error.rs | 4 + src-tauri/src/app_state.rs | 229 +++++++++++++- src-tauri/src/commands.rs | 21 ++ src-tauri/src/lib.rs | 2 + src-tauri/src/pe_parse/pe.rs | 4 +- src-tauri/src/services/file.rs | 2 +- src/components/DosHeader/DosHeader.tsx | 5 +- .../NodeTableComponent/NodeTableComponent.tsx | 2 + .../SectionHeaders/SectionHeaders.module.scss | 26 +- .../SectionHeaders/SectionHeaders.tsx | 285 +++++++++++++++++- src/pages/MainPage.tsx | 34 ++- 11 files changed, 585 insertions(+), 29 deletions(-) diff --git a/src-tauri/src/app_error.rs b/src-tauri/src/app_error.rs index 74c2edf..fed4fb8 100644 --- a/src-tauri/src/app_error.rs +++ b/src-tauri/src/app_error.rs @@ -25,6 +25,10 @@ pub enum AppError { /// 无法修改文件 #[error("无法修改文件!: {0}")] CannotModifyFile(String), + + /// 节名过长 + #[error("节名过长!")] + SectionNameTooLong, } impl serde::Serialize for AppError { diff --git a/src-tauri/src/app_state.rs b/src-tauri/src/app_state.rs index 4c6b897..170ab60 100644 --- a/src-tauri/src/app_state.rs +++ b/src-tauri/src/app_state.rs @@ -1,6 +1,9 @@ -use memmap2::Mmap; +use std::io::Seek; +use std::option; + use crate::app_error::AppError; use crate::{pe_parse::pe::ReadOnlyPE, services::file}; +use memmap2::{Mmap, MmapOptions}; /// 应用的状态结构体 #[derive(Default)] @@ -10,7 +13,7 @@ pub struct AppState { /// 文件Mmap的只读内存映射 mmap: Option, /// 文件是否是64位 - pub is_64_bit: Option + pub is_64_bit: Option, } impl AppState { @@ -24,6 +27,14 @@ impl AppState { Ok(()) } + /// close_file 关闭文件 + pub fn close_file(&mut self) -> Result<(), AppError> { + self.file_path = None; + self.mmap = None; + self.is_64_bit = None; + Ok(()) + } + /// 获取文件内存映射的引用 pub fn get_mmap_ref(&self) -> Result<&Mmap, AppError> { self.mmap.as_ref().ok_or(AppError::NoFileOpened) @@ -50,4 +61,216 @@ impl AppState { Ok(()) } -} \ No newline at end of file + /// 添加节 + pub fn add_section( + &mut self, + section_name: &str, + section_size: usize, + section_characteristics: u32, + ) -> Result<(), AppError> { + // 1. section_name不能超过8个字符 + if section_name.len() > 8 { + return Err(AppError::SectionNameTooLong); + } + // as_bytes 没有补0 + let mut section_name_bytes = [0u8; 8]; + // 循环拷贝section_name的数据 + for i in 0..section_name.len() { + if i < section_name.len() { + section_name_bytes[i] = section_name.as_bytes()[i]; + } + } + + // 判断是否可以添加节 + // if(size_of_headers - 最后一个节表的偏移 - 40) >= 40,则可以添加节表。 + // 2. 获取size_of_headers + let mmap = self.get_mmap_ref()?; + let origin_file_len = mmap.len(); + let optional_header = mmap.get_optional_header()?; + let file_header_offset = mmap.get_file_header_offset()?; + let optional_header_offset = mmap.get_optional_header_offset()?; + let file_header = mmap.get_file_header()?; + let size_of_headers = match optional_header { + crate::pe_parse::header::ImageOptionalHeader::OptionalHeader32( + image_optional_header32, + ) => image_optional_header32.size_of_headers, + crate::pe_parse::header::ImageOptionalHeader::OptionalHeader64( + image_optional_header64, + ) => image_optional_header64.size_of_headers, + }; + let section_table_offset = mmap.get_section_headers_offset()?; + // 获取节表的数量 + let number_of_sections = file_header.number_of_sections; + // 1. 获取偏移 + let section_alignment = match optional_header { + crate::pe_parse::header::ImageOptionalHeader::OptionalHeader32( + image_optional_header32, + ) => image_optional_header32.section_alignment, + crate::pe_parse::header::ImageOptionalHeader::OptionalHeader64( + image_optional_header64, + ) => image_optional_header64.section_alignment, + }; + + // 3. 判断是否可以添加节表 + let section_table_size = 40 * number_of_sections as usize; + // TODO: 这里有问题 + // 要判断第一个节表的pointer_to_raw_data - size_of_headers是否大于40 + let first_section_header_offset = section_table_offset; + let first_pointer_to_raw_data = u32::from_le_bytes( + mmap[first_section_header_offset + 20..first_section_header_offset + 24] + .try_into() + .unwrap(), + ); + + if first_pointer_to_raw_data - size_of_headers < 40 { + // 需要看一下是否可以拓展文件大小 + + // 2. 计算size_of_headers对齐之后的大小 例如:0x400 -> 0x1000 + let aligned_size_of_headers = + (size_of_headers + section_alignment - 1) & !(section_alignment - 1); + // 可以拓展的大小: 对齐之后的大小 - size_of_headers + let expand_size = aligned_size_of_headers - size_of_headers; + if expand_size < 40 { + // 如果可以拓展的大小小于40,则无法添加节表 + return Err(AppError::CannotModifyFile("无法拓展文件大小!".to_string())); + } + // 3. 拓展文件大小 + // 1. 打开文件 + let mut file = std::fs::OpenOptions::new() + .read(true) + .write(true) + .open(self.file_path.as_ref().ok_or(AppError::NoFileOpened)?)?; + // 设置游标到文件末尾 + file.seek(std::io::SeekFrom::End(0))?; + // 2. 拓展文件大小 + file.set_len(file.metadata()?.len() + expand_size as u64)?; + + // 2. 映射文件 + let mut rw_mmap = unsafe { + MmapOptions::new() + .map_mut(&file) + .map_err(|e| std::io::Error::new(std::io::ErrorKind::Other, e.to_string()))? + }; + // 循环拷贝数据 把size_of_headers之后的数据往后移动expand_size + for i in (size_of_headers as usize..origin_file_len).rev() { + rw_mmap[i + expand_size as usize] = rw_mmap[i]; + } + + // 把[size_of_headers, size_of_headers + expand_size]的数据清零 + for i in size_of_headers as usize..(size_of_headers as usize + expand_size as usize - 1) + { + rw_mmap[i] = 0; + } + // 修整所有节的pointer_to_raw_data + for i in 0..number_of_sections { + let section_header_offset = section_table_offset + 40 * i as usize; + let pointer_to_raw_data = u32::from_le_bytes( + rw_mmap[section_header_offset + 20..section_header_offset + 24] + .try_into() + .unwrap(), + ); + rw_mmap[section_header_offset + 20..section_header_offset + 24] + .copy_from_slice(&(pointer_to_raw_data + expand_size as u32).to_le_bytes()); + } + + // TODO: 修改SizeOfImage + let origin_size_of_image = unsafe { + // 在0x38的位置 + u32::from_le_bytes( + rw_mmap[optional_header_offset + 0x38..optional_header_offset + 0x3C] + .try_into() + .unwrap(), + ) + }; + + let add_image_size = (expand_size + section_alignment - 1) & !(section_alignment - 1); + + rw_mmap[optional_header_offset + 0x38..optional_header_offset + 0x3C] + .copy_from_slice(&(origin_size_of_image + add_image_size as u32).to_le_bytes()); + + rw_mmap.flush()?; + // 3. 重新映射文件 + self.mmap = Some(file::mmap_file(self.file_path.as_ref().unwrap())?); + } + // 4. 添加节表 + // 新的节区文件偏移是文件末尾 + // TODO: 如果增加的大小为0是否有问题? + let new_section_offset = self.get_mmap_ref()?.len(); + let new_section_header_offset = section_table_offset + section_table_size; + { + // 拓展文件大小 + let mut file = std::fs::OpenOptions::new() + .read(true) + .write(true) + .open(self.file_path.as_ref().ok_or(AppError::NoFileOpened)?)?; + // 设置游标到文件末尾 + file.seek(std::io::SeekFrom::End(0))?; + // 2. 拓展文件大小 + file.set_len(file.metadata()?.len() + section_size as u64)?; + } + // 写入节表数据 + let mut rw_mmap = file::mmap_file_rw(self.file_path.as_ref().unwrap())?; + // 1. 写入节表数据 + // 1.1 写入节名 + rw_mmap[new_section_header_offset..new_section_header_offset + 8] + .copy_from_slice(§ion_name_bytes); + // 写入节的VA + // TODO: 新节的VA应该是最后一个节的VA + section_size对齐后的大小 + let last_section_header_offset = + section_table_offset + 40 * (number_of_sections - 1) as usize; + let last_section_va = u32::from_le_bytes( + rw_mmap[last_section_header_offset + 12..last_section_header_offset + 16] + .try_into() + .unwrap(), + ); + // 对齐后的VA, 对齐方式(last_section_va + section_size) 和section_alignment进行对齐 + let aligned_va = (last_section_va + section_size as u32 + section_alignment - 1) + & !(section_alignment - 1); + + rw_mmap[new_section_header_offset + 12..new_section_header_offset + 16] + .copy_from_slice(&aligned_va.to_le_bytes()); + + // 写入新节的size_of_raw_data + rw_mmap[new_section_header_offset + 16..new_section_header_offset + 20] + .copy_from_slice(&(section_size as u32).to_le_bytes()); + + // 写入新节的PointerToRawData + rw_mmap[new_section_header_offset + 20..new_section_header_offset + 24] + .copy_from_slice(&(new_section_offset as u32).to_le_bytes()); + + // 1.4 写入节的特征 + rw_mmap[new_section_header_offset + 36..new_section_header_offset + 40] + .copy_from_slice(§ion_characteristics.to_le_bytes()); + + // 修改文件头的节表数量 + rw_mmap[file_header_offset + 2..file_header_offset + 4] + .copy_from_slice(&(number_of_sections + 1).to_le_bytes()); + + // 修改size_of_headers的大小 + let new_size_of_headers = size_of_headers + 40; + rw_mmap[optional_header_offset + 0x3C..optional_header_offset + 0x40] + .copy_from_slice(&new_size_of_headers.to_le_bytes()); + + // TODO: 修改SizeOfImage + let origin_size_of_image = unsafe { + // 在0x38的位置 + u32::from_le_bytes( + rw_mmap[optional_header_offset + 0x38..optional_header_offset + 0x3C] + .try_into() + .unwrap(), + ) + }; + // TODO: 增加的大小应该是section_size对齐后的大小 + let add_image_size = + (section_size as u32 + section_alignment - 1) & !(section_alignment - 1); + + rw_mmap[optional_header_offset + 0x38..optional_header_offset + 0x3C] + .copy_from_slice(&(origin_size_of_image + add_image_size).to_le_bytes()); + + rw_mmap.flush()?; + + // 2. 重新映射文件 + self.mmap = Some(file::mmap_file(self.file_path.as_ref().unwrap())?); + Ok(()) + } +} diff --git a/src-tauri/src/commands.rs b/src-tauri/src/commands.rs index cde84d5..94ea936 100644 --- a/src-tauri/src/commands.rs +++ b/src-tauri/src/commands.rs @@ -43,6 +43,13 @@ pub fn command_open_file( Ok(()) } +// 命令,关闭文件 +#[tauri::command] +pub fn command_close_file(app_state: State<'_, Mutex>) -> Result<(), AppError> { + app_state.lock().unwrap().close_file()?; + Ok(()) +} + #[tauri::command(async)] pub fn command_get_file_pe_node_tree_data( app_state: State<'_, Mutex>, @@ -180,4 +187,18 @@ pub fn command_write_data( let mut app_state = app_state.lock().unwrap(); app_state.write_data(offset, &data)?; Ok(()) +} + + +// 命令,添加节 +#[tauri::command] +pub fn command_add_section( + app_state: State<'_, Mutex>, + section_name: String, + section_size: usize, + section_characteristics: u32, +) -> Result<(), AppError> { + let mut app_state = app_state.lock().unwrap(); + app_state.add_section(§ion_name, section_size, section_characteristics)?; + Ok(()) } \ No newline at end of file diff --git a/src-tauri/src/lib.rs b/src-tauri/src/lib.rs index 7d66eca..626dba9 100644 --- a/src-tauri/src/lib.rs +++ b/src-tauri/src/lib.rs @@ -17,6 +17,7 @@ pub fn run() { .invoke_handler(tauri::generate_handler![ commands::set_complete, commands::command_open_file, + commands::command_close_file, commands::command_get_file_pe_node_tree_data, commands::command_get_pe_data_dos_header, commands::command_get_pe_data_nt_header, @@ -24,6 +25,7 @@ pub fn run() { commands::command_get_pe_data_optional_header, commands::command_get_pe_data_section_headers, commands::command_write_data, + commands::command_add_section, ]) .setup(|app| { app.manage(Mutex::new(app_state::AppState::default())); diff --git a/src-tauri/src/pe_parse/pe.rs b/src-tauri/src/pe_parse/pe.rs index d361dcf..db40d25 100644 --- a/src-tauri/src/pe_parse/pe.rs +++ b/src-tauri/src/pe_parse/pe.rs @@ -103,14 +103,14 @@ pub trait ReadOnlyPE: Deref + Sized { }; Ok(result) } - + /// 获取可选头的偏移 fn get_optional_header_offset(&self) -> Result { let file_header_offset = self.get_file_header_offset()?; let optional_header_offset = file_header_offset + std::mem::size_of::(); Ok(optional_header_offset) } - // 获取节区头的偏移 + /// 获取节区头的偏移 fn get_section_headers_offset(&self) -> Result { // 节区头偏移在可选头之后,可选头大小是可变的,所以需要计算 let optional_header_size = self.get_size_of_optional_header()?; diff --git a/src-tauri/src/services/file.rs b/src-tauri/src/services/file.rs index 484f3e1..fd11c18 100644 --- a/src-tauri/src/services/file.rs +++ b/src-tauri/src/services/file.rs @@ -18,4 +18,4 @@ pub fn mmap_file_rw(file_path: &str) -> Result { .map_mut(&file) .map_err(|e| std::io::Error::new(std::io::ErrorKind::Other, e.to_string())) } -} \ No newline at end of file +} diff --git a/src/components/DosHeader/DosHeader.tsx b/src/components/DosHeader/DosHeader.tsx index af44fac..a1b808f 100644 --- a/src/components/DosHeader/DosHeader.tsx +++ b/src/components/DosHeader/DosHeader.tsx @@ -260,17 +260,16 @@ export default function DosHeader() { ]; const handleSave = (item: DataType) => { - console.log("handleSave", item); if (!checkEditValue(item)) { return; } const bufs = formatEditValue(item); - console.log("change value", bufs); + console.log("bufs", bufs, item); invoke("command_write_data", { offset: item.offset, data: bufs, }) - .then((res) => { + .then((_res) => { // 提示 success(); getData(); diff --git a/src/components/NodeTableComponent/NodeTableComponent.tsx b/src/components/NodeTableComponent/NodeTableComponent.tsx index 3e95c69..43b1fb6 100644 --- a/src/components/NodeTableComponent/NodeTableComponent.tsx +++ b/src/components/NodeTableComponent/NodeTableComponent.tsx @@ -218,6 +218,8 @@ export default function NodeTableComponent(props: NodeTableComponentProps) { size: cloneData[key].array_element_size, value: resData.fields[key][i], key: `${key}[${i}]`, + data_type: "hex", + array_field: `${key}` }; array.push(item); } diff --git a/src/components/SectionHeaders/SectionHeaders.module.scss b/src/components/SectionHeaders/SectionHeaders.module.scss index 9d97a10..5c15ec9 100644 --- a/src/components/SectionHeaders/SectionHeaders.module.scss +++ b/src/components/SectionHeaders/SectionHeaders.module.scss @@ -1,16 +1,24 @@ .NodeTableComponentRootFlex { - min-width: calc(100% - 16px); + min-width: calc(100% - 16px); } -.NodeTableComponentTable{ - height: calc(100% - 16px); +.NodeTableComponentTable { + height: calc(100% - 16px); } -.content{ - // 垂直flex - display: flex; - flex-direction: column; +.content { + // 垂直flex + display: flex; + flex-direction: column; } .tableItem { - margin-bottom: 24px; -} \ No newline at end of file + margin-bottom: 24px; +} + +.optionContainer { + display: flex; + margin-bottom: 16px; +} +.optionItem { + margin-right: 16px; +} diff --git a/src/components/SectionHeaders/SectionHeaders.tsx b/src/components/SectionHeaders/SectionHeaders.tsx index 67546e0..bc42714 100644 --- a/src/components/SectionHeaders/SectionHeaders.tsx +++ b/src/components/SectionHeaders/SectionHeaders.tsx @@ -1,10 +1,21 @@ import NodeTableComponent, { DataTemplateInterface, } from "../NodeTableComponent/NodeTableComponent"; -import { useEffect, useState } from "react"; +import { useEffect, useMemo, useState } from "react"; import { invoke } from "@tauri-apps/api/core"; import styles from "./SectionHeaders.module.scss"; -import { Flex } from "antd"; +import { + Flex, + Button, + Modal, + Form, + Input, + InputNumber, + Select, + message, +} from "antd"; +import { PlusOutlined, EditOutlined } from "@ant-design/icons"; + const dataTemplate: DataTemplateInterface = { name: { name: "name", @@ -77,11 +88,163 @@ const dataTemplate: DataTemplateInterface = { description: "属性", }, }; +// 节区属性 +const sectionCharacteristics = [ + { + value: 0x80000000, + label: "可写", + }, + { + value: 0x40000000, + label: "可读", + }, + { + value: 0x20000000, + label: "可执行", + }, + { + value: 0x02000000, + label: "可丢弃", + }, + { + value: 0x04000000, + label: "不可缓存", + }, + { + value: 0x08000000, + label: "不可分页", + }, + { + value: 0x10000000, + label: "可共享", + }, + { + value: 0x01000000, + label: "包含扩展重定位", + }, + { + value: 0x00000020, + label: "包含可执行代码", + }, + { + value: 0x00000040, + label: "包含已初始化数据", + }, + { + value: 0x00000080, + label: "包含未初始化数据", + }, + { + value: 0x00000200, + label: "包含注释或信息", + }, + { + value: 0x00000800, + label: "将不会成为镜像的一部分", + }, + { + value: 0x00001000, + label: "包含 COMDAT 数据", + }, + { + value: 0x00100000, + label: "数据对齐到 1 字节边界", + }, + { + value: 0x00200000, + label: "数据对齐到 2 字节边界", + }, + { + value: 0x00300000, + label: "数据对齐到 4 字节边界", + }, + { + value: 0x00400000, + label: "数据对齐到 8 字节边界", + }, + { + value: 0x00500000, + label: "数据对齐到 16 字节边界", + }, + { + value: 0x00600000, + label: "数据对齐到 32 字节边界", + }, + { + value: 0x00700000, + label: "数据对齐到 64 字节边界", + }, + { + value: 0x00800000, + label: "数据对齐到 128 字节边界", + }, + { + value: 0x00900000, + label: "数据对齐到 256 字节边界", + }, + { + value: 0x00A00000, + label: "数据对齐到 512 字节边界", + }, + { + value: 0x00B00000, + label: "数据对齐到 1024 字节边界", + }, + { + value: 0x00C00000, + label: "数据对齐到 2048 字节边界", + }, + { + value: 0x00D00000, + label: "数据对齐到 4096 字节边界", + }, + { + value: 0x00E00000, + label: "数据对齐到 8192 字节边界", + }, + { + value: 0x00F00000, + label: "数据对齐掩码", + }, + { + value: 0x00000001, + label: "预留:DSECT 类型", + }, + { + value: 0x00000002, + label: "预留:不加载", + }, + { + value: 0x00000004, + label: "预留:组节区", + }, + { + value: 0x00000008, + label: "预留:不填充节区", + }, + { + value: 0x00000010, + label: "预留:COPY 类型", + }, + { + value: 0x00000100, + label: "预留:其他链接信息", + }, + { + value: 0x00000400, + label: "预留:覆盖节区", + }, +]; + + // 节区头大小 const sectionHeaderSize = 40; export default function SectionHeaders() { const [data, setData] = useState([]); + const [addSectionVisible, setAddSectionVisible] = useState(false); + const [addSectionLoading, setAddSectionLoading] = useState(false); + const [addSectionForm] = Form.useForm(); const command = "command_get_pe_data_section_headers"; @@ -128,13 +291,129 @@ export default function SectionHeaders() { ); }; - useEffect(() => { + const requestAddSection = async ( + sectionName: string, + sectionSize: number, + sectionCharacteristics: number + ) => + invoke("command_add_section", { + sectionName, + sectionSize, + sectionCharacteristics, + }); + + const AddSectionModel = useMemo(() => { + const handelOk = () => { + const { name, size, characteristics } = addSectionForm.getFieldsValue(); + console.log( + "send", + name, + size, + characteristics.reduce((a, b) => a + b) + ); + setAddSectionLoading(true); + requestAddSection( + name, + size, + characteristics.reduce((a, b) => a + b) + ) + .then((_res) => { + requestData(); + setAddSectionLoading(false); + setAddSectionVisible(false); + }) + .catch((_err) => { + setAddSectionLoading(false); + }); + }; + + return ( + setAddSectionVisible(false)} + > +
{ + console.log(values); + }} + style={{ maxWidth: 600 }} + > + + + + + + + + + +
+
+ ); + }, [addSectionVisible, addSectionLoading]); + + const requestData = () => { invoke(command).then((res) => { setData(formatDataHandler(res)); }); + }; + + useEffect(() => { + requestData(); }, []); return ( + {AddSectionModel} + + + + + {data.map((item, index) => { return (
diff --git a/src/pages/MainPage.tsx b/src/pages/MainPage.tsx index 98656c7..628cfb1 100644 --- a/src/pages/MainPage.tsx +++ b/src/pages/MainPage.tsx @@ -4,7 +4,7 @@ import { Layout, Flex, Button, Empty } from "antd"; import { listen } from "@tauri-apps/api/event"; import { invoke } from "@tauri-apps/api/core"; const { Content } = Layout; -import { SaveOutlined, FolderOpenFilled } from "@ant-design/icons"; +import { CloseOutlined, FolderOpenFilled } from "@ant-design/icons"; import SiderTree from "../components/side_tree/SideTree"; import DosHeader from "../components/DosHeader/DosHeader"; import NtHeader from "../components/NTHeader/NTHeader"; @@ -36,6 +36,14 @@ export default function MainPage() { } }; + const closeFile = async () => { + invoke("command_close_file").then(() => { + setFilePath(""); + setTreeData([]); + setSelectedKey(""); + }); + }; + const changeFile = (filePath: string) => { invoke("command_open_file", { filePath }).then(() => { invoke("command_get_file_pe_node_tree_data").then((res) => { @@ -74,20 +82,27 @@ export default function MainPage() { style={{ marginRight: "10px", }} - type="dashed" + type="primary" + color="primary" onClick={openFile} icon={} > 打开新文件 - + )}