diff --git a/SafeExamBrowser.Runtime/CompositionRoot.cs b/SafeExamBrowser.Runtime/CompositionRoot.cs index 7b3ca5bc..97c3b564 100644 --- a/SafeExamBrowser.Runtime/CompositionRoot.cs +++ b/SafeExamBrowser.Runtime/CompositionRoot.cs @@ -83,7 +83,7 @@ namespace SafeExamBrowser.Runtime var serviceProxy = new ServiceProxy(appConfig.ServiceAddress, new ProxyObjectFactory(), ModuleLogger(nameof(ServiceProxy)), Interlocutor.Runtime); var sessionContext = new SessionContext(); var splashScreen = uiFactory.CreateSplashScreen(appConfig); - var vmDetector = new VirtualMachineDetector(ModuleLogger(nameof(VirtualMachineDetector)), systemInfo); + var vmDetector = new VirtualMachineDetector(ModuleLogger(nameof(VirtualMachineDetector)), registry, systemInfo); var bootstrapOperations = new Queue(); var sessionOperations = new Queue(); diff --git a/SafeExamBrowser.SystemComponents.Contracts/Registry/IRegistry.cs b/SafeExamBrowser.SystemComponents.Contracts/Registry/IRegistry.cs index 25db0c17..da6aabcd 100644 --- a/SafeExamBrowser.SystemComponents.Contracts/Registry/IRegistry.cs +++ b/SafeExamBrowser.SystemComponents.Contracts/Registry/IRegistry.cs @@ -7,6 +7,7 @@ */ using SafeExamBrowser.SystemComponents.Contracts.Registry.Events; +using System.Collections.Generic; namespace SafeExamBrowser.SystemComponents.Contracts.Registry { @@ -34,5 +35,15 @@ namespace SafeExamBrowser.SystemComponents.Contracts.Registry /// Attempts to read the value of the given name under the specified registry key. /// bool TryRead(string key, string name, out object value); + + /// + /// Attempts to read the value names of the given registry key. + /// + bool TryGetNames(string key, out IEnumerable names); + + /// + /// Attempts to read the subkey names of the given registry key. + /// + bool TryGetSubKeys(string key, out IEnumerable subKeys); } } diff --git a/SafeExamBrowser.SystemComponents/Registry/Registry.cs b/SafeExamBrowser.SystemComponents/Registry/Registry.cs index cbbf958b..68d2f886 100644 --- a/SafeExamBrowser.SystemComponents/Registry/Registry.cs +++ b/SafeExamBrowser.SystemComponents/Registry/Registry.cs @@ -8,7 +8,10 @@ using System; using System.Collections.Concurrent; +using System.Collections.Generic; +using System.Security.Cryptography; using System.Timers; +using Microsoft.Win32; using SafeExamBrowser.Logging.Contracts; using SafeExamBrowser.SystemComponents.Contracts.Registry; using SafeExamBrowser.SystemComponents.Contracts.Registry.Events; @@ -86,6 +89,38 @@ namespace SafeExamBrowser.SystemComponents.Registry return success; } + public bool TryGetNames(string key, out IEnumerable names) + { + names = null; + + RegistryKey keyObj; + if (!TryOpenKey(key, out keyObj)) + return false; + + using (keyObj) + { + names = keyObj.GetValueNames(); + } + + return true; + } + + public bool TryGetSubKeys(string key, out IEnumerable subKeys) + { + subKeys = null; + + RegistryKey keyObj; + if (!TryOpenKey(key, out keyObj)) + return false; + + using (keyObj) + { + subKeys = keyObj.GetSubKeyNames(); + } + + return true; + } + private void Timer_Elapsed(object sender, ElapsedEventArgs e) { foreach (var item in values) @@ -104,5 +139,104 @@ namespace SafeExamBrowser.SystemComponents.Registry } } } + + /// + /// Parses a keyName and returns the basekey for it. + /// It will also store the subkey name in the out parameter. + /// If the keyName is not valid, we will return false. + /// Does not raise Exceptions. + /// Supports shortcuts. + /// + // yoinked (and partially modified to follow SEB conventions) private Win32 function: https://stackoverflow.com/a/58547945 + private bool GetBaseKeyFromKeyName(string keyName, out RegistryKey hiveKey, out string subKeyName) + { + hiveKey = null; + subKeyName = null; + + string basekeyName; + int i = keyName.IndexOf('\\'); + if (i != -1) + { + basekeyName = keyName.Substring(0, i).ToUpper(System.Globalization.CultureInfo.InvariantCulture); + } + else + { + basekeyName = keyName.ToUpper(System.Globalization.CultureInfo.InvariantCulture); + } + + // add shortcuts as well to be implicit + switch (basekeyName) + { + case "HKEY_CURRENT_USER": + case "HKCU": + hiveKey = RegistryKey.OpenBaseKey(RegistryHive.CurrentUser, RegistryView.Registry64); + break; + case "HKEY_LOCAL_MACHINE": + case "HKLM": + hiveKey = RegistryKey.OpenBaseKey(RegistryHive.LocalMachine, RegistryView.Registry64); + break; + case "HKEY_CLASSES_ROOT": + case "HKCR": + hiveKey = RegistryKey.OpenBaseKey(RegistryHive.ClassesRoot, RegistryView.Registry64); + break; + case "HKEY_USERS": + case "HKU": + hiveKey = RegistryKey.OpenBaseKey(RegistryHive.Users, RegistryView.Registry64); + break; + case "HKEY_PERFORMANCE_DATA": + case "HKPD": + hiveKey = RegistryKey.OpenBaseKey(RegistryHive.PerformanceData, RegistryView.Registry64); + break; + case "HKEY_CURRENT_CONFIG": + case "HKCC": + hiveKey = RegistryKey.OpenBaseKey(RegistryHive.CurrentConfig, RegistryView.Registry64); + break; + case "HKEY_DYN_DATA": + case "HKDD": + hiveKey = RegistryKey.OpenBaseKey(RegistryHive.DynData, RegistryView.Registry64); + break; + default: + // output is already set to null at the start + return false; + } + + if (i == -1 || i == keyName.Length) + { + subKeyName = string.Empty; + } + else + { + subKeyName = keyName.Substring(i + 1, keyName.Length - i - 1); + } + + return true; + } + + /// + /// Tries to open a key and outputs a RegistryKey object. Does not raise Exceptions, but returns false/true. + /// + private bool TryOpenKey(string key, out RegistryKey keyObj) + { + keyObj = null; + + string subHiveKey; + try + { + RegistryKey hiveObj; + if (!GetBaseKeyFromKeyName(key, out hiveObj, out subHiveKey)) + return false; + + keyObj = hiveObj.OpenSubKey(subHiveKey); + if (keyObj == null) + return false; + } + catch (Exception e) + { + logger.Error($"Failed to open registry key '{key}'!", e); + return false; + } + + return true; + } } } diff --git a/SafeExamBrowser.SystemComponents/VirtualMachineDetector.cs b/SafeExamBrowser.SystemComponents/VirtualMachineDetector.cs index 69a9e3fe..1cff1b81 100644 --- a/SafeExamBrowser.SystemComponents/VirtualMachineDetector.cs +++ b/SafeExamBrowser.SystemComponents/VirtualMachineDetector.cs @@ -10,7 +10,11 @@ using System.Linq; using System.Management; using SafeExamBrowser.Logging.Contracts; using SafeExamBrowser.SystemComponents.Contracts; +using SafeExamBrowser.SystemComponents.Contracts.Registry; using Microsoft.Win32; +using System.Collections; +using System.Collections.Generic; +using System; namespace SafeExamBrowser.SystemComponents { @@ -31,11 +35,13 @@ namespace SafeExamBrowser.SystemComponents private static readonly string VIRTUALBOX_MAC_PREFIX = "080027"; private readonly ILogger logger; + private readonly IRegistry registry; private readonly ISystemInfo systemInfo; - public VirtualMachineDetector(ILogger logger, ISystemInfo systemInfo) + public VirtualMachineDetector(ILogger logger, IRegistry registry, ISystemInfo systemInfo) { this.logger = logger; + this.registry = registry; this.systemInfo = systemInfo; } @@ -97,45 +103,70 @@ namespace SafeExamBrowser.SystemComponents { var isVirtualMachine = false; - // check historic hardware profiles - var hardwareConfKey = Microsoft.Win32.Registry.LocalMachine.OpenSubKey("SYSTEM\\HardwareConfig"); - if (hardwareConfKey != null) + /** + * check historic hardware profiles + * + * HKLM\SYSTEM\HardwareConfig\{configId=uuid}\ComputerIds + * - {computerId=uuid}: {computerSummary=hardwareInfo} + * + */ + IEnumerable hardwareConfigSubkeys; + if (!registry.TryGetSubKeys("HKLM\\SYSTEM\\HardwareConfig", out hardwareConfigSubkeys)) + return false; + + foreach (string configId in hardwareConfigSubkeys) { - foreach (string configId in hardwareConfKey.GetSubKeyNames()) + logger.Info($"scanning configId: {configId}"); + var configKey = $"HKEY_LOCAL_MACHINE\\SYSTEM\\HardwareConfig\\{configId}"; + + object biosVendor; + object biosVersion; + object systemManufacturer; + object systemProductName; + + bool success = true; + + success &= registry.TryRead(configKey, "BIOSVendor", out biosVendor); + success &= registry.TryRead(configKey, "BIOSVersion", out biosVersion); + success &= registry.TryRead(configKey, "SystemManufacturer", out systemManufacturer); + success &= registry.TryRead(configKey, "SystemProductName", out systemProductName); + + if (!success) + continue; + + // reconstruct the systemInfo.biosInfo string + string biosInfo = $"{(string) biosVendor} {(string) biosVersion}"; + + isVirtualMachine |= IsVirtualSystemInfo(biosInfo, (string) systemManufacturer, (string) systemProductName); + + // hardware information of profile throughout installation etc. + IEnumerable computerIds; + if (!registry.TryGetSubKeys($"HKLM\\SYSTEM\\HardwareConfig\\{configId}\\ComputerIds", out computerIds)) + return false; + + foreach (var computerId in computerIds) { - var configKey = Microsoft.Win32.Registry.LocalMachine.OpenSubKey($"SYSTEM\\HardwareConfig\\{configId}"); + logger.Info($"computerId: {computerId}"); + // e.g. manufacturer&version&sku&... + object computerSummary; // = (string) computerIds.GetValue(computerId); - if (configKey == null) - { + if (!registry.TryRead($"HKLM\\SYSTEM\\HardwareConfig\\{configId}\\ComputerIds", computerId, out computerSummary)) continue; - } - // reconstruct the systemInfo.biosInfo string - var biosInfo = (string) configKey.GetValue("BIOSVendor") + " " + (string) configKey.GetValue("BIOSVersion"); - var manufacturer = (string) configKey.GetValue("SystemManufacturer"); - var model = (string) configKey.GetValue("SystemProductName"); - - isVirtualMachine |= IsVirtualSystemInfo(biosInfo, manufacturer, model); - - // hardware information of profile throughout installation etc. - var computerIdsKey = Microsoft.Win32.Registry.LocalMachine.OpenSubKey($"SYSTEM\\HardwareConfig\\{configId}\\ComputerIds"); - - if (computerIdsKey == null) - { - continue; - } - - foreach (var computerId in computerIdsKey.GetSubKeyNames()) - { - // e.g. manufacturer&version&sku&... - var computerSummary = (string) computerIdsKey.GetValue(computerId); - isVirtualMachine |= IsVirtualSystemInfo(computerSummary, computerSummary, computerSummary); - } + isVirtualMachine |= IsVirtualSystemInfo((string) computerSummary, (string) systemManufacturer, (string) systemProductName); } } // check Windows timeline caches for current hardware config - var deviceCacheKey = Microsoft.Win32.Registry.CurrentUser.OpenSubKey($"SOFTWARE\\Microsoft\\Windows\\CurrentVersion\\TaskFlow\\DeviceCache"); + /*IEnumerable deviceCacheSubkeys; + if (registry.TryGetSubKeys($"HKCU\\SOFTWARE\\Microsoft\\Windows\\CurrentVersion\\TaskFlow\\DeviceCache", out deviceCacheSubkeys) + { + foreach (string deviceCacheKey in deviceCacheSubkeys) + { + if (registry.TryRead($"HKCU\\SOFTWARE\\Microsoft\\Windows\\CurrentVersion\\TaskFlow\\DeviceCache"))*/ + + + var deviceCacheKey = Microsoft.Win32.Registry.CurrentUser.OpenSubKey($"HKCU\\SOFTWARE\\Microsoft\\Windows\\CurrentVersion\\TaskFlow\\DeviceCache"); var currHostname = System.Environment.GetEnvironmentVariable("COMPUTERNAME"); if (deviceCacheKey != null && currHostname != null)