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,34 @@
/*
* 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;
namespace SafeExamBrowser.WindowsApi.Constants
{
/// <remarks>
/// See https://docs.microsoft.com/de-de/windows/desktop/SecAuthZ/access-mask
/// </remarks>
[Flags]
internal enum AccessMask : uint
{
DESKTOP_NONE = 0,
DESKTOP_READOBJECTS = 0x0001,
DESKTOP_CREATEWINDOW = 0x0002,
DESKTOP_CREATEMENU = 0x0004,
DESKTOP_HOOKCONTROL = 0x0008,
DESKTOP_JOURNALRECORD = 0x0010,
DESKTOP_JOURNALPLAYBACK = 0x0020,
DESKTOP_ENUMERATE = 0x0040,
DESKTOP_WRITEOBJECTS = 0x0080,
DESKTOP_SWITCHDESKTOP = 0x0100,
GENERIC_ALL = (DESKTOP_READOBJECTS | DESKTOP_CREATEWINDOW | DESKTOP_CREATEMENU | DESKTOP_HOOKCONTROL | DESKTOP_JOURNALRECORD
| DESKTOP_JOURNALPLAYBACK | DESKTOP_ENUMERATE | DESKTOP_WRITEOBJECTS | DESKTOP_SWITCHDESKTOP
| Constant.STANDARD_RIGHTS_REQUIRED)
}
}

View File

@@ -0,0 +1,244 @@
/*
* 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.WindowsApi.Constants
{
internal static class Constant
{
/// <summary>
/// A window has received mouse capture. This event is sent by the system, never by servers.
///
/// See https://msdn.microsoft.com/en-us/library/windows/desktop/dd318066(v=vs.85).aspx.
/// </summary>
internal const uint EVENT_SYSTEM_CAPTURESTART = 0x8;
/// <summary>
/// The foreground window has changed. The system sends this event even if the foreground window has changed to another window in
/// the same thread. Server applications never send this event.
/// For this event, the WinEventProc callback function's hwnd parameter is the handle to the window that is in the foreground, the
/// idObject parameter is OBJID_WINDOW, and the idChild parameter is CHILDID_SELF.
///
/// See https://msdn.microsoft.com/en-us/library/windows/desktop/dd318066(v=vs.85).aspx.
/// </summary>
internal const uint EVENT_SYSTEM_FOREGROUND = 0x3;
/// <summary>
/// The large icon of a window. See https://docs.microsoft.com/en-us/windows/win32/winmsg/wm-geticon#parameters.
/// </summary>
internal const int ICON_BIG = 1;
/// <summary>
/// The small icon of a window. See https://docs.microsoft.com/en-us/windows/win32/winmsg/wm-geticon#parameters.
/// </summary>
internal const int ICON_SMALL = 0;
/// <summary>
/// The small icon of an application. If an application does not provide one, the system uses a system-generated icon for a window.
/// See https://docs.microsoft.com/en-us/windows/win32/winmsg/wm-geticon#parameters.
/// </summary>
internal const int ICON_SMALL2 = 2;
/// <summary>
/// Minimize all open windows.
/// </summary>
internal const int MIN_ALL = 419;
/// <summary>
/// Bitmask to evaluate the origin of a mouse event.
///
/// See https://docs.microsoft.com/en-us/windows/desktop/tablet/system-events-and-mouse-messages.
/// </summary>
internal const uint MOUSEEVENTF_MASK = 0xFFFFFF00;
/// <summary>
/// The constant for a mouse event generated by a touch interface.
///
/// See https://docs.microsoft.com/en-us/windows/desktop/tablet/system-events-and-mouse-messages.
/// </summary>
internal const uint MOUSEEVENTF_FROMTOUCH = 0xFF515700;
/// <summary>
/// Specifies the default priority class for processes, i.e. a process with no special scheduling needs.
///
/// See https://msdn.microsoft.com/en-us/library/windows/desktop/ms686219(v=vs.85).aspx.
/// </summary>
internal const int NORMAL_PRIORITY_CLASS = 0x20;
/// <summary>
/// Standard access rights required for a desktop.
///
/// See https://docs.microsoft.com/de-de/windows/desktop/SecAuthZ/standard-access-rights.
/// </summary>
internal const int STANDARD_RIGHTS_REQUIRED = 0xF0000;
/// <summary>
/// The constant for the name of a user object.
///
/// See https://msdn.microsoft.com/en-us/library/windows/desktop/ms683238(v=vs.85).aspx.
/// </summary>
internal const int UOI_NAME = 2;
/// <summary>
/// The callback function is not mapped into the address space of the process that generates the event. Because the hook function
/// is called across process boundaries, the system must queue events. Although this method is asynchronous, events are guaranteed
/// to be in sequential order.
///
/// See https://msdn.microsoft.com/en-us/library/windows/desktop/dd373640(v=vs.85).aspx.
/// </summary>
internal const uint WINEVENT_OUTOFCONTEXT = 0x0;
/// <summary>
/// Sent when the user selects a command item from a menu, when a control sends a notification message to its parent window, or
/// when an accelerator keystroke is translated.
///
/// See https://msdn.microsoft.com/en-us/library/windows/desktop/ms647591(v=vs.85).aspx.
/// </summary>
internal const int WM_COMMAND = 0x111;
/// <summary>
/// Sent to a window to retrieve a handle to the large or small icon associated with a window. The system displays the large icon
/// in the ALT+TAB dialog, and the small icon in the window caption.
///
/// See https://docs.microsoft.com/en-us/windows/win32/winmsg/wm-geticon.
/// </summary>
internal const int WM_GETICON = 0x7F;
/// <summary>
/// Posted to the window with the keyboard focus when a nonsystem key is pressed. A nonsystem key is a key that is pressed when
/// the ALT key is not pressed.
///
/// See https://msdn.microsoft.com/en-us/library/windows/desktop/ms646280(v=vs.85).aspx.
/// </summary>
internal const int WM_KEYDOWN = 0x100;
/// <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.
///
/// See https://msdn.microsoft.com/en-us/library/windows/desktop/ms646281(v=vs.85).aspx.
/// </summary>
internal const int WM_KEYUP = 0x101;
/// <summary>
/// Posted when the user presses the left mouse button while the cursor is in the client area of a window. If the mouse is not
/// captured, the message is posted to the window beneath the cursor. Otherwise, the message is posted to the window that has
/// captured the mouse.
///
/// See https://msdn.microsoft.com/en-us/library/windows/desktop/ms645607(v=vs.85).aspx.
/// </summary>
internal const int WM_LBUTTONDOWN = 0x201;
/// <summary>
/// Posted when the user releases the left mouse button while the cursor is in the client area of a window. If the mouse is not
/// captured, the message is posted to the window beneath the cursor. Otherwise, the message is posted to the window that has
/// captured the mouse.
///
/// See https://msdn.microsoft.com/en-us/library/windows/desktop/ms645608(v=vs.85).aspx.
/// </summary>
internal const int WM_LBUTTONUP = 0x202;
/// <summary>
/// Posted when the user presses the middle mouse button while the cursor is in the client area of a window. If the mouse is not
/// captured, the message is posted to the window beneath the cursor. Otherwise, the message is posted to the window that has
/// captured the mouse.
///
/// See https://msdn.microsoft.com/en-us/library/windows/desktop/ms645610(v=vs.85).aspx.
/// </summary>
internal const int WM_MBUTTONDOWN = 0x207;
/// <summary>
/// Posted when the user releases the middle mouse button while the cursor is in the client area of a window. If the mouse is not
/// captured, the message is posted to the window beneath the cursor. Otherwise, the message is posted to the window that has
/// captured the mouse.
///
/// See https://msdn.microsoft.com/en-us/library/windows/desktop/ms645611(v=vs.85).aspx.
/// </summary>
internal const int WM_MBUTTONUP = 0x208;
/// <summary>
/// Posted to a window when the cursor moves. If the mouse is not captured, the message is posted to the window that contains the
/// cursor. Otherwise, the message is posted to the window that has captured the mouse.
///
/// See https://msdn.microsoft.com/en-us/library/windows/desktop/ms645616(v=vs.85).aspx.
/// </summary>
internal const int WM_MOUSEMOVE = 0x200;
/// <summary>
/// Sent to the focus window when the mouse wheel is rotated. The DefWindowProc function propagates the message to the window's
/// parent. There should be no internal forwarding of the message, since DefWindowProc propagates it up the parent chain until i
/// finds a window that processes it.
///
/// See https://msdn.microsoft.com/en-us/library/windows/desktop/ms645617(v=vs.85).aspx.
/// </summary>
internal const int WM_MOUSEWHEEL = 0x20A;
/// <summary>
/// Posted when the user presses the right mouse button while the cursor is in the client area of a window. If the mouse is not
/// captured, the message is posted to the window beneath the cursor. Otherwise, the message is posted to the window that has
/// captured the mouse.
///
/// See https://msdn.microsoft.com/en-us/library/windows/desktop/ms646242(v=vs.85).aspx.
/// </summary>
internal const int WM_RBUTTONDOWN = 0x204;
/// <summary>
/// Posted when the user releases the right mouse button while the cursor is in the client area of a window. If the mouse is not
/// captured, the message is posted to the window beneath the cursor. Otherwise, the message is posted to the window that has
/// captured the mouse.
///
/// See https://msdn.microsoft.com/en-us/library/windows/desktop/ms646243(v=vs.85).aspx.
/// </summary>
internal const int WM_RBUTTONUP = 0x205;
/// <summary>
/// A window receives this message when the user chooses a command from the Window menu (formerly known as the system or control
/// menu) or when the user chooses the maximize button, minimize button, restore button, or close button.
///
/// See https://msdn.microsoft.com/en-us/library/windows/desktop/ms646360(v=vs.85).aspx.
/// </summary>
internal const int WM_SYSCOMMAND = 0x112;
/// <summary>
/// Posted to the window with the keyboard focus when the user presses the F10 key (which activates the menu bar) or holds down
/// the ALT key and then presses another key. It also occurs when no window currently has the keyboard focus; in this case, the
/// WM_SYSKEYDOWN message is sent to the active window. The window that receives the message can distinguish between these two
/// contexts by checking the context code in the lParam parameter.
///
/// See https://msdn.microsoft.com/en-us/library/windows/desktop/ms646286(v=vs.85).aspx.
/// </summary>
internal const int WM_SYSKEYDOWN = 0x104;
/// <summary>
/// Posted to the window with the keyboard focus when the user releases a key that was pressed while the ALT key was held down.
/// It also occurs when no window currently has the keyboard focus; in this case, the WM_SYSKEYUP message is sent to the active
/// window. The window that receives the message can distinguish between these two contexts by checking the context code in the
/// lParam parameter.
///
/// See https://msdn.microsoft.com/en-us/library/windows/desktop/ms646287(v=vs.85).aspx
/// </summary>
internal const int WM_SYSKEYUP = 0x105;
/// <summary>
/// Posted when the user presses the first or second X button while the cursor is in the client area of a window. If the mouse is
/// not captured, the message is posted to the window beneath the cursor. Otherwise, the message is posted to the window that has
/// captured the mouse.
///
/// See https://docs.microsoft.com/de-de/windows/desktop/inputdev/wm-xbuttondown.
/// </summary>
internal const int WM_XBUTTONDOWN = 0x20B;
/// <summary>
/// Posted when the user releases the first or second X button while the cursor is in the client area of a window. If the mouse is
/// not captured, the message is posted to the window beneath the cursor. Otherwise, the message is posted to the window that has
/// captured the mouse.
///
/// See https://docs.microsoft.com/de-de/windows/desktop/inputdev/wm-xbuttonup.
/// </summary>
internal const int WM_XBUTTONUP = 0x20C;
}
}

View File

@@ -0,0 +1,100 @@
/*
* 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.WindowsApi.Constants
{
/// <remarks>
/// See http://www.pinvoke.net/default.aspx/Enums/HookType.html.
/// </remarks>
internal enum HookType
{
/// <summary>
/// Installs a hook procedure that records input messages posted to the system message queue. This hook is useful for recording
/// macros. For more information, see the JournalRecordProc hook procedure.
/// </summary>
WH_JOURNALRECORD = 0,
/// <summary>
/// Installs a hook procedure that posts messages previously recorded by a WH_JOURNALRECORD hook procedure. For more information,
/// see the JournalPlaybackProc hook procedure.
/// </summary>
WH_JOURNALPLAYBACK = 1,
/// <summary>
/// Installs a hook procedure that monitors keystroke messages. For more information, see the KeyboardProc hook procedure.
/// </summary>
WH_KEYBOARD = 2,
/// <summary>
/// Installs a hook procedure that monitors messages posted to a message queue. For more information, see the GetMsgProc hook
/// procedure.
/// </summary>
WH_GETMESSAGE = 3,
/// <summary>
/// Installs a hook procedure that monitors messages before the system sends them to the destination window procedure. For more
/// information, see the CallWndProc hook procedure.
/// </summary>
WH_CALLWNDPROC = 4,
/// <summary>
/// Installs a hook procedure that receives notifications useful to a CBT application. For more information, see the CBTProc hook
/// procedure.
/// </summary>
WH_CBT = 5,
/// <summary>
/// Installs a hook procedure that monitors messages generated as a result of an input event in a dialog box, message box, menu,
/// or scroll bar. The hook procedure monitors these messages for all applications in the same desktop as the calling thread. For
/// more information, see the SysMsgProc hook procedure.
/// </summary>
WH_SYSMSGFILTER = 6,
/// <summary>
/// Installs a hook procedure that monitors mouse messages. For more information, see the MouseProc hook procedure.
/// </summary>
WH_MOUSE = 7,
WH_HARDWARE = 8,
/// <summary>
/// Installs a hook procedure useful for debugging other hook procedures. For more information, see the DebugProc hook procedure.
/// </summary>
WH_DEBUG = 9,
/// <summary>
/// Installs a hook procedure that receives notifications useful to shell applications. For more information, see the ShellProc
/// hook procedure.
/// </summary>
WH_SHELL = 10,
/// <summary>
/// Installs a hook procedure that will be called when the application's foreground thread is about to become idle. This hook is
/// useful for performing low priority tasks during idle time. For more information, see the ForegroundIdleProc hook procedure.
/// </summary>
WH_FOREGROUNDIDLE = 11,
/// <summary>
/// Installs a hook procedure that monitors messages after they have been processed by the destination window procedure. For more
/// information, see the CallWndRetProc hook procedure.
/// </summary>
WH_CALLWNDPROCRET = 12,
/// <summary>
/// Installs a hook procedure that monitors low-level keyboard input events. For more information, see the LowLevelKeyboardProc
/// hook procedure.
/// </summary>
WH_KEYBOARD_LL = 13,
/// <summary>
/// Installs a hook procedure that monitors low-level mouse input events. For more information, see the LowLevelMouseProc hook
/// procedure.
/// </summary>
WH_MOUSE_LL = 14
}
}

View File

@@ -0,0 +1,59 @@
/*
* 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.WindowsApi.Constants
{
/// <remarks>
/// See https://learn.microsoft.com/en-us/windows/win32/api/winuser/nf-winuser-systemparametersinfow.
/// See http://www.pinvoke.net/default.aspx/Enums/SPI.html?diff=y.
/// </remarks>
internal enum SPI : uint
{
/// <summary>
/// Retrieves the full path of the bitmap file for the desktop wallpaper. The pvParam parameter must point to a buffer
/// that receives a null-terminated path string. Set the uiParam parameter to the size, in characters, of the pvParam buffer.
/// The returned string will not exceed MAX_PATH characters. If there is no desktop wallpaper, the returned string is empty.
/// </summary>
GETDESKWALLPAPER = 0x73,
/// <summary>
/// Retrieves information about the StickyKeys accessibility feature. The pvParam parameter must point to a STICKYKEYS structure
/// that receives the information. Set the cbSize member of this structure and the uiParam parameter to sizeof(STICKYKEYS).
/// </summary>
GETSTICKYKEYS = 0x3A,
/// <summary>
/// Retrieves the size of the work area on the primary display monitor. The work area is the portion of the screen
/// not obscured by the system taskbar or by application desktop toolbars. The pvParam parameter must point to a
/// RECT structure that receives the coordinates of the work area, expressed in virtual screen coordinates. To get
/// the work area of a monitor other than the primary display monitor, call the GetMonitorInfo function.
/// </summary>
GETWORKAREA = 0x30,
/// <summary>
/// Sets the desktop wallpaper. The value of the pvParam parameter determines the new wallpaper. To specify a wallpaper bitmap,
/// set pvParam to point to a null-terminated string containing the name of a bitmap file. Setting pvParam to "" removes the
/// wallpaper. Setting pvParam to SETWALLPAPER_DEFAULT or null reverts to the default wallpaper.
/// </summary>
SETDESKWALLPAPER = 0x14,
/// <summary>
/// Sets the parameters of the StickyKeys accessibility feature. The pvParam parameter must point to a STICKYKEYS structure
/// that contains the new parameters. Set the cbSize member of this structure and the uiParam parameter to sizeof(STICKYKEYS).
/// </summary>
SETSTICKYKEYS = 0x3B,
/// <summary>
/// Sets the size of the work area. The work area is the portion of the screen not obscured by the system taskbar
/// or by application desktop toolbars. The pvParam parameter is a pointer to a RECT structure that specifies the
/// new work area rectangle, expressed in virtual screen coordinates. In a system with multiple display monitors,
/// the function sets the work area of the monitor that contains the specified rectangle.
/// </summary>
SETWORKAREA = 0x2F,
}
}

View File

@@ -0,0 +1,36 @@
/*
* 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;
namespace SafeExamBrowser.WindowsApi.Constants
{
/// <remarks>
/// See http://www.pinvoke.net/default.aspx/Enums/SPIF.html.
/// </remarks>
[Flags]
internal enum SPIF
{
NONE = 0x00,
/// <summary>
/// Writes the new system-wide parameter setting to the user profile.
/// </summary>
UPDATEINIFILE = 0x01,
/// <summary>
/// Broadcasts the WM_SETTINGCHANGE message after updating the user profile.
/// </summary>
SENDCHANGE = 0x02,
/// <summary>
/// Performs UPDATEINIFILE and SENDCHANGE.
/// </summary>
UPDATEANDCHANGE = 0x03
}
}

View File

@@ -0,0 +1,97 @@
/*
* 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.WindowsApi.Constants
{
/// <remarks>
/// See http://www.pinvoke.net/default.aspx/Enums/ShowWindowCommand.html.
/// </remarks>
internal enum ShowWindowCommand
{
/// <summary>
/// Hides the window and activates another window.
/// </summary>
Hide = 0,
/// <summary>
/// Activates and displays a window. If the window is minimized or
/// maximized, the system restores it to its original size and position.
/// An application should specify this flag when displaying the window
/// for the first time.
/// </summary>
Normal = 1,
/// <summary>
/// Activates the window and displays it as a minimized window.
/// </summary>
ShowMinimized = 2,
/// <summary>
/// Maximizes the specified window.
/// </summary>
Maximize = 3,
/// <summary>
/// Activates the window and displays it as a maximized window.
/// </summary>
ShowMaximized = 3,
/// <summary>
/// Displays a window in its most recent size and position. This value
/// is similar to <see cref="Win32.ShowWindowCommand.Normal"/>, except
/// the window is not activated.
/// </summary>
ShowNoActivate = 4,
/// <summary>
/// Activates the window and displays it in its current size and position.
/// </summary>
Show = 5,
/// <summary>
/// Minimizes the specified window and activates the next top-level
/// window in the Z order.
/// </summary>
Minimize = 6,
/// <summary>
/// Displays the window as a minimized window. This value is similar to
/// <see cref="Win32.ShowWindowCommand.ShowMinimized"/>, except the
/// window is not activated.
/// </summary>
ShowMinNoActive = 7,
/// <summary>
/// Displays the window in its current size and position. This value is
/// similar to <see cref="Win32.ShowWindowCommand.Show"/>, except the
/// window is not activated.
/// </summary>
ShowNA = 8,
/// <summary>
/// Activates and displays the window. If the window is minimized or
/// maximized, the system restores it to its original size and position.
/// An application should specify this flag when restoring a minimized window.
/// </summary>
Restore = 9,
/// <summary>
/// Sets the show state based on the SW_* value specified in the
/// STARTUPINFO structure passed to the CreateProcess function by the
/// program that started the application.
/// </summary>
ShowDefault = 10,
/// <summary>
/// <b>Windows 2000/XP:</b> Minimizes a window, even if the thread
/// that owns the window is not responding. This flag should only be
/// used when minimizing windows from a different thread.
/// </summary>
ForceMinimize = 11
}
}

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;
namespace SafeExamBrowser.WindowsApi.Constants
{
/// <remarks>
/// See https://learn.microsoft.com/en-us/windows/win32/api/winuser/ns-winuser-stickykeys#members
/// </remarks>
[Flags]
internal enum StickyKeysFlags : int
{
/// <summary>
/// If this flag is set, the StickyKeys feature is on.
/// </summary>
ON = 0x1,
/// <summary>
/// If this flag is set, the StickyKeys feature is available.
/// </summary>
AVAILABLE = 0x2,
/// <summary>
/// If this flag is set, the user can turn the StickyKeys feature on and off by pressing the SHIFT key five times.
/// </summary>
HOTKEYACTIVE = 0x4,
/// <summary>
/// A confirmation dialog appears when the StickyKeys feature is activated by using the hot key.
/// </summary>
CONFIRMHOTKEY = 0x8,
/// <summary>
/// If this flag is set, the system plays a siren sound when the user turns the StickyKeys feature on or off by using the hot key.
/// </summary>
HOTKEYSOUND = 0x10,
/// <summary>
/// A visual indicator should be displayed when the StickyKeys feature is on.
/// </summary>
INDICATOR = 0x20,
/// <summary>
/// If this flag is set, the system plays a sound when the user latches, locks, or releases modifier keys using the StickyKeys feature.
/// </summary>
AUDIBLEFEEDBACK = 0x40,
/// <summary>
/// If this flag is set, pressing a modifier key twice in a row locks down the key until the user presses it a third time.
/// </summary>
TRISTATE = 0x80,
/// <summary>
/// If this flag is set, releasing a modifier key that has been pressed in combination with any other key turns off the StickyKeys feature.
/// </summary>
TWOKEYSOFF = 0x100,
}
}

View File

@@ -0,0 +1,21 @@
/*
* 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.WindowsApi.Constants
{
/// <summary>
/// See https://msdn.microsoft.com/en-us/library/windows/desktop/ms646360(v=vs.85).aspx.
/// </summary>
internal enum SystemCommand
{
/// <summary>
/// Closes the window.
/// </summary>
CLOSE = 0xF060
}
}

View File

@@ -0,0 +1,29 @@
/*
* 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;
namespace SafeExamBrowser.WindowsApi.Constants
{
/// <remarks>
/// See https://docs.microsoft.com/en-us/windows/desktop/ProcThread/thread-security-and-access-rights.
/// </remarks>
[Flags]
internal enum ThreadAccess : int
{
TERMINATE = 0x1,
SUSPEND_RESUME = 0x2,
GET_CONTEXT = 0x8,
SET_CONTEXT = 0x10,
SET_INFORMATION = 0x20,
QUERY_INFORMATION = 0x40,
SET_THREAD_TOKEN = 0x80,
IMPERSONATE = 0x100,
DIRECT_IMPERSONATION = 0x200
}
}

View File

@@ -0,0 +1,25 @@
/*
* 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.WindowsApi.Constants
{
/// <remarks>
/// See https://docs.microsoft.com/en-us/windows/desktop/inputdev/virtual-key-codes.
/// </remarks>
internal enum VirtualKeyCode
{
A = 0x41,
Q = 0x51,
Delete = 0x2E,
LeftAlt = 0xA4,
LeftControl = 0xA2,
LeftWindows = 0x5B,
RightAlt = 0xA5,
RightControl = 0xA3
}
}

View File

@@ -0,0 +1,17 @@
/*
* 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;
namespace SafeExamBrowser.WindowsApi.Delegates
{
/// <remarks>
/// See https://msdn.microsoft.com/en-us/library/windows/desktop/ms682612(v=vs.85).aspx
/// </remarks>
internal delegate bool EnumDesktopDelegate(string lpszDesktop, IntPtr lParam);
}

View File

@@ -0,0 +1,17 @@
/*
* 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;
namespace SafeExamBrowser.WindowsApi.Delegates
{
/// <remarks>
/// See https://msdn.microsoft.com/en-us/library/windows/desktop/ms633498(v=vs.85).aspx
/// </remarks>
internal delegate bool EnumWindowsDelegate(IntPtr hWnd, IntPtr lParam);
}

View File

@@ -0,0 +1,17 @@
/*
* 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;
namespace SafeExamBrowser.WindowsApi.Delegates
{
/// <remarks>
/// See https://docs.microsoft.com/de-de/windows/desktop/api/winuser/nc-winuser-wineventproc
/// </remarks>
internal delegate void EventDelegate(IntPtr hWinEventHook, uint eventType, IntPtr hwnd, int idObject, int idChild, uint dwEventThread, uint dwmsEventTime);
}

View File

@@ -0,0 +1,17 @@
/*
* 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;
namespace SafeExamBrowser.WindowsApi.Delegates
{
/// <remarks>
/// See https://docs.microsoft.com/de-de/windows/desktop/api/winuser/nc-winuser-hookproc
/// </remarks>
internal delegate IntPtr HookDelegate(int nCode, IntPtr wParam, IntPtr lParam);
}

View File

@@ -0,0 +1,42 @@
/*
* 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.ComponentModel;
using System.Runtime.InteropServices;
using SafeExamBrowser.WindowsApi.Contracts;
namespace SafeExamBrowser.WindowsApi.Desktops
{
internal class Desktop : IDesktop
{
public IntPtr Handle { get; private set; }
public string Name { get; private set; }
public Desktop(IntPtr handle, string name)
{
Handle = handle;
Name = name;
}
public void Activate()
{
}
public void Close()
{
}
public override string ToString()
{
return $"'{Name}' [{Handle}]";
}
}
}

View File

@@ -0,0 +1,123 @@
/*
* 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.ComponentModel;
using System.Runtime.InteropServices;
using SafeExamBrowser.Logging.Contracts;
using SafeExamBrowser.WindowsApi.Constants;
using SafeExamBrowser.WindowsApi.Contracts;
namespace SafeExamBrowser.WindowsApi.Desktops
{
public class DesktopFactory : IDesktopFactory
{
private readonly ILogger logger;
private readonly Random random;
public DesktopFactory(ILogger logger)
{
this.logger = logger;
this.random = new Random();
}
public IDesktop CreateNew(string name)
{
logger.Debug($"Attempting to create new desktop '{name}'...");
var handle = User32.CreateDesktop(name, IntPtr.Zero, IntPtr.Zero, 0, (uint) AccessMask.GENERIC_ALL, IntPtr.Zero);
if (handle == IntPtr.Zero)
{
logger.Error($"Failed to create new desktop '{name}'!");
throw new Win32Exception(Marshal.GetLastWin32Error());
}
var desktop = new Desktop(handle, name);
logger.Debug($"Successfully created desktop {desktop}.");
return desktop;
}
public IDesktop CreateRandom()
{
logger.Debug($"Attempting to create random desktop...");
var name = GenerateRandomDesktopName();
var handle = User32.CreateDesktop(name, IntPtr.Zero, IntPtr.Zero, 0, (uint) AccessMask.GENERIC_ALL, IntPtr.Zero);
if (handle == IntPtr.Zero)
{
logger.Error($"Failed to create random desktop '{name}'!");
throw new Win32Exception(Marshal.GetLastWin32Error());
}
var obfuscatedHandle = new IntPtr(random.Next(100, 10000));
var obfuscatedName = GenerateRandomDesktopName();
var desktop = new ObfuscatedDesktop(handle, name, obfuscatedHandle, obfuscatedName);
logger.Debug($"Successfully created random desktop {desktop}.");
return desktop;
}
public IDesktop GetCurrent()
{
var threadId = Kernel32.GetCurrentThreadId();
var handle = User32.GetThreadDesktop(threadId);
var name = string.Empty;
var nameLength = 0;
if (handle == IntPtr.Zero)
{
logger.Error($"Failed to get desktop handle for thread with ID = {threadId}!");
throw new Win32Exception(Marshal.GetLastWin32Error());
}
logger.Debug($"Found desktop handle for thread with ID = {threadId}. Attempting to get desktop name...");
User32.GetUserObjectInformation(handle, Constant.UOI_NAME, IntPtr.Zero, 0, ref nameLength);
var namePointer = Marshal.AllocHGlobal(nameLength);
var success = User32.GetUserObjectInformation(handle, Constant.UOI_NAME, namePointer, nameLength, ref nameLength);
if (!success)
{
logger.Error($"Failed to retrieve name for desktop with handle = {handle}!");
throw new Win32Exception(Marshal.GetLastWin32Error());
}
name = Marshal.PtrToStringAnsi(namePointer);
Marshal.FreeHGlobal(namePointer);
var desktop = new Desktop(handle, name);
logger.Debug($"Successfully determined current desktop {desktop}.");
return desktop;
}
private string GenerateRandomDesktopName()
{
var length = random.Next(5, 20);
var name = new char[length];
for (var letter = 0; letter < length; letter++)
{
name[letter] = (char) (random.Next(2) == 0 && letter != 0 ? random.Next('a', 'z' + 1) : random.Next('A', 'Z' + 1));
}
return new string(name);
}
}
}

View File

@@ -0,0 +1,55 @@
/*
* 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.Runtime.InteropServices;
using System.Timers;
using SafeExamBrowser.Logging.Contracts;
using SafeExamBrowser.WindowsApi.Constants;
using SafeExamBrowser.WindowsApi.Contracts;
namespace SafeExamBrowser.WindowsApi.Desktops
{
public class DesktopMonitor : IDesktopMonitor
{
private readonly ILogger logger;
private readonly Timer timer;
private IDesktop desktop;
public DesktopMonitor(ILogger logger)
{
this.logger = logger;
this.timer = new Timer(1000);
}
public void Start(IDesktop desktop)
{
this.desktop = desktop;
timer.AutoReset = false;
timer.Elapsed += Timer_Elapsed;
timer.Start();
logger.Info($"Started monitoring desktop {desktop}.");
}
public void Stop()
{
timer.Stop();
timer.Elapsed -= Timer_Elapsed;
logger.Info($"Stopped monitoring desktop {desktop}.");
}
private void Timer_Elapsed(object sender, ElapsedEventArgs e)
{
}
}
}

View File

@@ -0,0 +1,29 @@
/*
* 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;
namespace SafeExamBrowser.WindowsApi.Desktops
{
internal class ObfuscatedDesktop : Desktop
{
private readonly IntPtr obfuscatedHandle;
private readonly string obfuscatedName;
public ObfuscatedDesktop(IntPtr handle, string name, IntPtr obfuscatedHandle, string obfuscatedName) : base(handle, name)
{
this.obfuscatedHandle = obfuscatedHandle;
this.obfuscatedName = obfuscatedName;
}
public override string ToString()
{
return $"'{obfuscatedName}' [{obfuscatedHandle}]";
}
}
}

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;
using System.Collections.Generic;
using System.Diagnostics;
using System.IO;
using System.Linq;
using System.Threading;
using SafeExamBrowser.Logging.Contracts;
using SafeExamBrowser.WindowsApi.Contracts;
using SafeExamBrowser.WindowsApi.Types;
namespace SafeExamBrowser.WindowsApi
{
public class ExplorerShell : IExplorerShell
{
private ILogger logger;
private INativeMethods nativeMethods;
private IList<Window> minimizedWindows = new List<Window>();
public ExplorerShell(ILogger logger, INativeMethods nativeMethods)
{
this.logger = logger;
this.nativeMethods = nativeMethods;
this.minimizedWindows = new List<Window>();
}
public void HideAllWindows()
{
logger.Info("Searching for windows to be minimized...");
logger.Info("Minimizing all open windows...");
logger.Info("Open windows successfully minimized.");
}
public void RestoreAllWindows()
{
logger.Info("Restoring all minimized windows...");
logger.Info("Minimized windows successfully restored.");
}
public void Start()
{
}
public void Terminate()
{
}
private void KillExplorerShell(int processId)
{
var process = new System.Diagnostics.Process();
var taskkillPath = Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.System), "taskkill.exe");
logger.Warn("Failed to gracefully terminate explorer shell, attempting forceful termination...");
process.StartInfo.Arguments = $"/F /PID {processId}";
process.StartInfo.CreateNoWindow = true;
process.StartInfo.FileName = taskkillPath;
process.StartInfo.UseShellExecute = false;
process.StartInfo.WindowStyle = ProcessWindowStyle.Hidden;
process.StartInfo.WorkingDirectory = Environment.GetFolderPath(Environment.SpecialFolder.Windows);
process.Start();
process.WaitForExit();
}
}
}

View File

@@ -0,0 +1,128 @@
/*
* 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.Runtime.InteropServices;
using SafeExamBrowser.WindowsApi.Constants;
using SafeExamBrowser.WindowsApi.Contracts.Events;
using SafeExamBrowser.WindowsApi.Delegates;
using SafeExamBrowser.WindowsApi.Types;
namespace SafeExamBrowser.WindowsApi.Hooks
{
internal class KeyboardHook
{
private bool altPressed, ctrlPressed;
private KeyboardHookCallback callback;
private IntPtr handle;
private HookDelegate hookDelegate;
internal Guid Id { get; private set; }
internal KeyboardHook(KeyboardHookCallback callback)
{
this.callback = callback;
this.Id = Guid.NewGuid();
}
internal void Attach()
{
var process = System.Diagnostics.Process.GetCurrentProcess();
var module = process.MainModule;
var moduleHandle = Kernel32.GetModuleHandle(module.ModuleName);
// IMPORTANT:
// 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!
hookDelegate = new HookDelegate(LowLevelKeyboardProc);
handle = User32.SetWindowsHookEx(HookType.WH_KEYBOARD_LL, hookDelegate, moduleHandle, 0);
}
internal bool Detach()
{
return User32.UnhookWindowsHookEx(handle);
}
private IntPtr LowLevelKeyboardProc(int nCode, IntPtr wParam, IntPtr lParam)
{
if (nCode >= 0)
{
var keyData = (KBDLLHOOKSTRUCT) Marshal.PtrToStructure(lParam, typeof(KBDLLHOOKSTRUCT));
var modifier = GetModifiers(keyData, wParam.ToInt32());
var state = GetState(wParam.ToInt32());
if (callback((int) keyData.KeyCode, modifier, state))
{
return (IntPtr) 1;
}
}
return User32.CallNextHookEx(handle, nCode, wParam, lParam);
}
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.Unknown;
}
}
private KeyModifier GetModifiers(KBDLLHOOKSTRUCT keyData, int wParam)
{
var modifier = KeyModifier.None;
TrackCtrlAndAlt(keyData, wParam);
if (altPressed || keyData.Flags.HasFlag(KBDLLHOOKSTRUCTFlags.LLKHF_ALTDOWN))
{
modifier |= KeyModifier.Alt;
}
if (ctrlPressed)
{
modifier |= KeyModifier.Ctrl;
}
return modifier;
}
private void TrackCtrlAndAlt(KBDLLHOOKSTRUCT keyData, int wParam)
{
var keyCode = keyData.KeyCode;
if (keyCode == (uint) VirtualKeyCode.LeftControl || keyCode == (uint) VirtualKeyCode.RightControl)
{
ctrlPressed = IsPressed(wParam);
}
else if (keyCode == (uint) VirtualKeyCode.LeftAlt || keyCode == (uint) VirtualKeyCode.RightAlt)
{
altPressed = IsPressed(wParam);
}
if (ctrlPressed && altPressed && keyCode == (uint) VirtualKeyCode.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

@@ -0,0 +1,126 @@
/*
* 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.Runtime.InteropServices;
using SafeExamBrowser.WindowsApi.Constants;
using SafeExamBrowser.WindowsApi.Contracts.Events;
using SafeExamBrowser.WindowsApi.Delegates;
using SafeExamBrowser.WindowsApi.Types;
namespace SafeExamBrowser.WindowsApi.Hooks
{
internal class MouseHook
{
private MouseHookCallback callback;
private IntPtr handle;
private HookDelegate hookDelegate;
internal Guid Id { get; private set; }
internal MouseHook(MouseHookCallback callback)
{
this.callback = callback;
this.Id = Guid.NewGuid();
}
internal void Attach()
{
var process = System.Diagnostics.Process.GetCurrentProcess();
var module = process.MainModule;
var moduleHandle = Kernel32.GetModuleHandle(module.ModuleName);
// IMPORTANT:
// 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!
hookDelegate = new HookDelegate(LowLevelMouseProc);
handle = User32.SetWindowsHookEx(HookType.WH_MOUSE_LL, hookDelegate, moduleHandle, 0);
}
internal bool Detach()
{
return User32.UnhookWindowsHookEx(handle);
}
private IntPtr LowLevelMouseProc(int nCode, IntPtr wParam, IntPtr lParam)
{
if (nCode >= 0 && !Ignore(wParam.ToInt32()))
{
var mouseData = (MSLLHOOKSTRUCT) Marshal.PtrToStructure(lParam, typeof(MSLLHOOKSTRUCT));
var button = GetButton(wParam.ToInt32());
var state = GetState(wParam.ToInt32());
var info = GetInfo(mouseData);
if (callback(button, state, info))
{
return (IntPtr) 1;
}
}
return User32.CallNextHookEx(handle, nCode, wParam, lParam);
}
private bool Ignore(int wParam)
{
// For performance reasons, ignore mouse movement and wheel rotation...
return wParam == Constant.WM_MOUSEMOVE || wParam == Constant.WM_MOUSEWHEEL;
}
private MouseButton GetButton(int wParam)
{
switch (wParam)
{
case Constant.WM_LBUTTONDOWN:
case Constant.WM_LBUTTONUP:
return MouseButton.Left;
case Constant.WM_MBUTTONDOWN:
case Constant.WM_MBUTTONUP:
return MouseButton.Middle;
case Constant.WM_RBUTTONDOWN:
case Constant.WM_RBUTTONUP:
return MouseButton.Right;
case Constant.WM_XBUTTONDOWN:
case Constant.WM_XBUTTONUP:
return MouseButton.Auxiliary;
default:
return MouseButton.Unknown;
}
}
private MouseInformation GetInfo(MSLLHOOKSTRUCT mouseData)
{
var info = new MouseInformation();
var extraInfo = mouseData.DwExtraInfo.ToUInt32();
info.IsTouch = (extraInfo & Constant.MOUSEEVENTF_MASK) == Constant.MOUSEEVENTF_FROMTOUCH;
info.X = mouseData.Point.X;
info.Y = mouseData.Point.Y;
return info;
}
private MouseButtonState GetState(int wParam)
{
switch (wParam)
{
case Constant.WM_LBUTTONDOWN:
case Constant.WM_MBUTTONDOWN:
case Constant.WM_RBUTTONDOWN:
case Constant.WM_XBUTTONDOWN:
return MouseButtonState.Pressed;
case Constant.WM_LBUTTONUP:
case Constant.WM_MBUTTONUP:
case Constant.WM_RBUTTONUP:
case Constant.WM_XBUTTONUP:
return MouseButtonState.Released;
default:
return MouseButtonState.Unknown;
}
}
}
}

View File

@@ -0,0 +1,66 @@
/*
* 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;
using SafeExamBrowser.WindowsApi.Constants;
using SafeExamBrowser.WindowsApi.Contracts.Events;
using SafeExamBrowser.WindowsApi.Delegates;
namespace SafeExamBrowser.WindowsApi.Hooks
{
internal class SystemHook
{
private SystemEventCallback callback;
private AutoResetEvent detachEvent, detachResultAvailableEvent;
private bool detachSuccess;
private EventDelegate eventDelegate;
private uint eventId;
private IntPtr handle;
internal Guid Id { get; private set; }
public SystemHook(SystemEventCallback callback, uint eventId)
{
this.callback = callback;
this.detachEvent = new AutoResetEvent(false);
this.detachResultAvailableEvent = new AutoResetEvent(false);
this.eventId = eventId;
this.Id = Guid.NewGuid();
}
internal void Attach()
{
// IMPORTANT:
// 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!
eventDelegate = new EventDelegate(LowLevelSystemProc);
handle = User32.SetWinEventHook(eventId, eventId, IntPtr.Zero, eventDelegate, 0, 0, Constant.WINEVENT_OUTOFCONTEXT);
}
internal void AwaitDetach()
{
detachEvent.WaitOne();
detachSuccess = User32.UnhookWinEvent(handle);
detachResultAvailableEvent.Set();
}
internal bool Detach()
{
detachEvent.Set();
detachResultAvailableEvent.WaitOne();
return detachSuccess;
}
private void LowLevelSystemProc(IntPtr hWinEventHook, uint eventType, IntPtr hwnd, int idObject, int idChild, uint dwEventThread, uint dwmsEventTime)
{
callback(hwnd);
}
}
}

View File

@@ -0,0 +1,55 @@
/*
* 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.Runtime.InteropServices;
using SafeExamBrowser.WindowsApi.Constants;
using SafeExamBrowser.WindowsApi.Types;
namespace SafeExamBrowser.WindowsApi
{
/// <summary>
/// Provides access to the native Windows API exposed by <c>kernel32.dll</c>.
/// </summary>
internal class Kernel32
{
[DllImport("kernel32.dll", SetLastError = true)]
internal static extern bool CloseHandle(IntPtr hObject);
[DllImport("kernel32.dll", SetLastError = true)]
internal static extern bool CreateProcess(
string lpApplicationName,
string lpCommandLine,
IntPtr lpProcessAttributes,
IntPtr lpThreadAttributes,
bool bInheritHandles,
int dwCreationFlags,
IntPtr lpEnvironment,
string lpCurrentDirectory,
ref STARTUPINFO lpStartupInfo,
ref PROCESS_INFORMATION lpProcessInformation);
[DllImport("kernel32.dll", SetLastError = true)]
internal static extern int GetCurrentThreadId();
[DllImport("kernel32.dll", SetLastError = true)]
internal static extern IntPtr GetModuleHandle(string lpModuleName);
[DllImport("kernel32.dll", SetLastError = true)]
internal static extern IntPtr OpenThread(ThreadAccess dwDesiredAccess, bool bInheritHandle, uint dwThreadId);
[DllImport("kernel32.dll", SetLastError = true)]
internal static extern int ResumeThread(IntPtr hThread);
[DllImport("kernel32.dll", SetLastError = true)]
internal static extern EXECUTION_STATE SetThreadExecutionState(EXECUTION_STATE esFlags);
[DllImport("kernel32.dll", SetLastError = true)]
internal static extern int SuspendThread(IntPtr hThread);
}
}

View File

@@ -0,0 +1,541 @@
/*
* 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.Concurrent;
using System.Collections.Generic;
using System.ComponentModel;
using System.Linq;
using System.Runtime.InteropServices;
using System.Text;
using System.Threading;
using SafeExamBrowser.WindowsApi.Constants;
using SafeExamBrowser.WindowsApi.Contracts;
using SafeExamBrowser.WindowsApi.Contracts.Events;
using SafeExamBrowser.WindowsApi.Hooks;
using SafeExamBrowser.WindowsApi.Types;
namespace SafeExamBrowser.WindowsApi
{
public class NativeMethods : INativeMethods
{
private readonly ConcurrentDictionary<Guid, KeyboardHook> KeyboardHooks = new ConcurrentDictionary<Guid, KeyboardHook>();
private readonly ConcurrentDictionary<Guid, MouseHook> MouseHooks = new ConcurrentDictionary<Guid, MouseHook>();
private readonly ConcurrentDictionary<Guid, SystemHook> SystemHooks = new ConcurrentDictionary<Guid, SystemHook>();
/// <summary>
/// Upon finalization, unregister all active system events and hooks...
/// </summary>
~NativeMethods()
{
foreach (var hook in SystemHooks.Values)
{
hook.Detach();
}
foreach (var hook in KeyboardHooks.Values)
{
hook.Detach();
}
foreach (var hook in MouseHooks.Values)
{
hook.Detach();
}
}
public void ActivateWindow(IntPtr handle)
{
var placement = new WINDOWPLACEMENT();
User32.BringWindowToTop(handle);
User32.SetForegroundWindow(handle);
placement.length = Marshal.SizeOf(placement);
User32.GetWindowPlacement(handle, ref placement);
if (placement.showCmd == (int) ShowWindowCommand.ShowMinimized)
{
User32.ShowWindowAsync(handle, (int) ShowWindowCommand.Restore);
}
}
public void DeregisterKeyboardHook(Guid hookId)
{
var hook = KeyboardHooks.Values.FirstOrDefault(h => h.Id == hookId);
if (hook != default)
{
var success = hook.Detach();
if (!success)
{
throw new Win32Exception(Marshal.GetLastWin32Error());
}
KeyboardHooks.TryRemove(hookId, out _);
}
}
public void DeregisterMouseHook(Guid hookId)
{
var hook = MouseHooks.Values.FirstOrDefault(h => h.Id == hookId);
if (hook != default)
{
var success = hook.Detach();
if (!success)
{
throw new Win32Exception(Marshal.GetLastWin32Error());
}
MouseHooks.TryRemove(hookId, out _);
}
}
public void DeregisterSystemEventHook(Guid hookId)
{
var hook = SystemHooks.Values.FirstOrDefault(h => h.Id == hookId);
if (hook != default)
{
var success = hook.Detach();
if (!success)
{
throw new Win32Exception(Marshal.GetLastWin32Error());
}
SystemHooks.TryRemove(hookId, out _);
}
}
public bool DisableStickyKeys()
{
var success = TryGetStickyKeys(out var state);
if (success)
{
(state as StickyKeysState).Flags &= ~StickyKeysFlags.AVAILABLE;
(state as StickyKeysState).Flags &= ~StickyKeysFlags.HOTKEYACTIVE;
(state as StickyKeysState).Flags &= ~StickyKeysFlags.ON;
success = TrySetStickyKeys(state);
}
return success;
}
public void EmptyClipboard()
{
var success = true;
success &= User32.OpenClipboard(IntPtr.Zero);
success &= User32.EmptyClipboard();
success &= User32.CloseClipboard();
if (!success)
{
throw new Win32Exception(Marshal.GetLastWin32Error());
}
}
public bool EnableStickyKeys()
{
var success = TryGetStickyKeys(out var state);
if (success)
{
(state as StickyKeysState).Flags |= StickyKeysFlags.AVAILABLE;
(state as StickyKeysState).Flags |= StickyKeysFlags.HOTKEYACTIVE;
(state as StickyKeysState).Flags |= StickyKeysFlags.ON;
success = TrySetStickyKeys(state);
}
return success;
}
public (int x, int y) GetCursorPosition()
{
var position = new POINT();
User32.GetCursorPos(ref position);
return (position.X, position.Y);
}
public IEnumerable<IntPtr> GetOpenWindows()
{
var windows = new List<IntPtr>();
bool EnumWindows(IntPtr hWnd, IntPtr lParam)
{
if (hWnd != GetShellWindowHandle() && User32.IsWindowVisible(hWnd) && User32.GetWindowTextLength(hWnd) > 0)
{
windows.Add(hWnd);
}
return true;
}
var success = User32.EnumWindows(EnumWindows, IntPtr.Zero);
if (!success)
{
throw new Win32Exception(Marshal.GetLastWin32Error());
}
return windows;
}
public uint GetProcessIdFor(IntPtr window)
{
User32.GetWindowThreadProcessId(window, out var processId);
return processId;
}
public IntPtr GetShellWindowHandle()
{
return User32.FindWindow("Shell_TrayWnd", default);
}
public uint GetShellProcessId()
{
var handle = GetShellWindowHandle();
var threadId = User32.GetWindowThreadProcessId(handle, out var processId);
return processId;
}
public string GetWallpaperPath()
{
const int MAX_PATH = 260;
var buffer = new String('\0', MAX_PATH);
var success = User32.SystemParametersInfo(SPI.GETDESKWALLPAPER, buffer.Length, buffer, SPIF.NONE);
if (!success)
{
throw new Win32Exception(Marshal.GetLastWin32Error());
}
var path = buffer.Substring(0, buffer.IndexOf('\0'));
return path;
}
public IntPtr GetWindowIcon(IntPtr window)
{
var icon = User32.SendMessage(window, Constant.WM_GETICON, new IntPtr(Constant.ICON_BIG), IntPtr.Zero);
if (icon == IntPtr.Zero)
{
icon = User32.SendMessage(window, Constant.WM_GETICON, new IntPtr(Constant.ICON_SMALL), IntPtr.Zero);
}
if (icon == IntPtr.Zero)
{
icon = User32.SendMessage(window, Constant.WM_GETICON, new IntPtr(Constant.ICON_SMALL2), IntPtr.Zero);
}
return icon;
}
public string GetWindowTitle(IntPtr window)
{
var length = User32.GetWindowTextLength(window);
if (length > 0)
{
var builder = new StringBuilder(length);
User32.GetWindowText(window, builder, length + 1);
return builder.ToString();
}
return string.Empty;
}
public IBounds GetWorkingArea()
{
var workingArea = new RECT();
var success = User32.SystemParametersInfo(SPI.GETWORKAREA, 0, ref workingArea, SPIF.NONE);
if (!success)
{
throw new Win32Exception(Marshal.GetLastWin32Error());
}
return workingArea.ToBounds();
}
public bool HasInternetConnection()
{
return WinInet.InternetGetConnectedState(out _, 0);
}
public bool HideWindow(IntPtr window)
{
return User32.ShowWindow(window, (int) ShowWindowCommand.Hide);
}
public void MinimizeAllOpenWindows()
{
var handle = GetShellWindowHandle();
User32.SendMessage(handle, Constant.WM_COMMAND, (IntPtr) Constant.MIN_ALL, IntPtr.Zero);
}
public void PostCloseMessageToShell()
{
// NOTE: The close message 0x5B4 posted to the shell is undocumented and not officially supported:
// https://stackoverflow.com/questions/5689904/gracefully-exit-explorer-programmatically/5705965#5705965
var handle = GetShellWindowHandle();
var success = User32.PostMessage(handle, 0x5B4, IntPtr.Zero, IntPtr.Zero);
if (!success)
{
throw new Win32Exception(Marshal.GetLastWin32Error());
}
}
public Guid RegisterKeyboardHook(KeyboardHookCallback callback)
{
var hookId = default(Guid);
var hookReadyEvent = new AutoResetEvent(false);
var hookThread = new Thread(() =>
{
var hook = new KeyboardHook(callback);
var sleepEvent = new AutoResetEvent(false);
hook.Attach();
hookId = hook.Id;
KeyboardHooks[hookId] = hook;
hookReadyEvent.Set();
while (true)
{
sleepEvent.WaitOne();
}
});
hookThread.SetApartmentState(ApartmentState.STA);
hookThread.IsBackground = true;
hookThread.Start();
hookReadyEvent.WaitOne();
return hookId;
}
public Guid RegisterMouseHook(MouseHookCallback callback)
{
var hookId = default(Guid);
var hookReadyEvent = new AutoResetEvent(false);
var hookThread = new Thread(() =>
{
var hook = new MouseHook(callback);
var sleepEvent = new AutoResetEvent(false);
hook.Attach();
hookId = hook.Id;
MouseHooks[hookId] = hook;
hookReadyEvent.Set();
while (true)
{
sleepEvent.WaitOne();
}
});
hookThread.SetApartmentState(ApartmentState.STA);
hookThread.IsBackground = true;
hookThread.Start();
hookReadyEvent.WaitOne();
return hookId;
}
public Guid RegisterSystemCaptureStartEvent(SystemEventCallback callback)
{
return RegisterSystemEvent(callback, Constant.EVENT_SYSTEM_CAPTURESTART);
}
public Guid RegisterSystemForegroundEvent(SystemEventCallback callback)
{
return RegisterSystemEvent(callback, Constant.EVENT_SYSTEM_FOREGROUND);
}
internal Guid RegisterSystemEvent(SystemEventCallback callback, uint eventId)
{
var hookId = default(Guid);
var hookReadyEvent = new AutoResetEvent(false);
var hookThread = new Thread(() =>
{
var hook = new SystemHook(callback, eventId);
hook.Attach();
hookId = hook.Id;
SystemHooks[hookId] = hook;
hookReadyEvent.Set();
hook.AwaitDetach();
});
hookThread.SetApartmentState(ApartmentState.STA);
hookThread.IsBackground = true;
hookThread.Start();
hookReadyEvent.WaitOne();
return hookId;
}
public void RemoveWallpaper()
{
SetWallpaper(string.Empty);
}
public void RestoreWindow(IntPtr window)
{
User32.ShowWindow(window, (int) ShowWindowCommand.Restore);
}
public bool ResumeThread(int threadId)
{
const int FAILURE = -1;
var handle = Kernel32.OpenThread(ThreadAccess.SUSPEND_RESUME, false, (uint) threadId);
if (handle == IntPtr.Zero)
{
return false;
}
try
{
var result = Kernel32.ResumeThread(handle);
var success = result != FAILURE;
return success;
}
finally
{
Kernel32.CloseHandle(handle);
}
}
public void SendCloseMessageTo(IntPtr window)
{
User32.SendMessage(window, Constant.WM_SYSCOMMAND, (IntPtr) SystemCommand.CLOSE, IntPtr.Zero);
}
public void SetAlwaysOnState(bool display = true, bool system = true)
{
if (display || system)
{
var state = EXECUTION_STATE.CONTINUOUS;
state |= display ? EXECUTION_STATE.DISPLAY_REQUIRED : 0x0;
state |= system ? EXECUTION_STATE.SYSTEM_REQUIRED : 0x0;
Kernel32.SetThreadExecutionState(state);
}
}
public void SetWallpaper(string filePath)
{
var success = User32.SystemParametersInfo(SPI.SETDESKWALLPAPER, 0, filePath, SPIF.NONE);
if (!success)
{
throw new Win32Exception(Marshal.GetLastWin32Error());
}
}
public void SetWorkingArea(IBounds bounds)
{
var workingArea = new RECT { Left = bounds.Left, Top = bounds.Top, Right = bounds.Right, Bottom = bounds.Bottom };
var success = User32.SystemParametersInfo(SPI.SETWORKAREA, 0, ref workingArea, SPIF.NONE);
if (!success)
{
throw new Win32Exception(Marshal.GetLastWin32Error());
}
}
public bool SuspendThread(int threadId)
{
const int FAILURE = -1;
var handle = Kernel32.OpenThread(ThreadAccess.SUSPEND_RESUME, false, (uint) threadId);
if (handle == IntPtr.Zero)
{
return false;
}
try
{
var result = Kernel32.SuspendThread(handle);
var success = result != FAILURE;
return success;
}
finally
{
Kernel32.CloseHandle(handle);
}
}
public bool TryGetStickyKeys(out IStickyKeysState state)
{
var stickyKeys = new STICKYKEYS();
state = default;
stickyKeys.cbSize = Marshal.SizeOf(typeof(STICKYKEYS));
var success = User32.SystemParametersInfo(SPI.GETSTICKYKEYS, stickyKeys.cbSize, ref stickyKeys, SPIF.NONE);
if (success)
{
state = new StickyKeysState
{
Flags = stickyKeys.dwFlags,
IsAvailable = stickyKeys.dwFlags.HasFlag(StickyKeysFlags.AVAILABLE),
IsEnabled = stickyKeys.dwFlags.HasFlag(StickyKeysFlags.ON),
IsHotkeyActive = stickyKeys.dwFlags.HasFlag(StickyKeysFlags.HOTKEYACTIVE)
};
}
return success;
}
public bool TrySetStickyKeys(IStickyKeysState state)
{
var success = false;
var stickyKeys = new STICKYKEYS();
if (state is StickyKeysState stateWithFlags)
{
stickyKeys.cbSize = Marshal.SizeOf(typeof(STICKYKEYS));
stickyKeys.dwFlags = stateWithFlags.Flags;
success = User32.SystemParametersInfo(SPI.SETSTICKYKEYS, stickyKeys.cbSize, ref stickyKeys, SPIF.NONE);
}
else
{
success = state.IsEnabled ? EnableStickyKeys() : DisableStickyKeys();
}
return success;
}
}
}

View File

@@ -0,0 +1,171 @@
/*
* 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.Text;
using SafeExamBrowser.Logging.Contracts;
using SafeExamBrowser.WindowsApi.Contracts;
using SafeExamBrowser.WindowsApi.Contracts.Events;
namespace SafeExamBrowser.WindowsApi.Processes
{
internal class Process : IProcess
{
private readonly ILogger logger;
private readonly System.Diagnostics.Process process;
private bool eventInitialized;
public bool HasTerminated
{
get { return IsTerminated(); }
}
public int Id
{
get { return process.Id; }
}
public string Name { get; }
public string OriginalName { get; }
public string Path { get; }
public string Signature { get; }
private event ProcessTerminatedEventHandler TerminatedEvent;
public event ProcessTerminatedEventHandler Terminated
{
add { TerminatedEvent += value; InitializeEvent(); }
remove { TerminatedEvent -= value; }
}
internal Process(System.Diagnostics.Process process, string name, string originalName, ILogger logger, string path, string signature)
{
this.logger = logger;
this.process = process;
this.Name = name;
this.OriginalName = originalName;
this.Path = path;
this.Signature = signature?.ToLower();
}
public string GetAdditionalInfo()
{
var info = new StringBuilder();
info.Append($"Original Name: {(string.IsNullOrWhiteSpace(OriginalName) ? "n/a" : $"'{OriginalName}'")}, ");
info.Append($"Path: {(string.IsNullOrWhiteSpace(Path) ? "n/a" : $"'{Path}'")}, ");
info.Append($"Signature: {(string.IsNullOrWhiteSpace(Signature) ? "n/a" : Signature)}");
return info.ToString();
}
public bool TryClose(int timeout_ms = 0)
{
try
{
logger.Debug("Attempting to close process...");
process.Refresh();
var success = process.CloseMainWindow();
if (success)
{
logger.Debug("Successfully sent close message to main window.");
}
else
{
logger.Warn("Failed to send close message to main window!");
}
return success && WaitForTermination(timeout_ms);
}
catch (Exception e)
{
logger.Error("Failed to close main window!", e);
}
return false;
}
public bool TryKill(int timeout_ms = 0)
{
try
{
logger.Debug("Attempting to kill process...");
process.Refresh();
process.Kill();
return WaitForTermination(timeout_ms);
}
catch (Exception e)
{
logger.Error("Failed to kill process!", e);
}
return false;
}
public override string ToString()
{
return $"'{Name}' ({Id})";
}
private bool IsTerminated()
{
try
{
process.Refresh();
return process.HasExited;
}
catch (Exception e)
{
logger.Error("Failed to check whether process is terminated!", e);
}
return false;
}
private void InitializeEvent()
{
if (!eventInitialized)
{
eventInitialized = true;
process.Exited += Process_Exited;
process.EnableRaisingEvents = true;
logger.Debug("Initialized termination event.");
}
}
private bool WaitForTermination(int timeout_ms)
{
var terminated = process.WaitForExit(timeout_ms);
if (terminated)
{
logger.Debug($"Process has terminated within {timeout_ms}ms.");
}
else
{
logger.Warn($"Process failed to terminate within {timeout_ms}ms!");
}
return terminated;
}
private void Process_Exited(object sender, EventArgs e)
{
TerminatedEvent?.Invoke(process.ExitCode);
logger.Debug("Process has terminated.");
}
}
}

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.ComponentModel;
using System.Diagnostics;
using System.IO;
using System.Linq;
using System.Management;
using System.Runtime.InteropServices;
using System.Security.Cryptography.X509Certificates;
using SafeExamBrowser.Logging.Contracts;
using SafeExamBrowser.WindowsApi.Constants;
using SafeExamBrowser.WindowsApi.Contracts;
using SafeExamBrowser.WindowsApi.Types;
namespace SafeExamBrowser.WindowsApi.Processes
{
public class ProcessFactory : IProcessFactory
{
private readonly IModuleLogger logger;
public IDesktop StartupDesktop { private get; set; }
public ProcessFactory(IModuleLogger logger)
{
this.logger = logger;
}
public IList<IProcess> GetAllRunning()
{
var processes = new List<IProcess>();
var running = System.Diagnostics.Process.GetProcesses();
var names = LoadAllProcessNames();
foreach (var process in running)
{
if (names.Any(n => n.processId == process.Id))
{
var (_, name, originalName, path, signature) = names.First(n => n.processId == process.Id);
processes.Add(new Process(process, name, originalName, LoggerFor(process, name), path, signature));
}
}
return processes;
}
public IProcess StartNew(string path, params string[] args)
{
var raw = default(System.Diagnostics.Process);
logger.Info($"Attempting to start process '{path}'...");
if (StartupDesktop != default(IDesktop))
{
raw = StartOnDesktop(path, args);
}
else
{
raw = StartNormal(path, args);
}
var (name, originalName, _, signature) = LoadProcessNamesFor(raw);
var process = new Process(raw, name, originalName, LoggerFor(raw, name), path, signature);
logger.Info($"Successfully started process '{path}' with ID = {process.Id}.");
return process;
}
public bool TryGetById(int id, out IProcess process)
{
process = default;
try
{
var raw = System.Diagnostics.Process.GetProcessById(id);
var (name, originalName, path, signature) = LoadProcessNamesFor(raw);
process = new Process(raw, name, originalName, LoggerFor(raw, name), path, signature);
}
catch (Exception e)
{
logger.Error($"Failed to get process with ID = {id}!", e);
}
return process != default(IProcess);
}
private IEnumerable<(int processId, string name, string originalName, string path, string signature)> LoadAllProcessNames()
{
var names = new List<(int, string, string, string, string)>();
try
{
using (var searcher = new ManagementObjectSearcher($"SELECT ProcessId, Name, ExecutablePath FROM Win32_Process"))
using (var results = searcher.Get())
{
var processData = results.Cast<ManagementObject>().ToList();
foreach (var process in processData)
{
using (process)
{
var name = Convert.ToString(process["Name"]);
var originalName = default(string);
var path = Convert.ToString(process["ExecutablePath"]);
var processId = Convert.ToInt32(process["ProcessId"]);
var signature = default(string);
if (File.Exists(path))
{
TryLoadOriginalName(path, out originalName);
TryLoadSignature(path, out signature);
}
names.Add((processId, name, originalName, path, signature));
}
}
}
}
catch (Exception e)
{
logger.Error("Failed to load process names!", e);
}
return names;
}
private (string name, string originalName, string path, string signature) LoadProcessNamesFor(System.Diagnostics.Process process)
{
var name = process.ProcessName;
var originalName = default(string);
var path = default(string);
var signature = default(string);
try
{
using (var searcher = new ManagementObjectSearcher($"SELECT Name, ExecutablePath FROM Win32_Process WHERE ProcessId = {process.Id}"))
using (var results = searcher.Get())
using (var processData = results.Cast<ManagementObject>().First())
{
name = Convert.ToString(processData["Name"]);
path = Convert.ToString(processData["ExecutablePath"]);
if (File.Exists(path))
{
TryLoadOriginalName(path, out originalName);
TryLoadSignature(path, out signature);
}
}
}
catch (Exception e)
{
logger.Error($"Failed to load process names for {process.ProcessName}!", e);
}
return (name, originalName, path, signature);
}
private ILogger LoggerFor(System.Diagnostics.Process process, string name)
{
return logger.CloneFor($"{nameof(Process)} '{name}' ({process.Id})");
}
private System.Diagnostics.Process StartNormal(string path, params string[] args)
{
var process = new System.Diagnostics.Process();
process.StartInfo.Arguments = string.Join(" ", args);
process.StartInfo.FileName = path;
process.StartInfo.UseShellExecute = false;
process.StartInfo.WorkingDirectory = Environment.GetFolderPath(Environment.SpecialFolder.Windows);
process.Start();
return process;
}
private System.Diagnostics.Process StartOnDesktop(string path, params string[] args)
{
var commandLine = $"{'"' + path + '"'} {string.Join(" ", args)}";
var processInfo = new PROCESS_INFORMATION();
var startupInfo = new STARTUPINFO();
startupInfo.cb = Marshal.SizeOf(startupInfo);
startupInfo.lpDesktop = StartupDesktop?.Name;
var success = Kernel32.CreateProcess(null, commandLine, IntPtr.Zero, IntPtr.Zero, true, Constant.NORMAL_PRIORITY_CLASS, IntPtr.Zero, null, ref startupInfo, ref processInfo);
if (success)
{
return System.Diagnostics.Process.GetProcessById(processInfo.dwProcessId);
}
var errorCode = Marshal.GetLastWin32Error();
logger.Error($"Failed to start process '{path}' on desktop '{StartupDesktop}'! Error code: {errorCode}.");
throw new Win32Exception(errorCode);
}
private bool TryLoadOriginalName(string path, out string originalName)
{
originalName = default;
try
{
originalName = FileVersionInfo.GetVersionInfo(path).OriginalFilename;
}
catch
{
}
return originalName != default;
}
private bool TryLoadSignature(string path, out string signature)
{
signature = default;
try
{
using (var certificate = X509Certificate.CreateFromSignedFile(path))
{
signature = certificate.GetCertHashString();
}
}
catch
{
}
return signature != default;
}
}
}

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.WindowsApi")]
[assembly: AssemblyDescription("Safe Exam Browser")]
[assembly: AssemblyCompany("ETH Zürich")]
[assembly: AssemblyProduct("SafeExamBrowser.WindowsApi")]
[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("73724659-4150-4792-a94e-42f5f3c1b696")]
// 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,112 @@
<?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>{73724659-4150-4792-A94E-42F5F3C1B696}</ProjectGuid>
<OutputType>Library</OutputType>
<AppDesignerFolder>Properties</AppDesignerFolder>
<RootNamespace>SafeExamBrowser.WindowsApi</RootNamespace>
<AssemblyName>SafeExamBrowser.WindowsApi</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="Microsoft.CSharp" />
<Reference Include="System.Drawing" />
<Reference Include="System.Management" />
</ItemGroup>
<ItemGroup>
<Compile Include="Constants\Constant.cs" />
<Compile Include="Constants\HookType.cs" />
<Compile Include="Constants\StickyKeysFlags.cs" />
<Compile Include="Constants\ThreadAccess.cs" />
<Compile Include="Constants\VirtualKeyCode.cs" />
<Compile Include="Delegates\EnumDesktopDelegate.cs" />
<Compile Include="Delegates\EnumWindowsDelegate.cs" />
<Compile Include="Delegates\EventDelegate.cs" />
<Compile Include="Delegates\HookDelegate.cs" />
<Compile Include="Desktops\Desktop.cs" />
<Compile Include="Desktops\DesktopFactory.cs" />
<Compile Include="Desktops\DesktopMonitor.cs" />
<Compile Include="Desktops\ObfuscatedDesktop.cs" />
<Compile Include="ExplorerShell.cs" />
<Compile Include="Hooks\MouseHook.cs" />
<Compile Include="Hooks\SystemHook.cs" />
<Compile Include="Processes\Process.cs" />
<Compile Include="Processes\ProcessFactory.cs" />
<Compile Include="Constants\AccessMask.cs" />
<Compile Include="Types\Bounds.cs" />
<Compile Include="Types\EXECUTION_STATE.cs" />
<Compile Include="Types\KBDLLHOOKSTRUCT.cs" />
<Compile Include="Types\KBDLLHOOKSTRUCTFlags.cs" />
<Compile Include="Constants\ShowWindowCommand.cs" />
<Compile Include="Constants\SystemCommand.cs" />
<Compile Include="Kernel32.cs" />
<Compile Include="Hooks\KeyboardHook.cs" />
<Compile Include="NativeMethods.cs" />
<Compile Include="Properties\AssemblyInfo.cs" />
<Compile Include="Constants\SPI.cs" />
<Compile Include="Constants\SPIF.cs" />
<Compile Include="Types\MSLLHOOKSTRUCT.cs" />
<Compile Include="Types\POINT.cs" />
<Compile Include="Types\PROCESS_INFORMATION.cs" />
<Compile Include="Types\RECT.cs" />
<Compile Include="Types\STARTUPINFO.cs" />
<Compile Include="Types\STICKYKEYS.cs" />
<Compile Include="Types\StickyKeysState.cs" />
<Compile Include="Types\Window.cs" />
<Compile Include="Types\WINDOWPLACEMENT.cs" />
<Compile Include="User32.cs" />
<Compile Include="WinInet.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.WindowsApi.Contracts\SafeExamBrowser.WindowsApi.Contracts.csproj">
<Project>{7016f080-9aa5-41b2-a225-385ad877c171}</Project>
<Name>SafeExamBrowser.WindowsApi.Contracts</Name>
</ProjectReference>
</ItemGroup>
<Import Project="$(MSBuildToolsPath)\Microsoft.CSharp.targets" />
</Project>

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.WindowsApi.Types
{
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,25 @@
/*
* 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;
namespace SafeExamBrowser.WindowsApi.Types
{
/// <remarks>
/// See http://www.pinvoke.net/default.aspx/kernel32/SetThreadExecutionState.html.
/// See https://msdn.microsoft.com/en-us/library/aa373208(v=vs.85).aspx.
/// </remarks>
[Flags]
public enum EXECUTION_STATE : uint
{
AWAYMODE_REQUIRED = 0x00000040,
CONTINUOUS = 0x80000000,
DISPLAY_REQUIRED = 0x00000002,
SYSTEM_REQUIRED = 0x00000001
}
}

View File

@@ -0,0 +1,48 @@
/*
* 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.Runtime.InteropServices;
namespace SafeExamBrowser.WindowsApi.Types
{
/// <remarks>
/// See http://www.pinvoke.net/default.aspx/Structures/KBDLLHOOKSTRUCT.html.
/// </remarks>
[StructLayout(LayoutKind.Sequential)]
internal struct KBDLLHOOKSTRUCT
{
/// <summary>
/// A virtual-key code. The code must be a value in the range 1 to 254.
/// </summary>
internal uint KeyCode;
/// <summary>
/// A hardware scan code for the key.
/// </summary>
internal uint ScanCode;
/// <summary>
/// The extended-key flag, event-injected flags, context code, and transition-state flag. This member is specified as follows. An
/// application can use the following values to test the keystroke flags. Testing LLKHF_INJECTED (bit 4) will tell you whether the
/// event was injected. If it was, then testing LLKHF_LOWER_IL_INJECTED (bit 1) will tell you whether or not the event was injected
/// from a process running at lower integrity level.
/// </summary>
internal KBDLLHOOKSTRUCTFlags Flags;
/// <summary>
/// The time stamp for this message, equivalent to what <c>GetMessageTime</c> would return for this message.
/// </summary>
internal uint Time;
/// <summary>
/// Additional information associated with the message.
/// </summary>
internal IntPtr DwExtraInfo;
}
}

View File

@@ -0,0 +1,36 @@
/*
* 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.WindowsApi.Types
{
/// <remarks>
/// See http://www.pinvoke.net/default.aspx/Structures/KBDLLHOOKSTRUCT.html.
/// </remarks>
internal enum KBDLLHOOKSTRUCTFlags
{
/// <summary>
/// Test the extended-key flag.
/// </summary>
LLKHF_EXTENDED = 0x01,
/// <summary>
/// Test the event-injected (from any process) flag.
/// </summary>
LLKHF_INJECTED = 0x10,
/// <summary>
/// Test the context code.
/// </summary>
LLKHF_ALTDOWN = 0x20,
/// <summary>
/// Test the transition-state flag.
/// </summary>
LLKHF_UP = 0x80
}
}

View File

@@ -0,0 +1,23 @@
/*
* 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.Runtime.InteropServices;
namespace SafeExamBrowser.WindowsApi.Types
{
[StructLayout(LayoutKind.Sequential)]
internal struct MSLLHOOKSTRUCT
{
internal POINT Point;
internal int MouseData;
internal int Flags;
internal int Time;
internal UIntPtr DwExtraInfo;
}
}

View File

@@ -0,0 +1,19 @@
/*
* 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.Runtime.InteropServices;
namespace SafeExamBrowser.WindowsApi.Types
{
[StructLayout(LayoutKind.Sequential)]
internal struct POINT
{
internal int X;
internal int Y;
}
}

View File

@@ -0,0 +1,26 @@
/*
* 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.Runtime.InteropServices;
namespace SafeExamBrowser.WindowsApi.Types
{
/// <remarks>
/// See http://pinvoke.net/default.aspx/Structures/PROCESS_INFORMATION.html.
/// See https://msdn.microsoft.com/en-us/library/ms684873(VS.85).aspx.
/// </remarks>
[StructLayout(LayoutKind.Sequential)]
internal struct PROCESS_INFORMATION
{
public IntPtr hProcess;
public IntPtr hThread;
public int dwProcessId;
public int dwThreadId;
}
}

View File

@@ -0,0 +1,36 @@
/*
* 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.Runtime.InteropServices;
using SafeExamBrowser.WindowsApi.Contracts;
namespace SafeExamBrowser.WindowsApi.Types
{
/// <remarks>
/// See https://msdn.microsoft.com/en-us/library/windows/desktop/dd162897(v=vs.85).aspx.
/// </remarks>
[StructLayout(LayoutKind.Sequential)]
internal struct RECT
{
internal int Left;
internal int Top;
internal int Right;
internal int Bottom;
internal IBounds ToBounds()
{
return new Bounds
{
Left = Left,
Top = Top,
Right = Right,
Bottom = Bottom
};
}
}
}

View File

@@ -0,0 +1,39 @@
/*
* 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.Runtime.InteropServices;
namespace SafeExamBrowser.WindowsApi.Types
{
/// <remarks>
/// See https://msdn.microsoft.com/en-us/library/windows/desktop/ms686331(v=vs.85).aspx.
/// </remarks>
[StructLayout(LayoutKind.Sequential)]
internal struct STARTUPINFO
{
public int cb;
public string lpReserved;
public string lpDesktop;
public string lpTitle;
public int dwX;
public int dwY;
public int dwXSize;
public int dwYSize;
public int dwXCountChars;
public int dwYCountChars;
public int dwFillAttribute;
public int dwFlags;
public short wShowWindow;
public short cbReserved2;
public IntPtr lpReserved2;
public IntPtr hStdInput;
public IntPtr hStdOutput;
public IntPtr hStdError;
}
}

View File

@@ -0,0 +1,23 @@
/*
* 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.Runtime.InteropServices;
using SafeExamBrowser.WindowsApi.Constants;
namespace SafeExamBrowser.WindowsApi.Types
{
/// <remarks>
/// See https://learn.microsoft.com/en-us/windows/win32/api/winuser/ns-winuser-stickykeys.
/// </remarks>
[StructLayout(LayoutKind.Sequential)]
internal struct STICKYKEYS
{
internal int cbSize;
internal StickyKeysFlags dwFlags;
}
}

View File

@@ -0,0 +1,22 @@
/*
* 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.Constants;
using SafeExamBrowser.WindowsApi.Contracts;
namespace SafeExamBrowser.WindowsApi.Types
{
public class StickyKeysState : IStickyKeysState
{
internal StickyKeysFlags Flags { get; set; }
public bool IsAvailable { get; internal set; }
public bool IsEnabled { get; internal set; }
public bool IsHotkeyActive { get; internal set; }
}
}

View File

@@ -0,0 +1,22 @@
/*
* 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.Drawing;
namespace SafeExamBrowser.WindowsApi.Types
{
internal struct WINDOWPLACEMENT
{
public int length;
public int flags;
public int showCmd;
public Point ptMinPosition;
public Point ptMaxPosition;
public Rectangle rcNormalPosition;
}
}

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/.
*/
using System;
namespace SafeExamBrowser.WindowsApi.Types
{
internal struct Window
{
internal IntPtr Handle { get; set; }
internal string Title { get; set; }
}
}

