Restore SEBPatch

This commit is contained in:
2025-06-01 11:44:20 +02:00
commit 8c656e3137
1297 changed files with 142172 additions and 0 deletions

View File

@@ -0,0 +1,409 @@
/*
* Copyright (c) 2024 ETH Zürich, IT Services
*
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/.
*/
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
using System.Timers;
using SafeExamBrowser.Logging.Contracts;
using SafeExamBrowser.Monitoring.Contracts.Applications;
using SafeExamBrowser.Monitoring.Contracts.Applications.Events;
using SafeExamBrowser.Settings.Applications;
using SafeExamBrowser.WindowsApi.Contracts;
namespace SafeExamBrowser.Monitoring.Applications
{
public class ApplicationMonitor : IApplicationMonitor
{
private readonly IList<BlacklistApplication> blacklist;
private readonly ILogger logger;
private readonly INativeMethods nativeMethods;
private readonly IProcessFactory processFactory;
private readonly Timer timer;
private readonly IList<WhitelistApplication> whitelist;
private Guid? captureHookId;
private Guid? foregroundHookId;
private IList<IProcess> processes;
private Window activeWindow;
public event ExplorerStartedEventHandler ExplorerStarted;
public event InstanceStartedEventHandler InstanceStarted;
public event TerminationFailedEventHandler TerminationFailed;
public ApplicationMonitor(int interval_ms, ILogger logger, INativeMethods nativeMethods, IProcessFactory processFactory)
{
this.blacklist = new List<BlacklistApplication>();
this.logger = logger;
this.nativeMethods = nativeMethods;
this.processes = new List<IProcess>();
this.processFactory = processFactory;
this.timer = new Timer(interval_ms);
this.whitelist = new List<WhitelistApplication>();
}
public InitializationResult Initialize(ApplicationSettings settings)
{
var result = new InitializationResult();
return result;
}
public void Start()
{
timer.AutoReset = false;
timer.Elapsed += Timer_Elapsed;
timer.Start();
logger.Info("Started monitoring applications.");
captureHookId = nativeMethods.RegisterSystemCaptureStartEvent(SystemEvent_WindowChanged);
logger.Info($"Registered system capture start event with ID = {captureHookId}.");
foregroundHookId = nativeMethods.RegisterSystemForegroundEvent(SystemEvent_WindowChanged);
logger.Info($"Registered system foreground event with ID = {foregroundHookId}.");
}
public void Stop()
{
timer.Stop();
timer.Elapsed -= Timer_Elapsed;
logger.Info("Stopped monitoring applications.");
if (captureHookId.HasValue)
{
nativeMethods.DeregisterSystemEventHook(captureHookId.Value);
logger.Info($"Unregistered system capture start event with ID = {captureHookId}.");
}
if (foregroundHookId.HasValue)
{
nativeMethods.DeregisterSystemEventHook(foregroundHookId.Value);
logger.Info($"Unregistered system foreground event with ID = {foregroundHookId}.");
}
}
public bool TryGetActiveApplication(out ActiveApplication application)
{
application = default;
if (activeWindow != default && TryGetProcessFor(activeWindow, out var process))
{
var window = new Window
{
Handle = activeWindow.Handle,
Title = nativeMethods.GetWindowTitle(activeWindow.Handle)
};
application = new ActiveApplication(process, window);
}
return application != default;
}
public bool TryTerminate(RunningApplication application)
{
var success = true;
return success;
}
private void SystemEvent_WindowChanged(IntPtr handle)
{
}
private void Timer_Elapsed(object sender, ElapsedEventArgs e)
{
}
private void AddFailed(IProcess process, List<RunningApplication> failed)
{
var name = blacklist.First(a => BelongsToApplication(process, a)).ExecutableName;
var application = failed.FirstOrDefault(a => a.Name == name);
if (application == default(RunningApplication))
{
application = new RunningApplication(name);
failed.Add(application);
}
application.Processes.Add(process);
}
private void AddFailed(string name, IProcess process, InitializationResult result)
{
var application = result.FailedAutoTerminations.FirstOrDefault(a => a.Name == name);
if (application == default(RunningApplication))
{
application = new RunningApplication(name);
result.FailedAutoTerminations.Add(application);
}
application.Processes.Add(process);
logger.Error($"Process {process} belongs to application '{application.Name}' and could not be terminated automatically!");
}
private void AddForTermination(string name, IProcess process, InitializationResult result)
{
var application = result.RunningApplications.FirstOrDefault(a => a.Name == name);
if (application == default(RunningApplication))
{
application = new RunningApplication(name);
result.RunningApplications.Add(application);
}
application.Processes.Add(process);
logger.Debug($"Process {process} belongs to application '{application.Name}' and needs to be terminated.");
}
private bool BelongsToApplication(IProcess process, BlacklistApplication application)
{
var sameName = process.Name.Equals(application.ExecutableName, StringComparison.OrdinalIgnoreCase);
var sameOriginalName = process.OriginalName?.Equals(application.OriginalName, StringComparison.OrdinalIgnoreCase) == true;
return sameName || sameOriginalName;
}
private bool BelongsToApplication(IProcess process, WhitelistApplication application)
{
var ignoreOriginalName = string.IsNullOrWhiteSpace(application.OriginalName);
var ignoreSignature = string.IsNullOrWhiteSpace(application.Signature);
var sameName = process.Name.Equals(application.ExecutableName, StringComparison.OrdinalIgnoreCase);
var sameOriginalName = process.OriginalName?.Equals(application.OriginalName, StringComparison.OrdinalIgnoreCase) == true;
var sameSignature = process.Signature?.Equals(application.Signature?.ToLower(), StringComparison.OrdinalIgnoreCase) == true;
return sameName && (ignoreOriginalName || sameOriginalName) && (ignoreSignature || sameSignature);
}
private bool BelongsToSafeExamBrowser(IProcess process)
{
var isClient = true;
var isRuntime = true;
isClient &= process.Name == "SafeExamBrowser.Client.exe";
isClient &= process.OriginalName == "SafeExamBrowser.Client.exe";
isRuntime &= process.Name == "SafeExamBrowser.exe";
isRuntime &= process.OriginalName == "SafeExamBrowser.exe";
#if !DEBUG
isClient &= process.Signature == "2bc82fe8e56a39f96bc6c4b91d6703a0379b76a2";
isRuntime &= process.Signature == "2bc82fe8e56a39f96bc6c4b91d6703a0379b76a2";
#endif
return isClient || isRuntime;
}
private void Close(Window window)
{
nativeMethods.SendCloseMessageTo(window.Handle);
logger.Info($"Sent close message to window {window}.");
}
private void HandleExplorerStart(IProcess process)
{
logger.Warn($"A new instance of Windows Explorer {process} has been started!");
Task.Run(() => ExplorerStarted?.Invoke());
}
private void HandleInstanceStart(Guid applicationId, IProcess process)
{
logger.Debug($"Detected start of whitelisted application instance {process}.");
Task.Run(() => InstanceStarted?.Invoke(applicationId, process));
}
private void InitializeProcesses()
{
processes = processFactory.GetAllRunning();
logger.Debug($"Initialized {processes.Count} currently running processes.");
}
private void InitializeBlacklist(ApplicationSettings settings, InitializationResult result)
{
foreach (var application in settings.Blacklist)
{
blacklist.Add(application);
}
logger.Debug($"Initialized blacklist with {blacklist.Count} applications{(blacklist.Any() ? $": {string.Join(", ", blacklist.Select(a => a.ExecutableName))}" : ".")}");
foreach (var process in processes)
{
foreach (var application in blacklist)
{
var isBlacklisted = BelongsToApplication(process, application);
if (isBlacklisted)
{
if (!application.AutoTerminate)
{
AddForTermination(application.ExecutableName, process, result);
}
else if (application.AutoTerminate && !TryTerminate(process))
{
AddFailed(application.ExecutableName, process, result);
}
break;
}
}
}
}
private void InitializeWhitelist(ApplicationSettings settings, InitializationResult result)
{
foreach (var application in settings.Whitelist)
{
whitelist.Add(application);
}
logger.Debug($"Initialized whitelist with {whitelist.Count} applications{(whitelist.Any() ? $": {string.Join(", ", whitelist.Select(a => a.ExecutableName))}" : ".")}");
foreach (var process in processes)
{
foreach (var application in whitelist)
{
var isWhitelisted = BelongsToApplication(process, application);
if (isWhitelisted)
{
if (!application.AllowRunning && !application.AutoTerminate)
{
AddForTermination(application.ExecutableName, process, result);
}
else if (!application.AllowRunning && application.AutoTerminate && !TryTerminate(process))
{
AddFailed(application.ExecutableName, process, result);
}
break;
}
}
}
}
private bool IsAllowed(IProcess process)
{
foreach (var application in blacklist)
{
if (BelongsToApplication(process, application))
{
logger.Warn($"Process {process} belongs to blacklisted application '{application.ExecutableName}'!");
return false;
}
}
return true;
}
private bool IsAllowed(Window window)
{
var allowed = false;
if (TryGetProcessFor(window, out var process))
{
allowed = BelongsToSafeExamBrowser(process) || IsWhitelisted(process, out _);
}
if (!allowed)
{
logger.Warn($"Window {window} belongs to not whitelisted process '{process?.Name ?? "n/a"}'!");
}
return allowed;
}
private bool IsWhitelisted(IProcess process, out Guid? applicationId)
{
applicationId = default;
foreach (var application in whitelist)
{
if (BelongsToApplication(process, application))
{
applicationId = application.Id;
return true;
}
}
return false;
}
private bool TryGetProcessFor(Window window, out IProcess process)
{
var processId = Convert.ToInt32(nativeMethods.GetProcessIdFor(window.Handle));
if (!processFactory.TryGetById(processId, out process))
{
logger.Error($"Could not find process for window {window} and process ID = {processId}!");
}
return process != default;
}
private bool TryHide(Window window)
{
var success = nativeMethods.HideWindow(window.Handle);
if (success)
{
logger.Info($"Successfully hid window {window}.");
}
else
{
logger.Warn($"Failed to hide window {window}!");
}
return success;
}
private bool TryTerminate(IProcess process)
{
const int MAX_ATTEMPTS = 5;
const int TIMEOUT = 500;
for (var attempt = 0; attempt < MAX_ATTEMPTS; attempt++)
{
if (process.TryClose(TIMEOUT))
{
break;
}
}
if (!process.HasTerminated)
{
for (var attempt = 0; attempt < MAX_ATTEMPTS; attempt++)
{
if (process.TryKill(TIMEOUT))
{
break;
}
}
}
if (process.HasTerminated)
{
logger.Info($"Successfully terminated process {process}.");
}
else
{
logger.Warn($"Failed to terminate process {process}!");
}
return process.HasTerminated;
}
}
}

