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 for &byte in &buffer {
29 *frequency_map.entry(byte).or_insert(0) += 1;
30 }
31
32 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()) });
37
38 Ok(entropy)
39}
40
41pub 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); 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 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 if !contents.is_empty() {
149 let padding = 16 - (contents.len() % 16);
150 for _ in 0..padding {
151 result += " "; }
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 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 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 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 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 let mut file = File::open(file_path)?;
295
296 let mut buffer = Vec::new();
298 file.read_to_end(&mut buffer)?;
299
300 let pe = PE::parse(&buffer)?;
302
303 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 let mut result_string = String::new();
431
432 match Object::parse(&buffer) {
433 Ok(Object::Elf(elf)) => {
434 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 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 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 if let Some(text_section) = pe.sections.iter().find(|section| {
471 std::str::from_utf8(§ion.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}