Merge pull request #597 from Notselwyn/ProxmoxVMDetection
Proxmox vm detection
This commit is contained in:
commit
bd3b348f6a
4 changed files with 321 additions and 14 deletions
|
@ -83,7 +83,7 @@ namespace SafeExamBrowser.Runtime
|
||||||
var serviceProxy = new ServiceProxy(appConfig.ServiceAddress, new ProxyObjectFactory(), ModuleLogger(nameof(ServiceProxy)), Interlocutor.Runtime);
|
var serviceProxy = new ServiceProxy(appConfig.ServiceAddress, new ProxyObjectFactory(), ModuleLogger(nameof(ServiceProxy)), Interlocutor.Runtime);
|
||||||
var sessionContext = new SessionContext();
|
var sessionContext = new SessionContext();
|
||||||
var splashScreen = uiFactory.CreateSplashScreen(appConfig);
|
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<IOperation>();
|
var bootstrapOperations = new Queue<IOperation>();
|
||||||
var sessionOperations = new Queue<IRepeatableOperation>();
|
var sessionOperations = new Queue<IRepeatableOperation>();
|
||||||
|
|
|
@ -7,6 +7,7 @@
|
||||||
*/
|
*/
|
||||||
|
|
||||||
using SafeExamBrowser.SystemComponents.Contracts.Registry.Events;
|
using SafeExamBrowser.SystemComponents.Contracts.Registry.Events;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
|
||||||
namespace SafeExamBrowser.SystemComponents.Contracts.Registry
|
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.
|
/// Attempts to read the value of the given name under the specified registry key.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
bool TryRead(string key, string name, out object value);
|
bool TryRead(string key, string name, out object value);
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Attempts to read the value names of the given registry key.
|
||||||
|
/// </summary>
|
||||||
|
bool TryGetNames(string key, out IEnumerable<string> names);
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Attempts to read the subkey names of the given registry key.
|
||||||
|
/// </summary>
|
||||||
|
bool TryGetSubKeys(string key, out IEnumerable<string> subKeys);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -8,7 +8,10 @@
|
||||||
|
|
||||||
using System;
|
using System;
|
||||||
using System.Collections.Concurrent;
|
using System.Collections.Concurrent;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Security.Cryptography;
|
||||||
using System.Timers;
|
using System.Timers;
|
||||||
|
using Microsoft.Win32;
|
||||||
using SafeExamBrowser.Logging.Contracts;
|
using SafeExamBrowser.Logging.Contracts;
|
||||||
using SafeExamBrowser.SystemComponents.Contracts.Registry;
|
using SafeExamBrowser.SystemComponents.Contracts.Registry;
|
||||||
using SafeExamBrowser.SystemComponents.Contracts.Registry.Events;
|
using SafeExamBrowser.SystemComponents.Contracts.Registry.Events;
|
||||||
|
@ -86,6 +89,59 @@ namespace SafeExamBrowser.SystemComponents.Registry
|
||||||
return success;
|
return success;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public bool TryGetNames(string keyName, out IEnumerable<string> names)
|
||||||
|
{
|
||||||
|
names = default;
|
||||||
|
|
||||||
|
if (!TryOpenKey(keyName, out var key))
|
||||||
|
{
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
var success = true;
|
||||||
|
using (key)
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
names = key.GetValueNames();
|
||||||
|
}
|
||||||
|
catch (Exception e)
|
||||||
|
{
|
||||||
|
logger.Error($"Failed to get registry value names '{keyName}'!", e);
|
||||||
|
success = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
return success;
|
||||||
|
}
|
||||||
|
|
||||||
|
public bool TryGetSubKeys(string keyName, out IEnumerable<string> subKeys)
|
||||||
|
{
|
||||||
|
subKeys = default;
|
||||||
|
|
||||||
|
if (!TryOpenKey(keyName, out var key))
|
||||||
|
{
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
var success = true;
|
||||||
|
using (key)
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
subKeys = key.GetSubKeyNames();
|
||||||
|
}
|
||||||
|
catch (Exception e)
|
||||||
|
{
|
||||||
|
logger.Error($"Failed to get registry value names '{keyName}'!", e);
|
||||||
|
success = false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return success;
|
||||||
|
}
|
||||||
|
|
||||||
private void Timer_Elapsed(object sender, ElapsedEventArgs e)
|
private void Timer_Elapsed(object sender, ElapsedEventArgs e)
|
||||||
{
|
{
|
||||||
foreach (var item in values)
|
foreach (var item in values)
|
||||||
|
@ -104,5 +160,89 @@ namespace SafeExamBrowser.SystemComponents.Registry
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private bool TryGetBaseKeyFromKeyName(string keyName, out RegistryKey baseKey, out string subKeyName)
|
||||||
|
{
|
||||||
|
baseKey = default;
|
||||||
|
subKeyName = default;
|
||||||
|
|
||||||
|
string basekeyName;
|
||||||
|
var baseKeyLength = keyName.IndexOf('\\');
|
||||||
|
if (baseKeyLength != -1)
|
||||||
|
{
|
||||||
|
basekeyName = keyName.Substring(0, baseKeyLength).ToUpper(System.Globalization.CultureInfo.InvariantCulture);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
basekeyName = keyName.ToUpper(System.Globalization.CultureInfo.InvariantCulture);
|
||||||
|
}
|
||||||
|
|
||||||
|
switch (basekeyName)
|
||||||
|
{
|
||||||
|
case "HKEY_CURRENT_USER":
|
||||||
|
case "HKCU":
|
||||||
|
baseKey = RegistryKey.OpenBaseKey(RegistryHive.CurrentUser, RegistryView.Registry64);
|
||||||
|
break;
|
||||||
|
case "HKEY_LOCAL_MACHINE":
|
||||||
|
case "HKLM":
|
||||||
|
baseKey = RegistryKey.OpenBaseKey(RegistryHive.LocalMachine, RegistryView.Registry64);
|
||||||
|
break;
|
||||||
|
case "HKEY_CLASSES_ROOT":
|
||||||
|
case "HKCR":
|
||||||
|
baseKey = RegistryKey.OpenBaseKey(RegistryHive.ClassesRoot, RegistryView.Registry64);
|
||||||
|
break;
|
||||||
|
case "HKEY_USERS":
|
||||||
|
case "HKU":
|
||||||
|
baseKey = RegistryKey.OpenBaseKey(RegistryHive.Users, RegistryView.Registry64);
|
||||||
|
break;
|
||||||
|
case "HKEY_PERFORMANCE_DATA":
|
||||||
|
case "HKPD":
|
||||||
|
baseKey = RegistryKey.OpenBaseKey(RegistryHive.PerformanceData, RegistryView.Registry64);
|
||||||
|
break;
|
||||||
|
case "HKEY_CURRENT_CONFIG":
|
||||||
|
case "HKCC":
|
||||||
|
baseKey = RegistryKey.OpenBaseKey(RegistryHive.CurrentConfig, RegistryView.Registry64);
|
||||||
|
break;
|
||||||
|
case "HKEY_DYN_DATA":
|
||||||
|
case "HKDD":
|
||||||
|
baseKey = RegistryKey.OpenBaseKey(RegistryHive.DynData, RegistryView.Registry64);
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (baseKeyLength == -1 || baseKeyLength == keyName.Length)
|
||||||
|
{
|
||||||
|
subKeyName = string.Empty;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
subKeyName = keyName.Substring(baseKeyLength + 1, keyName.Length - baseKeyLength - 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
private bool TryOpenKey(string keyName, out RegistryKey key)
|
||||||
|
{
|
||||||
|
key = default;
|
||||||
|
|
||||||
|
try
|
||||||
|
{
|
||||||
|
logger.Info($"default(RegistryKey) == null: {key == null}");
|
||||||
|
if (TryGetBaseKeyFromKeyName(keyName, out var baseKey, out var subKey))
|
||||||
|
{
|
||||||
|
key = baseKey.OpenSubKey(subKey);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
catch (Exception e)
|
||||||
|
{
|
||||||
|
logger.Error($"Failed to open registry key '{keyName}'!", e);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
return key != default;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -7,8 +7,14 @@
|
||||||
*/
|
*/
|
||||||
|
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
|
using System.Management;
|
||||||
using SafeExamBrowser.Logging.Contracts;
|
using SafeExamBrowser.Logging.Contracts;
|
||||||
using SafeExamBrowser.SystemComponents.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
|
namespace SafeExamBrowser.SystemComponents
|
||||||
{
|
{
|
||||||
|
@ -38,35 +44,36 @@ namespace SafeExamBrowser.SystemComponents
|
||||||
};
|
};
|
||||||
|
|
||||||
private readonly ILogger logger;
|
private readonly ILogger logger;
|
||||||
|
private readonly IRegistry registry;
|
||||||
private readonly ISystemInfo systemInfo;
|
private readonly ISystemInfo systemInfo;
|
||||||
|
|
||||||
public VirtualMachineDetector(ILogger logger, ISystemInfo systemInfo)
|
public VirtualMachineDetector(ILogger logger, IRegistry registry, ISystemInfo systemInfo)
|
||||||
{
|
{
|
||||||
this.logger = logger;
|
this.logger = logger;
|
||||||
|
this.registry = registry;
|
||||||
this.systemInfo = systemInfo;
|
this.systemInfo = systemInfo;
|
||||||
}
|
}
|
||||||
|
|
||||||
public bool IsVirtualMachine()
|
public bool IsVirtualMachine()
|
||||||
{
|
{
|
||||||
var biosInfo = systemInfo.BiosInfo.ToLower();
|
|
||||||
var isVirtualMachine = false;
|
var isVirtualMachine = false;
|
||||||
|
|
||||||
|
var biosInfo = systemInfo.BiosInfo;
|
||||||
var macAddress = systemInfo.MacAddress;
|
var macAddress = systemInfo.MacAddress;
|
||||||
var manufacturer = systemInfo.Manufacturer.ToLower();
|
var manufacturer = systemInfo.Manufacturer;
|
||||||
var model = systemInfo.Model.ToLower();
|
var model = systemInfo.Model;
|
||||||
var devices = systemInfo.PlugAndPlayDeviceIds;
|
var devices = systemInfo.PlugAndPlayDeviceIds;
|
||||||
|
|
||||||
isVirtualMachine |= biosInfo.Contains("hyper-v");
|
// redundancy: registry check does this aswell (systemInfo may be using different methods)
|
||||||
isVirtualMachine |= biosInfo.Contains("virtualbox");
|
isVirtualMachine |= IsVirtualSystemInfo(biosInfo, manufacturer, model);
|
||||||
isVirtualMachine |= biosInfo.Contains("vmware");
|
isVirtualMachine |= IsVirtualWmi();
|
||||||
isVirtualMachine |= manufacturer.Contains("microsoft corporation") && !model.Contains("surface");
|
isVirtualMachine |= IsVirtualRegistry();
|
||||||
isVirtualMachine |= manufacturer.Contains("parallels software");
|
|
||||||
isVirtualMachine |= manufacturer.Contains("qemu");
|
|
||||||
isVirtualMachine |= manufacturer.Contains("vmware");
|
|
||||||
isVirtualMachine |= model.Contains("virtualbox");
|
|
||||||
|
|
||||||
if (macAddress != null && macAddress.Count() > 2)
|
if (macAddress != null && macAddress.Count() > 2)
|
||||||
{
|
{
|
||||||
isVirtualMachine |= macAddress.StartsWith(QEMU_MAC_PREFIX) || macAddress.StartsWith(VIRTUALBOX_MAC_PREFIX);
|
isVirtualMachine |= macAddress.StartsWith(QEMU_MAC_PREFIX);
|
||||||
|
isVirtualMachine |= macAddress.StartsWith(VIRTUALBOX_MAC_PREFIX);
|
||||||
|
isVirtualMachine |= macAddress.StartsWith("000000000000"); // indicates tampering
|
||||||
}
|
}
|
||||||
|
|
||||||
foreach (var device in devices)
|
foreach (var device in devices)
|
||||||
|
@ -78,5 +85,154 @@ namespace SafeExamBrowser.SystemComponents
|
||||||
|
|
||||||
return isVirtualMachine;
|
return isVirtualMachine;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private bool IsVirtualSystemInfo(string biosInfo, string manufacturer, string model)
|
||||||
|
{
|
||||||
|
var isVirtualMachine = false;
|
||||||
|
|
||||||
|
biosInfo = biosInfo.ToLower();
|
||||||
|
manufacturer = manufacturer.ToLower();
|
||||||
|
model = model.ToLower();
|
||||||
|
|
||||||
|
isVirtualMachine |= biosInfo.Contains("hyper-v");
|
||||||
|
isVirtualMachine |= biosInfo.Contains("virtualbox");
|
||||||
|
isVirtualMachine |= biosInfo.Contains("vmware");
|
||||||
|
isVirtualMachine |= biosInfo.Contains("ovmf");
|
||||||
|
isVirtualMachine |= biosInfo.Contains("edk ii unknown"); // qemu
|
||||||
|
isVirtualMachine |= manufacturer.Contains("microsoft corporation") && !model.Contains("surface");
|
||||||
|
isVirtualMachine |= manufacturer.Contains("parallels software");
|
||||||
|
isVirtualMachine |= manufacturer.Contains("qemu");
|
||||||
|
isVirtualMachine |= manufacturer.Contains("vmware");
|
||||||
|
isVirtualMachine |= model.Contains("virtualbox");
|
||||||
|
isVirtualMachine |= model.Contains("Q35 +"); // qemu
|
||||||
|
|
||||||
|
return isVirtualMachine;
|
||||||
|
}
|
||||||
|
|
||||||
|
private bool IsVirtualRegistry()
|
||||||
|
{
|
||||||
|
var isVirtualMachine = false;
|
||||||
|
|
||||||
|
// the resulting IsVirtualRegistry() would be massive so split it
|
||||||
|
isVirtualMachine |= HasHistoricVirtualMachineHardwareConfiguration();
|
||||||
|
isVirtualMachine |= HasLocalVirtualMachineDeviceCache();
|
||||||
|
|
||||||
|
return isVirtualMachine;
|
||||||
|
}
|
||||||
|
|
||||||
|
private bool HasHistoricVirtualMachineHardwareConfiguration()
|
||||||
|
{
|
||||||
|
var isVirtualMachine = false;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* scanned registry format:
|
||||||
|
*
|
||||||
|
* HKLM\SYSTEM\HardwareConfig\{configId=uuid}
|
||||||
|
* - BIOSVendor
|
||||||
|
* - SystemManufacturer
|
||||||
|
* - ...
|
||||||
|
* \ComputerIds
|
||||||
|
* - {computerId=uuid}: {computerSummary=hardwareInfo}
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
const string hardwareRootKey = "HKEY_LOCAL_MACHINE\\SYSTEM\\HardwareConfig";
|
||||||
|
if (!registry.TryGetSubKeys(hardwareRootKey, out var hardwareConfigSubkeys))
|
||||||
|
{
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
foreach (var configId in hardwareConfigSubkeys)
|
||||||
|
{
|
||||||
|
var hardwareConfigKey = $"{hardwareRootKey}\\{configId}";
|
||||||
|
var didReadKeys = true;
|
||||||
|
|
||||||
|
// collect system values for IsVirtualSystemInfo()
|
||||||
|
didReadKeys &= registry.TryRead(hardwareConfigKey, "BIOSVendor", out var biosVendor);
|
||||||
|
didReadKeys &= registry.TryRead(hardwareConfigKey, "BIOSVersion", out var biosVersion);
|
||||||
|
didReadKeys &= registry.TryRead(hardwareConfigKey, "SystemManufacturer", out var systemManufacturer);
|
||||||
|
didReadKeys &= registry.TryRead(hardwareConfigKey, "SystemProductName", out var systemProductName);
|
||||||
|
if (!didReadKeys)
|
||||||
|
{
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
// reconstruct the systemInfo.biosInfo string
|
||||||
|
var biosInfo = $"{(string) biosVendor} {(string) biosVersion}";
|
||||||
|
|
||||||
|
isVirtualMachine |= IsVirtualSystemInfo(biosInfo, (string) systemManufacturer, (string) systemProductName);
|
||||||
|
|
||||||
|
// check even more hardware information
|
||||||
|
var computerIdsKey = $"{hardwareConfigKey}\\ComputerIds";
|
||||||
|
if (!registry.TryGetNames(computerIdsKey, out var computerIdNames))
|
||||||
|
{
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
foreach (var computerIdName in computerIdNames)
|
||||||
|
{
|
||||||
|
// collect computer hardware summary (e.g. manufacturer&version&sku&...)
|
||||||
|
if (!registry.TryRead(computerIdsKey, computerIdName, out var computerSummary))
|
||||||
|
{
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
isVirtualMachine |= IsVirtualSystemInfo((string) computerSummary, (string) systemManufacturer, (string) systemProductName);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return isVirtualMachine;
|
||||||
|
}
|
||||||
|
|
||||||
|
private bool HasLocalVirtualMachineDeviceCache()
|
||||||
|
{
|
||||||
|
var isVirtualMachine = false;
|
||||||
|
|
||||||
|
// device cache contains hardware about other devices logged into as well, so lock onto this device in case an innocent VM was logged into.
|
||||||
|
// in the future, try to improve this check somehow since DeviceCache only gives ComputerName
|
||||||
|
var deviceName = System.Environment.GetEnvironmentVariable("COMPUTERNAME");
|
||||||
|
|
||||||
|
// check Windows timeline caches for current hardware config
|
||||||
|
const string deviceCacheParentKey = "HKEY_CURRENT_USER\\SOFTWARE\\Microsoft\\Windows\\CurrentVersion\\TaskFlow\\DeviceCache";
|
||||||
|
var hasDeviceCacheKeys = registry.TryGetSubKeys(deviceCacheParentKey, out var deviceCacheKeys);
|
||||||
|
|
||||||
|
if (deviceName != null && hasDeviceCacheKeys)
|
||||||
|
{
|
||||||
|
foreach (var cacheId in deviceCacheKeys)
|
||||||
|
{
|
||||||
|
var cacheIdKey = $"{deviceCacheParentKey}\\{cacheId}";
|
||||||
|
var didReadKeys = true;
|
||||||
|
|
||||||
|
didReadKeys &= registry.TryRead(cacheIdKey, "DeviceName", out var cacheDeviceName);
|
||||||
|
if (!didReadKeys || deviceName.ToLower() != ((string) cacheDeviceName).ToLower())
|
||||||
|
{
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
didReadKeys &= registry.TryRead(cacheIdKey, "DeviceMake", out var cacheDeviceManufacturer);
|
||||||
|
didReadKeys &= registry.TryRead(cacheIdKey, "DeviceModel", out var cacheDeviceModel);
|
||||||
|
if (!didReadKeys)
|
||||||
|
{
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
isVirtualMachine |= IsVirtualSystemInfo("", (string) cacheDeviceManufacturer, (string) cacheDeviceModel);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return isVirtualMachine;
|
||||||
|
}
|
||||||
|
|
||||||
|
private bool IsVirtualWmi()
|
||||||
|
{
|
||||||
|
var isVirtualMachine = false;
|
||||||
|
var cpuObjSearcher = new ManagementObjectSearcher("SELECT * FROM Win32_Processor");
|
||||||
|
|
||||||
|
foreach (var cpuObj in cpuObjSearcher.Get())
|
||||||
|
{
|
||||||
|
isVirtualMachine |= ((string) cpuObj["Name"]).ToLower().Contains(" kvm "); // qemu (KVM specifically)
|
||||||
|
}
|
||||||
|
|
||||||
|
return isVirtualMachine;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in a new issue