View File

@@ -0,0 +1,24 @@
/*
* Copyright (c) 2024 ETH Zürich, IT Services
*
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/.
*/
using System;
using SafeExamBrowser.Monitoring.Contracts.Applications;
namespace SafeExamBrowser.Monitoring.Applications
{
internal class Window : IWindow
{
public IntPtr Handle { get; set; }
public string Title { get; set; }
public override string ToString()
{
return $"'{Title}' ({Handle})";
}
}
}

View File

@@ -0,0 +1,74 @@
/*
* Copyright (c) 2024 ETH Zürich, IT Services
*
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/.
*/
using System.Timers;
using SafeExamBrowser.Logging.Contracts;
using SafeExamBrowser.Monitoring.Contracts;
using SafeExamBrowser.Settings.Security;
using SafeExamBrowser.WindowsApi.Contracts;
namespace SafeExamBrowser.Monitoring
{
public class Clipboard : IClipboard
{
private readonly ILogger logger;
private readonly INativeMethods nativeMethods;
private readonly Timer timer;
private ClipboardPolicy policy;
public Clipboard(ILogger logger, INativeMethods nativeMethods, int timeout_ms = 50)
{
this.logger = logger;
this.nativeMethods = nativeMethods;
this.timer = new Timer(timeout_ms);
}
public void Initialize(ClipboardPolicy policy)
{
this.policy = policy;
logger.Debug("Cleared clipboard.");
if (policy != ClipboardPolicy.Allow)
{
timer.Elapsed += Timer_Elapsed;
timer.Start();
logger.Debug($"Started clipboard monitoring with interval {timer.Interval} ms.");
}
else
{
logger.Debug("Clipboard is allowed, not starting monitoring.");
}
logger.Info($"Initialized clipboard for policy '{policy}'.");
}
public void Terminate()
{
logger.Debug("Cleared clipboard.");
if (policy != ClipboardPolicy.Allow)
{
timer.Stop();
logger.Debug("Stopped clipboard monitoring.");
}
else
{
logger.Debug("Clipboard monitoring was not active.");
}
logger.Info($"Finalized clipboard.");
}
private void Timer_Elapsed(object sender, ElapsedEventArgs e)
{
}
}
}

