#!/usr/bin/env python3 """Deleted file recovery agent using PhotoRec subprocess wrapper.""" import subprocess import os import sys import json import hashlib from pathlib import Path from collections import defaultdict from datetime import datetime def verify_photorec(): """Check that PhotoRec is installed and available.""" result = subprocess.run( ["photorec", "--version"], capture_output=True, text=True, timeout=120, ) if result.returncode == 0: return {"installed": True, "version": result.stdout.strip()} return {"installed": False} def get_image_info(image_path): """Get forensic image information.""" file_result = subprocess.run( ["file", image_path], capture_output=True, text=True, timeout=120, ) size = os.path.getsize(image_path) if os.path.exists(image_path) else 0 return { "path": image_path, "size_bytes": size, "size_gb": round(size / (1024 ** 3), 2), "type": file_result.stdout.strip(), } def run_photorec(image_path, output_dir, file_types=None, partition=None): """Run PhotoRec for file recovery using command-line interface.""" os.makedirs(output_dir, exist_ok=True) cmd = ["photorec", "/d", output_dir, "/cmd", image_path] options = [] if partition: options.append(f"partition_i_end,{partition}") if file_types: enable_list = ",".join(file_types) options.append(f"fileopt,everything,disable,{enable_list},enable") options.append("search") cmd.append(",".join(options) if options else "search") result = subprocess.run( cmd, capture_output=True, text=True, timeout=14400 ) return { "command": " ".join(cmd), "returncode": result.returncode, "stdout": result.stdout[-1000:] if result.stdout else "", "stderr": result.stderr[-500:] if result.stderr else "", "output_dir": output_dir, } def catalog_recovered_files(output_dir): """Catalog all recovered files by type, size, and hash.""" catalog = defaultdict(list) total_files = 0 total_bytes = 0 for root, dirs, files in os.walk(output_dir): for filename in files: filepath = os.path.join(root, filename) ext = Path(filename).suffix.lower() try: size = os.path.getsize(filepath) except OSError: continue total_files += 1 total_bytes += size entry = { "path": filepath, "filename": filename, "extension": ext, "size": size, } catalog[ext].append(entry) summary = { "total_files": total_files, "total_bytes": total_bytes, "total_mb": round(total_bytes / (1024 * 1024), 2), "by_extension": { ext: {"count": len(files), "total_bytes": sum(f["size"] for f in files)} for ext, files in sorted(catalog.items(), key=lambda x: -len(x[1])) }, } return summary def hash_recovered_files(output_dir, extensions=None): """Generate SHA-256 hashes for recovered files for evidence integrity.""" hashes = [] for root, dirs, files in os.walk(output_dir): for filename in files: filepath = os.path.join(root, filename) ext = Path(filename).suffix.lower() if extensions and ext not in extensions: continue try: with open(filepath, "rb") as f: sha256 = hashlib.sha256(f.read()).hexdigest() hashes.append({ "file": filepath, "sha256": sha256, "size": os.path.getsize(filepath), }) except (OSError, PermissionError): pass return hashes def sort_recovered_files(output_dir, sorted_dir): """Sort recovered files into categorized directories.""" categories = { "documents": [".doc", ".docx", ".pdf", ".xls", ".xlsx", ".ppt", ".pptx", ".odt", ".ods", ".txt", ".rtf", ".csv"], "images": [".jpg", ".jpeg", ".png", ".gif", ".bmp", ".tiff", ".svg", ".webp"], "databases": [".db", ".sqlite", ".mdb", ".accdb", ".sql"], "archives": [".zip", ".rar", ".7z", ".tar", ".gz", ".bz2"], "executables": [".exe", ".dll", ".bat", ".ps1", ".sh", ".msi"], "email": [".eml", ".msg", ".pst", ".ost", ".mbox"], "web": [".html", ".htm", ".css", ".js", ".json", ".xml"], } os.makedirs(sorted_dir, exist_ok=True) for cat in categories: os.makedirs(os.path.join(sorted_dir, cat), exist_ok=True) os.makedirs(os.path.join(sorted_dir, "other"), exist_ok=True) moved = defaultdict(int) for root, dirs, files in os.walk(output_dir): if root.startswith(sorted_dir): continue for filename in files: src = os.path.join(root, filename) ext = Path(filename).suffix.lower() target_cat = "other" for cat, exts in categories.items(): if ext in exts: target_cat = cat break dst = os.path.join(sorted_dir, target_cat, filename) counter = 1 while os.path.exists(dst): name, extension = os.path.splitext(filename) dst = os.path.join(sorted_dir, target_cat, f"{name}_{counter}{extension}") counter += 1 try: os.rename(src, dst) moved[target_cat] += 1 except OSError: pass return dict(moved) def full_recovery_pipeline(image_path, output_dir, file_types=None): """Run complete file recovery pipeline.""" results = {"timestamp": datetime.now().isoformat()} results["image_info"] = get_image_info(image_path) recovery_dir = os.path.join(output_dir, "recovered") results["recovery"] = run_photorec(image_path, recovery_dir, file_types=file_types) if os.path.exists(recovery_dir): results["catalog"] = catalog_recovered_files(recovery_dir) sorted_dir = os.path.join(output_dir, "sorted") results["sorting"] = sort_recovered_files(recovery_dir, sorted_dir) return results def print_report(results): print("File Recovery Report") print("=" * 50) print(f"Date: {results.get('timestamp', 'N/A')}") img = results.get("image_info", {}) print(f"Image: {img.get('path', 'N/A')} ({img.get('size_gb', 0)} GB)") cat = results.get("catalog", {}) print(f"\nRecovered: {cat.get('total_files', 0)} files ({cat.get('total_mb', 0)} MB)") print("\nBy Extension:") for ext, info in list(cat.get("by_extension", {}).items())[:15]: print(f" {ext:8s}: {info['count']:>5} files ({info['total_bytes'] // 1024:>8} KB)") sorting = results.get("sorting", {}) if sorting: print("\nSorted Categories:") for cat_name, count in sorting.items(): print(f" {cat_name:15s}: {count} files") if __name__ == "__main__": if len(sys.argv) < 3: print("Usage: python agent.py [file_types]") print(" file_types: comma-separated (e.g., jpg,pdf,doc)") sys.exit(1) image = sys.argv[1] output = sys.argv[2] types = sys.argv[3].split(",") if len(sys.argv) > 3 else None results = full_recovery_pipeline(image, output, file_types=types) print_report(results)