1use anyhow::{anyhow, Result};
4use nabla_scanner::binary::analysis::BinaryAnalysis;
5use serde::{Deserialize, Serialize};
6use sha2::{Digest, Sha256};
7use std::fs;
8use std::path::{Path, PathBuf};
9use std::sync::{Arc, RwLock};
10use tempfile::TempDir;
11use uuid::Uuid;
12use chrono::{DateTime, Utc};
13
14use crate::types::Address;
15
16#[derive(Debug, Clone, Serialize, Deserialize)]
17pub struct PatchSnapshot {
18 pub original_hash: String,
19 pub patches: Vec<AppliedPatch>,
20 pub created_at: chrono::DateTime<chrono::Utc>,
21 pub description: String,
22}
23
24#[derive(Debug, Clone, Serialize, Deserialize)]
25pub struct AppliedPatch {
26 pub address: Address,
27 pub original_bytes: Vec<u8>,
28 pub patched_bytes: Vec<u8>,
29 pub description: String,
30 pub pseudocode: String,
31}
32
33#[derive(Debug, Clone)]
34pub struct PatchRequest {
35 pub address: Address,
36 pub hex_data: String,
37 pub description: String,
38 pub dry_run: bool,
39}
40
41#[derive(Debug, Clone, Serialize, Deserialize)]
42pub struct SessionPatch {
43 pub id: Uuid,
44 pub address: Address,
45 pub original_bytes: Vec<u8>,
46 pub patched_bytes: Vec<u8>,
47 pub description: String,
48 pub applied_at: DateTime<Utc>,
49}
50
51pub struct BinaryPatcher {
52 binary_path: PathBuf,
53 temp_dir: TempDir,
54 snapshots: Vec<PatchSnapshot>,
55 current_hash: String,
56}
57
58impl BinaryPatcher {
59 pub fn new<P: AsRef<Path>>(binary_path: P) -> Result<Self> {
60 let binary_path = binary_path.as_ref().to_path_buf();
61 let temp_dir = TempDir::new()?;
62
63 let binary_data = fs::read(&binary_path)?;
65 let current_hash = format!("{:x}", Sha256::digest(&binary_data));
66
67 Ok(Self {
68 binary_path,
69 temp_dir,
70 snapshots: Vec::new(),
71 current_hash,
72 })
73 }
74
75 pub fn apply_patch(&mut self, request: PatchRequest, analysis: &BinaryAnalysis) -> Result<String> {
77 self.validate_patch_request(&request, analysis)?;
79
80 let machine_code = self.parse_hex_data(&request.hex_data)?;
82
83 if request.dry_run {
84 return Ok(format!(
85 "DRY RUN - Would patch {} bytes at 0x{:x}:\nHex data: {}\nBytes: {:02x?}",
86 machine_code.len(),
87 request.address,
88 request.hex_data,
89 machine_code
90 ));
91 }
92
93 let _snapshot = self.create_snapshot(&request.description)?;
95
96 let original_bytes = self.patch_binary_at_address(request.address, &machine_code)?;
98
99 let applied_patch = AppliedPatch {
101 address: request.address,
102 original_bytes,
103 patched_bytes: machine_code.clone(),
104 description: request.description.clone(),
105 pseudocode: format!("hex: {}", request.hex_data),
106 };
107
108 if let Some(last_snapshot) = self.snapshots.last_mut() {
109 last_snapshot.patches.push(applied_patch);
110 }
111
112 let binary_data = fs::read(&self.binary_path)?;
114 self.current_hash = format!("{:x}", Sha256::digest(&binary_data));
115
116 Ok(format!(
117 "ā
Successfully patched {} bytes at 0x{:x}\nš Description: {}\nš§ Hex data: {}\nš Bytes applied: {:02x?}",
118 machine_code.len(),
119 request.address,
120 request.description,
121 request.hex_data,
122 machine_code
123 ))
124 }
125
126 pub fn rollback(&mut self, snapshot_index: Option<usize>) -> Result<String> {
128 let target_index = snapshot_index.unwrap_or(0);
129
130 if target_index >= self.snapshots.len() {
131 return Err(anyhow!("Invalid snapshot index: {}", target_index));
132 }
133
134 let original_hash = self.snapshots[target_index].original_hash.clone();
136 let created_at = self.snapshots[target_index].created_at;
137 let description = self.snapshots[target_index].description.clone();
138
139 let backup_path = self.temp_dir.path().join(format!("backup_{}.bin", target_index));
141 if backup_path.exists() {
142 fs::copy(&backup_path, &self.binary_path)?;
143 self.current_hash = original_hash;
144
145 self.snapshots.truncate(target_index + 1);
147
148 Ok(format!(
149 "ā
Rolled back to snapshot {}\nš
Created: {}\nš Description: {}",
150 target_index,
151 created_at.format("%Y-%m-%d %H:%M:%S UTC"),
152 description
153 ))
154 } else {
155 Err(anyhow!("Backup file not found for snapshot {}", target_index))
156 }
157 }
158
159 pub fn list_snapshots(&self) -> String {
161 if self.snapshots.is_empty() {
162 return "No snapshots available".to_string();
163 }
164
165 let mut result = String::from("šø Binary Patch Snapshots:\n\n");
166
167 for (i, snapshot) in self.snapshots.iter().enumerate() {
168 result.push_str(&format!(
169 "#{}: {} ({})\n š
{}\n š§ {} patches\n š Hash: {}...\n\n",
170 i,
171 snapshot.description,
172 if i == self.snapshots.len() - 1 { "current" } else { "historical" },
173 snapshot.created_at.format("%Y-%m-%d %H:%M:%S UTC"),
174 snapshot.patches.len(),
175 &snapshot.original_hash[..16]
176 ));
177 }
178
179 result
180 }
181
182 fn validate_patch_request(&self, request: &PatchRequest, analysis: &BinaryAnalysis) -> Result<()> {
184 let target_section = analysis.code_sections.iter().find(|section| {
186 request.address >= section.start_address && request.address < section.end_address
187 });
188
189 let Some(_section) = target_section else {
190 return Err(anyhow!(
191 "Address 0x{:x} is not in a known code section. This could corrupt data.",
192 request.address
193 ));
194 };
195
196 let binary_data = fs::read(&self.binary_path)?;
198 let file_offset = self.virtual_address_to_file_offset(request.address, &binary_data)?;
199
200 if file_offset >= binary_data.len() {
202 return Err(anyhow!(
203 "Address 0x{:x} maps to file offset 0x{:x}, which is beyond binary bounds (size: 0x{:x})",
204 request.address,
205 file_offset,
206 binary_data.len()
207 ));
208 }
209
210 if request.hex_data.trim().is_empty() {
212 return Err(anyhow!("Hex data cannot be empty"));
213 }
214
215 Ok(())
216 }
217
218 fn parse_hex_data(&self, hex_data: &str) -> Result<Vec<u8>> {
220 let cleaned = hex_data.trim().replace("0x", "").replace(" ", "");
221
222 if cleaned.is_empty() {
224 return Err(anyhow!("Hex data cannot be empty"));
225 }
226
227 if !cleaned.chars().all(|c| c.is_ascii_hexdigit()) {
228 return Err(anyhow!("Invalid hex data '{}': contains non-hex characters", hex_data));
229 }
230
231 if cleaned.len() % 2 != 0 {
232 return Err(anyhow!("Invalid hex data '{}': must have even number of characters", hex_data));
233 }
234
235 let mut bytes = Vec::new();
237 for chunk in cleaned.as_bytes().chunks(2) {
238 let hex_str = std::str::from_utf8(chunk)
239 .map_err(|e| anyhow!("Invalid UTF-8 in hex data: {}", e))?;
240 let byte = u8::from_str_radix(hex_str, 16)
241 .map_err(|e| anyhow!("Invalid hex byte '{}': {}", hex_str, e))?;
242 bytes.push(byte);
243 }
244
245 if bytes.is_empty() {
246 return Err(anyhow!("Parsed hex data resulted in empty byte array"));
247 }
248
249 println!("Successfully parsed {} hex bytes: {:02x?}", bytes.len(), bytes);
250 Ok(bytes)
251 }
252
253
254
255 fn create_snapshot(&mut self, description: &str) -> Result<PatchSnapshot> {
257 let binary_data = fs::read(&self.binary_path)?;
258 let hash = format!("{:x}", Sha256::digest(&binary_data));
259
260 let backup_path = self.temp_dir.path().join(format!("backup_{}.bin", self.snapshots.len()));
262 fs::write(&backup_path, &binary_data)?;
263
264 let snapshot = PatchSnapshot {
265 original_hash: hash,
266 patches: Vec::new(),
267 created_at: chrono::Utc::now(),
268 description: description.to_string(),
269 };
270
271 self.snapshots.push(snapshot.clone());
272 Ok(snapshot)
273 }
274
275 fn patch_binary_at_address(&self, address: Address, new_bytes: &[u8]) -> Result<Vec<u8>> {
277 let mut binary_data = fs::read(&self.binary_path)?;
278
279 let file_offset = self.virtual_address_to_file_offset(address, &binary_data)?;
281 let start_addr = file_offset;
282 let end_addr = start_addr + new_bytes.len();
283
284 if end_addr > binary_data.len() {
285 return Err(anyhow!(
286 "Patch would extend beyond binary bounds (file offset: 0x{:x}, patch size: {}, binary size: 0x{:x})",
287 start_addr,
288 new_bytes.len(),
289 binary_data.len()
290 ));
291 }
292
293 let original_bytes = binary_data[start_addr..end_addr].to_vec();
295
296 binary_data.splice(start_addr..end_addr, new_bytes.iter().cloned());
298
299 fs::write(&self.binary_path, &binary_data)?;
301
302 Ok(original_bytes)
303 }
304
305 pub fn read_bytes(&self, address: Address, length: usize) -> Result<Vec<u8>> {
307 let binary_data = fs::read(&self.binary_path)?;
308 let file_offset = self.virtual_address_to_file_offset(address, &binary_data)?;
309
310 if file_offset + length > binary_data.len() {
311 return Err(anyhow!(
312 "Read would extend beyond binary bounds (file offset: 0x{:x}, read size: {}, binary size: 0x{:x})",
313 file_offset,
314 length,
315 binary_data.len()
316 ));
317 }
318
319 Ok(binary_data[file_offset..file_offset + length].to_vec())
320 }
321
322 pub fn write_bytes(&mut self, address: Address, bytes: &[u8]) -> Result<()> {
324 let mut binary_data = fs::read(&self.binary_path)?;
325 let file_offset = self.virtual_address_to_file_offset(address, &binary_data)?;
326 let end_offset = file_offset + bytes.len();
327
328 if end_offset > binary_data.len() {
329 return Err(anyhow!(
330 "Write would extend beyond binary bounds (file offset: 0x{:x}, write size: {}, binary size: 0x{:x})",
331 file_offset,
332 bytes.len(),
333 binary_data.len()
334 ));
335 }
336
337 binary_data.splice(file_offset..end_offset, bytes.iter().cloned());
339
340 fs::write(&self.binary_path, &binary_data)?;
342
343 self.current_hash = format!("{:x}", Sha256::digest(&binary_data));
345
346 Ok(())
347 }
348
349 fn virtual_address_to_file_offset(&self, virtual_address: u64, binary_data: &[u8]) -> Result<usize> {
351 let potential_offset = if virtual_address > 0x400000 {
355 (virtual_address - 0x400000) as usize
357 } else if virtual_address > 0x8000000 {
358 (virtual_address - 0x8000000) as usize
360 } else if virtual_address > 0x10000000 {
361 (virtual_address - 0x10000000) as usize
363 } else if virtual_address > 0x1000 {
364 (virtual_address - 0x1000) as usize
366 } else {
367 virtual_address as usize
369 };
370
371 if potential_offset < binary_data.len() {
373 Ok(potential_offset)
374 } else {
375 let direct_offset = virtual_address as usize;
377 if direct_offset < binary_data.len() {
378 Ok(direct_offset)
379 } else {
380 Err(anyhow!(
381 "Cannot map virtual address 0x{:x} to valid file offset (tried 0x{:x}, binary size: 0x{:x})",
382 virtual_address,
383 potential_offset,
384 binary_data.len()
385 ))
386 }
387 }
388 }
389}
390
391pub struct MemoryPatcher {
392 original_binary: Vec<u8>,
393 working_binary: Arc<RwLock<Vec<u8>>>,
394 applied_patches: Vec<SessionPatch>,
395}
396
397impl MemoryPatcher {
398 pub fn new(binary_data: Vec<u8>) -> Self {
399 Self {
400 original_binary: binary_data.clone(),
401 working_binary: Arc::new(RwLock::new(binary_data)),
402 applied_patches: Vec::new(),
403 }
404 }
405
406 pub fn apply_patch(&mut self, request: PatchRequest) -> Result<String> {
407 let address = request.address;
408
409 let machine_code = self.parse_hex_data(&request.hex_data)?;
411
412 if request.dry_run {
413 return Ok(format!(
414 "DRY RUN - Would patch {} bytes at 0x{:x}:\nHex data: {}\nBytes: {:02x?}",
415 machine_code.len(),
416 address,
417 request.hex_data,
418 machine_code
419 ));
420 }
421
422 let mut binary = self.working_binary.write().unwrap();
424
425 let file_offset = self.virtual_address_to_file_offset(address, &binary)?;
427 let start_idx = file_offset;
428 let end_idx = start_idx + machine_code.len();
429
430 if end_idx > binary.len() {
431 return Err(anyhow!("Patch would extend beyond binary bounds"));
432 }
433
434 let original_bytes = binary[start_idx..end_idx].to_vec();
436
437 binary[start_idx..end_idx].copy_from_slice(&machine_code);
439
440 let patch = SessionPatch {
442 id: Uuid::new_v4(),
443 address,
444 original_bytes,
445 patched_bytes: machine_code.clone(),
446 description: request.description.clone(),
447 applied_at: Utc::now(),
448 };
449
450 self.applied_patches.push(patch);
451
452 Ok(format!(
453 "ā
Successfully patched {} bytes at 0x{:x}\nš Description: {}\nš§ Hex data: {}\nš Bytes applied: {:02x?}",
454 machine_code.len(),
455 address,
456 request.description,
457 request.hex_data,
458 machine_code
459 ))
460 }
461
462 pub fn read_bytes(&self, address: Address, length: usize) -> Result<Vec<u8>> {
463 let binary = self.working_binary.read().unwrap();
464 let file_offset = self.virtual_address_to_file_offset(address, &binary)?;
465 let start_idx = file_offset;
466 let end_idx = start_idx + length;
467
468 if end_idx > binary.len() {
469 return Err(anyhow!("Read would extend beyond binary bounds"));
470 }
471
472 Ok(binary[start_idx..end_idx].to_vec())
473 }
474
475 pub fn get_working_binary(&self) -> Vec<u8> {
476 self.working_binary.read().unwrap().clone()
477 }
478
479 pub fn get_original_binary(&self) -> &[u8] {
480 &self.original_binary
481 }
482
483 pub fn get_applied_patches(&self) -> &[SessionPatch] {
484 &self.applied_patches
485 }
486
487 pub fn get_applied_patches_mut(&mut self) -> &mut Vec<SessionPatch> {
488 &mut self.applied_patches
489 }
490
491 pub fn get_working_binary_arc(&self) -> Arc<RwLock<Vec<u8>>> {
492 Arc::clone(&self.working_binary)
493 }
494
495 pub fn get_original_binary_ref(&self) -> &[u8] {
496 &self.original_binary
497 }
498
499 pub fn virtual_address_to_file_offset_public(&self, virtual_address: u64, binary_data: &[u8]) -> Result<usize> {
500 self.virtual_address_to_file_offset(virtual_address, binary_data)
501 }
502
503 pub fn rollback_patch(&mut self, patch_id: Uuid) -> Result<String> {
504 let patch_idx = self.applied_patches
506 .iter()
507 .position(|p| p.id == patch_id)
508 .ok_or_else(|| anyhow!("Patch not found: {}", patch_id))?;
509
510 let patches_to_rollback = self.applied_patches.split_off(patch_idx);
512
513 for patch in patches_to_rollback.iter().rev() {
514 let mut binary = self.working_binary.write().unwrap();
515 let file_offset = self.virtual_address_to_file_offset(patch.address, &binary)?;
516 let start_idx = file_offset;
517 let end_idx = start_idx + patch.original_bytes.len();
518 binary[start_idx..end_idx].copy_from_slice(&patch.original_bytes);
519 }
520
521 Ok(format!("ā
Rolled back patch and {} subsequent patches", patches_to_rollback.len() - 1))
522 }
523
524 pub fn rollback_all(&mut self) -> Result<String> {
525 let mut binary = self.working_binary.write().unwrap();
527 *binary = self.original_binary.clone();
528
529 let patch_count = self.applied_patches.len();
530 self.applied_patches.clear();
531
532 Ok(format!("ā
Rolled back all {} patches", patch_count))
533 }
534
535 fn parse_hex_data(&self, hex_data: &str) -> Result<Vec<u8>> {
536 let cleaned = hex_data.trim().replace("0x", "").replace(" ", "");
537
538 if cleaned.is_empty() {
539 return Err(anyhow!("Hex data cannot be empty"));
540 }
541
542 if !cleaned.chars().all(|c| c.is_ascii_hexdigit()) {
543 return Err(anyhow!("Invalid hex data '{}': contains non-hex characters", hex_data));
544 }
545
546 if cleaned.len() % 2 != 0 {
547 return Err(anyhow!("Invalid hex data '{}': must have even number of characters", hex_data));
548 }
549
550 let mut bytes = Vec::new();
551 for chunk in cleaned.as_bytes().chunks(2) {
552 let hex_str = std::str::from_utf8(chunk)?;
553 let byte = u8::from_str_radix(hex_str, 16)
554 .map_err(|e| anyhow!("Invalid hex byte '{}': {}", hex_str, e))?;
555 bytes.push(byte);
556 }
557
558 Ok(bytes)
559 }
560
561 fn virtual_address_to_file_offset(&self, virtual_address: u64, binary_data: &[u8]) -> Result<usize> {
562 let potential_offset = if virtual_address > 0x400000 {
563 (virtual_address - 0x400000) as usize
564 } else if virtual_address > 0x8000000 {
565 (virtual_address - 0x8000000) as usize
566 } else if virtual_address > 0x10000000 {
567 (virtual_address - 0x10000000) as usize
568 } else if virtual_address > 0x1000 {
569 (virtual_address - 0x1000) as usize
570 } else {
571 virtual_address as usize
572 };
573
574 if potential_offset < binary_data.len() {
575 Ok(potential_offset)
576 } else {
577 let direct_offset = virtual_address as usize;
578 if direct_offset < binary_data.len() {
579 Ok(direct_offset)
580 } else {
581 Err(anyhow!(
582 "Cannot map virtual address 0x{:x} to valid file offset",
583 virtual_address
584 ))
585 }
586 }
587 }
588}
589
590pub fn parse_address(addr_str: &str) -> Result<Address> {
592 let cleaned = addr_str.trim().to_lowercase();
593
594 if cleaned.starts_with("0x") {
595 u64::from_str_radix(&cleaned[2..], 16)
596 .map_err(|e| anyhow!("Invalid hex address '{}': {}", addr_str, e))
597 } else {
598 cleaned.parse::<u64>()
599 .map_err(|e| anyhow!("Invalid address '{}': {}", addr_str, e))
600 }
601}
602
603#[cfg(test)]
604mod tests {
605 use super::*;
606 use tempfile::NamedTempFile;
607
608 #[test]
609 fn test_parse_address() {
610 assert_eq!(parse_address("0x1000").unwrap(), 0x1000);
611 assert_eq!(parse_address("4096").unwrap(), 4096);
612 assert_eq!(parse_address("0X2000").unwrap(), 0x2000);
613 assert!(parse_address("invalid").is_err());
614 }
615
616 #[test]
617 fn test_parse_hex_data() {
618 let patcher = create_test_patcher().unwrap();
619
620 let machine_code = patcher.parse_hex_data("48c7c000000000").unwrap();
621 assert_eq!(machine_code, vec![0x48, 0xc7, 0xc0, 0x00, 0x00, 0x00, 0x00]);
622
623 let machine_code = patcher.parse_hex_data("0x90").unwrap();
624 assert_eq!(machine_code, vec![0x90]);
625
626 let machine_code = patcher.parse_hex_data("48 c7 c0 00").unwrap();
627 assert_eq!(machine_code, vec![0x48, 0xc7, 0xc0, 0x00]);
628
629 assert!(patcher.parse_hex_data("invalid").is_err());
630 assert!(patcher.parse_hex_data("4").is_err()); }
632
633 fn create_test_patcher() -> Result<BinaryPatcher> {
634 let temp_file = NamedTempFile::new()?;
635 let test_data = vec![0x48, 0xc7, 0xc0, 0x00, 0x00, 0x00, 0x00, 0xc3]; fs::write(temp_file.path(), &test_data)?;
637 BinaryPatcher::new(temp_file.path())
638 }
639}