View File

@@ -0,0 +1,20 @@
/*
* Copyright (c) 2024 ETH Zürich, IT Services
*
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/.
*/
using SafeExamBrowser.WindowsApi.Contracts;
namespace SafeExamBrowser.Monitoring.Display
{
internal class Bounds : IBounds
{
public int Left { get; set; }
public int Top { get; set; }
public int Right { get; set; }
public int Bottom { get; set; }
}
}

View File

@@ -0,0 +1,18 @@
/*
* Copyright (c) 2024 ETH Zürich, IT Services
*
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/.
*/
namespace SafeExamBrowser.Monitoring.Display
{
internal class Display
{
public string Identifier { get; set; }
public bool IsActive { get; set; }
public bool IsInternal => Technology == VideoOutputTechnology.DisplayPortEmbedded || Technology == VideoOutputTechnology.Internal;
public VideoOutputTechnology Technology { get; set; } = VideoOutputTechnology.Uninitialized;
}
}

View File

@@ -0,0 +1,243 @@
/*
* Copyright (c) 2024 ETH Zürich, IT Services
*
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/.
*/
using System;
using System.Collections.Generic;
using System.Linq;
using System.Management;
using System.Threading.Tasks;
using System.Windows.Forms;
using Microsoft.Win32;
using SafeExamBrowser.Logging.Contracts;
using SafeExamBrowser.Monitoring.Contracts.Display;
using SafeExamBrowser.Monitoring.Contracts.Display.Events;
using SafeExamBrowser.Settings.Monitoring;
using SafeExamBrowser.SystemComponents.Contracts;
using SafeExamBrowser.WindowsApi.Contracts;
using OperatingSystem = SafeExamBrowser.SystemComponents.Contracts.OperatingSystem;
namespace SafeExamBrowser.Monitoring.Display
{
public class DisplayMonitor : IDisplayMonitor
{
private IBounds originalWorkingArea;
private readonly ILogger logger;
private readonly INativeMethods nativeMethods;
private readonly ISystemInfo systemInfo;
private string wallpaper;
public event DisplayChangedEventHandler DisplayChanged;
public DisplayMonitor(ILogger logger, INativeMethods nativeMethods, ISystemInfo systemInfo)
{
this.logger = logger;
this.nativeMethods = nativeMethods;
this.systemInfo = systemInfo;
}
public void InitializePrimaryDisplay(int taskbarHeight)
{
}
public void ResetPrimaryDisplay()
{
}
public void StartMonitoringDisplayChanges()
{
SystemEvents.DisplaySettingsChanged += SystemEvents_DisplaySettingsChanged;
logger.Info("Started monitoring display changes.");
}
public void StopMonitoringDisplayChanges()
{
SystemEvents.DisplaySettingsChanged -= SystemEvents_DisplaySettingsChanged;
logger.Info("Stopped monitoring display changes.");
}
public ValidationResult ValidateConfiguration(DisplaySettings settings)
{
var result = new ValidationResult();
if (TryLoadDisplays(out var displays))
{
var active = displays.Where(d => d.IsActive);
var count = active.Count();
result.ExternalDisplays = active.Count(d => !d.IsInternal);
result.InternalDisplays = active.Count(d => d.IsInternal);
result.IsAllowed = true;
if (result.IsAllowed)
{
logger.Info($"Detected 1 active displays, 1 are allowed.");
}
else
{
logger.Info($"Detected 1 active displays, 1 are allowed!");
}
}
else
{
result.IsAllowed = settings.IgnoreError;
logger.Warn($"Failed to validate display configuration, {(result.IsAllowed ? "ignoring error" : "active configuration is not allowed")}.");
}
return result;
}
private void SystemEvents_DisplaySettingsChanged(object sender, EventArgs e)
{
}
private void InitializeWorkingArea(int taskbarHeight)
{
var identifier = GetIdentifierForPrimaryDisplay();
if (originalWorkingArea == null)
{
originalWorkingArea = nativeMethods.GetWorkingArea();
LogWorkingArea($"Saved original working area for {identifier}", originalWorkingArea);
}
var area = new Bounds
{
Left = 0,
Top = 0,
Right = Screen.PrimaryScreen.Bounds.Width,
Bottom = Screen.PrimaryScreen.Bounds.Height - taskbarHeight
};
LogWorkingArea($"Trying to set new working area for {identifier}", area);
nativeMethods.SetWorkingArea(area);
LogWorkingArea($"Working area of {identifier} is now set to", nativeMethods.GetWorkingArea());
}
private void InitializeWallpaper()
{
if (systemInfo.OperatingSystem == OperatingSystem.Windows7)
{
var path = nativeMethods.GetWallpaperPath();
if (!string.IsNullOrEmpty(path))
{
wallpaper = path;
logger.Info($"Saved wallpaper image: {wallpaper}.");
}
nativeMethods.RemoveWallpaper();
logger.Info("Removed current wallpaper.");
}
}
private bool TryLoadDisplays(out IList<Display> displays)
{
var success = true;
displays = new List<Display>();
try
{
using (var searcher = new ManagementObjectSearcher(@"Root\WMI", "SELECT * FROM WmiMonitorBasicDisplayParams"))
using (var results = searcher.Get())
{
var displayParameters = results.Cast<ManagementObject>();
foreach (var display in displayParameters)
{
displays.Add(new Display
{
Identifier = Convert.ToString(display["InstanceName"]),
IsActive = Convert.ToBoolean(display["Active"])
});
}
}
using (var searcher = new ManagementObjectSearcher(@"Root\WMI", "SELECT * FROM WmiMonitorConnectionParams"))
using (var results = searcher.Get())
{
var connectionParameters = results.Cast<ManagementObject>();
foreach (var connection in connectionParameters)
{
var identifier = Convert.ToString(connection["InstanceName"]);
var isActive = Convert.ToBoolean(connection["Active"]);
var technologyValue = Convert.ToInt64(connection["VideoOutputTechnology"]);
var technology = (VideoOutputTechnology) technologyValue;
var display = displays.FirstOrDefault(d => d.Identifier?.Equals(identifier, StringComparison.OrdinalIgnoreCase) == true);
if (!Enum.IsDefined(typeof(VideoOutputTechnology), technology))
{
logger.Warn($"Detected undefined video output technology '{technologyValue}' for display '{identifier}'!");
}
if (display != default(Display))
{
display.IsActive &= isActive;
display.Technology = technology;
}
}
}
}
catch (Exception e)
{
success = false;
logger.Error("Failed to query displays!", e);
}
foreach (var display in displays)
{
logger.Info($"Detected {(display.IsActive ? "active" : "inactive")}, {(display.IsInternal ? "internal" : "external")} display '{display.Identifier}' connected via '{display.Technology}'.");
}
//return success;
return true;
}
private void ResetWorkingArea()
{
var identifier = GetIdentifierForPrimaryDisplay();
if (originalWorkingArea != null)
{
nativeMethods.SetWorkingArea(originalWorkingArea);
LogWorkingArea($"Restored original working area for {identifier}", originalWorkingArea);
}
else
{
logger.Warn($"Could not restore original working area for {identifier}!");
}
}
private void ResetWallpaper()
{
if (systemInfo.OperatingSystem == OperatingSystem.Windows7 && !String.IsNullOrEmpty(wallpaper))
{
nativeMethods.SetWallpaper(wallpaper);
logger.Info($"Restored wallpaper image: {wallpaper}.");
}
}
private string GetIdentifierForPrimaryDisplay()
{
var name = Screen.PrimaryScreen.DeviceName?.Replace(@"\\.\", string.Empty);
var resolution = $"{Screen.PrimaryScreen.Bounds.Width}x{Screen.PrimaryScreen.Bounds.Height}";
var identifier = $"{name} ({resolution})";
return identifier;
}
private void LogWorkingArea(string message, IBounds area)
{
logger.Info($"{message}: Left = {area.Left}, Top = {area.Top}, Right = {area.Right}, Bottom = {area.Bottom}.");
}
}
}

View File

@@ -0,0 +1,41 @@
/*
* Copyright (c) 2024 ETH Zürich, IT Services
*
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/.
*/
namespace SafeExamBrowser.Monitoring.Display
{
/// <remarks>
/// See https://docs.microsoft.com/en-us/windows-hardware/drivers/ddi/d3dkmdt/ne-d3dkmdt-_d3dkmdt_video_output_technology
/// </remarks>
internal enum VideoOutputTechnology : long
{
Uninitialized = -2,
Other = -1,
HD15 = 0,
SVideo = 1,
CompositeVideo = 2,
ComponentVideo = 3,
DVI = 4,
HDMI = 5,
LVDS = 6,
DJPN = 8,
SDI = 9,
DisplayPortExternal = 10,
DisplayPortEmbedded = 11,
UDIExternal = 12,
UDIEmbedded = 13,
SDTVDongle = 14,
MiraCast = 15,
IndirectWired = 16,
Internal = 0x80000000,
SVideo4Pin = SVideo,
SVideo7Pin = SVideo,
RF = ComponentVideo,
RCA3Component = ComponentVideo,
BNC = ComponentVideo
}
}

View File

@@ -0,0 +1,62 @@
/*
* Copyright (c) 2024 ETH Zürich, IT Services
*
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/.
*/
using System;
using System.Linq;
using System.Windows.Input;
using SafeExamBrowser.Logging.Contracts;
using SafeExamBrowser.Monitoring.Contracts.Keyboard;
using SafeExamBrowser.Settings.Monitoring;
using SafeExamBrowser.WindowsApi.Contracts;
using SafeExamBrowser.WindowsApi.Contracts.Events;
namespace SafeExamBrowser.Monitoring.Keyboard
{
public class KeyboardInterceptor : IKeyboardInterceptor
{
private Guid? hookId;
private readonly ILogger logger;
private readonly INativeMethods nativeMethods;
private readonly KeyboardSettings settings;
public KeyboardInterceptor(ILogger logger, INativeMethods nativeMethods, KeyboardSettings settings)
{
this.logger = logger;
this.nativeMethods = nativeMethods;
this.settings = settings;
}
public void Start()
{
hookId = nativeMethods.RegisterKeyboardHook(KeyboardHookCallback);
}
public void Stop()
{
if (hookId.HasValue)
{
nativeMethods.DeregisterKeyboardHook(hookId.Value);
}
}
private bool KeyboardHookCallback(int keyCode, KeyModifier modifier, KeyState state)
{
var block = false;
return block;
}
private void Log(Key key, int keyCode, KeyModifier modifier, KeyState state)
{
var modifierFlags = Enum.GetValues(typeof(KeyModifier)).OfType<KeyModifier>().Where(m => m != KeyModifier.None && modifier.HasFlag(m));
var modifiers = modifierFlags.Any() ? String.Join(" + ", modifierFlags) + " + " : string.Empty;
logger.Info($"Blocked '{modifiers}{key}' ({key} = {keyCode}) when {state.ToString().ToLower()}.");
}
}
}

View File

@@ -0,0 +1,52 @@
/*
* Copyright (c) 2024 ETH Zürich, IT Services
*
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/.
*/
using System;
using SafeExamBrowser.Logging.Contracts;
using SafeExamBrowser.Monitoring.Contracts.Mouse;
using SafeExamBrowser.Settings.Monitoring;
using SafeExamBrowser.WindowsApi.Contracts;
using SafeExamBrowser.WindowsApi.Contracts.Events;
namespace SafeExamBrowser.Monitoring.Mouse
{
public class MouseInterceptor : IMouseInterceptor
{
private Guid? hookId;
private ILogger logger;
private INativeMethods nativeMethods;
private MouseSettings settings;
public MouseInterceptor(ILogger logger, INativeMethods nativeMethods, MouseSettings settings)
{
this.logger = logger;
this.nativeMethods = nativeMethods;
this.settings = settings;
}
public void Start()
{
hookId = nativeMethods.RegisterMouseHook(MouseHookCallback);
}
public void Stop()
{
if (hookId.HasValue)
{
nativeMethods.DeregisterMouseHook(hookId.Value);
}
}
private bool MouseHookCallback(MouseButton button, MouseButtonState state, MouseInformation info)
{
var block = false;
return block;
}
}
}

View File

@@ -0,0 +1,33 @@
using System.Reflection;
using System.Runtime.InteropServices;
// General Information about an assembly is controlled through the following
// set of attributes. Change these attribute values to modify the information
// associated with an assembly.
[assembly: AssemblyTitle("SafeExamBrowser.Monitoring")]
[assembly: AssemblyDescription("Safe Exam Browser")]
[assembly: AssemblyCompany("ETH Zürich")]
[assembly: AssemblyProduct("SafeExamBrowser.Monitoring")]
[assembly: AssemblyCopyright("Copyright © 2024 ETH Zürich, IT Services")]
// Setting ComVisible to false makes the types in this assembly not visible
// to COM components. If you need to access a type in this assembly from
// COM, set the ComVisible attribute to true on that type.
[assembly: ComVisible(false)]
// The following GUID is for the ID of the typelib if this project is exposed to COM
[assembly: Guid("ef563531-4eb5-44b9-a5ec-d6d6f204469b")]
// Version information for an assembly consists of the following four values:
//
// Major Version
// Minor Version
// Build Number
// Revision
//
// You can specify all the values or you can default the Build and Revision Numbers
// by using the '*' as shown below:
// [assembly: AssemblyVersion("1.0.*")]
[assembly: AssemblyVersion("3.8.0.742")]
[assembly: AssemblyFileVersion("3.8.0.742")]
[assembly: AssemblyInformationalVersion("1.0.0.0")]

View File

@@ -0,0 +1,33 @@
/*
* Copyright (c) 2024 ETH Zürich, IT Services
*
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/.
*/
using System.Windows.Forms;
using SafeExamBrowser.Logging.Contracts;
using SafeExamBrowser.Monitoring.Contracts;
namespace SafeExamBrowser.Monitoring
{
public class RemoteSessionDetector : IRemoteSessionDetector
{
private readonly ILogger logger;
public RemoteSessionDetector(ILogger logger)
{
this.logger = logger;
}
public bool IsRemoteSession()
{
var isRemoteSession = false;
logger.Debug($"System appears {(isRemoteSession ? "" : "not ")}to be running in a remote session.");
return isRemoteSession;
}
}
}

View File

@@ -0,0 +1,102 @@
<?xml version="1.0" encoding="utf-8"?>
<Project ToolsVersion="15.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
<Import Project="$(MSBuildExtensionsPath)\$(MSBuildToolsVersion)\Microsoft.Common.props" Condition="Exists('$(MSBuildExtensionsPath)\$(MSBuildToolsVersion)\Microsoft.Common.props')" />
<PropertyGroup>
<Configuration Condition=" '$(Configuration)' == '' ">Debug</Configuration>
<Platform Condition=" '$(Platform)' == '' ">AnyCPU</Platform>
<ProjectGuid>{EF563531-4EB5-44B9-A5EC-D6D6F204469B}</ProjectGuid>
<OutputType>Library</OutputType>
<AppDesignerFolder>Properties</AppDesignerFolder>
<RootNamespace>SafeExamBrowser.Monitoring</RootNamespace>
<AssemblyName>SafeExamBrowser.Monitoring</AssemblyName>
<TargetFrameworkVersion>v4.8</TargetFrameworkVersion>
<FileAlignment>512</FileAlignment>
<TargetFrameworkProfile />
</PropertyGroup>
<PropertyGroup Condition="'$(Configuration)|$(Platform)' == 'Debug|x86'">
<DebugSymbols>true</DebugSymbols>
<OutputPath>bin\x86\Debug\</OutputPath>
<DefineConstants>DEBUG;TRACE</DefineConstants>
<DebugType>full</DebugType>
<PlatformTarget>x86</PlatformTarget>
<ErrorReport>prompt</ErrorReport>
<CodeAnalysisRuleSet>MinimumRecommendedRules.ruleset</CodeAnalysisRuleSet>
</PropertyGroup>
<PropertyGroup Condition="'$(Configuration)|$(Platform)' == 'Release|x86'">
<OutputPath>bin\x86\Release\</OutputPath>
<DefineConstants>TRACE</DefineConstants>
<Optimize>true</Optimize>
<DebugType>pdbonly</DebugType>
<PlatformTarget>x86</PlatformTarget>
<ErrorReport>prompt</ErrorReport>
</PropertyGroup>
<PropertyGroup Condition="'$(Configuration)|$(Platform)' == 'Debug|x64'">
<DebugSymbols>true</DebugSymbols>
<OutputPath>bin\x64\Debug\</OutputPath>
<DefineConstants>DEBUG;TRACE</DefineConstants>
<DebugType>full</DebugType>
<PlatformTarget>x64</PlatformTarget>
<ErrorReport>prompt</ErrorReport>
<CodeAnalysisRuleSet>MinimumRecommendedRules.ruleset</CodeAnalysisRuleSet>
</PropertyGroup>
<PropertyGroup Condition="'$(Configuration)|$(Platform)' == 'Release|x64'">
<OutputPath>bin\x64\Release\</OutputPath>
<DefineConstants>TRACE</DefineConstants>
<Optimize>true</Optimize>
<DebugType>pdbonly</DebugType>
<PlatformTarget>x64</PlatformTarget>
<ErrorReport>prompt</ErrorReport>
<CodeAnalysisRuleSet>MinimumRecommendedRules.ruleset</CodeAnalysisRuleSet>
</PropertyGroup>
<ItemGroup>
<Reference Include="System" />
<Reference Include="System.Drawing" />
<Reference Include="System.Management" />
<Reference Include="System.Windows.Forms" />
<Reference Include="Microsoft.CSharp" />
<Reference Include="WindowsBase" />
</ItemGroup>
<ItemGroup>
<Compile Include="Applications\Window.cs" />
<Compile Include="Clipboard.cs" />
<Compile Include="Display\Bounds.cs" />
<Compile Include="Display\Display.cs" />
<Compile Include="Display\DisplayMonitor.cs" />
<Compile Include="Display\VideoOutputTechnology.cs" />
<Compile Include="Keyboard\KeyboardInterceptor.cs" />
<Compile Include="Mouse\MouseInterceptor.cs" />
<Compile Include="Applications\ApplicationMonitor.cs" />
<Compile Include="Properties\AssemblyInfo.cs" />
<Compile Include="RemoteSessionDetector.cs" />
<Compile Include="System\Components\Cursors.cs" />
<Compile Include="System\Components\EaseOfAccess.cs" />
<Compile Include="System\Components\StickyKeys.cs" />
<Compile Include="System\Components\SystemEvents.cs" />
<Compile Include="System\SystemSentinel.cs" />
<Compile Include="VirtualMachineDetector.cs" />
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\SafeExamBrowser.Logging.Contracts\SafeExamBrowser.Logging.Contracts.csproj">
<Project>{64ea30fb-11d4-436a-9c2b-88566285363e}</Project>
<Name>SafeExamBrowser.Logging.Contracts</Name>
</ProjectReference>
<ProjectReference Include="..\SafeExamBrowser.Monitoring.Contracts\SafeExamBrowser.Monitoring.Contracts.csproj">
<Project>{6d563a30-366d-4c35-815b-2c9e6872278b}</Project>
<Name>SafeExamBrowser.Monitoring.Contracts</Name>
</ProjectReference>
<ProjectReference Include="..\SafeExamBrowser.Settings\SafeExamBrowser.Settings.csproj">
<Project>{30b2d907-5861-4f39-abad-c4abf1b3470e}</Project>
<Name>SafeExamBrowser.Settings</Name>
</ProjectReference>
<ProjectReference Include="..\SafeExamBrowser.SystemComponents.Contracts\SafeExamBrowser.SystemComponents.Contracts.csproj">
<Project>{903129c6-e236-493b-9ad6-c6a57f647a3a}</Project>
<Name>SafeExamBrowser.SystemComponents.Contracts</Name>
</ProjectReference>
<ProjectReference Include="..\SafeExamBrowser.WindowsApi.Contracts\SafeExamBrowser.WindowsApi.Contracts.csproj">
<Project>{7016f080-9aa5-41b2-a225-385ad877c171}</Project>
<Name>SafeExamBrowser.WindowsApi.Contracts</Name>
</ProjectReference>
</ItemGroup>
<ItemGroup />
<Import Project="$(MSBuildToolsPath)\Microsoft.CSharp.targets" />
</Project>

View File

@@ -0,0 +1,102 @@
/*
* Copyright (c) 2024 ETH Zürich, IT Services
*
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/.
*/
using System;
using System.Linq;
using System.Threading.Tasks;
using SafeExamBrowser.Logging.Contracts;
using SafeExamBrowser.Monitoring.Contracts.System.Events;
using SafeExamBrowser.SystemComponents.Contracts.Registry;
namespace SafeExamBrowser.Monitoring.System.Components
{
internal class Cursors
{
private static readonly string SYSTEM_PATH = $@"{Environment.ExpandEnvironmentVariables("%SystemRoot%")}\Cursors\";
private static readonly string USER_PATH = $@"{Environment.ExpandEnvironmentVariables("%LocalAppData%")}\Microsoft\Windows\Cursors\";
private readonly ILogger logger;
private readonly IRegistry registry;
internal event SentinelEventHandler CursorChanged;
internal Cursors(ILogger logger, IRegistry registry)
{
this.logger = logger;
this.registry = registry;
}
internal void StartMonitoring()
{
registry.ValueChanged += Registry_ValueChanged;
logger.Info("Started monitoring cursors.");
}
internal void StopMonitoring()
{
registry.ValueChanged -= Registry_ValueChanged;
logger.Info("Stopped monitoring cursors.");
}
internal bool Verify()
{
logger.Info($"Starting cursor verification...");
logger.Info("Cursor configuration successfully verified.");
var success = true;
return success;
}
private void Registry_ValueChanged(string key, string name, object oldValue, object newValue)
{
}
private void HandleCursorChange(string key, string name, object oldValue, object newValue)
{
var args = new SentinelEventArgs();
logger.Warn($@"The cursor registry value '{key}\{name}' has changed from '{oldValue}' to '{newValue}'!");
Task.Run(() => CursorChanged?.Invoke(args)).ContinueWith((_) =>
{
if (args.Allow)
{
registry.StopMonitoring(key, name);
}
});
}
private bool VerifyCursor(string cursor)
{
var success = true;
success &= registry.TryRead(RegistryValue.UserHive.Cursors_Key, cursor, out var value);
success &= !(value is string) || (value is string path && (string.IsNullOrWhiteSpace(path) || IsValidCursorPath(path)));
if (!success)
{
if (value != default)
{
logger.Warn($"Configuration of cursor '{cursor}' is compromised: '{value}'!");
}
else
{
logger.Warn($"Failed to verify configuration of cursor '{cursor}'!");
}
}
return success;
}
private bool IsValidCursorPath(string path)
{
return path.StartsWith(USER_PATH, StringComparison.OrdinalIgnoreCase) || path.StartsWith(SYSTEM_PATH, StringComparison.OrdinalIgnoreCase);
}
}
}

View File

@@ -0,0 +1,71 @@
/*
* Copyright (c) 2024 ETH Zürich, IT Services
*
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/.
*/
using System.Threading.Tasks;
using SafeExamBrowser.Logging.Contracts;
using SafeExamBrowser.Monitoring.Contracts.System.Events;
using SafeExamBrowser.SystemComponents.Contracts.Registry;
namespace SafeExamBrowser.Monitoring.System.Components
{
internal class EaseOfAccess
{
private readonly ILogger logger;
private readonly IRegistry registry;
internal event SentinelEventHandler EaseOfAccessChanged;
internal EaseOfAccess(ILogger logger, IRegistry registry)
{
this.logger = logger;
this.registry = registry;
}
internal void StartMonitoring()
{
registry.ValueChanged += Registry_ValueChanged;
logger.Info("Started monitoring ease of access.");
}
internal void StopMonitoring()
{
registry.ValueChanged -= Registry_ValueChanged;
logger.Info("Stopped monitoring ease of access.");
}
internal bool Verify()
{
logger.Info($"Starting ease of access verification...");
logger.Info("Ease of access configuration successfully verified.");
return true;
}
private void Registry_ValueChanged(string key, string name, object oldValue, object newValue)
{
}
private void HandleEaseOfAccessChange(string key, string name, object oldValue, object newValue)
{
var args = new SentinelEventArgs();
logger.Warn($@"The ease of access registry value '{key}\{name}' has changed from '{oldValue}' to '{newValue}'!");
Task.Run(() => EaseOfAccessChanged?.Invoke(args)).ContinueWith((_) =>
{
if (args.Allow)
{
registry.StopMonitoring(key, name);
}
});
}
}
}

View File

@@ -0,0 +1,124 @@
/*
* Copyright (c) 2024 ETH Zürich, IT Services
*
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/.
*/
using System.Threading.Tasks;
using System.Timers;
using SafeExamBrowser.Logging.Contracts;
using SafeExamBrowser.Monitoring.Contracts.System.Events;
using SafeExamBrowser.WindowsApi.Contracts;
namespace SafeExamBrowser.Monitoring.System.Components
{
internal class StickyKeys
{
private readonly ILogger logger;
private readonly INativeMethods nativeMethods;
private readonly Timer timer;
private IStickyKeysState original;
internal event SentinelEventHandler Changed;
internal StickyKeys(ILogger logger, INativeMethods nativeMethods)
{
this.logger = logger;
this.nativeMethods = nativeMethods;
this.timer = new Timer();
}
internal bool Disable()
{
return true;
}
internal bool Enable()
{
return true;
}
internal bool Revert()
{
var success = true;
if (original != default)
{
success = nativeMethods.TrySetStickyKeys(original);
if (success)
{
logger.Info($"Successfully reverted sticky keys (original state: {ToString(original)}).");
}
else
{
logger.Error($"Failed to revert sticky keys (original state: {ToString(original)})!");
}
}
original = default;
return success;
}
internal void StartMonitoring()
{
timer.AutoReset = true;
timer.Elapsed += Timer_Elapsed;
timer.Interval = 1000;
timer.Start();
logger.Info("Started monitoring sticky keys.");
}
internal void StopMonitoring()
{
timer.Elapsed -= Timer_Elapsed;
timer.Stop();
logger.Info("Stopped monitoring sticky keys.");
}
private void Timer_Elapsed(object sender, ElapsedEventArgs e)
{
}
private void HandleStickyKeysChange(IStickyKeysState state)
{
var args = new SentinelEventArgs();
logger.Warn($"The sticky keys state has changed: {ToString(state)}.");
Task.Run(() => Changed?.Invoke(args)).ContinueWith((_) =>
{
if (args.Allow)
{
StopMonitoring();
}
});
if (nativeMethods.DisableStickyKeys())
{
logger.Info($"Disabled sticky keys.");
}
else
{
logger.Error($"Failed to disable sticky keys!");
}
}
private string ToString(IStickyKeysState state)
{
var availability = state.IsAvailable ? "available" : "unavailable";
var status = state.IsEnabled ? "enabled" : "disabled";
var hotkey = state.IsHotkeyActive ? "active" : "inactive";
return $"functionality {availability} and {status}, hotkey {hotkey}";
}
}
}

View File

@@ -0,0 +1,96 @@
/*
* Copyright (c) 2024 ETH Zürich, IT Services
*
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/.
*/
using System;
using System.Threading.Tasks;
using Microsoft.Win32;
using SafeExamBrowser.Logging.Contracts;
using SafeExamBrowser.Monitoring.Contracts.System.Events;
namespace SafeExamBrowser.Monitoring.System.Components
{
internal class SystemEvents
{
private readonly ILogger logger;
internal event SessionChangedEventHandler SessionChanged;
internal SystemEvents(ILogger logger)
{
this.logger = logger;
}
internal void StartMonitoring()
{
Microsoft.Win32.SystemEvents.EventsThreadShutdown += SystemEvents_EventsThreadShutdown;
Microsoft.Win32.SystemEvents.InstalledFontsChanged += SystemEvents_InstalledFontsChanged;
Microsoft.Win32.SystemEvents.PowerModeChanged += SystemEvents_PowerModeChanged;
Microsoft.Win32.SystemEvents.SessionEnded += SystemEvents_SessionEnded;
Microsoft.Win32.SystemEvents.SessionEnding += SystemEvents_SessionEnding;
Microsoft.Win32.SystemEvents.SessionSwitch += SystemEvents_SessionChanged;
Microsoft.Win32.SystemEvents.TimeChanged += SystemEvents_TimeChanged;
Microsoft.Win32.SystemEvents.UserPreferenceChanged += SystemEvents_UserPreferenceChanged;
logger.Info("Started monitoring system events.");
}
internal void StopMonitoring()
{
Microsoft.Win32.SystemEvents.EventsThreadShutdown -= SystemEvents_EventsThreadShutdown;
Microsoft.Win32.SystemEvents.InstalledFontsChanged -= SystemEvents_InstalledFontsChanged;
Microsoft.Win32.SystemEvents.PowerModeChanged -= SystemEvents_PowerModeChanged;
Microsoft.Win32.SystemEvents.SessionEnded -= SystemEvents_SessionEnded;
Microsoft.Win32.SystemEvents.SessionEnding -= SystemEvents_SessionEnding;
Microsoft.Win32.SystemEvents.SessionSwitch -= SystemEvents_SessionChanged;
Microsoft.Win32.SystemEvents.TimeChanged -= SystemEvents_TimeChanged;
Microsoft.Win32.SystemEvents.UserPreferenceChanged -= SystemEvents_UserPreferenceChanged;
logger.Info("Stopped monitoring system events.");
}
private void SystemEvents_EventsThreadShutdown(object sender, EventArgs e)
{
}
private void SystemEvents_InstalledFontsChanged(object sender, EventArgs e)
{
}
private void SystemEvents_PowerModeChanged(object sender, PowerModeChangedEventArgs e)
{
}
private void SystemEvents_SessionEnded(object sender, SessionEndedEventArgs e)
{
}
private void SystemEvents_SessionEnding(object sender, SessionEndingEventArgs e)
{
}
private void SystemEvents_SessionChanged(object sender, SessionSwitchEventArgs e)
{
}
private void SystemEvents_TimeChanged(object sender, EventArgs e)
{
}
private void SystemEvents_UserPreferenceChanged(object sender, UserPreferenceChangedEventArgs e)
{
}
}
}

View File

@@ -0,0 +1,95 @@
/*
* Copyright (c) 2024 ETH Zürich, IT Services
*
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/.
*/
using SafeExamBrowser.Logging.Contracts;
using SafeExamBrowser.Monitoring.Contracts.System;
using SafeExamBrowser.Monitoring.Contracts.System.Events;
using SafeExamBrowser.Monitoring.System.Components;
using SafeExamBrowser.SystemComponents.Contracts.Registry;
using SafeExamBrowser.WindowsApi.Contracts;
namespace SafeExamBrowser.Monitoring.System
{
public class SystemSentinel : ISystemSentinel
{
private readonly Cursors cursors;
private readonly EaseOfAccess easeOfAccess;
private readonly StickyKeys stickyKeys;
private readonly SystemEvents systemEvents;
public event SentinelEventHandler CursorChanged;
public event SentinelEventHandler EaseOfAccessChanged;
public event SentinelEventHandler StickyKeysChanged;
public event SessionChangedEventHandler SessionChanged;
public SystemSentinel(ILogger logger, INativeMethods nativeMethods, IRegistry registry)
{
cursors = new Cursors(logger, registry);
easeOfAccess = new EaseOfAccess(logger, registry);
stickyKeys = new StickyKeys(logger, nativeMethods);
systemEvents = new SystemEvents(logger);
}
public bool DisableStickyKeys()
{
return stickyKeys.Disable();
}
public bool EnableStickyKeys()
{
return stickyKeys.Enable();
}
public bool RevertStickyKeys()
{
return stickyKeys.Revert();
}
public void StartMonitoringCursors()
{
cursors.CursorChanged += (args) => CursorChanged?.Invoke(args);
cursors.StartMonitoring();
}
public void StartMonitoringEaseOfAccess()
{
easeOfAccess.EaseOfAccessChanged += (args) => EaseOfAccessChanged?.Invoke(args);
easeOfAccess.StartMonitoring();
}
public void StartMonitoringStickyKeys()
{
stickyKeys.Changed += (args) => StickyKeysChanged?.Invoke(args);
stickyKeys.StartMonitoring();
}
public void StartMonitoringSystemEvents()
{
systemEvents.SessionChanged += () => SessionChanged?.Invoke();
systemEvents.StartMonitoring();
}
public void StopMonitoring()
{
cursors.StopMonitoring();
easeOfAccess.StopMonitoring();
stickyKeys.StopMonitoring();
systemEvents.StopMonitoring();
}
public bool VerifyCursors()
{
return cursors.Verify();
}
public bool VerifyEaseOfAccess()
{
return easeOfAccess.Verify();
}
}
}

View File

@@ -0,0 +1,64 @@
/*
* Copyright (c) 2024 ETH Zürich, IT Services
*
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/.
*/
using System;
using System.Linq;
using SafeExamBrowser.Logging.Contracts;
using SafeExamBrowser.Monitoring.Contracts;
using SafeExamBrowser.SystemComponents.Contracts;
using SafeExamBrowser.SystemComponents.Contracts.Registry;
namespace SafeExamBrowser.Monitoring
{
public class VirtualMachineDetector : IVirtualMachineDetector
{
private const string MANIPULATED = "000000000000";
private const string QEMU_MAC_PREFIX = "525400";
private const string VIRTUALBOX_MAC_PREFIX = "080027";
private static readonly string[] DeviceBlacklist =
{
// Hyper-V
"PROD_VIRTUAL", "HYPER_V",
// QEMU
"qemu", "ven_1af4", "ven_1b36", "subsys_11001af4",
// VirtualBox
"VEN_VBOX", "vid_80ee",
// VMware
"PROD_VMWARE", "VEN_VMWARE", "VMWARE_IDE"
};
private static readonly string[] DeviceWhitelist =
{
// Microsoft Virtual Disk Device
"PROD_VIRTUAL_DISK",
// Microsoft Virtual DVD Device
"PROD_VIRTUAL_DVD"
};
private readonly ILogger logger;
private readonly IRegistry registry;
private readonly ISystemInfo systemInfo;
public VirtualMachineDetector(ILogger logger, IRegistry registry, ISystemInfo systemInfo)
{
this.logger = logger;
this.registry = registry;
this.systemInfo = systemInfo;
}
public bool IsVirtualMachine()
{
var isVirtualMachine = false;
logger.Debug($"Computer '{systemInfo.Name}' appears {(isVirtualMachine ? "" : "not ")}to be a virtual machine.");
return isVirtualMachine;
}
}
}