ICEYOU/src/iceyou/keyboard_block.py

157 lines
5.2 KiB
Python

"""Low-level Windows keyboard hook to block escape combos while ICEYOU is locked.
Blocks (when active):
- WIN keys (left/right) -> Start menu / Win+anything
- CTRL+ESC -> Start menu
- ALT+TAB, ALT+ESC -> task switching
- ALT+F4 -> close window
- ALT+SPACE -> window menu (Restore/Move/Close)
- CTRL+SHIFT+ESC -> Task Manager hotkey
CANNOT block (by Windows design):
- CTRL+ALT+DEL (Secure Attention Sequence)
- Hard reset / power button
These require Group Policy or admin / kernel privilege.
"""
import ctypes
import threading
from ctypes import wintypes
user32 = ctypes.WinDLL("user32", use_last_error=True)
kernel32 = ctypes.WinDLL("kernel32", use_last_error=True)
WH_KEYBOARD_LL = 13
WM_KEYDOWN = 0x0100
WM_SYSKEYDOWN = 0x0104
WM_QUIT = 0x0012
VK_LWIN = 0x5B
VK_RWIN = 0x5C
VK_TAB = 0x09
VK_ESCAPE = 0x1B
VK_F4 = 0x73
VK_SPACE = 0x20
VK_MENU = 0x12 # ALT
VK_CONTROL = 0x11
VK_SHIFT = 0x10
LRESULT = ctypes.c_long
ULONG_PTR = ctypes.c_size_t
class KBDLLHOOKSTRUCT(ctypes.Structure):
_fields_ = [
("vkCode", wintypes.DWORD),
("scanCode", wintypes.DWORD),
("flags", wintypes.DWORD),
("time", wintypes.DWORD),
("dwExtraInfo", ULONG_PTR),
]
HOOKPROC = ctypes.WINFUNCTYPE(LRESULT, ctypes.c_int, wintypes.WPARAM, wintypes.LPARAM)
user32.SetWindowsHookExW.restype = wintypes.HHOOK
user32.SetWindowsHookExW.argtypes = [ctypes.c_int, HOOKPROC, wintypes.HINSTANCE, wintypes.DWORD]
user32.UnhookWindowsHookEx.restype = wintypes.BOOL
user32.UnhookWindowsHookEx.argtypes = [wintypes.HHOOK]
user32.CallNextHookEx.restype = LRESULT
user32.CallNextHookEx.argtypes = [wintypes.HHOOK, ctypes.c_int, wintypes.WPARAM, wintypes.LPARAM]
user32.GetMessageW.argtypes = [ctypes.POINTER(wintypes.MSG), wintypes.HWND, wintypes.UINT, wintypes.UINT]
user32.GetMessageW.restype = ctypes.c_int
user32.TranslateMessage.argtypes = [ctypes.POINTER(wintypes.MSG)]
user32.DispatchMessageW.argtypes = [ctypes.POINTER(wintypes.MSG)]
user32.PostThreadMessageW.argtypes = [wintypes.DWORD, wintypes.UINT, wintypes.WPARAM, wintypes.LPARAM]
user32.PostThreadMessageW.restype = wintypes.BOOL
user32.GetAsyncKeyState.argtypes = [ctypes.c_int]
user32.GetAsyncKeyState.restype = ctypes.c_short
kernel32.GetCurrentThreadId.restype = wintypes.DWORD
class KeyboardBlocker:
"""Installs a WH_KEYBOARD_LL hook on its own thread with a message pump."""
def __init__(self):
self._hook = None
self._thread: threading.Thread = None
self._thread_id: int = 0
self._active = False
self._hook_proc = None # keep ref so GC doesn't free callback
self._ready = threading.Event()
@property
def active(self) -> bool:
return self._active
def start(self) -> None:
if self._active:
return
self._ready.clear()
self._thread = threading.Thread(target=self._run, daemon=True)
self._thread.start()
self._ready.wait(timeout=2.0)
def stop(self) -> None:
if not self._active:
return
self._active = False
if self._thread_id:
user32.PostThreadMessageW(self._thread_id, WM_QUIT, 0, 0)
if self._thread:
self._thread.join(timeout=2.0)
self._thread = None
self._thread_id = 0
@staticmethod
def _is_pressed(vk: int) -> bool:
return (user32.GetAsyncKeyState(vk) & 0x8000) != 0
def _hook_callback(self, nCode, wParam, lParam):
try:
if nCode == 0 and wParam in (WM_KEYDOWN, WM_SYSKEYDOWN):
kb = ctypes.cast(lParam, ctypes.POINTER(KBDLLHOOKSTRUCT))[0]
vk = kb.vkCode
if vk in (VK_LWIN, VK_RWIN):
return 1
alt = self._is_pressed(VK_MENU)
ctrl = self._is_pressed(VK_CONTROL)
if alt and vk in (VK_TAB, VK_ESCAPE, VK_F4, VK_SPACE):
return 1
if ctrl and vk == VK_ESCAPE:
return 1
except Exception:
pass
return user32.CallNextHookEx(self._hook, nCode, wParam, lParam)
def _run(self) -> None:
self._thread_id = kernel32.GetCurrentThreadId()
self._hook_proc = HOOKPROC(self._hook_callback)
self._hook = user32.SetWindowsHookExW(WH_KEYBOARD_LL, self._hook_proc, None, 0)
if not self._hook:
err = ctypes.get_last_error()
print(f"[KeyboardBlocker] SetWindowsHookEx failed (error {err})")
self._active = False
self._ready.set()
return
self._active = True
self._ready.set()
msg = wintypes.MSG()
try:
while True:
rc = user32.GetMessageW(ctypes.byref(msg), None, 0, 0)
if rc <= 0: # WM_QUIT (0) or error (-1)
break
user32.TranslateMessage(ctypes.byref(msg))
user32.DispatchMessageW(ctypes.byref(msg))
finally:
if self._hook:
user32.UnhookWindowsHookEx(self._hook)
self._hook = None
self._active = False