hex_patch/headers/
generic.rs

1use std::{collections::HashMap, io::Write};
2
3use object::{Object, ObjectSection, ObjectSymbol};
4use pdb::FallibleIterator;
5
6use crate::app::files::{filesystem::FileSystem, path};
7
8use super::{bitness::Bitness, section::Section};
9
10#[derive(Debug, Clone, Copy, PartialEq, Eq)]
11pub enum FileType {
12    Coff,
13    CoffBig,
14    Elf32,
15    Elf64,
16    MachO32,
17    MachO64,
18    Pe32,
19    Pe64,
20    Xcoff32,
21    Xcoff64,
22}
23
24#[derive(Debug, Clone, PartialEq, Eq)]
25pub struct GenericHeader {
26    pub(super) file_type: FileType,
27    pub(super) architecture: object::Architecture,
28    pub(super) bitness: Bitness,
29    pub(super) endianness: object::Endianness,
30    pub(super) entry: u64,
31    pub(super) sections: Vec<Section>,
32    pub(super) symbols: HashMap<u64, String>,
33    pub(super) symbols_by_name: HashMap<String, u64>,
34}
35
36impl GenericHeader {
37    fn demangle_symbol(symbol: &str) -> String {
38        let name = symbolic_demangle::demangle(symbol);
39        name.to_string()
40    }
41
42    pub fn parse_header(bytes: &[u8], file_path: &str, filesystem: &FileSystem) -> Option<Self> {
43        let header = object::File::parse(bytes);
44        if let Ok(header) = header {
45            let file_type = match &header {
46                object::File::Coff(_) => FileType::Coff,
47                object::File::CoffBig(_) => FileType::CoffBig,
48                object::File::Elf32(_) => FileType::Elf32,
49                object::File::Elf64(_) => FileType::Elf64,
50                object::File::MachO32(_) => FileType::MachO32,
51                object::File::MachO64(_) => FileType::MachO64,
52                object::File::Pe32(_) => FileType::Pe32,
53                object::File::Pe64(_) => FileType::Pe64,
54                object::File::Xcoff32(_) => FileType::Xcoff32,
55                object::File::Xcoff64(_) => FileType::Xcoff64,
56                _ => return None,
57            };
58
59            let architecture = header.architecture();
60
61            let bitness = if header.is_64() {
62                Bitness::Bit64
63            } else {
64                Bitness::Bit32
65            };
66
67            let endianness = header.endianness();
68
69            let mut entry = header.entry();
70
71            let sections: Vec<Section> = header
72                .sections()
73                .map(|section| Section {
74                    name: section.name().unwrap_or_default().to_string(),
75                    file_offset: section.file_range().unwrap_or_default().0,
76                    virtual_address: section.address(),
77                    size: section.file_range().unwrap_or_default().1,
78                })
79                .filter(|section| section.size > 0)
80                .collect();
81
82            match header {
83                object::File::MachO32(_) | object::File::MachO64(_) => {
84                    if let Some(text) = sections.iter().find(|section| section.name == "__text") {
85                        let text_offset = text.virtual_address.saturating_sub(text.file_offset);
86                        entry += text_offset;
87                    }
88                }
89                _ => {}
90            }
91
92            let mut symbols: Vec<(u64, String)> = header
93                .symbols()
94                .map(|symbol| {
95                    (
96                        symbol.address(),
97                        symbol.name().unwrap_or_default().to_string(),
98                    )
99                })
100                .collect();
101
102            let address_base = header.relative_address_base();
103
104            // this is used only for pe files
105            let (debug_data_directory, section_table) = match &header {
106                object::File::Pe32(pe) => (
107                    pe.data_directory(object::pe::IMAGE_DIRECTORY_ENTRY_DEBUG),
108                    Some(pe.section_table()),
109                ),
110                object::File::Pe64(pe) => (
111                    pe.data_directory(object::pe::IMAGE_DIRECTORY_ENTRY_DEBUG),
112                    Some(pe.section_table()),
113                ),
114                _ => (None, None),
115            };
116
117            if let Some(debug_data_directory) = debug_data_directory {
118                let section_table = section_table.expect(&t!("errors.pe_section_table_missing"));
119                let debug_data_dir_content = debug_data_directory.data(bytes, &section_table);
120                if let Ok(debug_data_dir_content) = debug_data_dir_content {
121                    let mut pdb_file_path = None;
122                    for entry in debug_data_dir_content.chunks_exact(28) {
123                        let _characteristics =
124                            u32::from_le_bytes([entry[0], entry[1], entry[2], entry[3]]);
125                        let _time_date_stamp =
126                            u32::from_le_bytes([entry[4], entry[5], entry[6], entry[7]]);
127                        let _major_version = u16::from_le_bytes([entry[8], entry[9]]);
128                        let _minor_version = u16::from_le_bytes([entry[10], entry[11]]);
129                        let ty = u32::from_le_bytes([entry[12], entry[13], entry[14], entry[15]]);
130                        let size_of_data =
131                            u32::from_le_bytes([entry[16], entry[17], entry[18], entry[19]]);
132                        let _address_of_raw_data =
133                            u32::from_le_bytes([entry[20], entry[21], entry[22], entry[23]]);
134                        let pointer_to_raw_data =
135                            u32::from_le_bytes([entry[24], entry[25], entry[26], entry[27]]);
136
137                        if ty == 2 {
138                            let data_header_size = 24;
139                            let path = String::from_utf8_lossy(
140                                &bytes[data_header_size + pointer_to_raw_data as usize
141                                    ..(pointer_to_raw_data + size_of_data) as usize],
142                            )
143                            .trim_end_matches('\0')
144                            .to_string();
145                            pdb_file_path = Some(path);
146                            break;
147                        }
148                    }
149                    if let Some(pdb_file_path) = pdb_file_path {
150                        let pdb_absolute_path = if path::is_absolute(&pdb_file_path) {
151                            pdb_file_path
152                        } else {
153                            path::join(
154                                path::parent(file_path).unwrap_or("./"),
155                                &pdb_file_path,
156                                filesystem.separator(),
157                            )
158                        };
159                        let file = filesystem.read(&pdb_absolute_path);
160                        if let Ok(file) = file {
161                            // TODO: maybe there is a better way to do this without writing to a file
162                            let mut tmp_file =
163                                tempfile::tempfile().expect(&t!("errors.create_temp_file"));
164                            tmp_file
165                                .write_all(&file)
166                                .expect(&t!("errors.write_temp_file"));
167
168                            let pdb = pdb::PDB::open(tmp_file);
169                            if let Ok(mut pdb) = pdb {
170                                let symbol_table = pdb.global_symbols();
171                                let address_map = pdb.address_map();
172                                if let (Ok(symbol_table), Ok(address_map)) =
173                                    (symbol_table, address_map)
174                                {
175                                    let mut iter = symbol_table.iter();
176                                    while let Ok(Some(symbol)) = iter.next() {
177                                        if let Ok(pdb::SymbolData::Public(public_symbol)) =
178                                            symbol.parse()
179                                        {
180                                            let address = public_symbol.offset.to_rva(&address_map);
181                                            if let Some(address) = address {
182                                                let name =
183                                                    public_symbol.name.to_string().to_string();
184                                                symbols
185                                                    .push((address.0 as u64 + address_base, name));
186                                            }
187                                        }
188                                    }
189                                }
190                            }
191                        }
192                    }
193                }
194            }
195
196            let symbols: HashMap<u64, String> = symbols
197                .into_iter()
198                .map(|(address, name)| (address, Self::demangle_symbol(&name)))
199                .collect();
200
201            let symbols_by_name = symbols
202                .iter()
203                .map(|(address, name)| (name.clone(), *address))
204                .collect();
205
206            Some(GenericHeader {
207                file_type,
208                architecture,
209                bitness,
210                endianness,
211                entry,
212                sections,
213                symbols,
214                symbols_by_name,
215            })
216        } else {
217            None
218        }
219    }
220
221    pub fn file_type(&self) -> FileType {
222        self.file_type
223    }
224}