diff --git a/README.md b/README.md index 60b1232..90ef69a 100644 --- a/README.md +++ b/README.md @@ -6,6 +6,7 @@ This repository houses a collection of test sample projects designed to demonstr The repository contains these folders: - **`DotnetSDK`** — Integration test samples for all supported .NET versions, used as part of the smoke testing suite - **`feature-tests`** — Isolated samples for testing specific functionalities +- **`tools`** - Internal tools used for testing and debugging obfuscated applications ## 🧪 Feature Tests This section lists the available feature-test solutions, each designed to test specific functionalities in isolation: @@ -15,6 +16,15 @@ This section lists the available feature-test solutions, each designed to test s - **MauiAppforChecks** — MAUI App for testing RASP Root check or other checks - **SimpleChecks** — .NET 10 and .NET Framework console samples for testing Debug Check and other RASP checks +## 🧰 Tools +Contains internal tools used in Dotfuscator development process, including testing and debugging. +- **EnvFileViewer** - Reads the environment file (`environment0f617e02.bin`) and displays data in JSON format + - Usage: run as Administrator `EnvFileViewer.exe --register` to register `Preview` action for `.bin` files + - Unregister Preview action: run as Administrator `EnvFileViewer.exe --unregister` + - Preview a file: drag-and-drop a file on `EnvFileViewer.exe` or run `EnvFileViewer.exe ...full-path\environment0f617e02.bin` + - By default, waits for a key press before exit + - Allows using `--no-wait` command line argument to avoid waiting for a key press. + ## 👥 Audience This repository is primarily intended for the technical team responsible for developing, testing, and maintaining Dotfuscator. The samples provide practical, code-based scenarios to aid in product validation, feature development, and understanding Dotfuscator's behavior across various application types. diff --git a/tools/EnvFileViewer/EnvFileViewer/FileAssociationManager.cs b/tools/EnvFileViewer/EnvFileViewer/FileAssociationManager.cs new file mode 100644 index 0000000..fa17dd4 --- /dev/null +++ b/tools/EnvFileViewer/EnvFileViewer/FileAssociationManager.cs @@ -0,0 +1,73 @@ +using Microsoft.Win32; + +namespace EnvFileViewer; + +public static class FileAssociationManager +{ + private const string Extension = ".bin"; + private const string ProgId = "binfile"; + private const string Verb = "Preview"; + + /// + /// Registers the "Preview" context menu action for *.bin files. + /// + /// Full path to your application executable. + public static void RegisterPreviewAction(string applicationPath) + { + try + { + // 1. Ensure .bin points to our ProgID (binfile) + using (RegistryKey extKey = Registry.ClassesRoot.CreateSubKey(Extension)) + { + extKey.SetValue("", ProgId); + } + + // 2. Create the shell verb structure: binfile\shell\Preview\command + string verbKeyPath = $@"{ProgId}\shell\{Verb}"; + using (RegistryKey verbKey = Registry.ClassesRoot.CreateSubKey(verbKeyPath)) + { + verbKey.SetValue("", Verb); // The text shown in the context menu + } + + using (RegistryKey commandKey = Registry.ClassesRoot.CreateSubKey($@"{verbKeyPath}\command")) + { + // Format: "C:\Path\To\App.exe" "%1" + commandKey.SetValue("", $"\"{applicationPath}\" \"%1\""); + } + + Console.WriteLine("Successfully registered 'Preview' action."); + } + catch (UnauthorizedAccessException) + { + Console.WriteLine("Error: You must run this application as an Administrator."); + } + catch (Exception ex) + { + Console.WriteLine($"An error occurred during registration: {ex.Message}"); + } + } + + /// + /// De-registers the "Preview" context menu action. + /// + public static void UnregisterPreviewAction() + { + try + { + string verbKeyPath = $@"{ProgId}\shell\{Verb}"; + + // Delete the Preview key and all its subkeys (like 'command') + Registry.ClassesRoot.DeleteSubKeyTree(verbKeyPath, throwOnMissingSubKey: false); + + Console.WriteLine("Successfully de-registered 'Preview' action."); + } + catch (UnauthorizedAccessException) + { + Console.WriteLine("Error: You must run this application as an Administrator."); + } + catch (Exception ex) + { + Console.WriteLine($"An error occurred during de-registration: {ex.Message}"); + } + } +} \ No newline at end of file diff --git a/tools/EnvFileViewer/EnvFileViewer/Program.cs b/tools/EnvFileViewer/EnvFileViewer/Program.cs index 8447a7b..904e860 100644 --- a/tools/EnvFileViewer/EnvFileViewer/Program.cs +++ b/tools/EnvFileViewer/EnvFileViewer/Program.cs @@ -1,4 +1,25 @@ -using EnvLib; +using EnvFileViewer; +using EnvLib; + +if (args.Contains("--register")) +{ + var applicationPath = System.Reflection.Assembly.GetExecutingAssembly().Location; + if (applicationPath.EndsWith(".dll", StringComparison.OrdinalIgnoreCase)) + { + var exePath = Path.Combine(Path.GetDirectoryName(applicationPath) ?? string.Empty, Path.GetFileNameWithoutExtension(applicationPath) + ".exe"); + if (File.Exists(exePath)) + { + applicationPath = exePath; + } + } + FileAssociationManager.RegisterPreviewAction(applicationPath); + return; +} +if (args.Contains("--unregister")) +{ + FileAssociationManager.UnregisterPreviewAction(); + return; +} var noWait = args.Contains("--no-wait"); var filePath = args.FirstOrDefault(a => a != "--no-wait"); @@ -6,6 +27,8 @@ if (string.IsNullOrWhiteSpace(filePath)) { Console.WriteLine("Usage: EnvFileViewer [--no-wait]"); + Console.WriteLine(" EnvFileViewer --register Register the application as a preview handler for .bin files"); + Console.WriteLine(" EnvFileViewer --unregister Unregister the application as a preview handler for .bin files"); return; } diff --git a/tools/EnvFileViewer/EnvLib/EnvFileModel.cs b/tools/EnvFileViewer/EnvLib/EnvFileModel.cs index 43f0aed..701bd97 100644 --- a/tools/EnvFileViewer/EnvLib/EnvFileModel.cs +++ b/tools/EnvFileViewer/EnvLib/EnvFileModel.cs @@ -43,6 +43,7 @@ public class ModuleInfo public bool FileExists { get; set; } public bool ContentMatches { get; set; } + public string? FileName { get; set; } public string FileNameHashHex => BitConverter.ToString(FileNameHash).Replace("-", "").ToLowerInvariant(); diff --git a/tools/EnvFileViewer/EnvLib/EnvFileParser.cs b/tools/EnvFileViewer/EnvLib/EnvFileParser.cs index d31a689..2d4583e 100644 --- a/tools/EnvFileViewer/EnvLib/EnvFileParser.cs +++ b/tools/EnvFileViewer/EnvLib/EnvFileParser.cs @@ -220,26 +220,28 @@ private static List GetModules(string folder, uint seed, byte[] cont private static void FillModuleStatus(string folder, uint seed, IEnumerable modules) { var files = Helper.GetModuleFiles(folder); - List<(byte[] fileNameHash, byte[] contentHash)> moduleHashes = new List<(byte[], byte[])>(); + List<(byte[] FileNameHash, byte[] ContentHash, string FileName)> moduleHashes = new List<(byte[] FileNameHash, byte[] ContentHash, string FileName)>(); foreach (var module in files) { - var fileName = Path.GetFileName(module)?.ToLowerInvariant(); - if (fileName == null) + var rawRelativePath = Path.GetRelativePath(folder, module).Replace("\\", "/"); + var relativeFilePath = GetModuleRelativePath(module, folder); + if (relativeFilePath == null) { continue; } - var fileNameHash = Helper.GetStringSHA256Hash(fileName, seed); + var relativePathHash = Helper.GetStringSHA256Hash(relativeFilePath, seed); var contentHash = Helper.GetSHA256Hash(module, seed); - moduleHashes.Add((fileNameHash, contentHash)); + moduleHashes.Add((relativePathHash, contentHash, rawRelativePath)); } foreach(var module in modules) { - var matchingModule = moduleHashes.FirstOrDefault(m => m.fileNameHash.SequenceEqual(module.FileNameHash)); - if (matchingModule.fileNameHash != null) + var matchingModule = moduleHashes.FirstOrDefault(m => m.FileNameHash.SequenceEqual(module.FileNameHash)); + if (matchingModule.FileNameHash != null) { module.FileExists = true; - module.ContentMatches = matchingModule.contentHash.SequenceEqual(module.ContentHash); + module.FileName = matchingModule.FileName; + module.ContentMatches = matchingModule.ContentHash.SequenceEqual(module.ContentHash); } else { @@ -248,4 +250,33 @@ private static void FillModuleStatus(string folder, uint seed, IEnumerable basePathNormalized.Length && + fullPathNormalized.Substring(0, basePathNormalized.Length).Equals(basePathNormalized, StringComparison.OrdinalIgnoreCase)) + { + relativePath = fullPathNormalized.Substring(basePathNormalized.Length); + } + else + { + // Fallback: if fullPath is not under basePath, use filename + relativePath = Path.GetFileName(fullPathNormalized); + } + + // Replace backslashes with forward slashes + relativePath = relativePath.Replace('\\', '/'); + + // Convert to lowercase + return relativePath.ToLower(); + } } diff --git a/tools/EnvFileViewer/EnvLib/Helper.cs b/tools/EnvFileViewer/EnvLib/Helper.cs index b26e565..49865d5 100644 --- a/tools/EnvFileViewer/EnvLib/Helper.cs +++ b/tools/EnvFileViewer/EnvLib/Helper.cs @@ -7,11 +7,32 @@ internal class Helper { public static string[] GetModuleFiles(string outputPath) { - var files = Directory.GetFiles(outputPath, "*", SearchOption.TopDirectoryOnly) - .Where(f => f.EndsWith(".dll", StringComparison.OrdinalIgnoreCase) - || f.EndsWith(".exe", StringComparison.OrdinalIgnoreCase) - || IsAotExecutable(f) - ) + string runtimesPath = Path.Combine(outputPath, "runtimes") + Path.DirectorySeparatorChar; + var files = Directory.GetFiles(outputPath, "*", SearchOption.AllDirectories) + .Where(f => + { + // Exclude files from runtimes subfolder + if (f.StartsWith(runtimesPath, StringComparison.OrdinalIgnoreCase)) + return false; + + var isDll = f.EndsWith(".dll", StringComparison.OrdinalIgnoreCase); + var isExe = f.EndsWith(".exe", StringComparison.OrdinalIgnoreCase); + + // .exe files only in top directory (direct children of outputPath) + if (isExe) + return Path.GetDirectoryName(f) == outputPath; + + // .dll files and AoT executables allowed anywhere (except runtimes) + if (isDll) + return true; + +#if NET6_0_OR_GREATER + if (IsAotExecutable(f)) + return true; +#endif + + return false; + }) .ToArray(); return files;