View File

@@ -0,0 +1,122 @@
/*
* 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.Runtime.InteropServices;
using System.Text;
using SafeExamBrowser.WindowsApi.Constants;
using SafeExamBrowser.WindowsApi.Delegates;
using SafeExamBrowser.WindowsApi.Types;
namespace SafeExamBrowser.WindowsApi
{
/// <summary>
/// Provides access to the native Windows API exposed by <c>user32.dll</c>.
/// </summary>
internal static class User32
{
[DllImport("user32.dll", SetLastError = true)]
internal static extern IntPtr CallNextHookEx(IntPtr hhk, int nCode, IntPtr wParam, IntPtr lParam);
[DllImport("user32.dll", SetLastError = true)]
internal static extern bool CloseClipboard();
[DllImport("user32.dll", SetLastError = true)]
internal static extern bool CloseDesktop(IntPtr hDesktop);
[DllImport("user32.dll", SetLastError = true)]
internal static extern IntPtr CreateDesktop(string lpszDesktop, IntPtr lpszDevice, IntPtr pDevmode, int dwFlags, uint dwDesiredAccess, IntPtr lpsa);
[DllImport("user32.dll", SetLastError = true)]
internal static extern bool BringWindowToTop(IntPtr hWnd);
[DllImport("user32.dll", SetLastError = true)]
internal static extern bool EmptyClipboard();
[DllImport("user32.dll", SetLastError = true)]
internal static extern bool EnumDesktops(IntPtr hwinsta, EnumDesktopDelegate lpEnumFunc, IntPtr lParam);
[DllImport("user32.dll", SetLastError = true)]
internal static extern bool EnumWindows(EnumWindowsDelegate enumProc, IntPtr lParam);
[DllImport("user32.dll", SetLastError = true)]
internal static extern IntPtr FindWindow(string lpClassName, string lpWindowName);
[DllImport("user32.dll", SetLastError = true)]
internal static extern bool GetCursorPos(ref POINT pt);
[DllImport("user32.dll", SetLastError = true)]
internal static extern IntPtr GetThreadDesktop(int dwThreadId);
[DllImport("user32.dll", SetLastError = true)]
internal static extern IntPtr GetProcessWindowStation();
[DllImport("user32.dll", SetLastError = true)]
internal static extern bool GetUserObjectInformation(IntPtr hObj, int nIndex, IntPtr pvInfo, int nLength, ref int lpnLengthNeeded);
[DllImport("user32.dll", SetLastError = true)]
internal static extern bool GetWindowPlacement(IntPtr hWnd, ref WINDOWPLACEMENT lpwndpl);
[DllImport("user32.dll", SetLastError = true)]
internal static extern int GetWindowText(IntPtr hWnd, StringBuilder strText, int maxCount);
[DllImport("user32.dll", SetLastError = true)]
internal static extern int GetWindowTextLength(IntPtr hWnd);
[DllImport("user32.dll", SetLastError = true)]
internal static extern uint GetWindowThreadProcessId(IntPtr hWnd, out uint lpdwProcessId);
[DllImport("user32.dll", SetLastError = true)]
internal static extern bool IsWindowVisible(IntPtr hWnd);
[DllImport("user32.dll", SetLastError = true)]
internal static extern bool OpenClipboard(IntPtr hWndNewOwner);
[DllImport("user32.dll", SetLastError = true)]
internal static extern IntPtr OpenInputDesktop(uint dwFlags, bool fInherit, uint dwDesiredAccess);
[DllImport("user32.dll", SetLastError = true)]
internal static extern bool PostMessage(IntPtr hWnd, uint msg, IntPtr wParam, IntPtr lParam);
[DllImport("user32.dll", SetLastError = true)]
internal static extern IntPtr SendMessage(IntPtr hWnd, uint msg, IntPtr wParam, IntPtr lParam);
[DllImport("user32.dll", SetLastError = true)]
internal static extern bool SetForegroundWindow(IntPtr hWnd);
[DllImport("user32.dll", SetLastError = true)]
internal static extern IntPtr SetWinEventHook(uint eventMin, uint eventMax, IntPtr hmodWinEventProc, EventDelegate lpfnWinEventProc, uint idProcess, uint idThread, uint dwFlags);
[DllImport("user32.dll", SetLastError = true)]
internal static extern IntPtr SetWindowsHookEx(HookType hookType, HookDelegate lpfn, IntPtr hMod, uint dwThreadId);
[DllImport("user32.dll", SetLastError = true)]
internal static extern bool ShowWindow(IntPtr hWnd, int nCmdShow);
[DllImport("user32.dll", SetLastError = true)]
internal static extern bool ShowWindowAsync(IntPtr hWnd, int nCmdShow);
[DllImport("user32.dll", SetLastError = true)]
internal static extern bool SwitchDesktop(IntPtr hDesktop);
[DllImport("user32.dll", SetLastError = true)]
internal static extern bool SystemParametersInfo(SPI uiAction, uint uiParam, ref RECT pvParam, SPIF fWinIni);
[DllImport("user32.dll", SetLastError = true)]
internal static extern bool SystemParametersInfo(SPI uiAction, int uiParam, ref STICKYKEYS pvParam, SPIF fWinIni);
[DllImport("user32.dll", SetLastError = true)]
internal static extern bool SystemParametersInfo(SPI uiAction, int uiParam, string pvParam, SPIF fWinIni);
[DllImport("user32.dll", SetLastError = true)]
internal static extern bool UnhookWinEvent(IntPtr hWinEventHook);
[DllImport("user32.dll", SetLastError = true)]
internal static extern bool UnhookWindowsHookEx(IntPtr hhk);
}
}

View File

@@ -0,0 +1,21 @@
/*
* 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.Runtime.InteropServices;
namespace SafeExamBrowser.WindowsApi
{
/// <summary>
/// Provides access to the native Windows API exposed by <c>wininet.dll</c>.
/// </summary>
internal static class WinInet
{
[DllImport("wininet.dll", SetLastError = true)]
internal static extern bool InternetGetConnectedState(out int description, int reservedValue);
}
}