"""ICEYOU Windows Service (supervisor / session launcher). Why a *supervisor* service instead of running the app directly as a service? -------------------------------------------------------------------------- ICEYOU is a GUI lock-down application (full-screen white-out, password/face prompt, tray icon, low-level keyboard hooks). A normal Windows service runs in **Session 0**, which—since Windows Vista—is isolated from the interactive desktop. Any window a Session-0 process creates is invisible to the logged-in user, so the white-screen lockout would never appear. This service therefore does NOT draw any UI itself. Instead it: 1. Runs as LocalSystem, auto-starts at boot, has no console window, and keeps running across logoff/logon (i.e. "without an interactive session"). 2. Detects the currently active interactive console session. 3. Launches the ICEYOU GUI agent (``pythonw main.py``) *inside that user's session* using CreateProcessAsUser, so the white screen / tray render where the user can actually see them. 4. Supervises the child: if the user logs off and another logs on, or the agent dies, it relaunches the agent in the new active session. When no user is logged in there is nothing to protect and no desktop to draw on, so the service simply waits and starts the agent the moment someone logs in. Usage (run an *elevated / Administrator* PowerShell): # one-time: register the pywin32 service host (if not already done) python .venv\\Scripts\\pywin32_postinstall.py -install # install + set to start automatically at boot python iceyou_service.py --startup auto install # control python iceyou_service.py start python iceyou_service.py stop python iceyou_service.py restart python iceyou_service.py remove """ import os import sys import time import logging from pathlib import Path # --------------------------------------------------------------------------- # Paths (resolved relative to this file so the service is install-location # independent). This file is expected to live at the project root next to # main.py. # --------------------------------------------------------------------------- PROJECT_ROOT = Path(__file__).resolve().parent def _bootstrap_pywin32_path() -> None: """Ensure the pywin32 sub-packages are importable. When Windows starts the service it runs ``pythonservice.exe`` with a minimal ``sys.path`` that may NOT include the pywin32 directories (``site-packages\\win32``, ``win32\\lib``, ``Pythonwin``) where ``servicemanager``/``win32event``/... actually live. Importing them would then fail with ``ModuleNotFoundError: No module named 'servicemanager'`` and the service times out (error 1053). We add those directories (and the DLL directory) up front so the host can import them regardless of how it was launched. """ # Always put the project root first so "import iceyou_service" and any # "from iceyou.xxx" imports succeed even if the service host's cwd or # initial sys.path does not contain the directory where this file lives. if str(PROJECT_ROOT) not in sys.path: sys.path.insert(0, str(PROJECT_ROOT)) site_dirs = [] # The project's virtualenv is the primary, reliable location. site_dirs.append(PROJECT_ROOT / ".venv" / "Lib" / "site-packages") try: import site for p in site.getsitepackages(): site_dirs.append(Path(p)) except Exception: pass # Fallback: site-packages relative to the running interpreter. site_dirs.append(Path(sys.prefix) / "Lib" / "site-packages") seen = set() for sp in site_dirs: if not sp or sp in seen or not sp.is_dir(): continue seen.add(sp) for sub in ("win32", os.path.join("win32", "lib"), "Pythonwin"): d = sp / sub if d.is_dir() and str(d) not in sys.path: sys.path.append(str(d)) dll_dir = sp / "pywin32_system32" if dll_dir.is_dir(): os.environ["PATH"] = str(dll_dir) + os.pathsep + os.environ.get("PATH", "") try: os.add_dll_directory(str(dll_dir)) except (AttributeError, OSError): pass def _early_crash_dump(exc: BaseException) -> None: """Write any import-time exception to a file so we can see it even if the service host never reaches our normal logger.""" try: with open(PROJECT_ROOT / "iceyou_service_startup_error.log", "a", encoding="utf-8") as f: import traceback f.write("\n" + "=" * 80 + "\n") f.write("ICEYOU service startup crash at " + __import__("datetime").datetime.now().isoformat() + "\n") f.write("".join(traceback.format_exception(type(exc), exc, exc.__traceback__))) f.write("=" * 80 + "\n") except Exception: pass try: _bootstrap_pywin32_path() import servicemanager import win32event import win32service import win32serviceutil import win32ts import win32con import win32process import win32profile import win32api except Exception as _bootstrap_exc: # pragma: no cover - only triggered on host failure _early_crash_dump(_bootstrap_exc) raise MAIN_SCRIPT = PROJECT_ROOT / "main.py" LOG_FILE = PROJECT_ROOT / "iceyou_service.log" # How often (seconds) the supervisor re-checks the active session / child. POLL_INTERVAL = 3.0 # Sentinel returned by WTSGetActiveConsoleSessionId when no one is at the # physical console (e.g. nobody logged in, or a fast-user-switch transition). NO_ACTIVE_SESSION = 0xFFFFFFFF def _find_pythonw() -> str: """Locate the windowed Python interpreter used to run the GUI agent. Prefer the project's virtualenv (so the agent gets the right packages), then a pythonw next to the current interpreter, then plain python. """ candidates = [ PROJECT_ROOT / ".venv" / "Scripts" / "pythonw.exe", Path(sys.executable).with_name("pythonw.exe"), PROJECT_ROOT / ".venv" / "Scripts" / "python.exe", Path(sys.executable), ] for c in candidates: if c.exists(): return str(c) # Last resort: hope pythonw is on PATH. return "pythonw.exe" def _setup_logging() -> logging.Logger: logger = logging.getLogger("iceyou.service") logger.setLevel(logging.INFO) if not logger.handlers: try: handler = logging.FileHandler(LOG_FILE, encoding="utf-8") except Exception: handler = logging.StreamHandler() handler.setFormatter( logging.Formatter("%(asctime)s [%(levelname)s] %(message)s") ) logger.addHandler(handler) return logger class ICEYOUService(win32serviceutil.ServiceFramework): _svc_name_ = "ICEYOUMonitor" _svc_display_name_ = "ICEYOU Anti-Intrusion Monitor" _svc_description_ = ( "Supervises the ICEYOU anti-intrusion GUI agent, launching it into the " "active interactive desktop session so the screen lock-out and tray " "icon are visible to the logged-in user. Runs without an interactive " "session and survives logoff/logon." ) # Tell the SCM we want to be notified of console session changes so we can # react immediately to logon/unlock rather than only via polling. def __init__(self, args): super().__init__(args) # Both auto-reset, initially non-signalled. self._stop_event = win32event.CreateEvent(None, 0, 0, None) self._wake_event = win32event.CreateEvent(None, 0, 0, None) self._stopping = False self._child_handle = None # PyHANDLE of the running agent process self._child_session = None # session id the agent was launched in self.log = _setup_logging() # ---- SCM control handlers ------------------------------------------ def SvcStop(self): self.ReportServiceStatus(win32service.SERVICE_STOP_PENDING) self.log.info("Stop requested; terminating agent and shutting down.") self._stopping = True self._terminate_child() win32event.SetEvent(self._stop_event) def SvcDoRun(self): servicemanager.LogMsg( servicemanager.EVENTLOG_INFORMATION_TYPE, servicemanager.PYS_SERVICE_STARTED, (self._svc_name_, ""), ) # MUST report RUNNING promptly (< ~30 s) or SCM times out with error 1053. self.ReportServiceStatus(win32service.SERVICE_RUNNING) self.log.info("ICEYOU service starting. project_root=%s", PROJECT_ROOT) try: self._run_supervisor() except Exception: # never let the service crash silently self.log.exception("Supervisor loop crashed.") self.log.info("ICEYOU service stopped.") def SvcOtherEx(self, control, event_type, data): # React promptly to logon / unlock by nudging the supervisor loop. if control == win32service.SERVICE_CONTROL_SESSIONCHANGE: self.log.info("Session change event: type=%s", event_type) win32event.SetEvent(self._wake_event) # ---- Supervisor loop ------------------------------------------------ def _run_supervisor(self): pythonw = _find_pythonw() self.log.info("GUI agent interpreter: %s", pythonw) self.log.info("GUI agent script: %s", MAIN_SCRIPT) while True: # Wake on: stop request, session change, or poll timeout. rc = win32event.WaitForMultipleObjects( (self._stop_event, self._wake_event), False, int(POLL_INTERVAL * 1000), ) if rc == win32event.WAIT_OBJECT_0 or self._stopping: break # stop event signalled try: self._ensure_agent(pythonw) except Exception: self.log.exception("Error while ensuring agent is running.") def _ensure_agent(self, pythonw: str): """Make sure exactly one agent is running in the active session.""" session_id = win32ts.WTSGetActiveConsoleSessionId() if session_id == NO_ACTIVE_SESSION: # No one at the console: nothing to protect. Make sure no stale # agent lingers and wait for a logon. if self._child_handle is not None: self.log.info("No active session; terminating stale agent.") self._terminate_child() return child_alive = self._child_alive() if child_alive and self._child_session == session_id: return # already running in the right session if child_alive and self._child_session != session_id: # Active session changed (e.g. fast user switch). Move the agent. self.log.info( "Active session changed %s -> %s; relaunching agent.", self._child_session, session_id, ) self._terminate_child() self._launch_agent(pythonw, session_id) def _child_alive(self) -> bool: if self._child_handle is None: return False rc = win32event.WaitForSingleObject(self._child_handle, 0) return rc == win32event.WAIT_TIMEOUT # still running def _terminate_child(self): if self._child_handle is None: return try: if self._child_alive(): win32process.TerminateProcess(self._child_handle, 0) except Exception: self.log.exception("Failed to terminate agent process.") finally: try: win32api.CloseHandle(self._child_handle) except Exception: pass self._child_handle = None self._child_session = None def _launch_agent(self, pythonw: str, session_id: int): """Spawn the GUI agent inside the given interactive session.""" user_token = None env = None try: # Requires SE_TCB_NAME, which LocalSystem holds. user_token = win32ts.WTSQueryUserToken(session_id) try: env = win32profile.CreateEnvironmentBlock(user_token, False) creation_flags = ( win32con.CREATE_NO_WINDOW | win32con.CREATE_UNICODE_ENVIRONMENT ) except Exception: self.log.warning( "CreateEnvironmentBlock failed; launching without env block." ) env = None creation_flags = win32con.CREATE_NO_WINDOW startup = win32process.STARTUPINFO() # Target the interactive default desktop so windows are visible. startup.lpDesktop = "winsta0\\default" cmdline = f'"{pythonw}" "{MAIN_SCRIPT}"' proc_info = win32process.CreateProcessAsUser( user_token, # hToken None, # appName (use cmdline) cmdline, # commandLine None, # processAttributes None, # threadAttributes False, # inheritHandles creation_flags, env, # environment str(PROJECT_ROOT), # currentDirectory startup, ) h_process, h_thread, pid, tid = proc_info try: win32api.CloseHandle(h_thread) except Exception: pass self._child_handle = h_process self._child_session = session_id self.log.info( "Launched ICEYOU agent in session %s (pid=%s).", session_id, pid ) except Exception: self.log.exception( "Failed to launch agent in session %s.", session_id ) finally: if user_token is not None: try: win32api.CloseHandle(user_token) except Exception: pass def _service_command_line() -> str: """Full command the SCM should launch to run this service. We bypass pywin32's ``pythonservice.exe`` host entirely and register the venv's ``python.exe`` running THIS script directly. When the SCM starts it with no extra arguments, ``main()`` calls ``StartServiceCtrlDispatcher`` and connects to the SCM. This avoids all the host-exe path/module problems that caused the silent error 1053. """ py = PROJECT_ROOT / ".venv" / "Scripts" / "python.exe" if not py.exists(): py = Path(sys.executable) script = Path(__file__).resolve() return f'"{py}" "{script}"' def _install_service(startup_type: int = win32service.SERVICE_AUTO_START) -> None: cmdline = _service_command_line() hscm = win32service.OpenSCManager(None, None, win32service.SC_MANAGER_ALL_ACCESS) try: # Remove a stale registration first so we always get a clean binPath. try: old = win32service.OpenService( hscm, ICEYOUService._svc_name_, win32service.SERVICE_ALL_ACCESS ) win32service.DeleteService(old) win32service.CloseServiceHandle(old) except Exception: pass hs = win32service.CreateService( hscm, ICEYOUService._svc_name_, ICEYOUService._svc_display_name_, win32service.SERVICE_ALL_ACCESS, win32service.SERVICE_WIN32_OWN_PROCESS, startup_type, win32service.SERVICE_ERROR_NORMAL, cmdline, None, # load order group 0, # tag id None, # dependencies None, # account (LocalSystem) None, # password ) try: win32service.ChangeServiceConfig2( hs, win32service.SERVICE_CONFIG_DESCRIPTION, ICEYOUService._svc_description_, ) except Exception: pass win32service.CloseServiceHandle(hs) print(f"Service {ICEYOUService._svc_name_} installed (auto-start).") print(f" binPath = {cmdline}") finally: win32service.CloseServiceHandle(hscm) def _remove_service() -> None: try: win32serviceutil.StopService(ICEYOUService._svc_name_) except Exception: pass try: win32serviceutil.RemoveService(ICEYOUService._svc_name_) print(f"Service {ICEYOUService._svc_name_} removed.") except Exception as e: print(f"Remove failed: {e}") def _run_as_service() -> None: """Entry point used when the SCM launches us as a service process.""" try: servicemanager.Initialize() servicemanager.PrepareToHostSingle(ICEYOUService) servicemanager.StartServiceCtrlDispatcher() except Exception as exc: _early_crash_dump(exc) raise def main(): if len(sys.argv) == 1: # No args => launched by the Service Control Manager. _run_as_service() return args_lower = [a.lower() for a in sys.argv[1:]] if "install" in args_lower: startup = ( win32service.SERVICE_DISABLED if "disabled" in args_lower else win32service.SERVICE_AUTO_START ) _install_service(startup) elif "remove" in args_lower or "uninstall" in args_lower: _remove_service() elif "start" in args_lower: win32serviceutil.StartService(ICEYOUService._svc_name_) print("Service start requested.") elif "stop" in args_lower: win32serviceutil.StopService(ICEYOUService._svc_name_) print("Service stop requested.") elif "restart" in args_lower: try: win32serviceutil.StopService(ICEYOUService._svc_name_) except Exception: pass win32serviceutil.StartService(ICEYOUService._svc_name_) print("Service restarted.") else: # Anything else: defer to pywin32's standard handler. win32serviceutil.HandleCommandLine(ICEYOUService) if __name__ == "__main__": main()