Implemented basic keyboard monitoring.

This commit is contained in:
dbuechel 2017-08-04 12:19:56 +02:00
parent 32980f9f97
commit a79f0c6d77
17 changed files with 273 additions and 17 deletions

View file

@ -62,6 +62,7 @@
<ItemGroup>
<Compile Include="Bounds.cs" />
<Compile Include="Settings\BrowserSettings.cs" />
<Compile Include="Settings\KeyboardSettings.cs" />
<Compile Include="Settings\SettingsImpl.cs" />
<Compile Include="WorkingArea.cs" />
<Compile Include="Properties\AssemblyInfo.cs" />

View file

@ -0,0 +1,21 @@
/*
* Copyright (c) 2017 ETH Zürich, Educational Development and Technology (LET)
*
* 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.Contracts.Configuration.Settings;
namespace SafeExamBrowser.Configuration.Settings
{
public class KeyboardSettings : IKeyboardSettings
{
public bool AllowAltTab => false;
public bool AllowEsc => false;
public bool AllowF5 => true;
}
}

View file

@ -24,6 +24,7 @@ namespace SafeExamBrowser.Configuration
public SettingsImpl()
{
Browser = new BrowserSettings(this);
Keyboard = new KeyboardSettings();
}
public string AppDataFolderName => "SafeExamBrowser";
@ -35,6 +36,8 @@ namespace SafeExamBrowser.Configuration
public IBrowserSettings Browser { get; private set; }
public IKeyboardSettings Keyboard { get; private set; }
public string LogFolderPath
{
get { return Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.ApplicationData), AppDataFolderName, "Logs"); }

View file

@ -0,0 +1,28 @@
/*
* Copyright (c) 2017 ETH Zürich, Educational Development and Technology (LET)
*
* 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.Contracts.Configuration.Settings
{
public interface IKeyboardSettings
{
/// <summary>
/// Determines whether the user may use the ALT+TAB shortcut.
/// </summary>
bool AllowAltTab { get; }
/// <summary>
/// Determines whether the user may use the escape key.
/// </summary>
bool AllowEsc { get; }
/// <summary>
/// Determines whether the user may use the F5 key.
/// </summary>
bool AllowF5 { get; }
}
}

View file

@ -25,6 +25,11 @@ namespace SafeExamBrowser.Contracts.Configuration.Settings
/// </summary>
IBrowserSettings Browser { get; }
/// <summary>
/// All keyboard-related settings.
/// </summary>
IKeyboardSettings Keyboard { get; }
/// <summary>
/// The path where the log files are to be stored.
/// </summary>

View file

@ -11,8 +11,8 @@ namespace SafeExamBrowser.Contracts.Monitoring
public interface IKeyboardInterceptor
{
/// <summary>
/// Returns <c>true</c> if the given key should be blocked, otherwise <c>false</c>.
/// Returns <c>true</c> if the given key should be blocked, otherwise <c>false</c>. The key code corresponds to a Win32 Virtual-Key.
/// </summary>
bool Block(uint keyCode, KeyModifier modifier);
bool Block(int keyCode, KeyModifier modifier, KeyState state);
}
}

View file

@ -14,7 +14,7 @@ namespace SafeExamBrowser.Contracts.Monitoring
public enum KeyModifier
{
None = 0,
Ctrl = 0b01,
Alt = 0b10
Alt = 0b1,
Ctrl = 0b10
}
}

View file

@ -0,0 +1,17 @@
/*
* Copyright (c) 2017 ETH Zürich, Educational Development and Technology (LET)
*
* 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.Contracts.Monitoring
{
public enum KeyState
{
None = 0,
Pressed,
Released
}
}

View file

@ -67,6 +67,7 @@
<Compile Include="Configuration\IApplicationInstance.cs" />
<Compile Include="Configuration\INotificationInfo.cs" />
<Compile Include="Configuration\Settings\IBrowserSettings.cs" />
<Compile Include="Configuration\Settings\IKeyboardSettings.cs" />
<Compile Include="Configuration\Settings\ISettings.cs" />
<Compile Include="Behaviour\IShutdownController.cs" />
<Compile Include="Behaviour\IStartupController.cs" />
@ -85,6 +86,7 @@
<Compile Include="Monitoring\IProcessMonitor.cs" />
<Compile Include="Monitoring\IWindowMonitor.cs" />
<Compile Include="Monitoring\KeyModifier.cs" />
<Compile Include="Monitoring\KeyState.cs" />
<Compile Include="UserInterface\IBrowserControl.cs" />
<Compile Include="UserInterface\IBrowserWindow.cs" />
<Compile Include="UserInterface\IMessageBox.cs" />

View file

@ -0,0 +1,46 @@
/*
* Copyright (c) 2017 ETH Zürich, Educational Development and Technology (LET)
*
* 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.Contracts.Behaviour;
using SafeExamBrowser.Contracts.Logging;
using SafeExamBrowser.Contracts.Monitoring;
using SafeExamBrowser.Contracts.UserInterface;
using SafeExamBrowser.Contracts.WindowsApi;
namespace SafeExamBrowser.Core.Behaviour.Operations
{
public class DeviceInterceptionOperation : IOperation
{
private IKeyboardInterceptor keyboardInterceptor;
private ILogger logger;
private INativeMethods nativeMethods;
public ISplashScreen SplashScreen { private get; set; }
public DeviceInterceptionOperation(IKeyboardInterceptor keyboardInterceptor, ILogger logger, INativeMethods nativeMethods)
{
this.keyboardInterceptor = keyboardInterceptor;
this.logger = logger;
this.nativeMethods = nativeMethods;
}
public void Perform()
{
logger.Info("Starting keyboard and mouse interception...");
nativeMethods.RegisterKeyboardHook(keyboardInterceptor);
}
public void Revert()
{
logger.Info("Stopping keyboard and mouse interception...");
nativeMethods.UnregisterKeyboardHook(keyboardInterceptor);
}
}
}

View file

@ -58,6 +58,7 @@
<Reference Include="System.Xml" />
</ItemGroup>
<ItemGroup>
<Compile Include="Behaviour\Operations\DeviceInterceptionOperation.cs" />
<Compile Include="Behaviour\RuntimeController.cs" />
<Compile Include="Behaviour\Operations\BrowserOperation.cs" />
<Compile Include="Behaviour\Operations\EventControllerOperation.cs" />

View file

@ -0,0 +1,72 @@
/*
* Copyright (c) 2017 ETH Zürich, Educational Development and Technology (LET)
*
* 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.Contracts.Configuration.Settings;
using SafeExamBrowser.Contracts.Logging;
using SafeExamBrowser.Contracts.Monitoring;
namespace SafeExamBrowser.Monitoring.Keyboard
{
public class KeyboardInterceptor : IKeyboardInterceptor
{
private IKeyboardSettings settings;
private ILogger logger;
public KeyboardInterceptor(IKeyboardSettings settings, ILogger logger)
{
this.logger = logger;
this.settings = settings;
}
public bool Block(int keyCode, KeyModifier modifier, KeyState state)
{
var block = false;
var key = KeyInterop.KeyFromVirtualKey(keyCode);
block |= key == Key.Apps;
block |= key == Key.F1;
block |= key == Key.F2;
block |= key == Key.F3;
block |= key == Key.F4;
block |= key == Key.F6;
block |= key == Key.F7;
block |= key == Key.F8;
block |= key == Key.F9;
block |= key == Key.F10;
block |= key == Key.F11;
block |= key == Key.F12;
block |= key == Key.PrintScreen;
block |= key == Key.Escape && modifier.HasFlag(KeyModifier.Alt);
block |= key == Key.Escape && modifier.HasFlag(KeyModifier.Ctrl);
block |= key == Key.Space && modifier.HasFlag(KeyModifier.Alt);
block |= !settings.AllowAltTab && key == Key.Tab && modifier.HasFlag(KeyModifier.Alt);
block |= !settings.AllowEsc && key == Key.Escape && modifier == KeyModifier.None;
block |= !settings.AllowF5 && key == Key.F5;
if (block)
{
Log(key, keyCode, modifier, state);
}
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

@ -57,8 +57,10 @@
<Reference Include="System.Data" />
<Reference Include="System.Net.Http" />
<Reference Include="System.Xml" />
<Reference Include="WindowsBase" />
</ItemGroup>
<ItemGroup>
<Compile Include="Keyboard\KeyboardInterceptor.cs" />
<Compile Include="Processes\ProcessMonitor.cs" />
<Compile Include="Properties\AssemblyInfo.cs" />
<Compile Include="Windows\WindowMonitor.cs" />
@ -70,7 +72,6 @@
</ProjectReference>
</ItemGroup>
<ItemGroup>
<Folder Include="Keyboard\" />
<Folder Include="Mouse\" />
</ItemGroup>
<Import Project="$(MSBuildToolsPath)\Microsoft.CSharp.targets" />

View file

@ -59,7 +59,7 @@ namespace SafeExamBrowser.WindowsApi.Constants
/// <summary>
/// Posted to the window with the keyboard focus when a nonsystem key is released. A nonsystem key is a key that is pressed when
/// the ALT key is not pressed, or a keyboard key that is pressed when a window has the keyboard focus.
/// the ALT key is not pressed, or a keyboard key that is pressed when a window has the keyboard focus.
///
/// See https://msdn.microsoft.com/en-us/library/windows/desktop/ms646281(v=vs.85).aspx.
/// </summary>

View file

@ -16,6 +16,15 @@ namespace SafeExamBrowser.WindowsApi.Monitoring
{
internal class KeyboardHook
{
private const int LEFT_CTRL = 162;
private const int RIGHT_CTRL = 163;
private const int LEFT_ALT = 164;
private const int RIGHT_ALT = 165;
private const int DELETE = 46;
private bool altPressed, ctrlPressed;
private HookProc hookProc;
internal IntPtr Handle { get; private set; }
internal IKeyboardInterceptor Interceptor { get; private set; }
@ -28,7 +37,12 @@ namespace SafeExamBrowser.WindowsApi.Monitoring
{
var module = Kernel32.GetModuleHandle(null);
Handle = User32.SetWindowsHookEx(HookType.WH_KEYBOARD_LL, LowLevelKeyboardProc, module, 0);
// IMORTANT:
// Ensures that the hook delegate does not get garbage collected prematurely, as it will be passed to unmanaged code.
// Not doing so will result in a <c>CallbackOnCollectedDelegate</c> error and subsequent application crash!
hookProc = new HookProc(LowLevelKeyboardProc);
Handle = User32.SetWindowsHookEx(HookType.WH_KEYBOARD_LL, hookProc, module, 0);
}
internal bool Detach()
@ -40,10 +54,11 @@ namespace SafeExamBrowser.WindowsApi.Monitoring
{
if (nCode >= 0)
{
var state = GetState(wParam.ToInt32());
var keyData = (KBDLLHOOKSTRUCT) Marshal.PtrToStructure(lParam, typeof(KBDLLHOOKSTRUCT));
var modifier = GetModifiers(keyData);
var modifier = GetModifiers(keyData, wParam.ToInt32());
if (Interceptor.Block(keyData.KeyCode, modifier))
if (Interceptor.Block((int) keyData.KeyCode, modifier, state))
{
return (IntPtr) 1;
}
@ -52,21 +67,64 @@ namespace SafeExamBrowser.WindowsApi.Monitoring
return User32.CallNextHookEx(Handle, nCode, wParam, lParam);
}
private KeyModifier GetModifiers(KBDLLHOOKSTRUCT keyData)
private KeyState GetState(int wParam)
{
switch (wParam)
{
case Constant.WM_KEYDOWN:
case Constant.WM_SYSKEYDOWN:
return KeyState.Pressed;
case Constant.WM_KEYUP:
case Constant.WM_SYSKEYUP:
return KeyState.Released;
default:
return KeyState.None;
}
}
private KeyModifier GetModifiers(KBDLLHOOKSTRUCT keyData, int wParam)
{
var modifier = KeyModifier.None;
if ((keyData.Flags & KBDLLHOOKSTRUCTFlags.LLKHF_ALTDOWN) == KBDLLHOOKSTRUCTFlags.LLKHF_ALTDOWN)
TrackCtrlAndAlt(keyData, wParam);
if (altPressed || keyData.Flags.HasFlag(KBDLLHOOKSTRUCTFlags.LLKHF_ALTDOWN))
{
modifier |= KeyModifier.Alt;
}
if (keyData.Flags == 0)
if (ctrlPressed)
{
modifier |= KeyModifier.Ctrl;
}
return modifier;
}
private void TrackCtrlAndAlt(KBDLLHOOKSTRUCT keyData, int wParam)
{
var keyCode = keyData.KeyCode;
if (keyCode == LEFT_CTRL || keyCode == RIGHT_CTRL)
{
ctrlPressed = IsPressed(wParam);
}
else if (keyCode == LEFT_ALT || keyCode == RIGHT_ALT)
{
altPressed = IsPressed(wParam);
}
if (ctrlPressed && altPressed && keyCode == DELETE)
{
// When the Secure Attention Sequence is pressed, the WM_KEYUP / WM_SYSKEYUP messages for CTRL and ALT get lost...
ctrlPressed = false;
altPressed = false;
}
}
private bool IsPressed(int wParam)
{
return wParam == Constant.WM_KEYDOWN || wParam == Constant.WM_SYSKEYDOWN;
}
}
}

View file

@ -27,7 +27,7 @@ namespace SafeExamBrowser.WindowsApi
private ConcurrentDictionary<IntPtr, KeyboardHook> KeyboardHooks = new ConcurrentDictionary<IntPtr, KeyboardHook>();
/// <summary>
/// Upon finalization, unregister all system events and hooks...
/// Upon finalization, unregister all active system events and hooks...
/// </summary>
~NativeMethods()
{
@ -144,9 +144,6 @@ namespace SafeExamBrowser.WindowsApi
hook.Attach();
// IMORTANT:
// Ensures that the hook does not get garbage collected prematurely, as it will be passed to unmanaged code.
// Not doing so will result in a <c>CallbackOnCollectedDelegate</c> error and subsequent application crash!
KeyboardHooks[hook.Handle] = hook;
}

View file

@ -21,6 +21,7 @@ using SafeExamBrowser.Core.Behaviour;
using SafeExamBrowser.Core.Behaviour.Operations;
using SafeExamBrowser.Core.I18n;
using SafeExamBrowser.Core.Logging;
using SafeExamBrowser.Monitoring.Keyboard;
using SafeExamBrowser.Monitoring.Processes;
using SafeExamBrowser.Monitoring.Windows;
using SafeExamBrowser.UserInterface;
@ -32,10 +33,11 @@ namespace SafeExamBrowser
{
private IApplicationController browserController;
private IApplicationInfo browserInfo;
private IRuntimeController runtimeController;
private IKeyboardInterceptor keyboardInterceptor;
private ILogger logger;
private INativeMethods nativeMethods;
private IProcessMonitor processMonitor;
private IRuntimeController runtimeController;
private ISettings settings;
private IText text;
private ITextResource textResource;
@ -62,6 +64,7 @@ namespace SafeExamBrowser
text = new Text(textResource);
browserController = new BrowserApplicationController(settings, text, uiFactory);
keyboardInterceptor = new KeyboardInterceptor(settings.Keyboard, new ModuleLogger(logger, typeof(KeyboardInterceptor)));
processMonitor = new ProcessMonitor(new ModuleLogger(logger, typeof(ProcessMonitor)), nativeMethods);
windowMonitor = new WindowMonitor(new ModuleLogger(logger, typeof(WindowMonitor)), nativeMethods);
workingArea = new WorkingArea(new ModuleLogger(logger, typeof(WorkingArea)), nativeMethods);
@ -71,6 +74,7 @@ namespace SafeExamBrowser
StartupController = new StartupController(logger, settings, text, uiFactory);
StartupOperations = new Queue<IOperation>();
StartupOperations.Enqueue(new DeviceInterceptionOperation(keyboardInterceptor, logger, nativeMethods));
StartupOperations.Enqueue(new WindowMonitorOperation(logger, windowMonitor));
StartupOperations.Enqueue(new ProcessMonitorOperation(logger, processMonitor));
StartupOperations.Enqueue(new WorkingAreaOperation(logger, Taskbar, workingArea));