reverse_engineering_lib/
lib.rs

1use capstone::arch::x86::ArchSyntax;
2use capstone::prelude::*;
3use goblin::elf::Elf;
4use goblin::pe::PE;
5use goblin::Object;
6use rand::prelude::SliceRandom;
7use rand::thread_rng;
8use rand::Rng;
9use sha2::{Digest, Sha256};
10use std::collections::HashMap;
11use std::env;
12use std::error::Error;
13use std::fmt::Write;
14use std::fs;
15use std::fs::File;
16use std::io::{self, Read};
17use std::process;
18
19pub fn calculate_entropy(file_path: &String) -> io::Result<f64> {
20    let mut file = File::open(file_path)?;
21    let mut buffer = Vec::new();
22    file.read_to_end(&mut buffer)?;
23
24    let total_bytes = buffer.len();
25    let mut frequency_map = HashMap::new();
26
27    // Count the frequency of each byte
28    for &byte in &buffer {
29        *frequency_map.entry(byte).or_insert(0) += 1;
30    }
31
32    // Calculate the entropy
33    let entropy = frequency_map.values().fold(0.0, |acc, &count| {
34        let probability = count as f64 / total_bytes as f64;
35        acc - (probability * probability.log2()) // Shannon entropy formula
36    });
37
38    Ok(entropy)
39}
40
41//return a vector of 8-bit color values based on the file content
42pub fn color_based_hex(file_path: String) -> Result<Vec<u8>, Box<dyn Error>> {
43    let mut file = File::open(file_path)?;
44    let mut buffer = Vec::new();
45    file.read_to_end(&mut buffer)?;
46    let color_squares: Vec<u8> = buffer.iter().map(|&byte| byte % 255).collect();
47
48    Ok(color_squares)
49}
50
51pub fn main() -> io::Result<()> {
52    let args: Vec<String> = env::args().collect();
53    if args.len() < 3 {
54        eprintln!("Usage: cargo run -- <view/edit/random/pe-header/elf-functions/entropy/strings> <file_path> [<offset> <new_value>]");
55        process::exit(1);
56    }
57
58    let mode = &args[1];
59    let file_path = &args[2];
60
61    let mut content = fs::read(file_path)?;
62    save_hash(&content)?;
63
64    match mode.as_str() {
65        "view" => {
66            let file_content = view_file(file_path).unwrap_or_else(|e| {
67                eprintln!("Error reading file: {}", e);
68                std::process::exit(1);
69            });
70            println!("{}", file_content);
71        }
72        "random" => random_edit(&mut content)?,
73
74        "pe-header" => {
75            let header_info = parse_pe_header(file_path);
76            println!("{:?}", header_info);
77            process::exit(1);
78        }
79        "elf-functions" => {
80            let functions = extract_function_names_from_elf(&content);
81            println!("{:?}", functions);
82            process::exit(1);
83        }
84        "entropy" => {
85            let entropy_results = calculate_entropy_by_offset(&content, 256); // Example window size
86            for (offset, entropy) in entropy_results {
87                println!("Offset: 0x{:x}, Entropy: {:.2}", offset, entropy);
88            }
89            process::exit(1);
90        }
91        "edit" => {
92            if args.len() == 5 {
93                let offset = usize::from_str_radix(&args[3], 16).unwrap_or_else(|_| {
94                    eprintln!("Invalid offset.");
95                    process::exit(1);
96                });
97                let new_value = u8::from_str_radix(&args[4], 16).unwrap_or_else(|_| {
98                    eprintln!("Invalid new value.");
99                    process::exit(1);
100                });
101                edit_file(&mut content, offset, new_value)?;
102            }
103        }
104        "strings" => {
105            let strings = extract_strings(file_path).unwrap_or_else(|e| {
106                eprintln!("Error extracting strings: {}", e);
107                process::exit(1);
108            });
109            for string in strings {
110                println!("{}", string);
111            }
112        }
113        _ => {
114            eprintln!("Invalid mode. Use 'view', 'edit', 'random', 'pe-header', 'elf-functions', or 'entropy'.");
115            process::exit(1);
116        }
117    }
118
119    Ok(())
120}
121
122pub fn view_file(file_path: &str) -> io::Result<String> {
123    let mut file = fs::File::open(file_path)?;
124    let mut contents = Vec::new();
125    file.read_to_end(&mut contents)?;
126
127    let mut result = String::new();
128    for (index, byte) in contents.iter().enumerate() {
129        if index % 16 == 0 {
130            if index != 0 {
131                // Append ASCII representation for the previous line before starting a new line
132                result += " |";
133                let start = if index < 16 { 0 } else { index - 16 };
134                let end = index;
135                let text = contents[start..end]
136                    .iter()
137                    .map(|&c| if c >= 32 && c <= 126 { c as char } else { '.' })
138                    .collect::<String>();
139                result += &text;
140                result += "|\n";
141            }
142            result += &format!("{:08x}: ", index);
143        }
144        result += &format!("{:02x} ", byte);
145    }
146
147    // Handle the ASCII preview for the last line if the file size isn't a multiple of 16
148    if !contents.is_empty() {
149        let padding = 16 - (contents.len() % 16);
150        for _ in 0..padding {
151            result += "   "; // Padding for the hex view
152        }
153        result += " |";
154        let start = contents.len() - (contents.len() % 16);
155        let text = contents[start..]
156            .iter()
157            .map(|&c| if c >= 32 && c <= 126 { c as char } else { '.' })
158            .collect::<String>();
159        result += &text;
160        result += "|";
161    }
162
163    Ok(result)
164}
165
166pub fn edit_file(content: &mut Vec<u8>, offset: usize, new_value: u8) -> io::Result<()> {
167    if offset < content.len() {
168        content[offset] = new_value;
169        println!(
170            "Byte at offset {:x} has been changed to {:02x}.",
171            offset, new_value
172        );
173    } else {
174        eprintln!("Offset {:x} is out of bounds.", offset);
175    }
176    Ok(())
177}
178
179pub fn random_edit(content: &mut Vec<u8>) -> io::Result<()> {
180    let mut rng = thread_rng();
181    let positions: Vec<usize> = content
182        .iter()
183        .enumerate()
184        .filter(|&(_, &value)| value == 0x00)
185        .map(|(i, _)| i)
186        .collect();
187
188    if let Some(&pos) = positions.choose(&mut rng) {
189        let random_value: u8 = rng.gen();
190        content[pos] = random_value;
191        println!(
192            "Byte at random zero position {:x} has been changed to {:02x}.",
193            pos, random_value
194        );
195    } else {
196        println!("No zero bytes to replace.");
197    }
198
199    Ok(())
200}
201
202pub fn save_hash(content: &[u8]) -> io::Result<()> {
203    let mut hasher = Sha256::new();
204    hasher.update(content);
205    let hash = hasher.finalize();
206    let hash_str = format!("{:x}", hash);
207    println!("SHA256: {}", hash_str);
208    let first_64_bits = &hash_str[..16];
209    let name = format!("{}.txt", first_64_bits);
210    print!("Saving hash to file {}... ", name);
211    fs::write(name, hash_str)?;
212    Ok(())
213}
214
215pub fn extract_detail_exe(
216    file_path: &String,
217) -> Result<HashMap<String, String>, Box<dyn std::error::Error>> {
218    let mut file = File::open(file_path)?;
219    let mut buffer = Vec::new();
220    file.read_to_end(&mut buffer)?;
221
222    let mut details = HashMap::new();
223
224    match PE::parse(&buffer) {
225        Ok(pe) => {
226            details.insert("Entry Point".to_string(), format!("0x{:x}", pe.entry));
227
228            // COFF Header details
229            details.insert(
230                "Machine".to_string(),
231                format!("0x{:x}", pe.header.coff_header.machine),
232            );
233            details.insert(
234                "Number of Sections".to_string(),
235                pe.header.coff_header.number_of_sections.to_string(),
236            );
237            details.insert(
238                "Time Date Stamp".to_string(),
239                pe.header.coff_header.time_date_stamp.to_string(),
240            );
241            details.insert(
242                "Pointer to Symbol Table".to_string(),
243                pe.header.coff_header.pointer_to_symbol_table.to_string(),
244            );
245            details.insert(
246                "Number of Sections".to_string(),
247                pe.header.coff_header.number_of_sections.to_string(),
248            );
249            details.insert(
250                "Size of Optional Header".to_string(),
251                pe.header.coff_header.size_of_optional_header.to_string(),
252            );
253            details.insert(
254                "Characteristics".to_string(),
255                format!("0x{:x}", pe.header.coff_header.characteristics),
256            );
257
258            // Section Headers
259            for (index, section) in pe.sections.iter().enumerate() {
260                let section_name = format!("Section {} Name", index + 1);
261                let section_detail = format!(
262                    "Virtual Size: 0x{:x}, Virtual Address: 0x{:x}",
263                    section.virtual_size, section.virtual_address
264                );
265                details.insert(section_name, section_detail);
266            }
267
268            // Imports
269            for (index, import) in pe.imports.iter().enumerate() {
270                let import_name = format!("Import {} Name", index + 1);
271                let import_fields = format!(
272                    "DLL: {}, Ordinal: {}, Offset: {}, RVA: 0x{:x}, Size: {}",
273                    import.dll, import.ordinal, import.offset, import.rva, import.size
274                );
275                details.insert(import_name, import_fields);
276            }
277            // Exports
278            for (index, export) in pe.exports.iter().enumerate() {
279                if let Some(name) = &export.name {
280                    let export_name = format!("Export {} Name", index + 1);
281                    let export_detail = format!("Name: {}, Address: 0x{:x}", name, export.rva);
282                    details.insert(export_name, export_detail);
283                }
284            }
285        }
286        Err(err) => return Err(Box::new(err)),
287    }
288
289    Ok(details)
290}
291
292pub fn parse_pe_header(file_path: &str) -> Result<goblin::pe::header::Header, Box<dyn Error>> {
293    // Open the file at the given path
294    let mut file = File::open(file_path)?;
295
296    // Read the file's contents into a buffer
297    let mut buffer = Vec::new();
298    file.read_to_end(&mut buffer)?;
299
300    // Parse the buffer as a PE file
301    let pe = PE::parse(&buffer)?;
302
303    // Return the PE header
304    Ok(pe.header)
305}
306#[allow(deprecated)]
307pub fn extract_function_names_from_elf(bytes: &[u8]) -> Result<Vec<String>, goblin::error::Error> {
308    let elf = Elf::parse(bytes)?;
309    let mut function_names = Vec::new();
310    for sym in elf.syms.iter() {
311        if let Some(Ok(name)) = elf
312            .strtab
313            .get(sym.st_name)
314            .map(|res| res.map(|s| s.to_string()))
315        {
316            function_names.push(name);
317        }
318    }
319    Ok(function_names)
320}
321
322pub fn calculate_entropy_by_offset(data: &[u8], window_size: usize) -> Vec<(usize, f64)> {
323    let mut results = Vec::new();
324    let mut offset = 0;
325
326    while offset + window_size <= data.len() {
327        let window = &data[offset..offset + window_size];
328        let entropy = calculate_entropy_byte(window);
329        results.push((offset, entropy));
330        offset += window_size;
331    }
332
333    results
334}
335
336pub fn calculate_entropy_byte(data: &[u8]) -> f64 {
337    let mut frequency = HashMap::new();
338
339    for &byte in data {
340        *frequency.entry(byte).or_insert(0) += 1;
341    }
342
343    let len = data.len() as f64;
344    frequency.values().fold(0.0, |acc, &count| {
345        let probability = count as f64 / len;
346        acc - (probability * probability.log2())
347    })
348}
349
350pub fn extract_strings(file_path: &str) -> Result<Vec<String>, Box<dyn Error>> {
351    let mut file = File::open(file_path)?;
352    let mut buffer = Vec::new();
353    file.read_to_end(&mut buffer)?;
354
355    let obj = Object::parse(&buffer)?;
356    let strings = match obj {
357        Object::PE(pe) => extract_strings_from_pe_sections(&pe.sections, &buffer),
358        Object::Elf(elf) => Ok(extract_strings_from_elf_sections(
359            &elf.section_headers,
360            &buffer,
361        )),
362        _ => Err("Unsupported format".into()),
363    }?;
364
365    Ok(strings)
366}
367
368fn extract_strings_from_pe_sections(
369    sections: &[goblin::pe::section_table::SectionTable],
370    buffer: &[u8],
371) -> Result<Vec<String>, Box<dyn Error>> {
372    let mut strings = Vec::new();
373    for section in sections {
374        let start = section.pointer_to_raw_data as usize;
375        let end = start + section.size_of_raw_data as usize;
376
377        if start < buffer.len() && end <= buffer.len() {
378            let section_data = &buffer[start..end];
379
380            let mut current_string = Vec::new();
381            for &byte in section_data {
382                if byte.is_ascii_graphic() || byte == b' ' {
383                    current_string.push(byte);
384                } else if byte == 0 && !current_string.is_empty() {
385                    if current_string.len() > 4 {
386                        if let Ok(string) = String::from_utf8(current_string.clone()) {
387                            strings.push(string);
388                        }
389                    }
390                    current_string.clear();
391                }
392            }
393        }
394    }
395
396    Ok(strings)
397}
398
399fn extract_strings_from_elf_sections(
400    headers: &[goblin::elf::section_header::SectionHeader],
401    buffer: &[u8],
402) -> Vec<String> {
403    let mut strings = Vec::new();
404
405    for header in headers {
406        let start = header.sh_offset as usize;
407        let end = start + header.sh_size as usize;
408        let section_data = &buffer[start..end];
409
410        let mut current_string = Vec::new();
411        for &byte in section_data {
412            if byte.is_ascii_graphic() || byte == b' ' {
413                current_string.push(byte);
414            } else if byte == 0 && !current_string.is_empty() {
415                if current_string.len() > 4 {
416                    if let Ok(string) = String::from_utf8(current_string.clone()) {
417                        strings.push(string);
418                    }
419                }
420                current_string.clear();
421            }
422        }
423    }
424    strings
425}
426pub fn disassemble(file_path: &str) -> Result<String, String> {
427    let buffer = fs::read(file_path).map_err(|e| e.to_string())?;
428
429    // Initialize an empty String to collect information
430    let mut result_string = String::new();
431
432    match Object::parse(&buffer) {
433        Ok(Object::Elf(elf)) => {
434            // Append ELF header information to the result string
435            writeln!(
436                result_string,
437                "ELF Type: {}\nEntry Point: 0x{:X}",
438                elf.header.e_type, elf.header.e_entry
439            )
440            .unwrap();
441
442            // Find and disassemble .text section, then append
443            if let Some(text_section) = elf
444                .section_headers
445                .iter()
446                .find(|sh| elf.shdr_strtab.get_at(sh.sh_name) == Some(".text"))
447            {
448                let code =
449                    &buffer[text_section.sh_offset as usize..][..text_section.sh_size as usize];
450                match disassemble_bin(code) {
451                    Ok(disassembly) => writeln!(result_string, "\n{}", disassembly).unwrap(),
452                    Err(e) => return Err(e.to_string()),
453                }
454            } else {
455                writeln!(result_string, "No .text section found in ELF").unwrap();
456            }
457        }
458        Ok(Object::PE(pe)) => {
459            // Append PE header information to the result string
460            let machine = pe.header.coff_header.machine;
461            let number_of_sections = pe.header.coff_header.number_of_sections;
462            writeln!(
463                result_string,
464                "PE Machine: {}\nNumber of Sections: {}\n",
465                machine, number_of_sections
466            )
467            .unwrap();
468
469            // Find and disassemble .text section, then append
470            if let Some(text_section) = pe.sections.iter().find(|section| {
471                std::str::from_utf8(&section.name)
472                    .unwrap_or_default()
473                    .trim_end_matches('\0')
474                    == ".text"
475            }) {
476                let code = &buffer[text_section.pointer_to_raw_data as usize..]
477                    [..text_section.size_of_raw_data as usize];
478                match disassemble_bin(code) {
479                    Ok(disassembly) => writeln!(result_string, "\n{}\n", disassembly).unwrap(),
480                    Err(e) => return Err(e.to_string()),
481                }
482            } else {
483                writeln!(result_string, "No .text section found in PE").unwrap();
484            }
485        }
486        _ => return Err("Unsupported or unknown file format".into()),
487    };
488
489    Ok(result_string)
490}
491
492fn disassemble_bin(code: &[u8]) -> capstone::CsResult<String> {
493    let cs = Capstone::new()
494        .x86()
495        .mode(arch::x86::ArchMode::Mode64)
496        .syntax(ArchSyntax::Intel)
497        .detail(true)
498        .build()?;
499
500    let insns = cs.disasm_all(code, 0x1000)?;
501    Ok(insns
502        .iter()
503        .map(|i| {
504            format!(
505                "0x{:x}: {}\t{}\n",
506                i.address(),
507                i.mnemonic().unwrap_or(""),
508                i.op_str().unwrap_or("")
509            )
510        })
511        .collect())
512}