├── InnoSetupScript.iss ├── NovaEDR.cs ├── README.md └── Wazuh-Fibratus-Rules.xml /InnoSetupScript.iss: -------------------------------------------------------------------------------- 1 | ; Nova EDR Agent Installer Script 2 | 3 | #define MyAppName "Nova EDR" 4 | #define MyAppVersion "1.0.0" 5 | #define MyAppPublisher "N0vaSky" 6 | #define MyAppURL "github.com/n0vasky" // CHANGE ME 7 | #define MyAppExeName "Nova EDR.exe" 8 | #define SourcePath "C:\Users\nova\source\repos\NovaEDR\bin\Debug" // CHANGE ME 9 | #define IconPath "C:\Users\nova\Downloads\novaedr.ico" // CHANGE ME 10 | #define ServerURL "https://raw.githubusercontent.com/N0vaSky/NovaEDR-deploy/" // CHANGE ME 11 | #define UpdateIntervalMinutes "60" // CHANGE ME IF YOU FEEL LIKE IT 12 | 13 | [Setup] 14 | ; Basic setup information 15 | AppId={{38D8AC11-6B1F-4E15-8842-D12A1C84A5A6} 16 | AppName={#MyAppName} 17 | AppVersion={#MyAppVersion} 18 | AppPublisher={#MyAppPublisher} 19 | AppPublisherURL={#MyAppURL} 20 | AppSupportURL={#MyAppURL} 21 | AppUpdatesURL={#MyAppURL} 22 | DefaultDirName={pf}\{#MyAppName} 23 | DefaultGroupName={#MyAppName} 24 | DisableProgramGroupPage=yes 25 | OutputDir=. 26 | OutputBaseFilename=Nova EDR Setup 27 | Compression=lzma 28 | SolidCompression=yes 29 | PrivilegesRequired=admin 30 | SetupIconFile={#IconPath} 31 | UninstallDisplayName={#MyAppName} 32 | UninstallDisplayIcon={app}\novaedr.ico 33 | 34 | ; Enable command line parameter processing 35 | SetupLogging=yes 36 | AllowNoIcons=yes 37 | 38 | [Languages] 39 | Name: "english"; MessagesFile: "compiler:Default.isl" 40 | 41 | [Files] 42 | ; Include all files from the Release directory 43 | Source: "{#SourcePath}\*"; DestDir: "{app}"; Flags: ignoreversion recursesubdirs createallsubdirs 44 | ; Include the icon 45 | Source: "{#IconPath}"; DestDir: "{app}"; Flags: ignoreversion 46 | 47 | ; Create directories 48 | [Dirs] 49 | Name: "{commonappdata}\NovaEDR\Config"; Permissions: everyone-full 50 | Name: "{commonappdata}\NovaEDR\Logs"; Permissions: everyone-full 51 | Name: "{commonappdata}\NovaEDR\Temp"; Permissions: everyone-full 52 | 53 | [Icons] 54 | Name: "{group}\{#MyAppName}"; Filename: "{app}\{#MyAppExeName}"; IconFilename: "{app}\novaedr.ico" 55 | Name: "{group}\Uninstall {#MyAppName}"; Filename: "{uninstallexe}"; IconFilename: "{app}\novaedr.ico" 56 | 57 | [Code] 58 | var 59 | ClientIDPage: TInputQueryWizardPage; 60 | WazuhGroupsPage: TInputQueryWizardPage; 61 | ClientIDValue: String; 62 | WazuhGroupsValue: String; 63 | IsClientIDFromParam: Boolean; 64 | IsWazuhGroupsFromParam: Boolean; 65 | 66 | // Check for admin rights during initialization 67 | function InitializeSetup(): Boolean; 68 | begin 69 | if not IsAdminLoggedOn then 70 | begin 71 | MsgBox('This installer requires administrator privileges. Please run as an administrator.', mbError, MB_OK); 72 | Result := False; 73 | end 74 | else 75 | Result := True; 76 | end; 77 | 78 | // Parse command line parameters 79 | function GetCommandLineParam(const ParamName: String): String; 80 | var 81 | I: Integer; 82 | Param: String; 83 | begin 84 | Result := ''; 85 | for I := 1 to ParamCount do 86 | begin 87 | Param := ParamStr(I); 88 | if Pos('/' + ParamName + '=', Param) = 1 then 89 | begin 90 | Delete(Param, 1, Length('/' + ParamName + '=')); 91 | Result := Param; 92 | Break; 93 | end; 94 | end; 95 | end; 96 | 97 | // Initialize the wizard 98 | procedure InitializeWizard; 99 | begin 100 | // Create Client ID page 101 | ClientIDPage := CreateInputQueryPage(wpWelcome, 102 | 'Client ID', 'Please enter your Client ID', 103 | 'This ID is used to identify which client this installation belongs to.'); 104 | ClientIDPage.Add('Client ID:', False); 105 | 106 | // Create Wazuh Groups page 107 | WazuhGroupsPage := CreateInputQueryPage(ClientIDPage.ID, 108 | 'Wazuh Agent Groups', 'Please enter your Wazuh Agent Groups', 109 | 'Multiple groups should be comma-separated (e.g., "group1,group2,group3"). Leave empty if not needed.'); 110 | WazuhGroupsPage.Add('Wazuh Agent Groups:', False); 111 | 112 | // Check for command-line parameters 113 | ClientIDValue := GetCommandLineParam('CLIENT_ID'); 114 | if ClientIDValue <> '' then 115 | begin 116 | ClientIDPage.Values[0] := ClientIDValue; 117 | IsClientIDFromParam := True; 118 | end 119 | else 120 | begin 121 | ClientIDPage.Values[0] := 'nhpdriXRA3M9Fs7rKkaAtG2lI'; // CHANGE ME 122 | IsClientIDFromParam := False; 123 | end; 124 | 125 | WazuhGroupsValue := GetCommandLineParam('WAZUH_GROUPS'); 126 | if WazuhGroupsValue <> '' then 127 | begin 128 | WazuhGroupsPage.Values[0] := WazuhGroupsValue; 129 | IsWazuhGroupsFromParam := True; 130 | end 131 | else 132 | begin 133 | WazuhGroupsPage.Values[0] := ''; 134 | IsWazuhGroupsFromParam := False; 135 | end; 136 | end; 137 | 138 | // Validate next button click 139 | function NextButtonClick(CurPageID: Integer): Boolean; 140 | begin 141 | if CurPageID = ClientIDPage.ID then 142 | begin 143 | // Validate Client ID 144 | ClientIDValue := ClientIDPage.Values[0]; 145 | if ClientIDValue = '' then 146 | begin 147 | MsgBox('Please enter a valid Client ID.', mbError, MB_OK); 148 | Result := False; 149 | end 150 | else 151 | Result := True; 152 | end 153 | else if CurPageID = WazuhGroupsPage.ID then 154 | begin 155 | // No validation needed for Wazuh Groups as it can be empty 156 | WazuhGroupsValue := WazuhGroupsPage.Values[0]; 157 | Result := True; 158 | end 159 | else 160 | Result := True; 161 | end; 162 | 163 | // Skip pages if values were provided as parameters 164 | function ShouldSkipPage(PageID: Integer): Boolean; 165 | begin 166 | Result := False; 167 | 168 | if (PageID = ClientIDPage.ID) and IsClientIDFromParam then 169 | Result := True; 170 | 171 | if (PageID = WazuhGroupsPage.ID) and IsWazuhGroupsFromParam then 172 | Result := True; 173 | end; 174 | 175 | // Create configuration file 176 | procedure CreateConfigFile; 177 | var 178 | ConfigDir, ConfigFile, ConfigContent: String; 179 | begin 180 | ConfigDir := ExpandConstant('{commonappdata}\NovaEDR\Config'); 181 | ConfigFile := ConfigDir + '\config.json'; 182 | 183 | // Create the configuration content with proper JSON escaping 184 | ConfigContent := '{' + #13#10 + 185 | ' "ServerUrl": "{#ServerURL}",' + #13#10 + 186 | ' "ClientId": "' + ClientIDValue + '",' + #13#10; 187 | 188 | // Add Wazuh Groups if specified 189 | if WazuhGroupsValue <> '' then 190 | ConfigContent := ConfigContent + ' "WazuhGroups": "' + WazuhGroupsValue + '",' + #13#10; 191 | 192 | ConfigContent := ConfigContent + 193 | ' "UpdateIntervalMinutes": {#UpdateIntervalMinutes},' + #13#10 + 194 | ' "LogLevel": "Info",' + #13#10 + 195 | ' "LogPath": "C:\\ProgramData\\NovaEDR\\Logs",' + #13#10 + 196 | ' "ConfigPath": "C:\\ProgramData\\NovaEDR\\Config",' + #13#10 + 197 | ' "TempPath": "C:\\ProgramData\\NovaEDR\\Temp"' + #13#10 + 198 | '}'; 199 | 200 | // Write to file 201 | if FileExists(ConfigFile) then 202 | DeleteFile(ConfigFile); 203 | 204 | if not ForceDirectories(ConfigDir) then 205 | MsgBox('Failed to create directory: ' + ConfigDir, mbError, MB_OK); 206 | 207 | if SaveStringToFile(ConfigFile, ConfigContent, False) then 208 | Log('Created configuration file: ' + ConfigFile) 209 | else 210 | MsgBox('Failed to create configuration file: ' + ConfigFile, mbError, MB_OK); 211 | end; 212 | 213 | // Return the Client ID to use in the run section 214 | function GetClientID(Param: String): String; 215 | begin 216 | Result := ClientIDValue; 217 | end; 218 | 219 | // Return the Wazuh Groups to use in the run section 220 | function GetWazuhGroups(Param: String): String; 221 | begin 222 | Result := WazuhGroupsValue; 223 | end; 224 | 225 | // Handle uninstallation steps with proper dependency removal 226 | procedure CurUninstallStepChanged(CurUninstallStep: TUninstallStep); 227 | var 228 | ResultCode: Integer; 229 | begin 230 | if CurUninstallStep = usUninstall then 231 | begin 232 | // Ensure dependencies are uninstalled first 233 | if Exec(ExpandConstant('{app}\{#MyAppExeName}'), '--uninstall', '', SW_HIDE, ewWaitUntilTerminated, ResultCode) then 234 | begin 235 | // Agent uninstall executed successfully 236 | Log('Successfully executed agent uninstall with result code: ' + IntToStr(ResultCode)); 237 | end 238 | else 239 | begin 240 | // Agent uninstall failed 241 | Log('Failed to execute agent uninstall: ' + SysErrorMessage(ResultCode)); 242 | end; 243 | end 244 | else if CurUninstallStep = usPostUninstall then 245 | begin 246 | // Clean up configuration files 247 | DelTree(ExpandConstant('{commonappdata}\NovaEDR'), True, True, True); 248 | end; 249 | end; 250 | 251 | [Run] 252 | ; Create config file and install service 253 | Filename: "{cmd}"; WorkingDir: "{app}"; Parameters: "/c echo Creating configuration file..."; Flags: runhidden 254 | Filename: "{app}\{#MyAppExeName}"; Parameters: "--install CLIENT_ID=""{code:GetClientID}"" WAZUH_GROUPS=""{code:GetWazuhGroups}"""; WorkingDir: "{app}"; StatusMsg: "Installing Nova EDR service..."; Flags: runhidden; BeforeInstall: CreateConfigFile 255 | 256 | [UninstallRun] 257 | ; First run the agent's uninstall function to remove dependencies 258 | Filename: "{app}\{#MyAppExeName}"; Parameters: "--uninstall"; WorkingDir: "{app}"; StatusMsg: "Removing Nova EDR components..."; Flags: runhidden; RunOnceId: "UninstallComponents" 259 | -------------------------------------------------------------------------------- /NovaEDR.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Diagnostics; 3 | using System.IO; 4 | using System.IO.Compression; 5 | using System.Net.Http; 6 | using System.Reflection; 7 | using System.Security.Cryptography; 8 | using System.ServiceProcess; 9 | using System.Text; 10 | using System.Text.Json; 11 | using System.Threading; 12 | using System.Threading.Tasks; 13 | using Microsoft.Win32; 14 | using System.Collections.Generic; 15 | using System.Linq; 16 | using System.ComponentModel; 17 | using System.Configuration.Install; 18 | using System.Security.Principal; 19 | 20 | namespace NovaEDR.Agent 21 | { 22 | 23 | // Hard-coded server URL that cannot be changed by clients 24 | public static class Constants 25 | { 26 | public const string SERVER_URL = "https://raw.githubusercontent.com/N0vaSky/NovaEDR-deploy/"; 27 | public const int DEFAULT_UPDATE_INTERVAL_MINUTES = 60; // Set to 60 minutes 28 | public const string DEFAULT_CLIENT_ID = "nhpdriXRA3M9Fs7rKkaAtG2lI"; // Default branch/client ID 29 | } 30 | 31 | public class NovaEDR 32 | { 33 | public static void Main(string[] args) 34 | { 35 | // Add basic console output for debugging 36 | Console.WriteLine($"Nova EDR Agent starting with args: {string.Join(", ", args)}"); 37 | 38 | try 39 | { 40 | if (args.Length > 0) 41 | { 42 | switch (args[0].ToLower()) 43 | { 44 | case "--console": 45 | // Run in console mode for debugging 46 | var agent = new NovaEDRAgent(); 47 | agent.StartConsole(args); 48 | Console.WriteLine("Press any key to exit..."); 49 | Console.ReadKey(); 50 | break; 51 | 52 | case "--install": 53 | // Install the service 54 | InstallService(); 55 | break; 56 | 57 | case "--uninstall": 58 | // Uninstall the service and components 59 | UninstallService(); 60 | break; 61 | 62 | case "--version": 63 | // Display version info 64 | Console.WriteLine($"Nova EDR Agent v{Assembly.GetExecutingAssembly().GetName().Version}"); 65 | break; 66 | 67 | default: 68 | ShowHelp(); 69 | break; 70 | } 71 | } 72 | else 73 | { 74 | // Run as Windows service 75 | ServiceBase.Run(new ServiceBase[] { new NovaEDRAgent() }); 76 | } 77 | } 78 | catch (Exception ex) 79 | { 80 | Console.WriteLine($"Critical error: {ex.Message}"); 81 | Console.WriteLine(ex.StackTrace); 82 | File.WriteAllText("NovaEDRError.log", $"{DateTime.Now}: {ex.Message}\n{ex.StackTrace}"); 83 | Console.WriteLine("Press any key to exit..."); 84 | Console.ReadKey(); 85 | } 86 | } 87 | 88 | private static void ShowHelp() 89 | { 90 | Console.WriteLine("Nova EDR Agent - Unified EDR Solution Agent"); 91 | Console.WriteLine("Usage:"); 92 | Console.WriteLine(" NovaEDRAgent.exe - Run as a service (normal operation)"); 93 | Console.WriteLine(" NovaEDRAgent.exe --console - Run in console mode for testing"); 94 | Console.WriteLine(" NovaEDRAgent.exe --install - Install the service"); 95 | Console.WriteLine(" NovaEDRAgent.exe --uninstall - Uninstall the service and components"); 96 | Console.WriteLine(" NovaEDRAgent.exe --version - Display version information"); 97 | } 98 | 99 | private static void InstallService() 100 | { 101 | try 102 | { 103 | // Parse command line arguments - only looking for CLIENT_ID now 104 | string clientId = null; 105 | string wazuhGroups = null; 106 | 107 | // Get command line arguments from Environment 108 | string[] args = Environment.GetCommandLineArgs(); 109 | for (int i = 0; i < args.Length; i++) 110 | { 111 | string arg = args[i]; 112 | 113 | // Look for CLIENT_ID parameter 114 | if (arg.StartsWith("CLIENT_ID=", StringComparison.OrdinalIgnoreCase)) 115 | { 116 | clientId = arg.Substring("CLIENT_ID=".Length).Trim('"'); 117 | Console.WriteLine($"Found CLIENT_ID: {clientId}"); 118 | } 119 | 120 | // Look for WAZUH_GROUPS parameter 121 | else if (arg.StartsWith("WAZUH_GROUPS=", StringComparison.OrdinalIgnoreCase)) 122 | { 123 | wazuhGroups = arg.Substring("WAZUH_GROUPS=".Length).Trim('"'); 124 | Console.WriteLine($"Found WAZUH_GROUPS: {wazuhGroups}"); 125 | } 126 | } 127 | 128 | // If no client ID specified, prompt for it 129 | if (string.IsNullOrEmpty(clientId)) 130 | { 131 | Console.WriteLine($"No CLIENT_ID specified. Please enter the client ID (default: {Constants.DEFAULT_CLIENT_ID}):"); 132 | clientId = Console.ReadLine()?.Trim(); 133 | 134 | if (string.IsNullOrEmpty(clientId)) 135 | { 136 | clientId = Constants.DEFAULT_CLIENT_ID; 137 | Console.WriteLine($"Using default CLIENT_ID: {clientId}"); 138 | } 139 | } 140 | 141 | Console.WriteLine($"Installing service with SERVER_URL={Constants.SERVER_URL}, CLIENT_ID={clientId}"); 142 | 143 | // Create required directories 144 | CreateProgramDataDirectories(); 145 | 146 | // Create initial config file 147 | string configPath = @"C:\ProgramData\NovaEDR\Config"; 148 | string configFilePath = Path.Combine(configPath, "config.json"); 149 | 150 | // Create configuration object with hardcoded server URL 151 | var config = new Dictionary 152 | { 153 | { "ServerUrl", Constants.SERVER_URL }, 154 | { "ClientId", clientId ?? "default" }, 155 | { "UpdateIntervalMinutes", Constants.DEFAULT_UPDATE_INTERVAL_MINUTES }, 156 | { "LogLevel", "Info" }, 157 | { "LogPath", @"C:\ProgramData\NovaEDR\Logs" }, 158 | { "ConfigPath", configPath }, 159 | { "TempPath", @"C:\ProgramData\NovaEDR\Temp" } 160 | }; 161 | 162 | // Add Wazuh Groups if specified 163 | if (!string.IsNullOrEmpty(wazuhGroups)) 164 | { 165 | config["WazuhGroups"] = wazuhGroups; 166 | } 167 | 168 | // Save configuration to file 169 | File.WriteAllText(configFilePath, JsonSerializer.Serialize(config, new JsonSerializerOptions { WriteIndented = true })); 170 | Console.WriteLine($"Created configuration file at {configFilePath}"); 171 | 172 | // Now install the service 173 | Console.WriteLine("Installing service..."); 174 | ManagedInstallerClass.InstallHelper(new string[] { Assembly.GetExecutingAssembly().Location }); 175 | Console.WriteLine("Nova EDR Agent service installed successfully."); 176 | } 177 | catch (Exception ex) 178 | { 179 | Console.WriteLine($"Error installing service: {ex.Message}"); 180 | File.WriteAllText("InstallError.log", $"{DateTime.Now}: {ex.Message}\n{ex.StackTrace}"); 181 | } 182 | } 183 | 184 | private static void CreateProgramDataDirectories() 185 | { 186 | string logPath = @"C:\ProgramData\NovaEDR\Logs"; 187 | string configPath = @"C:\ProgramData\NovaEDR\Config"; 188 | string tempPath = @"C:\ProgramData\NovaEDR\Temp"; 189 | 190 | if (!Directory.Exists(logPath)) Directory.CreateDirectory(logPath); 191 | if (!Directory.Exists(configPath)) Directory.CreateDirectory(configPath); 192 | if (!Directory.Exists(tempPath)) Directory.CreateDirectory(tempPath); 193 | 194 | Console.WriteLine("Created required directories"); 195 | } 196 | 197 | private static void StartProcess(string fileName, string arguments) 198 | { 199 | try 200 | { 201 | ProcessStartInfo startInfo = new ProcessStartInfo 202 | { 203 | FileName = fileName, 204 | Arguments = arguments, 205 | UseShellExecute = true, 206 | Verb = "runas", 207 | WindowStyle = ProcessWindowStyle.Normal // Make visible for debugging 208 | }; 209 | 210 | Process process = Process.Start(startInfo); 211 | process.WaitForExit(); 212 | Console.WriteLine($"Process {fileName} exited with code: {process.ExitCode}"); 213 | } 214 | catch (Exception ex) 215 | { 216 | Console.WriteLine($"Error executing process {fileName}: {ex.Message}"); 217 | } 218 | } 219 | 220 | private static void UninstallService() 221 | { 222 | try 223 | { 224 | // First uninstall all components using the improved method 225 | Console.WriteLine("Uninstalling components..."); 226 | 227 | // Use PowerShell-based uninstallation instead of WMIC 228 | UninstallProductWithPowerShell("Fibratus"); 229 | UninstallProductWithPowerShell("Velociraptor"); 230 | UninstallProductWithPowerShell("Wazuh"); 231 | UninstallProductWithPowerShell("Nova EDR"); 232 | 233 | // Now uninstall the service 234 | Console.WriteLine("Uninstalling Nova EDR Agent service..."); 235 | ManagedInstallerClass.InstallHelper(new string[] { "/u", Assembly.GetExecutingAssembly().Location }); 236 | Console.WriteLine("Nova EDR Agent service uninstalled successfully."); 237 | 238 | // Clean up configuration and logs 239 | try 240 | { 241 | string configDir = @"C:\ProgramData\NovaEDR"; 242 | if (Directory.Exists(configDir)) 243 | { 244 | Directory.Delete(configDir, true); 245 | Console.WriteLine("Removed Nova EDR configuration directory"); 246 | } 247 | } 248 | catch (Exception ex) 249 | { 250 | Console.WriteLine($"Error removing Nova EDR configuration: {ex.Message}"); 251 | } 252 | } 253 | catch (Exception ex) 254 | { 255 | Console.WriteLine($"Error uninstalling service: {ex.Message}"); 256 | File.WriteAllText("UninstallError.log", $"{DateTime.Now}: {ex.Message}\n{ex.StackTrace}"); 257 | } 258 | } 259 | 260 | private static void UninstallProductWithPowerShell(string productName) 261 | { 262 | try 263 | { 264 | Console.WriteLine($"Uninstalling {productName} using PowerShell..."); 265 | 266 | // Create PowerShell script to find and uninstall products 267 | string script = $@" 268 | $products = Get-WmiObject -Class Win32_Product | Where-Object {{ 269 | $_.Name -like '*{productName}*' -or 270 | $_.DisplayName -like '*{productName}*' -or 271 | $_.Description -like '*{productName}*' 272 | }} 273 | 274 | foreach ($product in $products) {{ 275 | Write-Host ""Found product: $($product.Name) - $($product.IdentifyingNumber)"" 276 | try {{ 277 | $result = $product.Uninstall() 278 | if ($result.ReturnValue -eq 0) {{ 279 | Write-Host ""Successfully uninstalled: $($product.Name)"" 280 | }} else {{ 281 | Write-Host ""Uninstall failed with return code: $($result.ReturnValue)"" 282 | }} 283 | }} catch {{ 284 | Write-Host ""Error uninstalling $($product.Name): $($_.Exception.Message)"" 285 | }} 286 | }} 287 | "; 288 | 289 | // Execute PowerShell script 290 | var process = new Process 291 | { 292 | StartInfo = new ProcessStartInfo 293 | { 294 | FileName = "powershell.exe", 295 | Arguments = $"-Command \"{script}\"", 296 | UseShellExecute = true, 297 | Verb = "runas", 298 | WindowStyle = ProcessWindowStyle.Normal, 299 | CreateNoWindow = false 300 | } 301 | }; 302 | 303 | process.Start(); 304 | process.WaitForExit(); 305 | 306 | Console.WriteLine($"PowerShell uninstall for {productName} completed with exit code: {process.ExitCode}"); 307 | 308 | // Verify removal and clean up manually if needed 309 | CleanupAfterUninstall(productName); 310 | } 311 | catch (Exception ex) 312 | { 313 | Console.WriteLine($"Error uninstalling {productName} with PowerShell: {ex.Message}"); 314 | // Fallback to manual cleanup 315 | CleanupAfterUninstall(productName); 316 | } 317 | } 318 | 319 | private static void CleanupAfterUninstall(string productName) 320 | { 321 | try 322 | { 323 | // Stop and remove any remaining services 324 | var services = FindServicesByProduct(productName); 325 | foreach (var serviceName in services) 326 | { 327 | StopAndRemoveService(serviceName); 328 | } 329 | 330 | // Remove directories 331 | var directories = GetProductDirectories(productName); 332 | foreach (var directory in directories) 333 | { 334 | DeleteDirectory(directory); 335 | } 336 | 337 | Console.WriteLine($"Manual cleanup completed for {productName}"); 338 | } 339 | catch (Exception ex) 340 | { 341 | Console.WriteLine($"Error during manual cleanup for {productName}: {ex.Message}"); 342 | } 343 | } 344 | 345 | private static List FindServicesByProduct(string productName) 346 | { 347 | var services = new List(); 348 | 349 | try 350 | { 351 | // Search by executable path using PowerShell 352 | string script = $@" 353 | Get-WmiObject -Class Win32_Service | Where-Object {{ 354 | $_.Name -like '*{productName}*' -or 355 | $_.DisplayName -like '*{productName}*' -or 356 | $_.PathName -like '*{productName}*' 357 | }} | ForEach-Object {{ $_.Name }} 358 | "; 359 | 360 | var process = new Process 361 | { 362 | StartInfo = new ProcessStartInfo 363 | { 364 | FileName = "powershell.exe", 365 | Arguments = $"-Command \"{script}\"", 366 | UseShellExecute = false, 367 | RedirectStandardOutput = true, 368 | CreateNoWindow = true 369 | } 370 | }; 371 | 372 | process.Start(); 373 | string output = process.StandardOutput.ReadToEnd(); 374 | process.WaitForExit(); 375 | 376 | if (!string.IsNullOrEmpty(output)) 377 | { 378 | var foundServices = output.Split(new[] { '\r', '\n' }, StringSplitOptions.RemoveEmptyEntries); 379 | services.AddRange(foundServices.Select(s => s.Trim()).Where(s => !string.IsNullOrEmpty(s))); 380 | } 381 | 382 | // Add known patterns as fallback 383 | var patterns = GetServiceNamePatterns(productName); 384 | services.AddRange(patterns.Where(ServiceExists)); 385 | 386 | Console.WriteLine($"Found {services.Count} services for {productName}: {string.Join(", ", services)}"); 387 | } 388 | catch (Exception ex) 389 | { 390 | Console.WriteLine($"Error finding services for {productName}: {ex.Message}"); 391 | // Fallback to hardcoded patterns 392 | services.AddRange(GetServiceNamePatterns(productName).Where(ServiceExists)); 393 | } 394 | 395 | return services.Distinct().ToList(); 396 | } 397 | 398 | private static List GetServiceNamePatterns(string productName) 399 | { 400 | var patterns = new List(); 401 | 402 | switch (productName.ToLower()) 403 | { 404 | case "fibratus": 405 | patterns.AddRange(new[] { "Fibratus", "fibratus" }); 406 | break; 407 | case "velociraptor": 408 | case "nova edr": 409 | patterns.AddRange(new[] { "Velociraptor", "velociraptor", "VelociraptorService" }); 410 | break; 411 | case "wazuh": 412 | patterns.AddRange(new[] { "Wazuh", "wazuh-agent", "WazuhSvc", "wazuh_agent", "WazuhAgent" }); 413 | break; 414 | } 415 | 416 | return patterns; 417 | } 418 | 419 | private static List GetProductDirectories(string productName) 420 | { 421 | var directories = new List(); 422 | 423 | switch (productName.ToLower()) 424 | { 425 | case "fibratus": 426 | directories.AddRange(new[] 427 | { 428 | @"C:\Program Files\Fibratus", 429 | @"C:\Program Files (x86)\Fibratus" 430 | }); 431 | break; 432 | case "velociraptor": 433 | case "nova edr": 434 | directories.AddRange(new[] 435 | { 436 | @"C:\Program Files\Velociraptor", 437 | @"C:\Program Files (x86)\Velociraptor" 438 | }); 439 | break; 440 | case "wazuh": 441 | directories.AddRange(new[] 442 | { 443 | @"C:\Program Files\Wazuh", 444 | @"C:\Program Files\Wazuh Agent", 445 | @"C:\Program Files (x86)\Wazuh", 446 | @"C:\Program Files (x86)\Wazuh Agent" 447 | }); 448 | break; 449 | } 450 | 451 | return directories.Where(Directory.Exists).ToList(); 452 | } 453 | 454 | private static bool CheckIfServiceExists(string serviceName) 455 | { 456 | if (serviceName.Equals("Fibratus", StringComparison.OrdinalIgnoreCase)) 457 | { 458 | return ServiceExists("Fibratus"); 459 | } 460 | else if (serviceName.Equals("Velociraptor", StringComparison.OrdinalIgnoreCase) || 461 | serviceName.Equals("Nova EDR", StringComparison.OrdinalIgnoreCase)) 462 | { 463 | return ServiceExists("Velociraptor"); 464 | } 465 | else if (serviceName.Equals("Wazuh", StringComparison.OrdinalIgnoreCase)) 466 | { 467 | return ServiceExists("Wazuh") || ServiceExists("wazuh-agent") || ServiceExists("WazuhSvc"); 468 | } 469 | 470 | return false; 471 | } 472 | 473 | private static bool ServiceExists(string serviceName) 474 | { 475 | try 476 | { 477 | using (var service = new ServiceController(serviceName)) 478 | { 479 | // Just access a property to see if it throws 480 | var status = service.Status; 481 | return true; 482 | } 483 | } 484 | catch 485 | { 486 | return false; 487 | } 488 | } 489 | 490 | private static void RemoveService(string serviceName) 491 | { 492 | if (serviceName.Equals("Fibratus", StringComparison.OrdinalIgnoreCase)) 493 | { 494 | StopAndRemoveService("Fibratus"); 495 | } 496 | else if (serviceName.Equals("Velociraptor", StringComparison.OrdinalIgnoreCase) || 497 | serviceName.Equals("Nova EDR", StringComparison.OrdinalIgnoreCase)) 498 | { 499 | StopAndRemoveService("Velociraptor"); 500 | } 501 | else if (serviceName.Equals("Wazuh", StringComparison.OrdinalIgnoreCase)) 502 | { 503 | StopAndRemoveService("Wazuh"); 504 | StopAndRemoveService("wazuh-agent"); 505 | StopAndRemoveService("WazuhSvc"); 506 | } 507 | } 508 | 509 | private static void StopAndRemoveService(string serviceName) 510 | { 511 | try 512 | { 513 | // First try to stop the service gracefully 514 | try 515 | { 516 | Console.WriteLine($"Stopping service {serviceName}..."); 517 | StartProcess("net", $"stop {serviceName}"); 518 | } 519 | catch 520 | { 521 | // Ignore errors stopping the service 522 | } 523 | 524 | // Then remove the service 525 | Console.WriteLine($"Removing service {serviceName}..."); 526 | StartProcess("sc", $"delete {serviceName}"); 527 | } 528 | catch (Exception ex) 529 | { 530 | Console.WriteLine($"Error removing service {serviceName}: {ex.Message}"); 531 | } 532 | } 533 | 534 | private static void CleanupProductDirectory(string productName) 535 | { 536 | if (productName.Equals("Fibratus", StringComparison.OrdinalIgnoreCase)) 537 | { 538 | DeleteDirectory(@"C:\Program Files\Fibratus"); 539 | } 540 | else if (productName.Equals("Velociraptor", StringComparison.OrdinalIgnoreCase) || 541 | productName.Equals("Nova EDR", StringComparison.OrdinalIgnoreCase)) 542 | { 543 | DeleteDirectory(@"C:\Program Files\Velociraptor"); 544 | } 545 | else if (productName.Equals("Wazuh", StringComparison.OrdinalIgnoreCase)) 546 | { 547 | DeleteDirectory(@"C:\Program Files\Wazuh"); 548 | DeleteDirectory(@"C:\Program Files\Wazuh Agent"); 549 | DeleteDirectory(@"C:\Program Files (x86)\Wazuh"); 550 | DeleteDirectory(@"C:\Program Files (x86)\Wazuh Agent"); 551 | } 552 | } 553 | 554 | private static void DeleteDirectory(string path) 555 | { 556 | if (!Directory.Exists(path)) return; 557 | 558 | try 559 | { 560 | Console.WriteLine($"Deleting directory: {path}"); 561 | Directory.Delete(path, true); 562 | Console.WriteLine($"Successfully deleted directory: {path}"); 563 | } 564 | catch (Exception ex) 565 | { 566 | Console.WriteLine($"Error deleting directory {path}: {ex.Message}"); 567 | 568 | // Try forceful deletion with cmd 569 | try 570 | { 571 | Console.WriteLine("Attempting forceful deletion..."); 572 | StartProcess("cmd.exe", $"/c rd /s /q \"{path}\""); 573 | } 574 | catch 575 | { 576 | // Ignore errors in forceful deletion 577 | } 578 | } 579 | } 580 | 581 | // Keep for compatibility with other parts of the code 582 | private static void RunMsiProcess(string fileName, string arguments) 583 | { 584 | using (var process = new Process()) 585 | { 586 | process.StartInfo.FileName = fileName; 587 | process.StartInfo.Arguments = arguments; 588 | process.StartInfo.UseShellExecute = true; 589 | process.StartInfo.Verb = "runas"; 590 | process.StartInfo.CreateNoWindow = false; 591 | process.Start(); 592 | process.WaitForExit(); 593 | 594 | if (process.ExitCode != 0 && process.ExitCode != 3010 && process.ExitCode != 1605) 595 | { 596 | throw new Exception($"Process exited with code {process.ExitCode}"); 597 | } 598 | } 599 | } 600 | } 601 | [RunInstaller(true)] 602 | public class NovaEDRAgentInstaller : Installer 603 | { 604 | public NovaEDRAgentInstaller() 605 | { 606 | var serviceInstaller = new ServiceInstaller 607 | { 608 | ServiceName = "NovaEDRAgent", 609 | DisplayName = "Nova EDR Agent", 610 | Description = "Manages Nova EDR security components and updates", 611 | StartType = ServiceStartMode.Automatic 612 | }; 613 | 614 | // Add recovery options 615 | serviceInstaller.Context = new InstallContext(); 616 | serviceInstaller.Context.Parameters["SC_RESTART_DELAY"] = "60000"; // 1 minute 617 | serviceInstaller.Context.Parameters["SC_ACTIONS"] = "restart/60000/restart/60000/restart/60000/run/1000"; 618 | 619 | var processInstaller = new ServiceProcessInstaller 620 | { 621 | Account = ServiceAccount.LocalSystem 622 | }; 623 | 624 | Installers.Add(processInstaller); 625 | Installers.Add(serviceInstaller); 626 | 627 | // Handle Custom Actions during install and uninstall 628 | AfterInstall += OnAfterInstall; 629 | BeforeUninstall += OnBeforeUninstall; 630 | } 631 | 632 | private void OnAfterInstall(object sender, InstallEventArgs e) 633 | { 634 | try 635 | { 636 | // Create necessary directories 637 | string logPath = @"C:\ProgramData\NovaEDR\Logs"; 638 | string configPath = @"C:\ProgramData\NovaEDR\Config"; 639 | string tempPath = @"C:\ProgramData\NovaEDR\Temp"; 640 | 641 | if (!Directory.Exists(logPath)) Directory.CreateDirectory(logPath); 642 | if (!Directory.Exists(configPath)) Directory.CreateDirectory(configPath); 643 | if (!Directory.Exists(tempPath)) Directory.CreateDirectory(tempPath); 644 | 645 | // Verify config file exists and is valid 646 | string configFilePath = Path.Combine(configPath, "config.json"); 647 | string serverUrl = "http://localhost/Repo"; 648 | string clientId = "default"; 649 | 650 | if (File.Exists(configFilePath)) 651 | { 652 | try 653 | { 654 | var configData = JsonSerializer.Deserialize>(File.ReadAllText(configFilePath)); 655 | if (configData.TryGetValue("ServerUrl", out var urlValue)) 656 | { 657 | serverUrl = urlValue.GetString(); 658 | } 659 | if (configData.TryGetValue("ClientId", out var clientValue)) 660 | { 661 | clientId = clientValue.GetString(); 662 | } 663 | 664 | File.AppendAllText(Path.Combine(logPath, "install.log"), 665 | $"[{DateTime.Now}] Found existing config values: ServerUrl={serverUrl}, ClientId={clientId}\r\n"); 666 | } 667 | catch (Exception configEx) 668 | { 669 | File.AppendAllText(Path.Combine(logPath, "install.log"), 670 | $"[{DateTime.Now}] Error reading config file: {configEx.Message}, using defaults\r\n"); 671 | } 672 | } 673 | else 674 | { 675 | // Create a new config file if it doesn't exist 676 | var config = new Dictionary 677 | { 678 | { "ServerUrl", serverUrl }, 679 | { "ClientId", clientId }, 680 | { "UpdateIntervalMinutes", Constants.DEFAULT_UPDATE_INTERVAL_MINUTES }, 681 | { "LogLevel", "Info" }, 682 | { "LogPath", logPath }, 683 | { "ConfigPath", configPath }, 684 | { "TempPath", tempPath } 685 | }; 686 | 687 | File.WriteAllText(configFilePath, JsonSerializer.Serialize(config, new JsonSerializerOptions { WriteIndented = true })); 688 | File.AppendAllText(Path.Combine(logPath, "install.log"), 689 | $"[{DateTime.Now}] Created new config file with defaults: ServerUrl={serverUrl}, ClientId={clientId}\r\n"); 690 | } 691 | 692 | // Log what we're about to do 693 | File.AppendAllText(Path.Combine(logPath, "install.log"), 694 | $"[{DateTime.Now}] Starting service initialization with ServerUrl={serverUrl}, ClientId={clientId}\r\n"); 695 | 696 | // Configure service recovery 697 | try 698 | { 699 | // Use SC.exe to configure recovery options 700 | Process.Start(new ProcessStartInfo 701 | { 702 | FileName = "sc.exe", 703 | Arguments = "failure NovaEDRAgent reset= 86400 actions= restart/60000/restart/60000/restart/60000", 704 | UseShellExecute = true, 705 | Verb = "runas", 706 | CreateNoWindow = true 707 | }).WaitForExit(); 708 | 709 | File.AppendAllText(Path.Combine(logPath, "install.log"), 710 | $"[{DateTime.Now}] Configured service recovery options\r\n"); 711 | } 712 | catch (Exception scEx) 713 | { 714 | File.AppendAllText(Path.Combine(logPath, "install.log"), 715 | $"[{DateTime.Now}] Error configuring service recovery: {scEx.Message}\r\n"); 716 | } 717 | 718 | // Start the service 719 | try 720 | { 721 | using (var controller = new ServiceController("NovaEDRAgent")) 722 | { 723 | if (controller.Status == ServiceControllerStatus.Stopped) 724 | { 725 | controller.Start(); 726 | Console.WriteLine("Service started successfully"); 727 | File.AppendAllText(Path.Combine(logPath, "install.log"), 728 | $"[{DateTime.Now}] Service started successfully\r\n"); 729 | } 730 | } 731 | } 732 | catch (Exception ex) 733 | { 734 | Console.WriteLine($"Error starting service: {ex.Message}"); 735 | File.AppendAllText(Path.Combine(logPath, "install.log"), 736 | $"[{DateTime.Now}] Error starting service: {ex.Message}\r\n{ex.StackTrace}\r\n"); 737 | } 738 | } 739 | catch (Exception ex) 740 | { 741 | string message = $"Error during post-install: {ex.Message}"; 742 | Console.WriteLine(message); 743 | 744 | try 745 | { 746 | string logPath = @"C:\ProgramData\NovaEDR\Logs"; 747 | if (!Directory.Exists(logPath)) Directory.CreateDirectory(logPath); 748 | 749 | File.AppendAllText(Path.Combine(logPath, "install_error.log"), 750 | $"[{DateTime.Now}] {message}\r\n{ex.StackTrace}\r\n"); 751 | } 752 | catch 753 | { 754 | // Suppress errors in error handling 755 | } 756 | } 757 | } 758 | 759 | private void OnBeforeUninstall(object sender, InstallEventArgs e) 760 | { 761 | try 762 | { 763 | // Stop the service first 764 | using (var controller = new ServiceController("NovaEDRAgent")) 765 | { 766 | if (controller.Status != ServiceControllerStatus.Stopped) 767 | { 768 | controller.Stop(); 769 | controller.WaitForStatus(ServiceControllerStatus.Stopped, TimeSpan.FromSeconds(30)); 770 | } 771 | } 772 | } 773 | catch (Exception ex) 774 | { 775 | EventLog.WriteEntry("NovaEDR", $"Error during pre-uninstall: {ex.Message}", EventLogEntryType.Error); 776 | } 777 | } 778 | } 779 | 780 | public class NovaEDRAgent : ServiceBase 781 | { 782 | private readonly HttpClient _httpClient; 783 | private readonly Logger _logger; 784 | private readonly ConfigManager _config; 785 | private CancellationTokenSource _cancelTokenSource; 786 | private Task _mainTask; 787 | private bool _isFirstRun = true; 788 | 789 | // Embedded update service 790 | private readonly UpdateService _updateService; 791 | 792 | // Status tracking 793 | private readonly StatusReporter _statusReporter; 794 | 795 | // Constants 796 | private const string SERVICE_NAME = "NovaEDRAgent"; 797 | private const string DISPLAY_NAME = "Nova EDR Agent"; 798 | private const string EVENT_SOURCE = "NovaEDR"; 799 | private const string EVENT_LOG = "Application"; 800 | 801 | public NovaEDRAgent() 802 | { 803 | ServiceName = SERVICE_NAME; 804 | AutoLog = false; 805 | 806 | // Initialize HttpClient with TLS 1.2 support 807 | var handler = new HttpClientHandler 808 | { 809 | ServerCertificateCustomValidationCallback = (message, cert, chain, errors) => true // For testing only, remove in production 810 | }; 811 | _httpClient = new HttpClient(handler); 812 | _httpClient.Timeout = TimeSpan.FromMinutes(10); // Long timeout for large file downloads 813 | 814 | // Initialize logger 815 | _logger = new Logger(EVENT_SOURCE, EVENT_LOG); 816 | 817 | // Initialize configuration 818 | _config = new ConfigManager(); 819 | 820 | // Initialize update service 821 | _updateService = new UpdateService(_httpClient, _logger, _config); 822 | 823 | // Initialize status reporter 824 | _statusReporter = new StatusReporter(_httpClient, _logger, _config); 825 | 826 | // Set up event handlers 827 | CanHandlePowerEvent = true; 828 | CanHandleSessionChangeEvent = true; 829 | CanShutdown = true; 830 | CanStop = true; 831 | } 832 | protected override void OnStart(string[] args) 833 | { 834 | _logger.Info("Nova EDR Agent starting"); 835 | 836 | try 837 | { 838 | // Load configuration 839 | _config.Load(); 840 | // Force update interval to be 15 minutes 841 | _logger.Info($"Loaded update interval: {_config.UpdateIntervalMinutes} minutes"); 842 | if (_config.UpdateIntervalMinutes != Constants.DEFAULT_UPDATE_INTERVAL_MINUTES) 843 | { 844 | _logger.Warning($"Update interval incorrect ({_config.UpdateIntervalMinutes}), forcing to {Constants.DEFAULT_UPDATE_INTERVAL_MINUTES} minutes"); 845 | _config.UpdateIntervalMinutes = Constants.DEFAULT_UPDATE_INTERVAL_MINUTES; 846 | _config.Save(); // Make sure this change persists 847 | } 848 | _logger.Info($"Using update interval: {_config.UpdateIntervalMinutes} minutes"); 849 | 850 | // Initialize cancellation token 851 | _cancelTokenSource = new CancellationTokenSource(); 852 | 853 | // Start main service task 854 | _mainTask = Task.Run(() => MainServiceLoop(_cancelTokenSource.Token)); 855 | 856 | _logger.Info("Nova EDR Agent started successfully"); 857 | } 858 | catch (Exception ex) 859 | { 860 | _logger.Error($"Failed to start Nova EDR Agent: {ex.Message}", ex); 861 | Stop(); 862 | } 863 | } 864 | 865 | public void StartConsole(string[] args) 866 | { 867 | _logger.Info("Nova EDR Agent starting in console mode"); 868 | 869 | try 870 | { 871 | // Parse console arguments 872 | if (args.Length > 1) 873 | { 874 | if (args[1].Equals("--update", StringComparison.OrdinalIgnoreCase)) 875 | { 876 | _logger.Info("Running immediate update"); 877 | _config.Load(); 878 | _updateService.PerformUpdate(true).Wait(); 879 | _logger.Info("Update completed"); 880 | return; 881 | } 882 | else if (args[1].Equals("--status", StringComparison.OrdinalIgnoreCase)) 883 | { 884 | _logger.Info("Checking component status"); 885 | _config.Load(); 886 | var status = _statusReporter.CollectStatusInfo(); 887 | Console.WriteLine(JsonSerializer.Serialize(status, new JsonSerializerOptions { WriteIndented = true })); 888 | return; 889 | } 890 | } 891 | 892 | // Start like a service but in console 893 | OnStart(args); 894 | 895 | // Keep console open 896 | Console.WriteLine("Nova EDR Agent running. Press Ctrl+C to stop."); 897 | Console.CancelKeyPress += (sender, e) => 898 | { 899 | e.Cancel = true; 900 | OnStop(); 901 | }; 902 | } 903 | catch (Exception ex) 904 | { 905 | _logger.Error($"Failed to start Nova EDR Agent in console mode: {ex.Message}", ex); 906 | } 907 | } 908 | protected override void OnStop() 909 | { 910 | _logger.Info("Nova EDR Agent stopping"); 911 | 912 | try 913 | { 914 | // Signal the main loop to stop 915 | _cancelTokenSource?.Cancel(); 916 | 917 | // Wait for the main task to complete 918 | if (_mainTask != null) 919 | { 920 | _mainTask.Wait(TimeSpan.FromSeconds(30)); 921 | } 922 | 923 | _statusReporter.ReportStatus("Stopped"); 924 | _logger.Info("Nova EDR Agent stopped successfully"); 925 | } 926 | catch (Exception ex) 927 | { 928 | _logger.Error($"Error during Nova EDR Agent shutdown: {ex.Message}", ex); 929 | } 930 | } 931 | 932 | private async Task MainServiceLoop(CancellationToken cancellationToken) 933 | { 934 | _logger.Info("Main service loop started"); 935 | 936 | try 937 | { 938 | // Report startup 939 | await _statusReporter.ReportStatus("Started"); 940 | 941 | // Initial update check 942 | if (_isFirstRun) 943 | { 944 | _logger.Info("Performing initial update check"); 945 | await _updateService.PerformUpdate(false); 946 | _isFirstRun = false; 947 | 948 | // Explicitly log the update interval for debugging 949 | _logger.Info($"Using update interval of {_config.UpdateIntervalMinutes} minutes"); 950 | } 951 | 952 | // Main service loop 953 | while (!cancellationToken.IsCancellationRequested) 954 | { 955 | try 956 | { 957 | // Check component status 958 | _logger.Info("Checking component status"); 959 | await InternalCheckComponentStatus(); 960 | 961 | // Now check for updates on the configured interval 962 | _logger.Info("Checking for updates"); 963 | await _updateService.PerformUpdate(false); 964 | _logger.Info("Performing MANDATORY Fibratus restart after update check"); 965 | try 966 | { 967 | bool fibratusRunning = false; 968 | 969 | // Check if Fibratus is installed 970 | try 971 | { 972 | using (var service = new ServiceController("Fibratus")) 973 | { 974 | fibratusRunning = (service.Status == ServiceControllerStatus.Running); 975 | 976 | // Stop Fibratus if it's running 977 | if (fibratusRunning) 978 | { 979 | _logger.Info("Stopping Fibratus service for mandatory restart"); 980 | service.Stop(); 981 | service.WaitForStatus(ServiceControllerStatus.Stopped, TimeSpan.FromSeconds(30)); 982 | _logger.Info("Fibratus service stopped successfully"); 983 | } 984 | } 985 | } 986 | catch (Exception ex) 987 | { 988 | _logger.Warning($"Error checking/stopping Fibratus: {ex.Message}"); 989 | fibratusRunning = false; 990 | } 991 | 992 | // Give it a moment to fully stop 993 | await Task.Delay(3000); 994 | 995 | // Only try to start if we found it running before 996 | if (fibratusRunning) 997 | { 998 | try 999 | { 1000 | using (var service = new ServiceController("Fibratus")) 1001 | { 1002 | _logger.Info("Starting Fibratus service after mandatory restart"); 1003 | service.Start(); 1004 | service.WaitForStatus(ServiceControllerStatus.Running, TimeSpan.FromSeconds(30)); 1005 | _logger.Info("Fibratus service started successfully after mandatory restart"); 1006 | } 1007 | } 1008 | catch (Exception ex) 1009 | { 1010 | _logger.Error($"Error starting Fibratus after mandatory restart: {ex.Message}"); 1011 | 1012 | // Emergency restart using command line 1013 | try 1014 | { 1015 | _logger.Info("Attempting emergency restart of Fibratus via command line"); 1016 | Process.Start(new ProcessStartInfo 1017 | { 1018 | FileName = "cmd.exe", 1019 | Arguments = "/c net start Fibratus", 1020 | UseShellExecute = true, 1021 | Verb = "runas", 1022 | CreateNoWindow = true 1023 | }); 1024 | } 1025 | catch (Exception cmdEx) 1026 | { 1027 | _logger.Error($"Emergency restart command failed: {cmdEx.Message}"); 1028 | } 1029 | } 1030 | } 1031 | else 1032 | { 1033 | _logger.Warning("Fibratus was not running or not found, skipping restart"); 1034 | } 1035 | } 1036 | catch (Exception ex) 1037 | { 1038 | _logger.Error($"Critical error during Fibratus restart: {ex.Message}", ex); 1039 | } 1040 | 1041 | if (!cancellationToken.IsCancellationRequested) 1042 | { 1043 | // Always use exactly the configured interval 1044 | int waitMinutes = Constants.DEFAULT_UPDATE_INTERVAL_MINUTES; // Use the constant to ensure consistency 1045 | _logger.Info($"Waiting EXACTLY {waitMinutes} minutes until next update check"); 1046 | await Task.Delay(TimeSpan.FromMinutes(waitMinutes), cancellationToken); 1047 | 1048 | // Report status after update check 1049 | await _statusReporter.ReportStatus("Running"); 1050 | } 1051 | } 1052 | catch (TaskCanceledException) 1053 | { 1054 | // Expected during cancellation, do nothing 1055 | } 1056 | catch (Exception ex) 1057 | { 1058 | _logger.Error($"Error in main service loop: {ex.Message}", ex); 1059 | await Task.Delay(TimeSpan.FromMinutes(1), cancellationToken); 1060 | } 1061 | } 1062 | } 1063 | catch (TaskCanceledException) 1064 | { 1065 | // Expected during cancellation, do nothing 1066 | } 1067 | catch (Exception ex) 1068 | { 1069 | _logger.Error($"Critical error in main service loop: {ex.Message}", ex); 1070 | } 1071 | 1072 | _logger.Info("Main service loop ended"); 1073 | } 1074 | 1075 | private async Task InternalCheckComponentStatus() 1076 | { 1077 | try 1078 | { 1079 | var componentStatus = new Dictionary(); 1080 | 1081 | // Check Fibratus 1082 | try 1083 | { 1084 | var service = new ServiceController("Fibratus"); 1085 | componentStatus["Fibratus"] = service.Status.ToString(); 1086 | } 1087 | catch 1088 | { 1089 | componentStatus["Fibratus"] = "NotInstalled"; 1090 | } 1091 | 1092 | // Check Velociraptor 1093 | try 1094 | { 1095 | var service = new ServiceController("Velociraptor"); 1096 | componentStatus["Velociraptor"] = service.Status.ToString(); 1097 | } 1098 | catch 1099 | { 1100 | componentStatus["Velociraptor"] = "NotInstalled"; 1101 | } 1102 | 1103 | // Check Wazuh - try multiple service names 1104 | bool wazuhFound = false; 1105 | foreach (string name in new[] { "Wazuh", "wazuh-agent", "WazuhSvc" }) 1106 | { 1107 | try 1108 | { 1109 | var service = new ServiceController(name); 1110 | componentStatus["Wazuh"] = service.Status.ToString(); 1111 | wazuhFound = true; 1112 | break; 1113 | } 1114 | catch 1115 | { 1116 | // Service not found, try next name 1117 | } 1118 | } 1119 | 1120 | // If no service found, check for Wazuh installation by directories 1121 | if (!wazuhFound) 1122 | { 1123 | componentStatus["Wazuh"] = "NotInstalled"; 1124 | 1125 | // Check program files directories 1126 | string[] possiblePaths = { 1127 | @"C:\Program Files\Wazuh", 1128 | @"C:\Program Files\Wazuh Agent", 1129 | @"C:\Program Files (x86)\Wazuh", 1130 | @"C:\Program Files (x86)\Wazuh Agent" 1131 | }; 1132 | 1133 | foreach (string path in possiblePaths) 1134 | { 1135 | if (Directory.Exists(path)) 1136 | { 1137 | componentStatus["Wazuh"] = "Unknown"; 1138 | break; 1139 | } 1140 | } 1141 | } 1142 | 1143 | // If any component is not running, attempt to restart it 1144 | foreach (var component in componentStatus) 1145 | { 1146 | if (component.Value != "Running") 1147 | { 1148 | _logger.Warning($"Component {component.Key} is not running (Status: {component.Value}), attempting to restart"); 1149 | bool success = await _updateService.RestartComponent(component.Key); 1150 | if (success) 1151 | { 1152 | _logger.Info($"Successfully restarted {component.Key}"); 1153 | } 1154 | else 1155 | { 1156 | _logger.Error($"Failed to restart {component.Key}"); 1157 | } 1158 | } 1159 | } 1160 | } 1161 | catch (Exception ex) 1162 | { 1163 | _logger.Error($"Error checking component status: {ex.Message}", ex); 1164 | } 1165 | } 1166 | } 1167 | #region Support Classes 1168 | 1169 | public class ConfigManager 1170 | { 1171 | private const string CONFIG_FILE_NAME = "config.json"; 1172 | private const string DEFAULT_LOG_PATH = @"C:\ProgramData\NovaEDR\Logs"; 1173 | private const string DEFAULT_CONFIG_PATH = @"C:\ProgramData\NovaEDR\Config"; 1174 | private const string DEFAULT_TEMP_PATH = @"C:\ProgramData\NovaEDR\Temp"; 1175 | private const string VERSION_FILE = "version.json"; 1176 | 1177 | // Default values 1178 | private const string DEFAULT_CLIENT_ID = Constants.DEFAULT_CLIENT_ID; // Use the default from Constants 1179 | // Using constants from the Constants class 1180 | 1181 | // Configuration properties 1182 | public string ServerUrl { get; private set; } 1183 | public string ClientId { get; private set; } 1184 | public int UpdateIntervalMinutes { get; set; } // Changed to public set to allow modification 1185 | public string LogPath { get; private set; } 1186 | public string ConfigPath { get; private set; } 1187 | public string TempPath { get; private set; } 1188 | public string AgentVersion { get; private set; } 1189 | public string LogLevel { get; private set; } 1190 | public string WazuhGroups { get; private set; } = ""; 1191 | 1192 | // Component versions 1193 | public Dictionary ComponentVersions { get; private set; } 1194 | 1195 | public ConfigManager() 1196 | { 1197 | // Set default values 1198 | ServerUrl = Constants.SERVER_URL; // Always use the hardcoded server URL 1199 | ClientId = DEFAULT_CLIENT_ID; 1200 | UpdateIntervalMinutes = Constants.DEFAULT_UPDATE_INTERVAL_MINUTES; 1201 | LogPath = DEFAULT_LOG_PATH; 1202 | ConfigPath = DEFAULT_CONFIG_PATH; 1203 | TempPath = DEFAULT_TEMP_PATH; 1204 | AgentVersion = GetAssemblyVersion(); 1205 | LogLevel = "Info"; 1206 | ComponentVersions = new Dictionary(); 1207 | 1208 | // Ensure directories exist 1209 | EnsureDirectoriesExist(); 1210 | } 1211 | 1212 | public void Load() 1213 | { 1214 | try 1215 | { 1216 | // Log current process information 1217 | var currentUser = WindowsIdentity.GetCurrent().Name; 1218 | EventLog.WriteEntry("NovaEDR", $"Loading configuration as user: {currentUser}", EventLogEntryType.Information); 1219 | 1220 | // Read from config file 1221 | string configFilePath = Path.Combine(ConfigPath, CONFIG_FILE_NAME); 1222 | if (File.Exists(configFilePath)) 1223 | { 1224 | EventLog.WriteEntry("NovaEDR", "Config file found, reading values", EventLogEntryType.Information); 1225 | 1226 | string jsonContent = File.ReadAllText(configFilePath); 1227 | var configData = JsonSerializer.Deserialize>(jsonContent); 1228 | 1229 | // ServerUrl is hardcoded, ignore any value in config file 1230 | ServerUrl = Constants.SERVER_URL; 1231 | 1232 | if (configData.TryGetValue("ClientId", out var clientId)) 1233 | ClientId = clientId.GetString(); 1234 | 1235 | // Look for either UpdateIntervalMinutes (new) or UpdateIntervalHours (legacy) 1236 | if (configData.TryGetValue("UpdateIntervalMinutes", out var updateIntervalMins)) 1237 | UpdateIntervalMinutes = updateIntervalMins.GetInt32(); 1238 | else if (configData.TryGetValue("UpdateIntervalHours", out var updateIntervalHours)) 1239 | UpdateIntervalMinutes = updateIntervalHours.GetInt32() * 60; // Convert hours to minutes 1240 | 1241 | if (configData.TryGetValue("LogPath", out var logPath)) 1242 | LogPath = logPath.GetString(); 1243 | 1244 | if (configData.TryGetValue("ConfigPath", out var configPath)) 1245 | ConfigPath = configPath.GetString(); 1246 | 1247 | if (configData.TryGetValue("TempPath", out var tempPath)) 1248 | TempPath = tempPath.GetString(); 1249 | 1250 | if (configData.TryGetValue("LogLevel", out var logLevel)) 1251 | LogLevel = logLevel.GetString(); 1252 | 1253 | if (configData.TryGetValue("WazuhGroups", out var wazuhGroups)) 1254 | WazuhGroups = wazuhGroups.GetString(); 1255 | 1256 | // Validate update interval 1257 | if (UpdateIntervalMinutes <= 0 || UpdateIntervalMinutes > 1440) 1258 | { 1259 | UpdateIntervalMinutes = Constants.DEFAULT_UPDATE_INTERVAL_MINUTES; 1260 | EventLog.WriteEntry("NovaEDR", $"Invalid UpdateIntervalMinutes, reset to default: {UpdateIntervalMinutes}", EventLogEntryType.Warning); 1261 | } 1262 | 1263 | EventLog.WriteEntry("NovaEDR", $"Loaded values: ServerUrl={ServerUrl}, ClientId={ClientId}, UpdateIntervalMinutes={UpdateIntervalMinutes}", EventLogEntryType.Information); 1264 | } 1265 | else 1266 | { 1267 | EventLog.WriteEntry("NovaEDR", "Config file not found, creating default config", EventLogEntryType.Warning); 1268 | 1269 | // Create a new configuration file with default values 1270 | var configData = new Dictionary 1271 | { 1272 | { "ServerUrl", ServerUrl }, // This is the hardcoded value 1273 | { "ClientId", ClientId }, 1274 | { "UpdateIntervalMinutes", UpdateIntervalMinutes }, 1275 | { "LogPath", LogPath }, 1276 | { "ConfigPath", ConfigPath }, 1277 | { "TempPath", TempPath }, 1278 | { "LogLevel", LogLevel } 1279 | }; 1280 | 1281 | string json = JsonSerializer.Serialize(configData, new JsonSerializerOptions { WriteIndented = true }); 1282 | File.WriteAllText(configFilePath, json); 1283 | EventLog.WriteEntry("NovaEDR", "Created config file with default values", EventLogEntryType.Information); 1284 | } 1285 | 1286 | // Ensure directories exist after loading config 1287 | EnsureDirectoriesExist(); 1288 | 1289 | // Load component versions from version file 1290 | LoadComponentVersions(); 1291 | 1292 | // Log configuration summary 1293 | EventLog.WriteEntry("NovaEDR", 1294 | $"Configuration loaded: Server={ServerUrl}, Client={ClientId}, " + 1295 | $"UpdateInterval={UpdateIntervalMinutes}m, LogPath={LogPath}", 1296 | EventLogEntryType.Information); 1297 | 1298 | // Create a log file entry 1299 | try 1300 | { 1301 | if (!Directory.Exists(LogPath)) 1302 | Directory.CreateDirectory(LogPath); 1303 | 1304 | File.AppendAllText(Path.Combine(LogPath, "config.log"), 1305 | $"[{DateTime.Now}] Configuration loaded: ServerUrl={ServerUrl}, ClientId={ClientId}, UpdateIntervalMinutes={UpdateIntervalMinutes}\r\n"); 1306 | } 1307 | catch 1308 | { 1309 | // Suppress errors in logging 1310 | } 1311 | } 1312 | catch (Exception ex) 1313 | { 1314 | // Log to event log since logger might not be initialized yet 1315 | EventLog.WriteEntry("NovaEDR", $"Error loading configuration: {ex.Message}", EventLogEntryType.Error); 1316 | File.WriteAllText(Path.Combine(DEFAULT_LOG_PATH, "config_error.log"), $"{DateTime.Now}: {ex.Message}\n{ex.StackTrace}"); 1317 | } 1318 | } 1319 | 1320 | public void Save() 1321 | { 1322 | try 1323 | { 1324 | // Save to config file 1325 | string configFilePath = Path.Combine(ConfigPath, CONFIG_FILE_NAME); 1326 | var configData = new Dictionary 1327 | { 1328 | { "ServerUrl", ServerUrl }, // This will be the hardcoded value 1329 | { "ClientId", ClientId }, 1330 | { "WazuhGroups", WazuhGroups }, 1331 | { "UpdateIntervalMinutes", UpdateIntervalMinutes }, 1332 | { "LogPath", LogPath }, 1333 | { "ConfigPath", ConfigPath }, 1334 | { "TempPath", TempPath }, 1335 | { "LogLevel", LogLevel } 1336 | }; 1337 | 1338 | string json = JsonSerializer.Serialize(configData, new JsonSerializerOptions { WriteIndented = true }); 1339 | File.WriteAllText(configFilePath, json); 1340 | 1341 | // Save component versions to version file 1342 | SaveComponentVersions(); 1343 | } 1344 | catch (Exception ex) 1345 | { 1346 | // Log to event log since logger might not be initialized yet 1347 | EventLog.WriteEntry("NovaEDR", $"Error saving configuration: {ex.Message}", EventLogEntryType.Error); 1348 | } 1349 | } 1350 | 1351 | public void UpdateComponentVersion(string component, string version) 1352 | { 1353 | if (ComponentVersions.ContainsKey(component)) 1354 | { 1355 | ComponentVersions[component] = version; 1356 | } 1357 | else 1358 | { 1359 | ComponentVersions.Add(component, version); 1360 | } 1361 | 1362 | // Save updated versions 1363 | SaveComponentVersions(); 1364 | } 1365 | 1366 | private void LoadComponentVersions() 1367 | { 1368 | var versionFile = Path.Combine(ConfigPath, VERSION_FILE); 1369 | 1370 | if (File.Exists(versionFile)) 1371 | { 1372 | try 1373 | { 1374 | var json = File.ReadAllText(versionFile); 1375 | var versions = JsonSerializer.Deserialize>(json); 1376 | ComponentVersions = versions ?? new Dictionary(); 1377 | } 1378 | catch 1379 | { 1380 | // If loading fails, initialize a new dictionary 1381 | ComponentVersions = new Dictionary(); 1382 | } 1383 | } 1384 | else 1385 | { 1386 | ComponentVersions = new Dictionary(); 1387 | } 1388 | } 1389 | 1390 | private void SaveComponentVersions() 1391 | { 1392 | var versionFile = Path.Combine(ConfigPath, VERSION_FILE); 1393 | 1394 | try 1395 | { 1396 | var json = JsonSerializer.Serialize(ComponentVersions); 1397 | File.WriteAllText(versionFile, json); 1398 | } 1399 | catch (Exception ex) 1400 | { 1401 | // Log to event log since logger might not be initialized yet 1402 | EventLog.WriteEntry("NovaEDR", $"Error saving component versions: {ex.Message}", EventLogEntryType.Error); 1403 | } 1404 | } 1405 | 1406 | private void EnsureDirectoriesExist() 1407 | { 1408 | try 1409 | { 1410 | if (!Directory.Exists(LogPath)) 1411 | Directory.CreateDirectory(LogPath); 1412 | 1413 | if (!Directory.Exists(ConfigPath)) 1414 | Directory.CreateDirectory(ConfigPath); 1415 | 1416 | if (!Directory.Exists(TempPath)) 1417 | Directory.CreateDirectory(TempPath); 1418 | } 1419 | catch (Exception ex) 1420 | { 1421 | // Log to event log since logger might not be initialized yet 1422 | EventLog.WriteEntry("NovaEDR", $"Error creating directories: {ex.Message}", EventLogEntryType.Error); 1423 | } 1424 | } 1425 | 1426 | private string GetAssemblyVersion() 1427 | { 1428 | try 1429 | { 1430 | return Assembly.GetExecutingAssembly().GetName().Version.ToString(); 1431 | } 1432 | catch 1433 | { 1434 | return "1.0.0.0"; 1435 | } 1436 | } 1437 | } 1438 | 1439 | public class Logger 1440 | { 1441 | private readonly string _source; 1442 | private readonly string _log; 1443 | private readonly string _logFilePath; 1444 | 1445 | public Logger(string source, string log) 1446 | { 1447 | _source = source; 1448 | _log = log; 1449 | 1450 | // Ensure event source exists 1451 | if (!EventLog.SourceExists(source)) 1452 | { 1453 | try 1454 | { 1455 | EventLog.CreateEventSource(source, log); 1456 | } 1457 | catch 1458 | { 1459 | // May fail if not running as admin, but we'll still use file logging 1460 | } 1461 | } 1462 | 1463 | // Set up file logging 1464 | var logDir = Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.CommonApplicationData), "NovaEDR", "Logs"); 1465 | if (!Directory.Exists(logDir)) 1466 | Directory.CreateDirectory(logDir); 1467 | 1468 | _logFilePath = Path.Combine(logDir, $"NovaEDRAgent_{DateTime.Now:yyyyMMdd}.log"); 1469 | } 1470 | 1471 | public void Info(string message) 1472 | { 1473 | WriteToEventLog(message, EventLogEntryType.Information); 1474 | WriteToFile("INFO", message); 1475 | } 1476 | 1477 | public void Warning(string message) 1478 | { 1479 | WriteToEventLog(message, EventLogEntryType.Warning); 1480 | WriteToFile("WARNING", message); 1481 | } 1482 | 1483 | public void Error(string message, Exception ex = null) 1484 | { 1485 | string fullMessage = message; 1486 | 1487 | if (ex != null) 1488 | { 1489 | fullMessage += $"\nException: {ex.Message}\nStack Trace: {ex.StackTrace}"; 1490 | if (ex.InnerException != null) 1491 | { 1492 | fullMessage += $"\nInner Exception: {ex.InnerException.Message}"; 1493 | } 1494 | } 1495 | 1496 | WriteToEventLog(fullMessage, EventLogEntryType.Error); 1497 | WriteToFile("ERROR", fullMessage); 1498 | } 1499 | 1500 | private void WriteToEventLog(string message, EventLogEntryType entryType) 1501 | { 1502 | try 1503 | { 1504 | EventLog.WriteEntry(_source, message, entryType); 1505 | } 1506 | catch 1507 | { 1508 | // Fail silently if event log writing fails 1509 | } 1510 | } 1511 | 1512 | private void WriteToFile(string level, string message) 1513 | { 1514 | try 1515 | { 1516 | using (var writer = new StreamWriter(_logFilePath, true)) 1517 | { 1518 | writer.WriteLine($"[{DateTime.Now:yyyy-MM-dd HH:mm:ss}] [{level}] {message}"); 1519 | } 1520 | } 1521 | catch 1522 | { 1523 | // Fail silently if file writing fails 1524 | } 1525 | } 1526 | } 1527 | 1528 | public class UpdateService 1529 | { 1530 | private readonly HttpClient _httpClient; 1531 | private readonly Logger _logger; 1532 | private readonly ConfigManager _config; 1533 | 1534 | // Component handling 1535 | private readonly Dictionary>> _componentUpdaters; 1536 | 1537 | // Fibratus specific rule files to remove 1538 | private readonly string[] _fibratusRulesToRemove = new string[] 1539 | { 1540 | "defense_evasion_unsigned_dll_injection_via_remote_thread.yml", 1541 | "defense_evasion_potential_process_injection_via_tainted_memory_section.yml", 1542 | "credential_access_potential_sam_hive_dumping.yml", 1543 | "defense_evasion_dotnet_assembly_loaded_by_unmanaged_process.yml", 1544 | "defense_evasion_hidden_registry_key_creation.yml" 1545 | }; 1546 | 1547 | public UpdateService(HttpClient httpClient, Logger logger, ConfigManager config) 1548 | { 1549 | _httpClient = httpClient; 1550 | _logger = logger; 1551 | _config = config; 1552 | 1553 | // Initialize component updaters 1554 | _componentUpdaters = new Dictionary>> 1555 | { 1556 | { "Fibratus", UpdateFibratus }, 1557 | { "Velociraptor", UpdateVelociraptor }, 1558 | { "Wazuh", UpdateWazuh } 1559 | }; 1560 | } 1561 | 1562 | public async Task PerformUpdate(bool forceUpdate) 1563 | { 1564 | _logger.Info($"Starting update check (ForceUpdate: {forceUpdate})"); 1565 | bool success = true; 1566 | 1567 | try 1568 | { 1569 | // Check for agent updates first 1570 | if (await CheckForAgentUpdate(forceUpdate)) 1571 | { 1572 | _logger.Info("Agent update available, restarting agent"); 1573 | // The update process will restart the service, so we can return here 1574 | return true; 1575 | } 1576 | 1577 | // Update components one by one 1578 | foreach (var componentUpdater in _componentUpdaters) 1579 | { 1580 | try 1581 | { 1582 | _logger.Info($"Checking for {componentUpdater.Key} updates"); 1583 | if (!await componentUpdater.Value()) 1584 | { 1585 | _logger.Warning($"Update check for {componentUpdater.Key} failed"); 1586 | success = false; 1587 | } 1588 | } 1589 | catch (Exception ex) 1590 | { 1591 | _logger.Error($"Error updating {componentUpdater.Key}: {ex.Message}", ex); 1592 | success = false; 1593 | } 1594 | } 1595 | 1596 | _logger.Info($"Update check completed. Success: {success}"); 1597 | return success; 1598 | } 1599 | catch (Exception ex) 1600 | { 1601 | _logger.Error($"Error during update check: {ex.Message}", ex); 1602 | return false; 1603 | } 1604 | } 1605 | 1606 | public async Task RestartComponent(string componentName) 1607 | { 1608 | _logger.Info($"Attempting to restart {componentName}"); 1609 | 1610 | try 1611 | { 1612 | // More aggressive restart approach 1613 | switch (componentName.ToLower()) 1614 | { 1615 | case "fibratus": 1616 | return await ForceRestartService("Fibratus"); 1617 | 1618 | case "velociraptor": 1619 | return await ForceRestartService("Velociraptor"); 1620 | 1621 | case "wazuh": 1622 | // Try to find the correct Wazuh service name 1623 | string wazuhServiceName = null; 1624 | 1625 | // Try the various possible service names for Wazuh 1626 | string[] possibleNames = { "Wazuh", "wazuh-agent", "WazuhSvc" }; 1627 | foreach (string name in possibleNames) 1628 | { 1629 | if (IsServiceInstalled(name)) 1630 | { 1631 | wazuhServiceName = name; 1632 | break; 1633 | } 1634 | } 1635 | 1636 | if (!string.IsNullOrEmpty(wazuhServiceName)) 1637 | { 1638 | return await ForceRestartService(wazuhServiceName); 1639 | } 1640 | else 1641 | { 1642 | // Try each possible name 1643 | foreach (string name in possibleNames) 1644 | { 1645 | if (await TryStartService(name)) 1646 | { 1647 | return true; 1648 | } 1649 | } 1650 | _logger.Warning("Could not find Wazuh service to restart"); 1651 | return false; 1652 | } 1653 | 1654 | default: 1655 | _logger.Warning($"Unknown component name: {componentName}"); 1656 | return false; 1657 | } 1658 | } 1659 | catch (Exception ex) 1660 | { 1661 | _logger.Error($"Error restarting {componentName}: {ex.Message}", ex); 1662 | 1663 | // Last resort - use net stop/start commands 1664 | try 1665 | { 1666 | _logger.Info($"Attempting to restart {componentName} with net commands"); 1667 | 1668 | // For Wazuh, try to find the correct service name 1669 | string actualServiceName = componentName; 1670 | if (componentName.ToLower() == "wazuh") 1671 | { 1672 | // Try the various possible service names for Wazuh 1673 | string[] possibleNames = { "Wazuh", "wazuh-agent", "WazuhSvc" }; 1674 | foreach (string name in possibleNames) 1675 | { 1676 | if (IsServiceInstalled(name)) 1677 | { 1678 | actualServiceName = name; 1679 | break; 1680 | } 1681 | } 1682 | } 1683 | 1684 | Process.Start(new ProcessStartInfo 1685 | { 1686 | FileName = "net", 1687 | Arguments = $"stop {actualServiceName}", 1688 | UseShellExecute = true, 1689 | Verb = "runas" 1690 | }).WaitForExit(); 1691 | 1692 | Thread.Sleep(2000); // Wait 2 seconds between stop and start 1693 | 1694 | Process.Start(new ProcessStartInfo 1695 | { 1696 | FileName = "net", 1697 | Arguments = $"start {actualServiceName}", 1698 | UseShellExecute = true, 1699 | Verb = "runas" 1700 | }).WaitForExit(); 1701 | 1702 | return true; 1703 | } 1704 | catch (Exception netEx) 1705 | { 1706 | _logger.Error($"Error restarting {componentName} with net commands: {netEx.Message}"); 1707 | return false; 1708 | } 1709 | } 1710 | } 1711 | 1712 | private async Task ForceRestartService(string serviceName) 1713 | { 1714 | _logger.Info($"Force restarting service {serviceName}"); 1715 | 1716 | try 1717 | { 1718 | // First try to stop the service properly 1719 | await StopService(serviceName); 1720 | 1721 | // Wait a bit longer for the service to fully stop 1722 | Thread.Sleep(3000); // Increased from 2000ms to 3000ms 1723 | 1724 | // Check if the service is truly stopped before attempting to start 1725 | using (var checkService = new ServiceController(serviceName)) 1726 | { 1727 | checkService.Refresh(); 1728 | if (checkService.Status != ServiceControllerStatus.Stopped) 1729 | { 1730 | _logger.Info($"Service {serviceName} not fully stopped yet. Using SC command to force stop..."); 1731 | // Use SC command to force stop the service 1732 | Process.Start(new ProcessStartInfo 1733 | { 1734 | FileName = "sc.exe", 1735 | Arguments = $"stop {serviceName}", 1736 | UseShellExecute = true, 1737 | Verb = "runas", 1738 | CreateNoWindow = true 1739 | }).WaitForExit(); 1740 | 1741 | Thread.Sleep(3000); // Wait again after force stop 1742 | } 1743 | } 1744 | 1745 | // Now try to start the service 1746 | await StartService(serviceName); 1747 | 1748 | // Add more verification attempts 1749 | int verificationAttempts = 0; 1750 | bool serviceRunning = false; 1751 | 1752 | while (!serviceRunning && verificationAttempts < 3) 1753 | { 1754 | verificationAttempts++; 1755 | 1756 | // Verify the service is running 1757 | using (var service = new ServiceController(serviceName)) 1758 | { 1759 | service.Refresh(); 1760 | if (service.Status == ServiceControllerStatus.Running) 1761 | { 1762 | serviceRunning = true; 1763 | _logger.Info($"Successfully restarted {serviceName}"); 1764 | } 1765 | else 1766 | { 1767 | _logger.Warning($"Service {serviceName} is in {service.Status} state after restart attempt #{verificationAttempts}"); 1768 | 1769 | if (verificationAttempts < 3) 1770 | { 1771 | _logger.Info($"Waiting and trying again..."); 1772 | Thread.Sleep(2000); 1773 | 1774 | if (service.Status == ServiceControllerStatus.Stopped) 1775 | { 1776 | // Try to start again 1777 | try 1778 | { 1779 | service.Start(); 1780 | await Task.Run(() => service.WaitForStatus(ServiceControllerStatus.Running, TimeSpan.FromSeconds(30))); 1781 | } 1782 | catch (Exception ex) 1783 | { 1784 | _logger.Error($"Error starting service on retry #{verificationAttempts}: {ex.Message}"); 1785 | } 1786 | } 1787 | } 1788 | } 1789 | } 1790 | } 1791 | 1792 | if (!serviceRunning) 1793 | { 1794 | // Last resort - use net start command 1795 | _logger.Info($"Final attempt to restart {serviceName} with net start command"); 1796 | Process.Start(new ProcessStartInfo 1797 | { 1798 | FileName = "net", 1799 | Arguments = $"start {serviceName}", 1800 | UseShellExecute = true, 1801 | Verb = "runas", 1802 | CreateNoWindow = true 1803 | }).WaitForExit(); 1804 | 1805 | // Final check 1806 | using (var service = new ServiceController(serviceName)) 1807 | { 1808 | service.Refresh(); 1809 | return service.Status == ServiceControllerStatus.Running; 1810 | } 1811 | } 1812 | 1813 | return serviceRunning; 1814 | } 1815 | catch (Exception ex) 1816 | { 1817 | _logger.Error($"Error in ForceRestartService for {serviceName}: {ex.Message}", ex); 1818 | 1819 | // Emergency restart attempt using command line 1820 | try 1821 | { 1822 | _logger.Info($"Emergency restart attempt for {serviceName}"); 1823 | Process.Start(new ProcessStartInfo 1824 | { 1825 | FileName = "cmd.exe", 1826 | Arguments = $"/c net stop {serviceName} && timeout /t 5 && net start {serviceName}", 1827 | UseShellExecute = true, 1828 | Verb = "runas" 1829 | }); 1830 | return true; // Assume it worked since we can't verify in this emergency path 1831 | } 1832 | catch 1833 | { 1834 | return false; 1835 | } 1836 | } 1837 | } 1838 | 1839 | private async Task CheckForAgentUpdate(bool forceUpdate) 1840 | { 1841 | try 1842 | { 1843 | // Check for agent updates from server 1844 | string updateUrl = GetComponentUrl("NovaEDRAgent.json"); 1845 | 1846 | var response = await _httpClient.GetAsync(updateUrl); 1847 | if (!response.IsSuccessStatusCode) 1848 | { 1849 | // Agent update info not available, just log and continue 1850 | _logger.Info($"Agent update check skipped - update info not available (Status code: {response.StatusCode})"); 1851 | return false; 1852 | } 1853 | 1854 | var content = await response.Content.ReadAsStringAsync(); 1855 | var updateInfo = JsonSerializer.Deserialize>(content); 1856 | 1857 | if (!updateInfo.TryGetValue("version", out var latestVersion)) 1858 | { 1859 | _logger.Warning("Agent update check failed: Invalid update format"); 1860 | return false; 1861 | } 1862 | 1863 | // Compare versions 1864 | if (!forceUpdate && Version.Parse(latestVersion) <= Version.Parse(_config.AgentVersion)) 1865 | { 1866 | _logger.Info($"Agent is up to date (Current: {_config.AgentVersion}, Latest: {latestVersion})"); 1867 | return false; 1868 | } 1869 | 1870 | _logger.Info($"Agent update available: {latestVersion} (Current: {_config.AgentVersion})"); 1871 | 1872 | // Download and apply update 1873 | if (!updateInfo.TryGetValue("url", out var updateFileUrl)) 1874 | { 1875 | _logger.Warning("Agent update check failed: Missing update URL"); 1876 | return false; 1877 | } 1878 | 1879 | string downloadUrl = GetComponentUrl(updateFileUrl); 1880 | string updateFilePath = Path.Combine(_config.TempPath, "NovaEDRAgent_Update.exe"); 1881 | 1882 | // Download update file 1883 | var updateResponse = await _httpClient.GetAsync(downloadUrl); 1884 | if (!updateResponse.IsSuccessStatusCode) 1885 | { 1886 | _logger.Warning($"Failed to download agent update. Status code: {updateResponse.StatusCode}"); 1887 | return false; 1888 | } 1889 | 1890 | using (var fileStream = new FileStream(updateFilePath, FileMode.Create, FileAccess.Write)) 1891 | { 1892 | await updateResponse.Content.CopyToAsync(fileStream); 1893 | } 1894 | 1895 | _logger.Info($"Downloaded agent update to {updateFilePath}"); 1896 | 1897 | // Verify update file 1898 | if (updateInfo.TryGetValue("hash", out var expectedHash)) 1899 | { 1900 | using (var sha256 = SHA256.Create()) 1901 | using (var stream = File.OpenRead(updateFilePath)) 1902 | { 1903 | var hash = BitConverter.ToString(sha256.ComputeHash(stream)).Replace("-", "").ToLowerInvariant(); 1904 | if (hash != expectedHash.ToLowerInvariant()) 1905 | { 1906 | _logger.Warning("Agent update file hash verification failed"); 1907 | File.Delete(updateFilePath); 1908 | return false; 1909 | } 1910 | } 1911 | } 1912 | 1913 | // Execute update process 1914 | _logger.Info("Starting agent update process"); 1915 | 1916 | // Create a batch file to: 1917 | // 1. Stop the service 1918 | // 2. Replace the executable 1919 | // 3. Start the service 1920 | string batchFilePath = Path.Combine(_config.TempPath, "AgentUpdate.bat"); 1921 | string servicePath = Process.GetCurrentProcess().MainModule.FileName; 1922 | 1923 | string batchContent = $@"@echo off 1924 | echo Updating Nova EDR Agent... 1925 | timeout /t 3 /nobreak > NUL 1926 | net stop NovaEDRAgent 1927 | timeout /t 2 /nobreak > NUL 1928 | copy /Y ""{updateFilePath}"" ""{servicePath}"" 1929 | timeout /t 1 /nobreak > NUL 1930 | net start NovaEDRAgent 1931 | timeout /t 1 /nobreak > NUL 1932 | del ""{updateFilePath}"" 1933 | del ""%~f0"" 1934 | "; 1935 | 1936 | File.WriteAllText(batchFilePath, batchContent); 1937 | 1938 | // Start the batch file process 1939 | Process.Start(new ProcessStartInfo 1940 | { 1941 | FileName = "cmd.exe", 1942 | Arguments = $"/c {batchFilePath}", 1943 | CreateNoWindow = true, 1944 | UseShellExecute = true, 1945 | WindowStyle = ProcessWindowStyle.Hidden 1946 | }); 1947 | 1948 | _logger.Info("Agent update process started"); 1949 | return true; 1950 | } 1951 | catch (Exception ex) 1952 | { 1953 | _logger.Error($"Error checking for agent updates: {ex.Message}", ex); 1954 | return false; 1955 | } 1956 | } 1957 | 1958 | private async Task UpdateFibratus() 1959 | { 1960 | _logger.Info("Checking for Fibratus updates"); 1961 | 1962 | try 1963 | { 1964 | // Check if Fibratus is installed 1965 | if (!IsServiceInstalled("Fibratus")) 1966 | { 1967 | _logger.Info("Fibratus is not installed, installing..."); 1968 | return await InstallFibratus(); 1969 | } 1970 | 1971 | // Check for rule updates 1972 | string rulesVersionUrl = GetComponentUrl("Custom-Rules.zip.version"); 1973 | 1974 | var response = await _httpClient.GetAsync(rulesVersionUrl); 1975 | if (!response.IsSuccessStatusCode) 1976 | { 1977 | _logger.Warning($"Failed to check for Fibratus rule updates. Status code: {response.StatusCode}"); 1978 | return false; 1979 | } 1980 | 1981 | var latestVersion = await response.Content.ReadAsStringAsync(); 1982 | latestVersion = latestVersion.Trim(); 1983 | 1984 | // Get current installed version 1985 | string currentVersion = _config.ComponentVersions.ContainsKey("FibratusRules") 1986 | ? _config.ComponentVersions["FibratusRules"] 1987 | : "0"; 1988 | 1989 | // Check if update is needed 1990 | if (latestVersion == currentVersion) 1991 | { 1992 | _logger.Info($"Fibratus rules are up to date (version: {currentVersion})"); 1993 | return true; 1994 | } 1995 | 1996 | _logger.Info($"Fibratus rule update available: {latestVersion} (Current: {currentVersion})"); 1997 | 1998 | // Download new rules 1999 | string rulesUrl = GetComponentUrl("Custom-Rules.zip"); 2000 | string rulesPath = Path.Combine(_config.TempPath, "Custom-Rules.zip"); 2001 | string extractPath = Path.Combine(_config.TempPath, "FibratusRules"); 2002 | 2003 | var rulesResponse = await _httpClient.GetAsync(rulesUrl); 2004 | if (!rulesResponse.IsSuccessStatusCode) 2005 | { 2006 | _logger.Warning($"Failed to download Fibratus rules. Status code: {rulesResponse.StatusCode}"); 2007 | return false; 2008 | } 2009 | 2010 | using (var fileStream = new FileStream(rulesPath, FileMode.Create, FileAccess.Write)) 2011 | { 2012 | await rulesResponse.Content.CopyToAsync(fileStream); 2013 | } 2014 | 2015 | _logger.Info($"Downloaded Fibratus rules to {rulesPath}"); 2016 | 2017 | // Clean up extraction directory if it exists 2018 | if (Directory.Exists(extractPath)) 2019 | Directory.Delete(extractPath, true); 2020 | 2021 | Directory.CreateDirectory(extractPath); 2022 | 2023 | // Extract rules 2024 | ZipFile.ExtractToDirectory(rulesPath, extractPath); 2025 | _logger.Info($"Extracted rules to {extractPath}"); 2026 | 2027 | // Stop Fibratus service before updating rules 2028 | _logger.Info("Stopping Fibratus service to update rules..."); 2029 | await StopService("Fibratus"); 2030 | 2031 | _logger.Info("Preparing to apply rule updates while preserving default rules"); 2032 | 2033 | // CRITICAL - DO NOT DELETE OR RECREATE THE RULES DIRECTORY 2034 | string fibratusRulesPath = @"C:\Program Files\Fibratus\Rules"; 2035 | 2036 | // Create directories if they don't exist (without deleting anything) 2037 | if (!Directory.Exists(fibratusRulesPath)) 2038 | { 2039 | Directory.CreateDirectory(fibratusRulesPath); 2040 | _logger.Info("Created Fibratus rules directory"); 2041 | } 2042 | 2043 | // Step 1: Remove specific rule files that should be excluded 2044 | bool rulesChanged = false; 2045 | foreach (var ruleToRemove in _fibratusRulesToRemove) 2046 | { 2047 | string rulePath = Path.Combine(fibratusRulesPath, ruleToRemove); 2048 | if (File.Exists(rulePath)) 2049 | { 2050 | _logger.Info($"Removing excluded rule: {ruleToRemove}"); 2051 | File.Delete(rulePath); 2052 | rulesChanged = true; 2053 | } 2054 | } 2055 | 2056 | // Step 2: Apply custom rule files - only replace existing or add new (NO MACROS HANDLING) 2057 | int newRuleCount = 0; 2058 | int updatedRuleCount = 0; 2059 | 2060 | var customRuleFiles = Directory.GetFiles(extractPath, "*.yml", SearchOption.AllDirectories) 2061 | .Where(f => !Path.GetFileName(f).Equals("macros.yml", StringComparison.OrdinalIgnoreCase)) 2062 | .ToList(); 2063 | 2064 | _logger.Info($"Found {customRuleFiles.Count} custom rule files to process (excluding macros.yml)"); 2065 | 2066 | foreach (var customRulePath in customRuleFiles) 2067 | { 2068 | string fileName = Path.GetFileName(customRulePath); 2069 | string destPath = Path.Combine(fibratusRulesPath, fileName); 2070 | 2071 | bool fileExists = File.Exists(destPath); 2072 | File.Copy(customRulePath, destPath, true); // Overwrite if exists 2073 | 2074 | if (fileExists) 2075 | { 2076 | _logger.Info($"Updated rule: {fileName}"); 2077 | updatedRuleCount++; 2078 | } 2079 | else 2080 | { 2081 | _logger.Info($"Added new rule: {fileName}"); 2082 | newRuleCount++; 2083 | } 2084 | rulesChanged = true; 2085 | } 2086 | 2087 | _logger.Info($"Rule processing complete: {newRuleCount} new rules added, {updatedRuleCount} existing rules updated"); 2088 | 2089 | // Always restart Fibratus after rule updates 2090 | _logger.Info("Restarting Fibratus service after rule updates..."); 2091 | await StartService("Fibratus"); 2092 | 2093 | if (rulesChanged) 2094 | { 2095 | _logger.Info("Detected rule changes - Fibratus restart was required"); 2096 | } 2097 | else 2098 | { 2099 | _logger.Info("No rule changes detected, but restarted Fibratus as a precaution"); 2100 | } 2101 | 2102 | // Update version information 2103 | _config.UpdateComponentVersion("FibratusRules", latestVersion); 2104 | 2105 | // Cleanup 2106 | File.Delete(rulesPath); 2107 | Directory.Delete(extractPath, true); 2108 | 2109 | _logger.Info($"Fibratus rules updated to version {latestVersion}"); 2110 | return true; 2111 | } 2112 | catch (Exception ex) 2113 | { 2114 | _logger.Error($"Error updating Fibratus: {ex.Message}", ex); 2115 | 2116 | // Try to restart the service if it was stopped 2117 | try 2118 | { 2119 | await StartService("Fibratus"); 2120 | } 2121 | catch 2122 | { 2123 | // Ignore errors during restart attempt 2124 | } 2125 | 2126 | return false; 2127 | } 2128 | } 2129 | 2130 | private async Task InstallFibratus() 2131 | { 2132 | try 2133 | { 2134 | _logger.Info("Installing Fibratus"); 2135 | 2136 | // Download Fibratus MSI 2137 | string msiUrl = GetComponentUrl("Fibratus.msi"); 2138 | string msiPath = Path.Combine(_config.TempPath, "Fibratus.msi"); 2139 | 2140 | var response = await _httpClient.GetAsync(msiUrl); 2141 | if (!response.IsSuccessStatusCode) 2142 | { 2143 | _logger.Warning($"Failed to download Fibratus MSI. Status code: {response.StatusCode}"); 2144 | return false; 2145 | } 2146 | 2147 | using (var fileStream = new FileStream(msiPath, FileMode.Create, FileAccess.Write)) 2148 | { 2149 | await response.Content.CopyToAsync(fileStream); 2150 | } 2151 | 2152 | _logger.Info($"Downloaded Fibratus MSI to {msiPath}"); 2153 | 2154 | // Install Fibratus 2155 | var process = new Process 2156 | { 2157 | StartInfo = new ProcessStartInfo 2158 | { 2159 | FileName = "msiexec.exe", 2160 | Arguments = $"/i \"{msiPath}\" /quiet /norestart", 2161 | CreateNoWindow = true, 2162 | UseShellExecute = false 2163 | } 2164 | }; 2165 | 2166 | process.Start(); 2167 | await Task.Run(() => process.WaitForExit()); 2168 | 2169 | if (process.ExitCode != 0 && process.ExitCode != 3010) 2170 | { 2171 | _logger.Warning($"Fibratus installation failed with exit code: {process.ExitCode}"); 2172 | return false; 2173 | } 2174 | 2175 | _logger.Info("Fibratus installed successfully"); 2176 | 2177 | // After installation, let's apply our custom rules without calling UpdateFibratus again 2178 | // (which would cause recursion and potentially wipe out official rules) 2179 | 2180 | try 2181 | { 2182 | // Download our custom rules 2183 | string rulesUrl = GetComponentUrl("Custom-Rules.zip"); 2184 | string rulesPath = Path.Combine(_config.TempPath, "Custom-Rules.zip"); 2185 | string extractPath = Path.Combine(_config.TempPath, "FibratusRules"); 2186 | 2187 | _logger.Info("Downloading custom rules for initial setup"); 2188 | var rulesResponse = await _httpClient.GetAsync(rulesUrl); 2189 | if (rulesResponse.IsSuccessStatusCode) 2190 | { 2191 | using (var fileStream = new FileStream(rulesPath, FileMode.Create, FileAccess.Write)) 2192 | { 2193 | await rulesResponse.Content.CopyToAsync(fileStream); 2194 | } 2195 | 2196 | _logger.Info($"Downloaded initial custom rules to {rulesPath}"); 2197 | 2198 | // Extract rules 2199 | if (Directory.Exists(extractPath)) 2200 | Directory.Delete(extractPath, true); 2201 | 2202 | Directory.CreateDirectory(extractPath); 2203 | ZipFile.ExtractToDirectory(rulesPath, extractPath); 2204 | 2205 | // Apply the rules (preserving official rules that come with Fibratus) 2206 | string fibratusRulesPath = @"C:\Program Files\Fibratus\Rules"; 2207 | 2208 | // First, remove specific rule files that should be excluded 2209 | foreach (var ruleToRemove in _fibratusRulesToRemove) 2210 | { 2211 | string rulePath = Path.Combine(fibratusRulesPath, ruleToRemove); 2212 | if (File.Exists(rulePath)) 2213 | { 2214 | File.Delete(rulePath); 2215 | _logger.Info($"Removed excluded rule: {ruleToRemove}"); 2216 | } 2217 | } 2218 | 2219 | // Apply our custom rules as needed (NO MACROS HANDLING) 2220 | var allYmlFiles = Directory.GetFiles(extractPath, "*.yml", SearchOption.AllDirectories); 2221 | _logger.Info($"Found {allYmlFiles.Length} YML files in initial custom rules package"); 2222 | 2223 | // Process all custom rule files (excluding macros.yml) 2224 | foreach (var ymlFile in allYmlFiles) 2225 | { 2226 | // Skip the macro file completely - no longer handling it 2227 | if (Path.GetFileName(ymlFile).Equals("macros.yml", StringComparison.OrdinalIgnoreCase)) 2228 | { 2229 | _logger.Info("Skipping macros.yml - no longer overwriting default macros"); 2230 | continue; 2231 | } 2232 | 2233 | // Copy the file to the rules directory (will overwrite existing files with same name) 2234 | string destPath = Path.Combine(fibratusRulesPath, Path.GetFileName(ymlFile)); 2235 | bool fileExists = File.Exists(destPath); 2236 | File.Copy(ymlFile, destPath, true); 2237 | 2238 | if (fileExists) 2239 | { 2240 | _logger.Info($"Updated rule: {Path.GetFileName(ymlFile)}"); 2241 | } 2242 | else 2243 | { 2244 | _logger.Info($"Added new rule: {Path.GetFileName(ymlFile)}"); 2245 | } 2246 | } 2247 | 2248 | // Update version information from the server 2249 | string rulesVersionUrl = GetComponentUrl("Custom-Rules.zip.version"); 2250 | var versionResponse = await _httpClient.GetAsync(rulesVersionUrl); 2251 | if (versionResponse.IsSuccessStatusCode) 2252 | { 2253 | var latestVersion = await versionResponse.Content.ReadAsStringAsync(); 2254 | latestVersion = latestVersion.Trim(); 2255 | _config.UpdateComponentVersion("FibratusRules", latestVersion); 2256 | _logger.Info($"Updated Fibratus rules version to {latestVersion}"); 2257 | } 2258 | 2259 | // Cleanup 2260 | File.Delete(rulesPath); 2261 | Directory.Delete(extractPath, true); 2262 | } 2263 | else 2264 | { 2265 | _logger.Warning($"Failed to download initial custom rules. Status: {rulesResponse.StatusCode}"); 2266 | } 2267 | } 2268 | catch (Exception ex) 2269 | { 2270 | _logger.Error($"Error applying initial custom rules: {ex.Message}", ex); 2271 | } 2272 | 2273 | // Clean up 2274 | File.Delete(msiPath); 2275 | 2276 | // ADDED: Force restart Fibratus after initial installation 2277 | _logger.Info("Forcing Fibratus restart after initial installation"); 2278 | try 2279 | { 2280 | // First stop the service if it's running 2281 | await StopService("Fibratus"); 2282 | 2283 | // Wait a moment to ensure complete shutdown 2284 | await Task.Delay(3000); 2285 | 2286 | // Start the service again 2287 | await StartService("Fibratus"); 2288 | 2289 | // Verify the service started 2290 | using (var service = new ServiceController("Fibratus")) 2291 | { 2292 | service.Refresh(); 2293 | if (service.Status == ServiceControllerStatus.Running) 2294 | { 2295 | _logger.Info("Successfully restarted Fibratus after initial installation"); 2296 | } 2297 | else 2298 | { 2299 | _logger.Warning($"Fibratus status after restart attempt: {service.Status}"); 2300 | // Emergency restart using command line 2301 | _logger.Info("Attempting emergency restart of Fibratus via command line"); 2302 | Process.Start(new ProcessStartInfo 2303 | { 2304 | FileName = "cmd.exe", 2305 | Arguments = "/c net start Fibratus", 2306 | UseShellExecute = true, 2307 | Verb = "runas", 2308 | CreateNoWindow = true 2309 | }); 2310 | } 2311 | } 2312 | } 2313 | catch (Exception ex) 2314 | { 2315 | _logger.Error($"Error restarting Fibratus after initial installation: {ex.Message}", ex); 2316 | // Still continue with return true so the installation process proceeds 2317 | } 2318 | 2319 | return true; 2320 | } 2321 | catch (Exception ex) 2322 | { 2323 | _logger.Error($"Error installing Fibratus: {ex.Message}", ex); 2324 | return false; 2325 | } 2326 | } 2327 | 2328 | private async Task UpdateVelociraptor() 2329 | { 2330 | _logger.Info("Checking for Velociraptor updates"); 2331 | 2332 | try 2333 | { 2334 | // Velociraptor doesn't have regular updates like Fibratus rules 2335 | // For now, just check if it's installed and install if needed 2336 | if (!IsServiceInstalled("Velociraptor")) 2337 | { 2338 | _logger.Info("Velociraptor is not installed, installing..."); 2339 | return await InstallVelociraptor(); 2340 | } 2341 | 2342 | _logger.Info("Velociraptor is already installed and running"); 2343 | return true; 2344 | } 2345 | catch (Exception ex) 2346 | { 2347 | _logger.Error($"Error updating Velociraptor: {ex.Message}", ex); 2348 | return false; 2349 | } 2350 | } 2351 | 2352 | private async Task InstallVelociraptor() 2353 | { 2354 | try 2355 | { 2356 | _logger.Info("Installing Velociraptor"); 2357 | 2358 | // Download Velociraptor MSI (now renamed to Velo.msi) 2359 | string msiUrl = GetComponentUrl("Velo.msi"); 2360 | string msiPath = Path.Combine(_config.TempPath, "Velo.msi"); 2361 | 2362 | var response = await _httpClient.GetAsync(msiUrl); 2363 | if (!response.IsSuccessStatusCode) 2364 | { 2365 | _logger.Warning($"Failed to download Velociraptor MSI. Status code: {response.StatusCode}"); 2366 | return false; 2367 | } 2368 | 2369 | using (var fileStream = new FileStream(msiPath, FileMode.Create, FileAccess.Write)) 2370 | { 2371 | await response.Content.CopyToAsync(fileStream); 2372 | } 2373 | 2374 | _logger.Info($"Downloaded Velociraptor MSI to {msiPath}"); 2375 | 2376 | // Install Velociraptor 2377 | var process = new Process 2378 | { 2379 | StartInfo = new ProcessStartInfo 2380 | { 2381 | FileName = "msiexec.exe", 2382 | Arguments = $"/i \"{msiPath}\" /quiet /norestart", 2383 | CreateNoWindow = true, 2384 | UseShellExecute = false 2385 | } 2386 | }; 2387 | 2388 | process.Start(); 2389 | await Task.Run(() => process.WaitForExit()); 2390 | 2391 | if (process.ExitCode != 0 && process.ExitCode != 3010) 2392 | { 2393 | _logger.Warning($"Velociraptor installation failed with exit code: {process.ExitCode}"); 2394 | return false; 2395 | } 2396 | 2397 | _logger.Info("Velociraptor installed successfully"); 2398 | 2399 | // Clean up 2400 | File.Delete(msiPath); 2401 | 2402 | return true; 2403 | } 2404 | catch (Exception ex) 2405 | { 2406 | _logger.Error($"Error installing Velociraptor: {ex.Message}", ex); 2407 | return false; 2408 | } 2409 | } 2410 | 2411 | private async Task UpdateWazuh() 2412 | { 2413 | _logger.Info("Checking for Wazuh updates"); 2414 | 2415 | try 2416 | { 2417 | // First check if Wazuh PowerShell script exists 2418 | string scriptUrl = GetComponentUrl("Wazuh.ps1"); 2419 | 2420 | try 2421 | { 2422 | var response = await _httpClient.GetAsync(scriptUrl); 2423 | if (!response.IsSuccessStatusCode) 2424 | { 2425 | _logger.Info("Wazuh installation script not found, skipping Wazuh installation"); 2426 | return true; 2427 | } 2428 | } 2429 | catch 2430 | { 2431 | _logger.Info("Could not access Wazuh installation script, skipping Wazuh installation"); 2432 | return true; 2433 | } 2434 | 2435 | // Use dynamic service discovery instead of hardcoded checks 2436 | var wazuhServices = FindWazuhServices(); 2437 | bool wazuhInstalled = wazuhServices.Count > 0; 2438 | 2439 | // Also check for installation directories if no services found 2440 | if (!wazuhInstalled) 2441 | { 2442 | var wazuhDirectories = GetWazuhDirectories(); 2443 | wazuhInstalled = wazuhDirectories.Count > 0; 2444 | 2445 | if (wazuhInstalled) 2446 | { 2447 | _logger.Info($"Found Wazuh installation directories: {string.Join(", ", wazuhDirectories)}"); 2448 | } 2449 | } 2450 | 2451 | // Check for version marker in our config 2452 | if (!wazuhInstalled && _config.ComponentVersions.ContainsKey("Wazuh") && 2453 | _config.ComponentVersions["Wazuh"] == "installed") 2454 | { 2455 | wazuhInstalled = true; 2456 | } 2457 | 2458 | if (!wazuhInstalled) 2459 | { 2460 | _logger.Info("Wazuh is not installed, installing..."); 2461 | return await InstallWazuh(); 2462 | } 2463 | 2464 | _logger.Info("Wazuh is already installed, checking if services are running"); 2465 | 2466 | // Start all found Wazuh services 2467 | bool servicesStarted = await StartWazuhServices(); 2468 | 2469 | if (!servicesStarted) 2470 | { 2471 | _logger.Warning("Failed to start any Wazuh services"); 2472 | } 2473 | 2474 | return true; 2475 | } 2476 | catch (Exception ex) 2477 | { 2478 | _logger.Error($"Error updating Wazuh: {ex.Message}", ex); 2479 | return false; 2480 | } 2481 | } 2482 | private List FindWazuhServices() 2483 | { 2484 | var wazuhServices = new List(); 2485 | 2486 | try 2487 | { 2488 | _logger.Info("Searching for Wazuh services dynamically..."); 2489 | 2490 | // Use PowerShell to find Wazuh-related services - FIXED SYNTAX 2491 | string script = @" 2492 | Get-WmiObject -Class Win32_Service | Where-Object { 2493 | $_.Name -like '*wazuh*' -or 2494 | $_.DisplayName -like '*wazuh*' -or 2495 | $_.PathName -like '*wazuh*' 2496 | } | ForEach-Object { 2497 | Write-Output ""$($_.Name)|$($_.DisplayName)|$($_.State)"" 2498 | } 2499 | "; 2500 | 2501 | var process = new Process 2502 | { 2503 | StartInfo = new ProcessStartInfo 2504 | { 2505 | FileName = "powershell.exe", 2506 | Arguments = $"-Command \"{script}\"", 2507 | UseShellExecute = false, 2508 | RedirectStandardOutput = true, 2509 | RedirectStandardError = true, 2510 | CreateNoWindow = true 2511 | } 2512 | }; 2513 | 2514 | process.Start(); 2515 | string output = process.StandardOutput.ReadToEnd(); 2516 | string error = process.StandardError.ReadToEnd(); 2517 | process.WaitForExit(); 2518 | 2519 | if (!string.IsNullOrEmpty(output)) 2520 | { 2521 | var lines = output.Split(new[] { '\r', '\n' }, StringSplitOptions.RemoveEmptyEntries); 2522 | foreach (var line in lines) 2523 | { 2524 | var parts = line.Split('|'); 2525 | if (parts.Length >= 1 && !string.IsNullOrEmpty(parts[0])) 2526 | { 2527 | wazuhServices.Add(parts[0].Trim()); 2528 | _logger.Info($"Found Wazuh service: {parts[0]} ({(parts.Length > 1 ? parts[1] : "N/A")})"); 2529 | } 2530 | } 2531 | } 2532 | 2533 | if (!string.IsNullOrEmpty(error)) 2534 | { 2535 | _logger.Warning($"PowerShell error during Wazuh service search: {error}"); 2536 | } 2537 | 2538 | // Fallback to known patterns if nothing found 2539 | if (wazuhServices.Count == 0) 2540 | { 2541 | _logger.Info("No services found via PowerShell, trying known patterns..."); 2542 | var knownPatterns = new[] { "Wazuh", "wazuh-agent", "WazuhSvc", "wazuh_agent", "WazuhAgent", "OssecSvc" }; 2543 | foreach (var pattern in knownPatterns) 2544 | { 2545 | if (IsServiceInstalled(pattern)) 2546 | { 2547 | wazuhServices.Add(pattern); 2548 | _logger.Info($"Found Wazuh service via pattern matching: {pattern}"); 2549 | } 2550 | } 2551 | } 2552 | } 2553 | catch (Exception ex) 2554 | { 2555 | _logger.Error($"Error finding Wazuh services: {ex.Message}", ex); 2556 | 2557 | // Final fallback to hardcoded list 2558 | var fallbackServices = new[] { "Wazuh", "wazuh-agent", "WazuhSvc", "OssecSvc" }; 2559 | foreach (var service in fallbackServices) 2560 | { 2561 | if (IsServiceInstalled(service)) 2562 | { 2563 | wazuhServices.Add(service); 2564 | } 2565 | } 2566 | } 2567 | 2568 | _logger.Info($"Total Wazuh services found: {wazuhServices.Count}"); 2569 | return wazuhServices.Distinct().ToList(); 2570 | } 2571 | 2572 | private List GetWazuhDirectories() 2573 | { 2574 | var directories = new List 2575 | { 2576 | @"C:\Program Files\Wazuh", 2577 | @"C:\Program Files\Wazuh Agent", 2578 | @"C:\Program Files (x86)\Wazuh", 2579 | @"C:\Program Files (x86)\Wazuh Agent" 2580 | }; 2581 | 2582 | return directories.Where(Directory.Exists).ToList(); 2583 | } 2584 | 2585 | private async Task StartWazuhServices() 2586 | { 2587 | var wazuhServices = FindWazuhServices(); 2588 | 2589 | if (wazuhServices.Count == 0) 2590 | { 2591 | _logger.Warning("No Wazuh services found to start"); 2592 | return false; 2593 | } 2594 | 2595 | bool anyStarted = false; 2596 | foreach (var serviceName in wazuhServices) 2597 | { 2598 | try 2599 | { 2600 | _logger.Info($"Attempting to start Wazuh service: {serviceName}"); 2601 | 2602 | using (var service = new ServiceController(serviceName)) 2603 | { 2604 | service.Refresh(); 2605 | 2606 | if (service.Status == ServiceControllerStatus.Running) 2607 | { 2608 | _logger.Info($"Wazuh service {serviceName} is already running"); 2609 | anyStarted = true; 2610 | continue; 2611 | } 2612 | 2613 | if (service.Status == ServiceControllerStatus.Stopped) 2614 | { 2615 | service.Start(); 2616 | await Task.Run(() => service.WaitForStatus(ServiceControllerStatus.Running, TimeSpan.FromSeconds(30))); 2617 | _logger.Info($"Successfully started Wazuh service: {serviceName}"); 2618 | anyStarted = true; 2619 | } 2620 | else 2621 | { 2622 | _logger.Warning($"Wazuh service {serviceName} is in {service.Status} state"); 2623 | } 2624 | } 2625 | } 2626 | catch (Exception ex) 2627 | { 2628 | _logger.Error($"Error starting Wazuh service {serviceName}: {ex.Message}", ex); 2629 | 2630 | // Try alternative method using net start 2631 | try 2632 | { 2633 | _logger.Info($"Trying to start {serviceName} with net start command"); 2634 | var process = Process.Start(new ProcessStartInfo 2635 | { 2636 | FileName = "net", 2637 | Arguments = $"start \"{serviceName}\"", 2638 | UseShellExecute = true, 2639 | Verb = "runas", 2640 | CreateNoWindow = true, 2641 | WindowStyle = ProcessWindowStyle.Hidden 2642 | }); 2643 | 2644 | if (process != null) 2645 | { 2646 | process.WaitForExit(); 2647 | if (process.ExitCode == 0) 2648 | { 2649 | _logger.Info($"Successfully started {serviceName} with net start"); 2650 | anyStarted = true; 2651 | } 2652 | } 2653 | } 2654 | catch (Exception netEx) 2655 | { 2656 | _logger.Error($"Failed to start {serviceName} with net start: {netEx.Message}"); 2657 | } 2658 | } 2659 | } 2660 | 2661 | return anyStarted; 2662 | } 2663 | private void DebugListAllServices() 2664 | { 2665 | try 2666 | { 2667 | _logger.Info("=== DEBUG: Listing all services containing 'wazuh' ==="); 2668 | 2669 | var services = ServiceController.GetServices(); 2670 | var wazuhServices = services.Where(s => 2671 | s.ServiceName.ToLower().Contains("wazuh") || 2672 | s.DisplayName.ToLower().Contains("wazuh") 2673 | ).ToList(); 2674 | 2675 | if (wazuhServices.Any()) 2676 | { 2677 | foreach (var service in wazuhServices) 2678 | { 2679 | _logger.Info($"Found service: Name='{service.ServiceName}', DisplayName='{service.DisplayName}', Status={service.Status}"); 2680 | } 2681 | } 2682 | else 2683 | { 2684 | _logger.Info("No Wazuh-related services found in system"); 2685 | } 2686 | 2687 | _logger.Info("=== END DEBUG SERVICE LIST ==="); 2688 | } 2689 | catch (Exception ex) 2690 | { 2691 | _logger.Error($"Error listing services: {ex.Message}", ex); 2692 | } 2693 | } 2694 | private async Task InstallWazuh() 2695 | { 2696 | try 2697 | { 2698 | _logger.Info("Installing Wazuh"); 2699 | 2700 | // Download Wazuh PowerShell script 2701 | string scriptUrl = GetComponentUrl("Wazuh.ps1"); 2702 | string scriptPath = Path.Combine(_config.TempPath, "Wazuh.ps1"); 2703 | 2704 | var response = await _httpClient.GetAsync(scriptUrl); 2705 | if (!response.IsSuccessStatusCode) 2706 | { 2707 | _logger.Warning($"Failed to download Wazuh installation script. Status code: {response.StatusCode}"); 2708 | return false; 2709 | } 2710 | 2711 | using (var fileStream = new FileStream(scriptPath, FileMode.Create, FileAccess.Write)) 2712 | { 2713 | await response.Content.CopyToAsync(fileStream); 2714 | } 2715 | 2716 | _logger.Info($"Downloaded Wazuh installation script to {scriptPath}"); 2717 | 2718 | // Check if Wazuh Groups were specified 2719 | string modifiedScriptPath = scriptPath; 2720 | 2721 | if (!string.IsNullOrEmpty(_config.WazuhGroups)) 2722 | { 2723 | _logger.Info($"Adding Wazuh Groups to installation: {_config.WazuhGroups}"); 2724 | 2725 | // Read the original PowerShell script 2726 | string scriptContent = File.ReadAllText(scriptPath); 2727 | 2728 | // Look for the main command line in the script 2729 | // Based on the script from document #6, we need to add the WAZUH_AGENT_GROUP parameter 2730 | // to the msiexec command 2731 | 2732 | if (scriptContent.Contains("msiexec.exe")) 2733 | { 2734 | // Add the WAZUH_AGENT_GROUP parameter to the msiexec command 2735 | scriptContent = scriptContent.Replace( 2736 | "msiexec.exe /i $env:tmp\\wazuh-agent /q WAZUH_MANAGER='soc.novasky.io'", 2737 | $"msiexec.exe /i $env:tmp\\wazuh-agent /q WAZUH_MANAGER='soc.novasky.io' WAZUH_AGENT_GROUP='{_config.WazuhGroups}'"); 2738 | 2739 | // Save the modified script for debugging 2740 | modifiedScriptPath = Path.Combine(_config.TempPath, "Wazuh_Modified.ps1"); 2741 | File.WriteAllText(modifiedScriptPath, scriptContent); 2742 | 2743 | _logger.Info($"Created modified Wazuh script with group parameter at: {modifiedScriptPath}"); 2744 | } 2745 | else 2746 | { 2747 | // Save the script for review if we couldn't identify where to modify it 2748 | string debugScriptPath = Path.Combine(_config.TempPath, "Wazuh_Debug.ps1"); 2749 | File.WriteAllText(debugScriptPath, scriptContent); 2750 | _logger.Warning($"Could not identify where to insert Wazuh group parameter - saved script for debugging at: {debugScriptPath}"); 2751 | } 2752 | } 2753 | 2754 | // Execute PowerShell script to install Wazuh 2755 | var process = new Process 2756 | { 2757 | StartInfo = new ProcessStartInfo 2758 | { 2759 | FileName = "powershell.exe", 2760 | Arguments = $"-ExecutionPolicy Bypass -File \"{modifiedScriptPath}\"", 2761 | CreateNoWindow = false, 2762 | UseShellExecute = true, 2763 | Verb = "runas", 2764 | RedirectStandardOutput = false, 2765 | RedirectStandardError = false 2766 | } 2767 | }; 2768 | 2769 | process.Start(); 2770 | await Task.Run(() => process.WaitForExit()); 2771 | 2772 | // Give the installation some time to complete registration of the service 2773 | _logger.Info("Wazuh installation completed, checking for services..."); 2774 | await Task.Delay(10000); // Increased to 10 seconds 2775 | 2776 | // Add debug service listing 2777 | DebugListAllServices(); 2778 | 2779 | // Try multiple attempts to find the service with delays 2780 | string serviceName = null; 2781 | for (int attempt = 1; attempt <= 3; attempt++) 2782 | { 2783 | _logger.Info($"Attempting to find Wazuh service (attempt {attempt}/3)..."); 2784 | 2785 | foreach (string possibleName in new[] { "Wazuh", "wazuh-agent", "WazuhSvc", "OssecSvc" }) 2786 | { 2787 | if (IsServiceInstalled(possibleName)) 2788 | { 2789 | serviceName = possibleName; 2790 | _logger.Info($"Found Wazuh service: {serviceName}"); 2791 | break; 2792 | } 2793 | } 2794 | 2795 | if (!string.IsNullOrEmpty(serviceName)) 2796 | break; 2797 | 2798 | if (attempt < 3) 2799 | { 2800 | _logger.Info("No Wazuh service found yet, waiting 5 more seconds..."); 2801 | await Task.Delay(5000); 2802 | } 2803 | } 2804 | 2805 | if (!string.IsNullOrEmpty(serviceName)) 2806 | { 2807 | // Start Wazuh service 2808 | _logger.Info($"Starting Wazuh service ({serviceName})..."); 2809 | bool started = await StartService(serviceName); 2810 | 2811 | if (!started) 2812 | { 2813 | // Try alternative method via net start 2814 | _logger.Info("Attempting to start Wazuh service with net start command..."); 2815 | try 2816 | { 2817 | Process netProcess = Process.Start(new ProcessStartInfo 2818 | { 2819 | FileName = "net", 2820 | Arguments = $"start {serviceName}", 2821 | UseShellExecute = true, 2822 | Verb = "runas", 2823 | CreateNoWindow = false 2824 | }); 2825 | netProcess.WaitForExit(); 2826 | 2827 | if (netProcess.ExitCode == 0) 2828 | { 2829 | _logger.Info("Successfully started Wazuh service with net start command"); 2830 | } 2831 | else 2832 | { 2833 | _logger.Warning($"Failed to start Wazuh service with net start command. Exit code: {netProcess.ExitCode}"); 2834 | } 2835 | } 2836 | catch (Exception ex) 2837 | { 2838 | _logger.Error($"Error executing net start command: {ex.Message}", ex); 2839 | } 2840 | } 2841 | } 2842 | else 2843 | { 2844 | _logger.Warning("Could not determine Wazuh service name after installation"); 2845 | 2846 | // Try possible service names as fallback 2847 | await TryStartService("Wazuh"); 2848 | await TryStartService("wazuh-agent"); 2849 | await TryStartService("WazuhSvc"); 2850 | } 2851 | 2852 | // Clean up all script files 2853 | try 2854 | { 2855 | // Delete the original script file 2856 | if (File.Exists(scriptPath)) 2857 | { 2858 | File.Delete(scriptPath); 2859 | } 2860 | 2861 | // Delete the modified script file if it exists and is different from the original 2862 | if (modifiedScriptPath != scriptPath && File.Exists(modifiedScriptPath)) 2863 | { 2864 | File.Delete(modifiedScriptPath); 2865 | _logger.Info("Removed all temporary Wazuh installation scripts"); 2866 | } 2867 | 2868 | // Clean up any debug script file that might have been created 2869 | string debugScriptPath = Path.Combine(_config.TempPath, "Wazuh_Debug.ps1"); 2870 | if (File.Exists(debugScriptPath)) 2871 | { 2872 | File.Delete(debugScriptPath); 2873 | } 2874 | } 2875 | catch (Exception ex) 2876 | { 2877 | // Just log the error but continue - not cleaning up temp files isn't critical 2878 | _logger.Warning($"Error cleaning up temporary script files: {ex.Message}"); 2879 | } 2880 | // Set a version marker to prevent constant reinstallation 2881 | _config.UpdateComponentVersion("Wazuh", "installed"); 2882 | 2883 | _logger.Info("Wazuh installed successfully"); 2884 | return true; 2885 | } 2886 | catch (Exception ex) 2887 | { 2888 | _logger.Error($"Error installing Wazuh: {ex.Message}", ex); 2889 | return false; 2890 | } 2891 | } 2892 | 2893 | private bool IsServiceInstalled(string serviceName) 2894 | { 2895 | try 2896 | { 2897 | using (var service = new ServiceController(serviceName)) 2898 | { 2899 | // Actually try to access the service properties to verify it exists 2900 | service.Refresh(); 2901 | var status = service.Status; // This will throw if service doesn't exist 2902 | var displayName = service.DisplayName; // Additional verification 2903 | _logger.Info($"Service {serviceName} exists with status: {status}"); 2904 | return true; 2905 | } 2906 | } 2907 | catch (Exception ex) 2908 | { 2909 | _logger.Info($"Service {serviceName} does not exist: {ex.Message}"); 2910 | return false; 2911 | } 2912 | } 2913 | 2914 | // Helper methods for Wazuh service management 2915 | private string GetWazuhServiceName() 2916 | { 2917 | // Try the various possible service names for Wazuh 2918 | string[] possibleNames = { "Wazuh", "wazuh-agent", "WazuhSvc" }; 2919 | 2920 | foreach (string name in possibleNames) 2921 | { 2922 | if (IsServiceInstalled(name)) 2923 | { 2924 | return name; 2925 | } 2926 | } 2927 | 2928 | return null; 2929 | } 2930 | 2931 | private bool IsProductInstalled(string productName) 2932 | { 2933 | try 2934 | { 2935 | // Check for product in registry 2936 | string uninstallKey = @"SOFTWARE\Microsoft\Windows\CurrentVersion\Uninstall"; 2937 | using (var baseKey = Registry.LocalMachine.OpenSubKey(uninstallKey)) 2938 | { 2939 | if (baseKey != null) 2940 | { 2941 | foreach (string subKeyName in baseKey.GetSubKeyNames()) 2942 | { 2943 | using (var subKey = baseKey.OpenSubKey(subKeyName)) 2944 | { 2945 | string displayName = subKey.GetValue("DisplayName") as string; 2946 | if (!string.IsNullOrEmpty(displayName) && displayName.IndexOf(productName, StringComparison.OrdinalIgnoreCase) >= 0) 2947 | { 2948 | return true; 2949 | } 2950 | } 2951 | } 2952 | } 2953 | } 2954 | 2955 | // Also check 32-bit registry on 64-bit systems 2956 | string uninstallKey32 = @"SOFTWARE\Wow6432Node\Microsoft\Windows\CurrentVersion\Uninstall"; 2957 | using (var baseKey = Registry.LocalMachine.OpenSubKey(uninstallKey32)) 2958 | { 2959 | if (baseKey != null) 2960 | { 2961 | foreach (string subKeyName in baseKey.GetSubKeyNames()) 2962 | { 2963 | using (var subKey = baseKey.OpenSubKey(subKeyName)) 2964 | { 2965 | string displayName = subKey.GetValue("DisplayName") as string; 2966 | if (!string.IsNullOrEmpty(displayName) && displayName.IndexOf(productName, StringComparison.OrdinalIgnoreCase) >= 0) 2967 | { 2968 | return true; 2969 | } 2970 | } 2971 | } 2972 | } 2973 | } 2974 | 2975 | // Also check Program Files for Wazuh folders 2976 | string[] possiblePaths = { 2977 | @"C:\Program Files\Wazuh", 2978 | @"C:\Program Files\Wazuh Agent", 2979 | @"C:\Program Files (x86)\Wazuh", 2980 | @"C:\Program Files (x86)\Wazuh Agent" 2981 | }; 2982 | 2983 | foreach (string path in possiblePaths) 2984 | { 2985 | if (Directory.Exists(path)) 2986 | { 2987 | return true; 2988 | } 2989 | } 2990 | 2991 | return false; 2992 | } 2993 | catch (Exception ex) 2994 | { 2995 | _logger.Error($"Error checking if product is installed: {ex.Message}", ex); 2996 | return false; 2997 | } 2998 | } 2999 | 3000 | private async Task TryStartService(string serviceName) 3001 | { 3002 | try 3003 | { 3004 | try 3005 | { 3006 | // Check if service exists 3007 | using (var service = new ServiceController(serviceName)) 3008 | { 3009 | _logger.Info($"Found Wazuh service with name: {serviceName}, attempting to start"); 3010 | return await StartService(serviceName); 3011 | } 3012 | } 3013 | catch 3014 | { 3015 | // Service does not exist 3016 | return false; 3017 | } 3018 | } 3019 | catch (Exception ex) 3020 | { 3021 | _logger.Warning($"Error trying to start service {serviceName}: {ex.Message}"); 3022 | return false; 3023 | } 3024 | } 3025 | 3026 | private async Task StopService(string serviceName) 3027 | { 3028 | try 3029 | { 3030 | var service = new ServiceController(serviceName); 3031 | 3032 | if (service.Status != ServiceControllerStatus.Stopped && service.Status != ServiceControllerStatus.StopPending) 3033 | { 3034 | _logger.Info($"Stopping {serviceName} service"); 3035 | service.Stop(); 3036 | await Task.Run(() => service.WaitForStatus(ServiceControllerStatus.Stopped, TimeSpan.FromSeconds(30))); 3037 | } 3038 | 3039 | return true; 3040 | } 3041 | catch (Exception ex) 3042 | { 3043 | _logger.Error($"Error stopping {serviceName} service: {ex.Message}", ex); 3044 | return false; 3045 | } 3046 | } 3047 | 3048 | private async Task StartService(string serviceName) 3049 | { 3050 | try 3051 | { 3052 | _logger.Info($"Attempting to start service: {serviceName}"); 3053 | 3054 | // First verify the service actually exists 3055 | if (!IsServiceInstalled(serviceName)) 3056 | { 3057 | _logger.Warning($"Cannot start {serviceName} - service does not exist"); 3058 | return false; 3059 | } 3060 | 3061 | using (var service = new ServiceController(serviceName)) 3062 | { 3063 | service.Refresh(); 3064 | _logger.Info($"Current {serviceName} status: {service.Status}"); 3065 | 3066 | if (service.Status == ServiceControllerStatus.Running) 3067 | { 3068 | _logger.Info($"Service {serviceName} is already running"); 3069 | return true; 3070 | } 3071 | 3072 | if (service.Status == ServiceControllerStatus.Stopped) 3073 | { 3074 | _logger.Info($"Starting {serviceName} service..."); 3075 | service.Start(); 3076 | 3077 | // Wait for the service to start with multiple verification attempts 3078 | for (int i = 0; i < 6; i++) // 6 attempts = 30 seconds max wait 3079 | { 3080 | await Task.Delay(5000); // Wait 5 seconds between checks 3081 | service.Refresh(); 3082 | 3083 | _logger.Info($"Service {serviceName} status after {(i + 1) * 5} seconds: {service.Status}"); 3084 | 3085 | if (service.Status == ServiceControllerStatus.Running) 3086 | { 3087 | _logger.Info($"Successfully started {serviceName} service"); 3088 | return true; 3089 | } 3090 | 3091 | if (service.Status == ServiceControllerStatus.StartPending) 3092 | { 3093 | _logger.Info($"Service {serviceName} is still starting, waiting..."); 3094 | continue; 3095 | } 3096 | 3097 | // If it's stopped or failed, something went wrong 3098 | if (service.Status == ServiceControllerStatus.Stopped) 3099 | { 3100 | _logger.Warning($"Service {serviceName} stopped unexpectedly during startup"); 3101 | break; 3102 | } 3103 | } 3104 | 3105 | // Final status check 3106 | service.Refresh(); 3107 | if (service.Status == ServiceControllerStatus.Running) 3108 | { 3109 | _logger.Info($"Service {serviceName} is now running"); 3110 | return true; 3111 | } 3112 | else 3113 | { 3114 | _logger.Error($"Failed to start {serviceName}. Final status: {service.Status}"); 3115 | return false; 3116 | } 3117 | } 3118 | else 3119 | { 3120 | _logger.Warning($"Service {serviceName} is in {service.Status} state, cannot start"); 3121 | return false; 3122 | } 3123 | } 3124 | } 3125 | catch (Exception ex) 3126 | { 3127 | _logger.Error($"Error starting {serviceName} service: {ex.Message}", ex); 3128 | return false; 3129 | } 3130 | } 3131 | 3132 | private async Task RestartService(string serviceName) 3133 | { 3134 | await StopService(serviceName); 3135 | return await StartService(serviceName); 3136 | } 3137 | 3138 | private string GetComponentUrl(string fileName) 3139 | { 3140 | // Construct client-specific URL using the client ID 3141 | return $"{_config.ServerUrl}{_config.ClientId}/{fileName}"; 3142 | } 3143 | } 3144 | 3145 | public class StatusReporter 3146 | { 3147 | private readonly HttpClient _httpClient; 3148 | private readonly Logger _logger; 3149 | private readonly ConfigManager _config; 3150 | 3151 | public StatusReporter(HttpClient httpClient, Logger logger, ConfigManager config) 3152 | { 3153 | _httpClient = httpClient; 3154 | _logger = logger; 3155 | _config = config; 3156 | } 3157 | 3158 | public async Task ReportStatus(string status) 3159 | { 3160 | try 3161 | { 3162 | // Create status report 3163 | var statusReport = new 3164 | { 3165 | eventType = "AgentStatus", 3166 | clientId = _config.ClientId, 3167 | computerName = Environment.MachineName, 3168 | status = status, 3169 | timestamp = DateTime.Now.ToString("o"), 3170 | agentVersion = _config.AgentVersion, 3171 | components = await CheckComponentStatus(), 3172 | osVersion = Environment.OSVersion.VersionString 3173 | }; 3174 | 3175 | // Send to server 3176 | string statusUrl = $"{_config.ServerUrl}api/telemetry"; 3177 | var jsonContent = new StringContent( 3178 | JsonSerializer.Serialize(statusReport), 3179 | Encoding.UTF8, 3180 | "application/json" 3181 | ); 3182 | 3183 | var response = await _httpClient.PostAsync(statusUrl, jsonContent); 3184 | if (!response.IsSuccessStatusCode) 3185 | { 3186 | _logger.Warning($"Failed to report status. Status code: {response.StatusCode}"); 3187 | return false; 3188 | } 3189 | 3190 | _logger.Info($"Status reported successfully: {status}"); 3191 | return true; 3192 | } 3193 | catch (Exception ex) 3194 | { 3195 | _logger.Error($"Error reporting status: {ex.Message}", ex); 3196 | return false; 3197 | } 3198 | } 3199 | 3200 | public async Task> CheckComponentStatus() 3201 | { 3202 | var status = new Dictionary(); 3203 | 3204 | try 3205 | { 3206 | // Check Fibratus 3207 | status["Fibratus"] = await GetServiceStatus("Fibratus"); 3208 | 3209 | // Check Velociraptor 3210 | status["Velociraptor"] = await GetServiceStatus("Velociraptor"); 3211 | 3212 | // Check Wazuh if installed - use direct service checks 3213 | bool wazuhFound = false; 3214 | foreach (string serviceName in new[] { "Wazuh", "wazuh-agent", "WazuhSvc" }) 3215 | { 3216 | try 3217 | { 3218 | using (var service = new ServiceController(serviceName)) 3219 | { 3220 | // If we get here, the service exists 3221 | status["Wazuh"] = service.Status.ToString(); 3222 | wazuhFound = true; 3223 | break; 3224 | } 3225 | } 3226 | catch 3227 | { 3228 | // Service not found, try next name 3229 | } 3230 | } 3231 | 3232 | // If no service found, check if Wazuh is installed in other ways 3233 | if (!wazuhFound) 3234 | { 3235 | // Check program files directories 3236 | string[] possiblePaths = { 3237 | @"C:\Program Files\Wazuh", 3238 | @"C:\Program Files\Wazuh Agent", 3239 | @"C:\Program Files (x86)\Wazuh", 3240 | @"C:\Program Files (x86)\Wazuh Agent" 3241 | }; 3242 | 3243 | foreach (string path in possiblePaths) 3244 | { 3245 | if (Directory.Exists(path)) 3246 | { 3247 | status["Wazuh"] = "Unknown"; 3248 | break; 3249 | } 3250 | } 3251 | } 3252 | 3253 | if (!wazuhFound) 3254 | { 3255 | status["Wazuh"] = "NotInstalled"; 3256 | } 3257 | 3258 | return status; 3259 | } 3260 | catch (Exception ex) 3261 | { 3262 | _logger.Error($"Error checking component status: {ex.Message}", ex); 3263 | return status; 3264 | } 3265 | } 3266 | 3267 | public async Task> CollectStatusInfo() 3268 | { 3269 | var statusInfo = new Dictionary(); 3270 | 3271 | try 3272 | { 3273 | // Basic system info 3274 | statusInfo["ComputerName"] = Environment.MachineName; 3275 | statusInfo["OSVersion"] = Environment.OSVersion.VersionString; 3276 | statusInfo["AgentVersion"] = _config.AgentVersion; 3277 | statusInfo["ClientId"] = _config.ClientId; 3278 | statusInfo["Timestamp"] = DateTime.Now.ToString("o"); 3279 | 3280 | // Component versions 3281 | statusInfo["ComponentVersions"] = _config.ComponentVersions; 3282 | 3283 | // Component status 3284 | statusInfo["ComponentStatus"] = await CheckComponentStatus(); 3285 | 3286 | // Additional system info 3287 | statusInfo["ProcessorCount"] = Environment.ProcessorCount; 3288 | statusInfo["SystemDirectory"] = Environment.SystemDirectory; 3289 | statusInfo["UserDomainName"] = Environment.UserDomainName; 3290 | statusInfo["UserName"] = Environment.UserName; 3291 | 3292 | // Network info 3293 | try 3294 | { 3295 | var ipAddresses = System.Net.Dns.GetHostAddresses(Environment.MachineName) 3296 | .Where(ip => ip.AddressFamily == System.Net.Sockets.AddressFamily.InterNetwork) 3297 | .Select(ip => ip.ToString()) 3298 | .ToArray(); 3299 | 3300 | statusInfo["IPAddresses"] = ipAddresses; 3301 | } 3302 | catch 3303 | { 3304 | statusInfo["IPAddresses"] = new string[0]; 3305 | } 3306 | 3307 | return statusInfo; 3308 | } 3309 | catch (Exception ex) 3310 | { 3311 | _logger.Error($"Error collecting status info: {ex.Message}", ex); 3312 | statusInfo["Error"] = ex.Message; 3313 | return statusInfo; 3314 | } 3315 | } 3316 | 3317 | private async Task GetServiceStatus(string serviceName) 3318 | { 3319 | try 3320 | { 3321 | var service = new ServiceController(serviceName); 3322 | return service.Status.ToString(); 3323 | } 3324 | catch 3325 | { 3326 | return "NotInstalled"; 3327 | } 3328 | } 3329 | } 3330 | } 3331 | #endregion 3332 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Nova EDR 2 | 3 | Nova EDR is a comprehensive, lightweight endpoint detection and response solution that combines three powerful open-source security tools managed by a single Windows agent/service. This project is primarily meant so that you can set up your own EDR/SOC capabilties for free, in your lab or organization and monitor the alerts yourself. This will give you the ability to create detection logic to detect adversary behavior, respond, and remediate threats as they arise. 4 | 5 | ## More Documenation on the Agent itself and it's setup can be found [here](https://share.evernote.com/note/961e1b55-874c-6903-a2cb-d7036500d52c) 6 | 7 | ## 🏗️ Architecture Overview 8 | 9 | Nova EDR integrates three core security components managed by a single C# Windows service: 10 | 11 | ### 🎯 **Fibratus** - Primary Detection Engine 12 | - **ETW-based threat detection** using Windows kernel events 13 | - **YAML-based detection rules** for customizable threat detection 14 | - **Real-time process monitoring** with kill capabilities 15 | - **Pulls all kernel space ETW providers** File I/O Events, Network Events, Registry Events, Process Events, etc. 16 | - **Host isolation** features for containment 17 | - **Website**: [fibratus.io](https://www.fibratus.io/#/) 18 | - **GitHub**: [rabbitstack/fibratus](https://github.com/rabbitstack/fibratus/tree/master) 19 | 20 | ### 🔍 **Velociraptor** - Digital Forensics & Incident Response 21 | - **Endpoint visibility** and remote monitoring 22 | - **Artifact collection** and forensic analysis 23 | - **Host isolation** capabilities 24 | - **Remote command execution** for malware cleanup 25 | - **Documentation**: [Velociraptor Docs](https://docs.velociraptor.app/) 26 | 27 | ### 📊 **Wazuh** - Security Information Management (Optional) 28 | - **Centralized log management** and correlation 29 | - **Alert aggregation** from Fibratus detections 30 | - **Custom rules** for security event processing 31 | - **Documentation**: [Wazuh Documentation](https://documentation.wazuh.com/) 32 | 33 | ### 🤖 **Nova EDR Agent** - Unified Management Service 34 | - **Automatic installation** and management of all components 35 | - **GitHub-based updates** for detection rules and binaries 36 | - **Service health monitoring** with automatic recovery 37 | - **Centralized configuration** management 38 | 39 | ## 🚀 Getting Started 40 | 41 | ### Prerequisites 42 | 43 | #### Server Infrastructure Required: 44 | 1. **Velociraptor Server** - Deploy following [installation guide](https://socfortress.medium.com/free-incident-response-with-velociraptor-bedd2583415d) (Note: Modify any version numbers in the commands of the guide to match the most current version that you set up. Also, you can generate a Windows MSI for endpoints on the server itself you don't need to compile one like in the guide. Just use the `Server.Utils.CreateMSI` Server Artifact) 45 | 2. **Wazuh Server** - Deploy following [installation guide](https://documentation.wazuh.com/current/quickstart.html) 46 | 3. **GitHub Repository** - Host detection rules and agent binaries 47 | 48 | #### Client Requirements: 49 | - Windows 10/11 or Windows Server 2016+ 50 | - Administrator privileges 51 | - Internet connectivity to GitHub repository 52 | 53 | ### GitHub Repository Structure 54 | 55 | Your GitHub repository should contain: 56 | ``` 57 | your-repo/ 58 | ├── client-id/ 59 | │ ├── Fibratus.msi # Fibratus installer 60 | │ ├── Velo.msi # Velociraptor client (configured for your server) 61 | │ ├── Wazuh.ps1 # Wazuh installation script (configured for your server) 62 | │ ├── Custom-Rules.zip # Fibratus YAML detection rules 63 | | | ├── Custom Rules 64 | | | ├── Modified Official Rules 65 | | | | ├── modified_official_rules.yml # If you edit the contents of an offical rule file but stick it with the same name here it will overwrite the official rule that is installed with Fibratus by default. 66 | | | ├── custom_rule_file.yml # These are all your custom detection rules that will be added to Fibratus when it installs. 67 | │ └── Custom-Rules.zip.version # Version tracking for rules 68 | ``` 69 | ## 🔧 Nova EDR Agent Capabilities 70 | 71 | The Nova EDR Agent is a Windows service that provides: 72 | 73 | ### 🔄 **Automatic Management** 74 | - **Component Installation**: Automatically installs Fibratus, Velociraptor, and Wazuh 75 | - **Rule Updates**: Regularly pulls latest detection rules from GitHub 76 | - **Service Monitoring**: Automatically restarts crashed services 77 | - **Version Control**: Tracks component versions to avoid unnecessary updates 78 | 79 | ### 📡 **GitHub Integration** 80 | - **Direct Repository Access**: Pulls updates directly from your GitHub repo 81 | - **Configurable Update Intervals**: Set custom check frequencies (default: 60 minutes) 82 | - **Version Tracking**: Only updates when new versions are available 83 | - **Clean Rule Deployment**: Replaces detection rules completely on each update 84 | 85 | ### 🛡️ **Security Features** 86 | - **Hash Validation**: Verifies integrity of downloaded files 87 | - **Service Recovery**: Automatically restarts failed security services 88 | - **Logging**: Comprehensive logging to Windows Event Log and files 89 | 90 | ## 🎯 Detection Rule Development 91 | 92 | ### Creating Custom Fibratus Rules 93 | 94 | Fibratus uses YAML-based detection rules that monitor Windows ETW events: 95 | 96 | 1. **Study existing rules**: Browse the [Fibratus rules directory](https://github.com/rabbitstack/fibratus/tree/master/rules) 97 | 2. **Create custom rules**: Write YAML rules targeting specific threats 98 | 3. **Add to Custom-Rules.zip**: Package your rules and upload to GitHub 99 | 4. **Automatic deployment**: Nova EDR Agent will pull and apply new rules 100 | 101 | Example detection rule structure: 102 | ```yaml 103 | name: Custom Malware Detection 104 | description: Detects suspicious process behavior 105 | condition: > 106 | kevt.name = 'CreateProcess' and 107 | ps.name contains 'malware.exe' 108 | action: kill # Can kill processes automatically 109 | ``` 110 | ## 📈 Alert Flow & Integration 111 | ### Fibratus → Wazuh → Discord Pipeline 112 | 113 | Detection: Fibratus generates alerts based on YAML rules 114 | Ingestion: Wazuh receives Fibratus alerts via XML rule configuration 115 | Processing: Wazuh correlates and enriches security events 116 | Notification: Alerts forwarded to Discord for real-time monitoring 117 | 118 | ### Discord Integration Setup 119 | Follow this comprehensive guide to set up Discord notifications: https://www.learntohomelab.com/homelabseries/EP19_wazuhdiscordalerts/ 120 | Result: Real-time EDR alerts delivered to your Discord channel:![image](https://github.com/user-attachments/assets/1d365802-c84a-4179-a1e8-dc9b2653f116) 121 | 122 | -------------------------------------------------------------------------------- /Wazuh-Fibratus-Rules.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 12 | 13 | 14 | 15 | 60600 16 | no_full_log 17 | 40678 18 | Fibratus Alert: Rundll32 accessing backup credentials. Attackers may abuse this to retrieve stored creds. Validate the process and command line. 19 | windows 20 | 21 | 22 | 23 | 24 | 60600 25 | no_full_log 26 | 62873 27 | Fibratus Alert: Credential discovery via VaultCmd.exe. Attackers can harvest stored credentials. Check process usage and user privileges. 28 | windows 29 | 30 | 31 | 32 | 33 | 60600 34 | no_full_log 35 | 39735 36 | Fibratus Alert: LSASS memory dumping attempt. Often used to steal credential hashes. Confirm if this is an expected diagnostic or malicious action. 37 | windows 38 | 39 | 40 | 41 | 42 | 60600 43 | no_full_log 44 | 28416 45 | Fibratus Alert: LSASS dump prepared via SilentProcessExit. Attackers may configure silent dumping for credential theft. Investigate registry settings and usage. 46 | windows 47 | 48 | 49 | 50 | 51 | 60600 52 | no_full_log 53 | 31619 54 | Fibratus Alert: LSASS memory dump through Windows Error Reporting. Attackers abuse WER to extract creds. Verify the triggering process and context. 55 | windows 56 | 57 | 58 | 67 | 68 | 69 | 70 | 60600 71 | no_full_log 72 | 1371 73 | Fibratus Alert: Remote thread injected into LSASS. Attackers often do this to dump credentials. Confirm if the injecting process is approved. 74 | windows 75 | 76 | 77 | 78 | 79 | 60600 80 | no_full_log 81 | 59232 82 | Fibratus Alert: Possibly suspicious read of the AD domain database. Could yield domain hashes. Verify if performed by a legitimate domain admin task. 83 | windows 84 | 85 | 86 | 87 | 88 | 60600 89 | no_full_log 90 | 1117 91 | Fibratus Alert: Access to Unattended Panther files, which may store setup credentials. Validate if the reading process is authorized. 92 | windows 93 | 94 | 95 | 96 | 97 | 60600 98 | no_full_log 99 | 28625 100 | Fibratus Alert: DPAPI master keys accessed. Attackers can decrypt sensitive data. Investigate who accessed it and confirm if it's standard OS usage. 101 | windows 102 | 103 | 104 | 113 | 114 | 115 | 116 | 60600 117 | no_full_log 118 | 44790 119 | Fibratus Alert: Windows Vault files accessed. Could indicate credential harvesting. Verify if the user and process accessing these is valid. 120 | windows 121 | 122 | 123 | 124 | 125 | 60600 126 | no_full_log 127 | 46110 128 | Fibratus Alert: A suspicious security package DLL was loaded, possibly hooking authentication. Inspect the DLL path and process for potential tampering. 129 | windows 130 | 131 | 132 | 133 | 134 | 60600 135 | no_full_log 136 | 45628 137 | Fibratus Alert: SSH key files accessed. Attackers can use stolen keys for lateral movement. Confirm whether this read is expected or suspicious. 138 | windows 139 | 140 | 141 | 150 | 151 | 152 | 153 | 60600 154 | no_full_log 155 | 6230 156 | Fibratus Alert: Windows credential history files read. Attackers can retrieve old password data. Investigate whether this action is authorized. 157 | windows 158 | 159 | 160 | 161 | 162 | 60600 163 | no_full_log 164 | 45259 165 | Fibratus Alert: DLL side-loading with a copied binary. Attackers load malicious DLLs to bypass security. Confirm the binary and DLL are signed or trusted. 166 | windows 167 | 168 | 169 | 178 | 179 | 180 | 181 | 60600 182 | no_full_log 183 | 60551 184 | Fibratus Alert: Process Doppelganging suspected. Attackers modify the file transaction to run malicious code. Check if this process's memory sections are legitimate. The malicious process was terminated. 185 | windows 186 | 187 | 188 | 189 | 190 | 60600 191 | no_full_log 192 | 58977 193 | Fibratus Alert: Process hollowing attempt. Attackers replace a legitimate process image with malicious code. Investigate the new thread or memory sections in the target process. The malicious process was terminated. 194 | windows 195 | 196 | 197 | 206 | 207 | 208 | 209 | 60600 210 | no_full_log 211 | 27220 212 | Fibratus Alert: Thread execution hijacking. Attackers redirect existing threads to malicious code. Examine the call stack and memory mapping of the hijacked thread. The malicious process was terminated. 213 | windows 214 | 215 | 216 | 217 | 218 | 60600 219 | no_full_log 220 | 62997 221 | Fibratus Alert: A remote thread created a new process. Attackers often spawn hidden payloads. Inspect parent-child relationships and command lines. The spawned process was terminated. 222 | windows 223 | 224 | 225 | 226 | 227 | 60600 228 | no_full_log 229 | 61701 230 | Fibratus Alert: Regsvr32 scriptlet execution. Attackers exploit regsvr32 to bypass whitelisting. Check the scriptlet path and verify legitimate usage. 231 | windows 232 | 233 | 234 | 235 | 236 | 60600 237 | no_full_log 238 | 10824 239 | Fibratus Alert: DLL loaded via a mapped memory section. Attackers can inject malicious libraries. Investigate the loaded DLL path and the calling process context. The malicious process was terminated. 240 | windows 241 | 242 | 243 | 244 | 245 | 60600 246 | no_full_log 247 | 17035 248 | Fibratus Alert: Rundll32 used as a proxy for system binary execution. Attackers exploit this to run code stealthily. Check the DLL or command line invoked by rundll32. The malicious process was terminated. 249 | windows 250 | 251 | 252 | 253 | 254 | 60600 255 | no_full_log 256 | 53541 257 | Fibratus Alert: Thread context changed from unbacked memory. May indicate code injection or hooking. Validate the memory region and the reason for the context switch. 258 | windows 259 | 260 | 261 | 270 | 271 | 272 | 273 | 60600 274 | no_full_log 275 | 53835 276 | Fibratus Alert: Macro-enabled Office doc created an executable. Common malware tactic. Investigate the doc source and newly created file to confirm legitimacy. 277 | windows 278 | 279 | 280 | 281 | 282 | 60600 283 | no_full_log 284 | 57098 285 | Fibratus Alert: Code run under a Microsoft Office process. Attackers may exploit macros or embedded objects. Check the command line and user context. 286 | windows 287 | 288 | 289 | 290 | 291 | 60600 292 | no_full_log 293 | 33594 294 | Fibratus Alert: Macro invoked a script interpreter (e.g. cscript, powershell). Attackers can pivot from Office macros. Confirm if the macro is safe or malicious. 295 | windows 296 | 297 | 298 | 299 | 300 | 60600 301 | no_full_log 302 | 14813 303 | Fibratus Alert: A macro-enabled Office doc spawned a child process. Attackers often drop payloads this way. Validate the doc's source and the resulting process. 304 | windows 305 | 306 | 307 | 308 | 309 | 60600 310 | no_full_log 311 | 35557 312 | Fibratus Alert: Microsoft Office loaded a suspicious DLL. Attackers may hijack Office for malicious modules. Check the DLL path and signature thoroughly. 313 | windows 314 | 315 | 316 | 317 | 318 | 60600 319 | no_full_log 320 | 26307 321 | Fibratus Alert: Potentially malicious embedded object in an Office doc. Attackers can store hidden code. Investigate the object with a sandbox or advanced scan. 322 | windows 323 | 324 | 325 | 326 | 327 | 60600 328 | no_full_log 329 | 51145 330 | Fibratus Alert: A startup folder script/executable made a network connection. Attackers persist here for C2. Verify the script's origin and whether it's legitimate. 331 | windows 332 | 333 | 334 | 335 | 336 | 60600 337 | no_full_log 338 | 22323 339 | Fibratus Alert: RID hijacking attempt. Attackers can remap a normal user to an admin RID. Inspect the SAM or security hive for unauthorized changes. 340 | windows 341 | 342 | 343 | 344 | 345 | 60600 346 | no_full_log 347 | 12778 348 | Fibratus Alert: Detected persistence attempts via Registry Run keys or Startup folder entries, modified by a script interpreter or an untrusted process. Such methods are commonly exploited by adversaries for reboot persistence. Review the modifying entity and context for potential security risks. The malicious process was terminated. 349 | windows 350 | 351 | 352 | 353 | 354 | 60600 355 | no_full_log 356 | 30152 357 | Fibratus Alert: Possibly malicious Office template. Attackers use custom templates to auto-launch macros. Validate the template location and signature. 358 | windows 359 | 360 | 361 | 362 | 363 | 60600 364 | no_full_log 365 | 51188 366 | Fibratus Alert: Registry-based persistence attempt. Attackers may add run keys or scheduled tasks. Examine the modified registry path for suspicious entries. 367 | windows 368 | 369 | 370 | 371 | 372 | 60600 373 | no_full_log 374 | 30145 375 | Fibratus Alert: Suspicious port monitor loaded. Attackers can hijack spooler or printing sub-system. Validate the driver's source and authenticity. 376 | windows 377 | 378 | 379 | 380 | 381 | 60600 382 | no_full_log 383 | 19040 384 | Fibratus Alert: Startup shell folder was modified. Attackers may add scripts or exes here for persistence. Check the new files or subfolders carefully. 385 | windows 386 | 387 | 388 | 389 | 390 | 60600 391 | no_full_log 392 | 443 393 | Fibratus Alert: A new file appeared in the Startup folder. Attackers often drop malware here for auto-run. Investigate the file's hash and source. 394 | windows 395 | 396 | 397 | 398 | 399 | 60600 400 | no_full_log 401 | 20521 402 | Fibratus Alert: A process changed a registry run key in an unusual manner. Attackers can persist here. Check what was added or modified in HKLM/HKCU run. 403 | windows 404 | 405 | 406 | 407 | 408 | 60600 409 | no_full_log 410 | 124 411 | Fibratus Alert: Phantom DLL hijacking attempt for privilege escalation. Attackers trick an app into loading their DLL. Validate the library path and caller process. 412 | windows 413 | 414 | 415 | 416 | 417 | 60600 418 | no_full_log 419 | 48013 420 | Fibratus Alert: A malicious or vulnerable driver file was dropped. Attackers can use driver exploits for kernel-level access. Examine the driver's signature and origin. 421 | windows 422 | 423 | 424 | 425 | 426 | 60600 427 | no_full_log 428 | 51523 429 | Fibratus Alert: A vulnerable or malicious driver loaded into kernel space. Attackers gain high privileges via driver exploits. Validate the driver's authenticity immediately. 430 | windows 431 | 432 | 433 | 434 | 435 | 60600 436 | no_full_log 437 | 63763 438 | Fibratus Alert: AppDomain Manager injection via CLR search order hijacking. Attackers can hijack .NET processes for stealthy code execution. Investigate .NET usage and loaded assemblies. 439 | windows 440 | 441 | 442 | 451 | 452 | 453 | 454 | 60600 455 | no_full_log 456 | 43721 457 | Fibratus Alert: A DLL was loaded via APC queue. Attackers can queue APC calls in a remote thread to inject code. Validate the DLL's path and the calling process. 458 | windows 459 | 460 | 461 | 462 | 463 | 60600 464 | no_full_log 465 | 35404 466 | Fibratus Alert: DLL loaded through a callback function. Attackers may exploit callbacks to load malicious libraries. Inspect the function pointer references and loaded module. 467 | windows 468 | 469 | 470 | 479 | 480 | 489 | 490 | 491 | 492 | 60600 493 | no_full_log 494 | 38412 495 | Fibratus Alert: An image was loaded within an NTFS transaction. Attackers can hide malicious code in transacted file operations. Confirm the file's integrity and origin. 496 | windows 497 | 498 | 499 | 500 | 501 | 60600 502 | no_full_log 503 | 51121 504 | Fibratus Alert: .NET debugging injection attempt. Attackers can attach a debugger to manipulate .NET assemblies at runtime. Verify if debugging is legitimate or malicious. 505 | windows 506 | 507 | 508 | 509 | 510 | 60600 511 | no_full_log 512 | 3627 513 | Fibratus Alert: A process launched from a self-deleting binary. Attackers often remove the file post-execution to evade detection. Investigate any residue and validate process origin. 514 | windows 515 | 516 | 517 | 518 | 519 | 60600 520 | no_full_log 521 | 39897 522 | Fibratus Alert: A Hidden Local Account was created. Attackers can maintain stealthy access with such accounts. Check user listings and group memberships for anomalies. 523 | windows 524 | 525 | 526 | 527 | 528 | 60600 529 | no_full_log 530 | 36050 531 | Fibratus Alert: Reverse shell or remote script execution detected. Indicates potential C2 activity, payload downloads, or post-exploitation behavior. 532 | windows 533 | 534 | 535 | 536 | 537 | 60600 538 | no_full_log 539 | 45476 540 | Fibratus Alert: Suspicious DNS query detected, potential command and control (C2), data exfiltration, or infostealer malware. 541 | windows 542 | 543 | 544 | 545 | 546 | 60600 547 | no_full_log 548 | 58793 549 | Fibratus Alert: Detected VPN or Proxy usage based on process executions, network connections, and DNS queries. 550 | windows 551 | 552 | 553 | 554 | 555 | 60600 556 | no_full_log 557 | 63003 558 | Fibratus Alert: Detected modification to the Windows Hosts file, potentially to block security updates, redirect domains, or disrupt network operations. The malicious process was terminated. 559 | windows 560 | 561 | 562 | 563 | 564 | 60600 565 | no_full_log 566 | 57169 567 | Fibratus Alert: Detected modification to Windows Defender exclusions via registry operations. Possible defense evasion. 568 | windows 569 | 570 | 571 | 572 | 573 | 60600 574 | no_full_log 575 | 64278 576 | Fibratus Alert: Windows Defender Disabling Attempt! May indicate tampering via registry, PowerShell, or executable manipulation. The malicious process was terminated. 577 | windows 578 | 579 | 580 | 581 | 582 | 60600 583 | no_full_log 584 | 17448 585 | Fibratus Alert: Possible Malicious System DLL Abuse detected! Immediate investigation required for possible ransomware or malware. 586 | windows,ransomware 587 | 588 | 589 | 590 | 591 | 60600 592 | no_full_log 593 | 40975 594 | Fibratus Alert: Ransomware in progress detected - File I/O variant with Enum/Delete/Create/Read/Write operations. Immediate containment and isolation recommended. The malicious process was terminated. 595 | windows,file_operations 596 | 597 | 598 | 599 | 600 | 60600 601 | no_full_log 602 | 54578 603 | Fibratus Alert: Ransomware in progress detected - File-to-File Operation with Deletion. Suspicious file copying and immediate deletion pattern detected. The malicious process was terminated. 604 | windows,file_operations 605 | 606 | 607 | 608 | 609 | 60600 610 | no_full_log 611 | 52681 612 | Fibratus Alert: Ransomware in progress detected - File-to-File Operation with Rename and Delete. Potentially suspicious file renaming and deletion sequence. The malicious process was terminated. 613 | windows,file_operations 614 | 615 | 616 | 617 | 618 | 60600 619 | no_full_log 620 | 53449 621 | Fibratus Alert: Gamma Ransomware Early Detection Pattern. Potential ransomware activity identified in early stages. The malicious process was terminated. 622 | windows,ransomware 623 | 624 | 625 | 626 | 627 | 60600 628 | no_full_log 629 | 31483 630 | Fibratus Alert: Ransomware in progress detected - Memory-to-File Post-Overwrite Detected. Suspicious file overwriting method typical of ransomware. The malicious process was terminated. 631 | windows,ransomware 632 | 633 | 634 | 635 | 636 | 60600 637 | no_full_log 638 | 56872 639 | Fibratus Alert: Ransomware in progress detected - Memory-to-File Pre-Overwrite Detected. Potential pre-encryption file manipulation observed. The malicious process was terminated. 640 | windows,ransomware 641 | 642 | 643 | 644 | 645 | 60600 646 | no_full_log 647 | 48384 648 | Fibratus Alert: Unsigned executable with suspicious pattern detected. Potential Malware or Ransomware attempting to bypass security checks. Validate the executable's origin and integrity. The malicious process was terminated. 649 | windows,ransomware 650 | 651 | 652 | 653 | 654 | 60600 655 | no_full_log 656 | 43956 657 | Fibratus Alert: Potential Ransomware or Malware process detected. The malicious process was terminated. 658 | windows,ransomware 659 | 660 | 661 | 662 | 663 | 60600 664 | no_full_log 665 | 64101 666 | Fibratus Alert: Potential Ransomware or Malware process detected. 667 | windows,ransomware 668 | 669 | 670 | 671 | 672 | 673 | 674 | 60600 675 | no_full_log 676 | 8124 677 | Fibratus Alert: LSASS access from unsigned executable. Unsigned processes accessing LSASS may indicate credential dumping attempts. Verify the process integrity and purpose. The malicious process was terminated. 678 | windows 679 | 680 | 681 | 682 | 683 | 60600 684 | no_full_log 685 | 7047 686 | Fibratus Alert: LSASS handle leak via Seclogon service. Attackers may exploit handle leaks to access LSASS memory. Investigate the Seclogon service and related processes. 687 | windows 688 | 689 | 690 | 691 | 692 | 60600 693 | no_full_log 694 | 56527 695 | Fibratus Alert: LSASS memory dump via MiniDumpWriteDump API. Direct API call to dump LSASS memory for credential extraction. Validate the calling process and context. The malicious process was terminated. 696 | windows 697 | 698 | 699 | 700 | 701 | 60600 702 | no_full_log 703 | 17693 704 | Fibratus Alert: LSASS process clone created via reflection. Advanced technique to clone LSASS for credential dumping while evading detection. Investigate immediately. The malicious process was terminated. 705 | windows 706 | 707 | 708 | 709 | 710 | 60600 711 | no_full_log 712 | 18100 713 | Fibratus Alert: Suspicious Vault client DLL loaded. Attackers may exploit vault client libraries to access stored credentials. Verify the DLL's authenticity and loading context. 714 | windows 715 | 716 | 717 | 718 | 719 | 60600 720 | no_full_log 721 | 5828 722 | Fibratus Alert: DLL loaded via LdrpKernel32 overwrite. Advanced DLL injection technique using loader manipulation. Investigate the loaded DLL and injection method. The malicious process was terminated. 723 | windows 724 | 725 | 726 | 727 | 728 | 60600 729 | no_full_log 730 | 13569 731 | Fibratus Alert: DLL side-loading via Microsoft Office dropped file. Office document may have dropped malicious DLL for side-loading attack. Examine the Office document and DLL. 732 | windows 733 | 734 | 735 | 736 | 737 | 60600 738 | no_full_log 739 | 28932 740 | Fibratus Alert: Process creation via shellcode detected. Shellcode execution often indicates code injection or exploit activity. Investigate the shellcode source and target process. 741 | windows 742 | 743 | 744 | 745 | 746 | 60600 747 | no_full_log 748 | 4785 749 | Fibratus Alert: Shellcode execution via ETW logger thread. Advanced technique using Event Tracing for Windows thread for shellcode execution. Investigate the ETW manipulation. 750 | windows 751 | 752 | 753 | 754 | 755 | 60600 756 | no_full_log 757 | 11847 758 | Fibratus Alert: Suspicious HTML Application (HTA) script execution. HTA files can bypass security controls and execute scripts. Verify the HTA source and content. The malicious process was terminated. 759 | windows 760 | 761 | 762 | 763 | 764 | 60600 765 | no_full_log 766 | 28878 767 | Fibratus Alert: Suspicious XSL script execution. XSL stylesheets can contain executable code to bypass application whitelisting. Examine the XSL file and execution context. 768 | windows 769 | 770 | 771 | 772 | 773 | 60600 774 | no_full_log 775 | 59815 776 | Fibratus Alert: Suspicious object symbolic link creation. Attackers may create symbolic links for privilege escalation or file system manipulation. Investigate the link target and purpose. 777 | windows 778 | 779 | 780 | 781 | 782 | 60600 783 | no_full_log 784 | 11019 785 | Fibratus Alert: Suspicious Windows Defender exclusions registry modification. Different from standard exclusion modification, may indicate advanced evasion technique. Verify the registry changes. The malicious process was terminated. 786 | windows 787 | 788 | 789 | 790 | 791 | 60600 792 | no_full_log 793 | 46085 794 | Fibratus Alert: Windows Defender protection tampering via registry. Registry-based tampering with Defender protection features. Investigate the registry modifications and their source. The malicious process was terminated. 795 | windows 796 | 797 | 798 | 799 | 800 | 60600 801 | no_full_log 802 | 22269 803 | Fibratus Alert: Generic ransomware initialization pattern detected. Early-stage ransomware activity identified. Immediate isolation and investigation recommended. The malicious process was terminated. 804 | windows,ransomware 805 | 806 | 807 | 808 | 809 | 60600 810 | no_full_log 811 | 56510 812 | Fibratus Alert: Microsoft Office file execution via script interpreter. Office document may be executing malicious scripts. Verify the document source and script content. The malicious process was terminated. 813 | windows 814 | 815 | 816 | 817 | 818 | 60600 819 | no_full_log 820 | 19803 821 | Fibratus Alert: Microsoft Office file execution via WMI. Office document using WMI for execution, potentially bypassing security controls. Investigate the WMI usage and Office document. The malicious process was terminated. 822 | windows 823 | 824 | 825 | 826 | 827 | 60600 828 | no_full_log 829 | 22507 830 | Fibratus Alert: Potential ClickFix infection chain via Run window. Social engineering technique tricking users to run malicious commands. Investigate the Run window usage and commands. The malicious process was terminated. 831 | windows 832 | 833 | 834 | 835 | 836 | 60600 837 | no_full_log 838 | 49294 839 | Fibratus Alert: Suspicious WMI execution from Microsoft Office process. Office process using WMI for potentially malicious execution. Examine the WMI commands and Office document. 840 | windows 841 | 842 | 843 | 844 | 845 | 60600 846 | no_full_log 847 | 57017 848 | Fibratus Alert: Suspicious Microsoft Office add-in loaded. Malicious add-ins can provide persistence and code execution. Verify the add-in's authenticity and functionality. 849 | windows 850 | 851 | 852 | 853 | 854 | 60600 855 | no_full_log 856 | 13254 857 | Fibratus Alert: Executable file dropped by unsigned service DLL. Unsigned service DLL dropping executables may indicate malicious service installation. Investigate the service and dropped file. 858 | windows 859 | 860 | 861 | 862 | 863 | 60600 864 | no_full_log 865 | 5978 866 | Fibratus Alert: Port monitor or print processor persistence via registry modification. Attackers may abuse print system for persistence. Examine the registry modifications and print system components. 867 | windows 868 | 869 | 870 | 871 | 872 | 60600 873 | no_full_log 874 | 43846 875 | Fibratus Alert: Suspicious Netsh Helper DLL execution. Netsh helpers can provide persistence and code execution. Verify the helper DLL's legitimacy and registration. 876 | windows 877 | 878 | 879 | 880 | 881 | 60600 882 | no_full_log 883 | 49689 884 | Fibratus Alert: Suspicious print processor loaded. Print processors can be abused for persistence and privilege escalation. Validate the print processor's source and functionality. 885 | windows 886 | 887 | 888 | 889 | --------------------------------------------------------------------------------