1181 lines
38 KiB
Python
1181 lines
38 KiB
Python
from PYAS_Diagnostics import log_exception
|
|
import os
|
|
import time
|
|
import winreg
|
|
import threading
|
|
import ctypes
|
|
import ctypes.wintypes
|
|
from PYAS_WinAPI import (
|
|
LUID,
|
|
PYAS_FULL_MESSAGE,
|
|
PYAS_USER_MESSAGE,
|
|
SERVICE_STATUS_PROCESS,
|
|
TOKEN_PRIVILEGES,
|
|
)
|
|
from PYAS_WinAPI import (
|
|
ERROR_NOT_FOUND,
|
|
ERROR_OPERATION_ABORTED,
|
|
FLT_PORT_FLAG_SYNC_HANDLE,
|
|
HRESULT_IO_PENDING,
|
|
OVERLAPPED,
|
|
PYAS_CONNECTION_CONTEXT,
|
|
PYAS_CONNECTION_MAGIC,
|
|
PYAS_CONNECTION_VERSION,
|
|
WAIT_OBJECT_0,
|
|
WAIT_TIMEOUT,
|
|
)
|
|
|
|
|
|
class DriverMixin:
|
|
def _query_service_state_handle(self, service):
|
|
status = SERVICE_STATUS_PROCESS()
|
|
needed = ctypes.wintypes.DWORD(0)
|
|
buffer = ctypes.cast(ctypes.byref(status), ctypes.POINTER(ctypes.c_ubyte))
|
|
|
|
if not self.advapi32.QueryServiceStatusEx(
|
|
service, 0, buffer, ctypes.sizeof(status), ctypes.byref(needed)
|
|
):
|
|
return None, ctypes.get_last_error()
|
|
|
|
return int(status.dwCurrentState), 0
|
|
|
|
def _query_driver_service_state(self):
|
|
scm = self.advapi32.OpenSCManagerW(None, None, 0x0001)
|
|
|
|
if not scm:
|
|
return None
|
|
|
|
service = None
|
|
|
|
try:
|
|
service = self.advapi32.OpenServiceW(scm, "PYAS_Driver", 0x0004)
|
|
|
|
if not service:
|
|
return None
|
|
|
|
state, _ = self._query_service_state_handle(service)
|
|
return state
|
|
finally:
|
|
if service:
|
|
self.advapi32.CloseServiceHandle(service)
|
|
|
|
self.advapi32.CloseServiceHandle(scm)
|
|
|
|
def _get_driver_client_image_path(self):
|
|
buffer = ctypes.create_unicode_buffer(32768)
|
|
size = ctypes.wintypes.DWORD(len(buffer))
|
|
|
|
if not self.kernel32.QueryFullProcessImageNameW(
|
|
self.kernel32.GetCurrentProcess(), 0, buffer, ctypes.byref(size)
|
|
):
|
|
return None
|
|
|
|
image_path = os.path.abspath(buffer.value)
|
|
drive, tail = os.path.splitdrive(image_path)
|
|
|
|
if not drive or not tail:
|
|
return None
|
|
|
|
device_buffer = ctypes.create_unicode_buffer(32768)
|
|
|
|
if not self.kernel32.QueryDosDeviceW(drive, device_buffer, len(device_buffer)):
|
|
return None
|
|
|
|
return device_buffer.value.rstrip("\\") + tail
|
|
|
|
def _configure_driver_identity(self):
|
|
image_path = self._get_driver_client_image_path()
|
|
|
|
if not image_path:
|
|
return False, ctypes.get_last_error() or 1
|
|
|
|
try:
|
|
access = winreg.KEY_SET_VALUE | getattr(winreg, "KEY_WOW64_64KEY", 0)
|
|
|
|
with winreg.CreateKeyEx(
|
|
winreg.HKEY_LOCAL_MACHINE,
|
|
r"SYSTEM\CurrentControlSet\Services\PYAS_Driver",
|
|
0,
|
|
access,
|
|
) as key:
|
|
winreg.SetValueEx(key, "ClientImagePath", 0, winreg.REG_SZ, image_path)
|
|
|
|
return True, 0
|
|
except OSError as e:
|
|
log_exception("PYAS_Driver.DriverMixin._configure_driver_identity:74")
|
|
return False, int(getattr(e, "winerror", 0) or 1)
|
|
|
|
def _ensure_driver_service(self):
|
|
desired_service_access = 0x0002 | 0x0004 | 0x0010 | 0x00010000
|
|
dependencies = ctypes.create_unicode_buffer("FltMgr\0\0")
|
|
dependencies_ptr = ctypes.cast(dependencies, ctypes.wintypes.LPCWSTR)
|
|
last_error = 0
|
|
|
|
for attempt in range(20):
|
|
scm = self.advapi32.OpenSCManagerW(None, None, 0x0001 | 0x0002)
|
|
|
|
if not scm:
|
|
return False, ctypes.get_last_error()
|
|
|
|
service = None
|
|
|
|
try:
|
|
ctypes.set_last_error(0)
|
|
service = self.advapi32.CreateServiceW(
|
|
scm,
|
|
"PYAS_Driver",
|
|
"PYAS_Driver",
|
|
desired_service_access,
|
|
0x00000001,
|
|
0x00000003,
|
|
0x00000001,
|
|
self.path_drivers,
|
|
"FSFilter Activity Monitor",
|
|
None,
|
|
dependencies_ptr,
|
|
None,
|
|
None,
|
|
)
|
|
|
|
if not service:
|
|
last_error = ctypes.get_last_error()
|
|
|
|
if last_error in (1073, 1078):
|
|
service = self.advapi32.OpenServiceW(
|
|
scm, "PYAS_Driver", desired_service_access
|
|
)
|
|
|
|
if not service:
|
|
last_error = ctypes.get_last_error()
|
|
|
|
elif last_error == 1072:
|
|
pass
|
|
|
|
if service:
|
|
ctypes.set_last_error(0)
|
|
changed = self.advapi32.ChangeServiceConfigW(
|
|
service,
|
|
0x00000001,
|
|
0x00000003,
|
|
0x00000001,
|
|
self.path_drivers,
|
|
"FSFilter Activity Monitor",
|
|
None,
|
|
dependencies_ptr,
|
|
None,
|
|
None,
|
|
None,
|
|
)
|
|
|
|
if not changed:
|
|
last_error = ctypes.get_last_error()
|
|
return False, last_error
|
|
|
|
return True, 0
|
|
finally:
|
|
if service:
|
|
self.advapi32.CloseServiceHandle(service)
|
|
|
|
self.advapi32.CloseServiceHandle(scm)
|
|
|
|
if last_error != 1072:
|
|
break
|
|
|
|
if attempt < 19:
|
|
time.sleep(0.05)
|
|
|
|
return False, last_error or 1
|
|
|
|
def _start_driver_service(self):
|
|
scm = self.advapi32.OpenSCManagerW(None, None, 0x0001)
|
|
|
|
if not scm:
|
|
return False, ctypes.get_last_error()
|
|
|
|
service = None
|
|
|
|
try:
|
|
service = self.advapi32.OpenServiceW(scm, "PYAS_Driver", 0x0004 | 0x0010)
|
|
|
|
if not service:
|
|
return False, ctypes.get_last_error()
|
|
|
|
state, error = self._query_service_state_handle(service)
|
|
|
|
if state in (2, 4):
|
|
return True, 0
|
|
|
|
if error:
|
|
return False, error
|
|
|
|
ctypes.set_last_error(0)
|
|
|
|
if self.advapi32.StartServiceW(service, 0, None):
|
|
return True, 0
|
|
|
|
error = ctypes.get_last_error()
|
|
|
|
if error == 1056:
|
|
return True, 0
|
|
|
|
return False, error
|
|
finally:
|
|
if service:
|
|
self.advapi32.CloseServiceHandle(service)
|
|
|
|
self.advapi32.CloseServiceHandle(scm)
|
|
|
|
def uninstall_system_driver(self):
|
|
if not self.stop_system_driver():
|
|
return False, 1051
|
|
|
|
return self._delete_driver_service()
|
|
|
|
def _delete_driver_service(self, timeout=15.0):
|
|
deadline = time.monotonic() + timeout
|
|
last_error = 0
|
|
delete_requested = False
|
|
|
|
while time.monotonic() < deadline:
|
|
scm = self.advapi32.OpenSCManagerW(None, None, 0x0001)
|
|
|
|
if not scm:
|
|
return False, ctypes.get_last_error()
|
|
|
|
service = None
|
|
|
|
try:
|
|
ctypes.set_last_error(0)
|
|
service = self.advapi32.OpenServiceW(scm, "PYAS_Driver", 0x0004 | 0x00010000)
|
|
|
|
if not service:
|
|
last_error = ctypes.get_last_error()
|
|
|
|
if last_error == 1060:
|
|
return True, 0
|
|
|
|
if last_error != 1072:
|
|
return False, last_error
|
|
else:
|
|
state, status_error = self._query_service_state_handle(service)
|
|
|
|
if status_error:
|
|
return False, status_error
|
|
|
|
if state not in (2, 3, 4) and not delete_requested:
|
|
ctypes.set_last_error(0)
|
|
|
|
if self.advapi32.DeleteService(service):
|
|
delete_requested = True
|
|
last_error = 1072
|
|
else:
|
|
last_error = ctypes.get_last_error()
|
|
|
|
if last_error == 1060:
|
|
return True, 0
|
|
|
|
if last_error == 1072:
|
|
delete_requested = True
|
|
else:
|
|
return False, last_error
|
|
elif state in (2, 3, 4):
|
|
last_error = 1051
|
|
finally:
|
|
if service:
|
|
self.advapi32.CloseServiceHandle(service)
|
|
|
|
self.advapi32.CloseServiceHandle(scm)
|
|
|
|
time.sleep(0.1)
|
|
|
|
return False, last_error or 1072
|
|
|
|
def install_system_driver(self):
|
|
try:
|
|
ensured, service_error = self._ensure_driver_service()
|
|
|
|
if not ensured:
|
|
self.write_log(
|
|
"WARN",
|
|
"Driver Service",
|
|
detail=f"CreateServiceW/ChangeServiceConfigW failed: 0x{service_error & 0xFFFFFFFF:08X}",
|
|
success=False,
|
|
)
|
|
return False
|
|
|
|
configured, identity_error = self._configure_driver_identity()
|
|
|
|
if not configured:
|
|
self.write_log(
|
|
"WARN",
|
|
"Driver Identity",
|
|
detail=f"ClientImagePath configuration failed: 0x{identity_error & 0xFFFFFFFF:08X}",
|
|
success=False,
|
|
)
|
|
return False
|
|
|
|
self._reg_write(
|
|
winreg.HKEY_LOCAL_MACHINE,
|
|
r"SYSTEM\CurrentControlSet\Services\PYAS_Driver\Instances",
|
|
"DefaultInstance",
|
|
winreg.REG_SZ,
|
|
"PYAS Instance",
|
|
)
|
|
self._reg_write(
|
|
winreg.HKEY_LOCAL_MACHINE,
|
|
r"SYSTEM\CurrentControlSet\Services\PYAS_Driver\Instances\PYAS Instance",
|
|
"Altitude",
|
|
winreg.REG_SZ,
|
|
"320000",
|
|
)
|
|
self._reg_write(
|
|
winreg.HKEY_LOCAL_MACHINE,
|
|
r"SYSTEM\CurrentControlSet\Services\PYAS_Driver\Instances\PYAS Instance",
|
|
"Flags",
|
|
winreg.REG_DWORD,
|
|
0,
|
|
)
|
|
|
|
started, start_error = self._start_driver_service()
|
|
|
|
if not started:
|
|
self.write_log(
|
|
"WARN",
|
|
"Driver Service",
|
|
detail=f"StartServiceW failed: 0x{start_error & 0xFFFFFFFF:08X}",
|
|
success=False,
|
|
)
|
|
return False
|
|
|
|
final_state = None
|
|
last_start_attempt = time.monotonic()
|
|
deadline = time.monotonic() + 3.0
|
|
|
|
while time.monotonic() < deadline:
|
|
service_state = self._query_driver_service_state()
|
|
|
|
if service_state == 4:
|
|
final_state = self._query_driver_state()
|
|
|
|
if final_state == 2:
|
|
return True
|
|
|
|
if final_state in (4, 5):
|
|
break
|
|
elif service_state == 1:
|
|
now = time.monotonic()
|
|
|
|
if now - last_start_attempt >= 0.1:
|
|
started, start_error = self._start_driver_service()
|
|
last_start_attempt = now
|
|
|
|
if not started:
|
|
break
|
|
elif service_state is None:
|
|
break
|
|
|
|
time.sleep(0.02)
|
|
|
|
if self.check_system_driver():
|
|
self.stop_system_driver()
|
|
|
|
detail = (
|
|
"Driver entered unload-retry state during startup"
|
|
if final_state == 4
|
|
else "Driver did not reach running state"
|
|
)
|
|
self.write_log("WARN", "Driver Protection", detail=detail, success=False)
|
|
return False
|
|
|
|
except Exception as e:
|
|
log_exception("PYAS_Driver.DriverMixin.install_system_driver:267")
|
|
self.write_log("WARN", "install_system_driver", detail=str(e), success=False)
|
|
return False
|
|
|
|
def _driver_handle_value(self, handle):
|
|
if handle is None:
|
|
return None
|
|
|
|
value = getattr(handle, "value", handle)
|
|
|
|
if value is None:
|
|
return None
|
|
|
|
try:
|
|
return int(value)
|
|
except Exception:
|
|
log_exception("PYAS_Driver.DriverMixin._driver_handle_value:281")
|
|
return None
|
|
|
|
def _connect_driver_port(self, asynchronous=False):
|
|
context = PYAS_CONNECTION_CONTEXT()
|
|
context.Size = ctypes.sizeof(context)
|
|
context.Version = PYAS_CONNECTION_VERSION
|
|
context.Magic = PYAS_CONNECTION_MAGIC
|
|
context.ProcessId = os.getpid()
|
|
|
|
temp_port = ctypes.wintypes.HANDLE()
|
|
status = self.fltlib.FilterConnectCommunicationPort(
|
|
"\\PYAS_Output_Pipe",
|
|
0 if asynchronous else FLT_PORT_FLAG_SYNC_HANDLE,
|
|
ctypes.byref(context),
|
|
ctypes.sizeof(context),
|
|
None,
|
|
ctypes.byref(temp_port),
|
|
)
|
|
|
|
if status != 0:
|
|
return None
|
|
|
|
return temp_port
|
|
|
|
def _detach_driver_port(self, expected_port=None):
|
|
expected_value = self._driver_handle_value(expected_port)
|
|
|
|
with self.lock_driver:
|
|
current_port = self.driver_port
|
|
current_value = self._driver_handle_value(current_port)
|
|
|
|
if current_value is None:
|
|
return None
|
|
|
|
if expected_port is not None and current_value != expected_value:
|
|
return None
|
|
|
|
self.driver_port = None
|
|
return current_port
|
|
|
|
def _close_driver_port(self, expected_port=None):
|
|
port = self._detach_driver_port(expected_port)
|
|
|
|
if not port:
|
|
return False
|
|
|
|
try:
|
|
self.kernel32.CloseHandle(port)
|
|
except Exception:
|
|
log_exception("PYAS_Driver.DriverMixin._close_driver_port:326")
|
|
pass
|
|
|
|
return True
|
|
|
|
def _driver_listener_should_run(self):
|
|
if self.driver_stop_event.is_set():
|
|
return False
|
|
|
|
with self.lock_config:
|
|
return bool(self.pyas_config.get("driver_switch", False))
|
|
|
|
def start_driver_listener(self, wait_ready=True):
|
|
with self.lock_driver:
|
|
current = self.driver_listener_thread
|
|
|
|
if current and current.is_alive():
|
|
return (
|
|
self.driver_listener_ready_event.is_set()
|
|
and not self.driver_stop_event.is_set()
|
|
)
|
|
|
|
self.driver_stop_event.clear()
|
|
self.driver_listener_ready_event.clear()
|
|
self.driver_listener_failed_event.clear()
|
|
thread = threading.Thread(target=self.pipe_server_thread, daemon=True)
|
|
self.driver_listener_thread = thread
|
|
thread.start()
|
|
|
|
if not wait_ready:
|
|
return True
|
|
|
|
deadline = time.monotonic() + 5.0
|
|
|
|
while time.monotonic() < deadline:
|
|
if self.driver_listener_ready_event.wait(0.02):
|
|
return True
|
|
|
|
if self.driver_listener_failed_event.is_set() or not thread.is_alive():
|
|
break
|
|
|
|
self._stop_driver_listener()
|
|
return False
|
|
|
|
def _stop_driver_listener(self):
|
|
self.driver_stop_event.set()
|
|
|
|
with self.lock_driver:
|
|
thread = self.driver_listener_thread
|
|
port = self.driver_port
|
|
|
|
if port:
|
|
try:
|
|
self.kernel32.CancelIoEx(port, None)
|
|
except Exception:
|
|
log_exception("PYAS_Driver.DriverMixin._stop_driver_listener:373")
|
|
pass
|
|
|
|
if thread and thread is not threading.current_thread():
|
|
thread.join(timeout=2.0)
|
|
|
|
alive = bool(thread and thread.is_alive())
|
|
|
|
if not alive:
|
|
with self.lock_driver:
|
|
if self.driver_listener_thread is thread:
|
|
self.driver_listener_thread = None
|
|
|
|
self.driver_listener_ready_event.clear()
|
|
self._close_driver_port()
|
|
|
|
return not alive
|
|
|
|
def _resume_driver_listener(self):
|
|
if not self.check_system_driver():
|
|
return False
|
|
|
|
with self.lock_config:
|
|
enabled = bool(self.pyas_config.get("driver_switch", False))
|
|
|
|
if not enabled:
|
|
return False
|
|
|
|
self._close_driver_port()
|
|
return self.start_driver_listener(wait_ready=True)
|
|
|
|
def _set_driver_unload_authorization(self, enabled):
|
|
msg = PYAS_USER_MESSAGE()
|
|
msg.Command = 5 if enabled else 6
|
|
msg.Path = ""
|
|
|
|
for _ in range(3):
|
|
if not self._ensure_driver_port():
|
|
time.sleep(0.1)
|
|
continue
|
|
|
|
with self.lock_driver:
|
|
current_port = self.driver_port
|
|
|
|
if not current_port:
|
|
continue
|
|
|
|
bytes_returned = ctypes.wintypes.DWORD(0)
|
|
|
|
try:
|
|
status = self.fltlib.FilterSendMessage(
|
|
current_port,
|
|
ctypes.byref(msg),
|
|
ctypes.sizeof(msg),
|
|
None,
|
|
0,
|
|
ctypes.byref(bytes_returned),
|
|
)
|
|
except Exception as e:
|
|
log_exception("PYAS_Driver.DriverMixin._set_driver_unload_authorization:427")
|
|
self.write_log(
|
|
"WARN", "Driver Unload Authorization", detail=str(e), success=False
|
|
)
|
|
status = -1
|
|
|
|
if status == 0:
|
|
return True
|
|
|
|
self._close_driver_port(current_port)
|
|
time.sleep(0.1)
|
|
|
|
return False
|
|
|
|
def _ensure_driver_port(self):
|
|
for _ in range(20):
|
|
with self.lock_driver:
|
|
if self.driver_port:
|
|
return True
|
|
|
|
temp_port = self._connect_driver_port()
|
|
|
|
if temp_port:
|
|
with self.lock_driver:
|
|
if not self.driver_port:
|
|
self.driver_port = temp_port
|
|
return True
|
|
|
|
try:
|
|
self.kernel32.CloseHandle(temp_port)
|
|
except Exception:
|
|
log_exception("PYAS_Driver.DriverMixin._ensure_driver_port:454")
|
|
pass
|
|
|
|
return True
|
|
|
|
time.sleep(0.05)
|
|
|
|
return False
|
|
|
|
def _query_driver_state(self):
|
|
for use_existing in (True, False):
|
|
local_port = None
|
|
port = None
|
|
|
|
if use_existing:
|
|
with self.lock_driver:
|
|
if self.driver_port:
|
|
port = self.driver_port
|
|
|
|
if not port:
|
|
continue
|
|
else:
|
|
local_port = self._connect_driver_port()
|
|
|
|
if not local_port:
|
|
return None
|
|
|
|
port = local_port
|
|
|
|
msg = PYAS_USER_MESSAGE()
|
|
msg.Command = 7
|
|
msg.Path = ""
|
|
state = ctypes.wintypes.DWORD(0)
|
|
bytes_returned = ctypes.wintypes.DWORD(0)
|
|
|
|
try:
|
|
status = self.fltlib.FilterSendMessage(
|
|
port,
|
|
ctypes.byref(msg),
|
|
ctypes.sizeof(msg),
|
|
ctypes.byref(state),
|
|
ctypes.sizeof(state),
|
|
ctypes.byref(bytes_returned),
|
|
)
|
|
|
|
if status == 0 and bytes_returned.value == ctypes.sizeof(state):
|
|
return int(state.value)
|
|
except Exception:
|
|
log_exception("PYAS_Driver.DriverMixin._query_driver_state:496")
|
|
pass
|
|
finally:
|
|
if local_port:
|
|
try:
|
|
self.kernel32.CloseHandle(local_port)
|
|
except Exception:
|
|
log_exception("PYAS_Driver.DriverMixin._query_driver_state:502")
|
|
pass
|
|
|
|
if use_existing:
|
|
self._close_driver_port(port)
|
|
|
|
return None
|
|
|
|
def _enable_token_privilege(self, privilege_name):
|
|
token = ctypes.wintypes.HANDLE()
|
|
desired_access = 0x0020 | 0x0008
|
|
|
|
if not self.advapi32.OpenProcessToken(
|
|
self.kernel32.GetCurrentProcess(), desired_access, ctypes.byref(token)
|
|
):
|
|
return None, ctypes.get_last_error()
|
|
|
|
luid = LUID()
|
|
|
|
if not self.advapi32.LookupPrivilegeValueW(None, privilege_name, ctypes.byref(luid)):
|
|
error = ctypes.get_last_error()
|
|
self.kernel32.CloseHandle(token)
|
|
return None, error
|
|
|
|
new_state = TOKEN_PRIVILEGES()
|
|
new_state.PrivilegeCount = 1
|
|
new_state.Privileges[0].Luid = luid
|
|
new_state.Privileges[0].Attributes = 0x00000002
|
|
|
|
previous_state = TOKEN_PRIVILEGES()
|
|
return_length = ctypes.wintypes.DWORD(0)
|
|
ctypes.set_last_error(0)
|
|
|
|
adjusted = self.advapi32.AdjustTokenPrivileges(
|
|
token,
|
|
False,
|
|
ctypes.byref(new_state),
|
|
ctypes.sizeof(previous_state),
|
|
ctypes.byref(previous_state),
|
|
ctypes.byref(return_length),
|
|
)
|
|
error = ctypes.get_last_error()
|
|
|
|
if not adjusted or error == 1300:
|
|
self.kernel32.CloseHandle(token)
|
|
return None, error or 1
|
|
|
|
return (token, previous_state, return_length.value), 0
|
|
|
|
def _restore_token_privilege(self, privilege_state):
|
|
if not privilege_state:
|
|
return
|
|
|
|
token, previous_state, previous_length = privilege_state
|
|
|
|
try:
|
|
if previous_length:
|
|
self.advapi32.AdjustTokenPrivileges(
|
|
token, False, ctypes.byref(previous_state), 0, None, None
|
|
)
|
|
finally:
|
|
self.kernel32.CloseHandle(token)
|
|
|
|
def _filter_unload_with_privilege(self, filter_name):
|
|
result = {
|
|
"status": None,
|
|
"privilege_error": 0,
|
|
"authorization_failed": False,
|
|
"exception": None,
|
|
"attempts": 0,
|
|
}
|
|
privilege_state = None
|
|
unload_authorized = False
|
|
|
|
self.driver_unload_worker = None
|
|
self.driver_unload_result = result
|
|
|
|
try:
|
|
privilege_state, privilege_error = self._enable_token_privilege("SeLoadDriverPrivilege")
|
|
|
|
if not privilege_state:
|
|
result["privilege_error"] = privilege_error
|
|
return result
|
|
|
|
retryable_statuses = {0x80070522, 0x80070005, 0x800700AA}
|
|
|
|
for delay in (0.0, 0.05, 0.15):
|
|
if delay:
|
|
time.sleep(delay)
|
|
|
|
if not self.check_system_driver():
|
|
result["status"] = 0
|
|
unload_authorized = False
|
|
return result
|
|
|
|
if not self._set_driver_unload_authorization(True):
|
|
continue
|
|
|
|
unload_authorized = True
|
|
result["attempts"] += 1
|
|
unload_status = self.fltlib.FilterUnload(filter_name)
|
|
result["status"] = unload_status
|
|
|
|
if unload_status == 0:
|
|
unload_authorized = False
|
|
return result
|
|
|
|
revoked = False
|
|
|
|
if self.check_system_driver():
|
|
try:
|
|
revoked = self._set_driver_unload_authorization(False)
|
|
except Exception:
|
|
log_exception("PYAS_Driver.DriverMixin._filter_unload_with_privilege:606")
|
|
revoked = False
|
|
|
|
unload_authorized = not revoked
|
|
|
|
if (unload_status & 0xFFFFFFFF) not in retryable_statuses:
|
|
return result
|
|
|
|
if result["status"] is None:
|
|
result["authorization_failed"] = True
|
|
|
|
except Exception as e:
|
|
log_exception("PYAS_Driver.DriverMixin._filter_unload_with_privilege:616")
|
|
result["exception"] = str(e)
|
|
finally:
|
|
if unload_authorized and self.check_system_driver():
|
|
try:
|
|
self._set_driver_unload_authorization(False)
|
|
except Exception:
|
|
log_exception("PYAS_Driver.DriverMixin._filter_unload_with_privilege:622")
|
|
pass
|
|
|
|
self._restore_token_privilege(privilege_state)
|
|
|
|
return result
|
|
|
|
def stop_system_driver(self):
|
|
with self.lock_driver_unload:
|
|
runtime_unloaded = False
|
|
unload_confirmed = False
|
|
|
|
try:
|
|
if not self._stop_driver_listener():
|
|
self.write_log(
|
|
"WARN",
|
|
"Driver Protection",
|
|
detail="Driver listener cancellation did not complete",
|
|
success=False,
|
|
)
|
|
return False
|
|
|
|
if not self.check_system_driver():
|
|
runtime_unloaded = True
|
|
unload_confirmed = True
|
|
else:
|
|
if not self._ensure_driver_port():
|
|
if not self.check_system_driver():
|
|
runtime_unloaded = True
|
|
unload_confirmed = True
|
|
else:
|
|
self.write_log(
|
|
"WARN",
|
|
"Driver Protection",
|
|
detail="Control port unavailable",
|
|
success=False,
|
|
)
|
|
return False
|
|
|
|
if not runtime_unloaded:
|
|
unload_result = self._filter_unload_with_privilege("PYAS_Driver")
|
|
|
|
if unload_result["privilege_error"]:
|
|
error = unload_result["privilege_error"]
|
|
self.write_log(
|
|
"WARN",
|
|
"Driver Protection",
|
|
detail=f"SeLoadDriverPrivilege unavailable: 0x{error & 0xFFFFFFFF:08X}",
|
|
success=False,
|
|
)
|
|
return False
|
|
|
|
if unload_result["authorization_failed"]:
|
|
self.write_log(
|
|
"WARN",
|
|
"Driver Protection",
|
|
detail="Unload authorization rejected",
|
|
success=False,
|
|
)
|
|
return False
|
|
|
|
if unload_result["exception"]:
|
|
self.write_log(
|
|
"WARN",
|
|
"stop_system_driver",
|
|
detail=unload_result["exception"],
|
|
success=False,
|
|
)
|
|
return False
|
|
|
|
unload_status = unload_result["status"]
|
|
|
|
if unload_status == 0:
|
|
runtime_unloaded = True
|
|
unload_confirmed = True
|
|
elif not self.check_system_driver():
|
|
runtime_unloaded = True
|
|
unload_confirmed = True
|
|
else:
|
|
attempts = unload_result["attempts"]
|
|
self.write_log(
|
|
"WARN",
|
|
"Driver Protection",
|
|
detail=f"FilterUnload failed after {attempts} attempt(s): 0x{unload_status & 0xFFFFFFFF:08X}",
|
|
success=False,
|
|
)
|
|
return False
|
|
|
|
self._close_driver_port()
|
|
|
|
if unload_confirmed:
|
|
deadline = time.monotonic() + 0.5
|
|
|
|
while time.monotonic() < deadline and self.check_system_driver():
|
|
time.sleep(0.01)
|
|
|
|
if self.check_system_driver():
|
|
self.write_log(
|
|
"INFO",
|
|
"Driver Protection",
|
|
detail="Filter unloaded; SCM state is still settling",
|
|
)
|
|
|
|
return runtime_unloaded
|
|
except Exception as e:
|
|
log_exception("PYAS_Driver.DriverMixin.stop_system_driver:689")
|
|
self.write_log("WARN", "stop_system_driver", detail=str(e), success=False)
|
|
return False
|
|
finally:
|
|
if not runtime_unloaded and self.check_system_driver():
|
|
self._resume_driver_listener()
|
|
|
|
def check_system_driver(self):
|
|
runtime_state = self._query_driver_state()
|
|
|
|
if runtime_state in (1, 2, 3, 4):
|
|
return True
|
|
|
|
service_state = self._query_driver_service_state()
|
|
return service_state in (2, 3, 4)
|
|
|
|
def clear_driver_rules(self):
|
|
with self.lock_driver:
|
|
if not self.driver_port:
|
|
return False
|
|
|
|
msg = PYAS_USER_MESSAGE()
|
|
msg.Command = 4
|
|
msg.Path = ""
|
|
bytes_returned = ctypes.wintypes.DWORD(0)
|
|
|
|
try:
|
|
return (
|
|
self.fltlib.FilterSendMessage(
|
|
self.driver_port,
|
|
ctypes.byref(msg),
|
|
ctypes.sizeof(msg),
|
|
None,
|
|
0,
|
|
ctypes.byref(bytes_returned),
|
|
)
|
|
== 0
|
|
)
|
|
except Exception:
|
|
log_exception("PYAS_Driver.DriverMixin.clear_driver_rules:723")
|
|
return False
|
|
|
|
def load_driver_rule_file(self, json_path):
|
|
with self.lock_driver:
|
|
if not self.driver_port:
|
|
return False
|
|
|
|
norm_path = os.path.abspath(json_path)
|
|
|
|
if not os.path.exists(norm_path):
|
|
return False
|
|
|
|
nt_path = f"\\??\\{norm_path}"
|
|
|
|
msg = PYAS_USER_MESSAGE()
|
|
msg.Command = 3
|
|
msg.Path = nt_path
|
|
bytes_returned = ctypes.wintypes.DWORD(0)
|
|
|
|
try:
|
|
return (
|
|
self.fltlib.FilterSendMessage(
|
|
self.driver_port,
|
|
ctypes.byref(msg),
|
|
ctypes.sizeof(msg),
|
|
None,
|
|
0,
|
|
ctypes.byref(bytes_returned),
|
|
)
|
|
== 0
|
|
)
|
|
except Exception:
|
|
log_exception("PYAS_Driver.DriverMixin.load_driver_rule_file:751")
|
|
return False
|
|
|
|
def _cancel_driver_receive(self, port, overlapped):
|
|
try:
|
|
ctypes.set_last_error(0)
|
|
cancelled = self.kernel32.CancelIoEx(port, ctypes.byref(overlapped))
|
|
error = ctypes.get_last_error()
|
|
|
|
if not cancelled and error not in (0, ERROR_NOT_FOUND):
|
|
return False
|
|
|
|
wait_status = self.kernel32.WaitForSingleObject(overlapped.hEvent, 1000)
|
|
|
|
if wait_status != WAIT_OBJECT_0:
|
|
return False
|
|
|
|
transferred = ctypes.wintypes.DWORD(0)
|
|
self.kernel32.GetOverlappedResult(
|
|
port, ctypes.byref(overlapped), ctypes.byref(transferred), False
|
|
)
|
|
return True
|
|
except Exception:
|
|
log_exception("PYAS_Driver.DriverMixin._cancel_driver_receive:774")
|
|
return False
|
|
|
|
def _receive_driver_message(self, port):
|
|
message = PYAS_FULL_MESSAGE()
|
|
overlapped = OVERLAPPED()
|
|
event_handle = self.kernel32.CreateEventW(None, True, False, None)
|
|
|
|
if not event_handle:
|
|
return "error", None
|
|
|
|
overlapped.hEvent = event_handle
|
|
|
|
try:
|
|
status = self.fltlib.FilterGetMessage(
|
|
port,
|
|
ctypes.byref(message),
|
|
ctypes.sizeof(PYAS_FULL_MESSAGE),
|
|
ctypes.byref(overlapped),
|
|
)
|
|
|
|
if status == 0:
|
|
return "message", message
|
|
|
|
if (status & 0xFFFFFFFF) != HRESULT_IO_PENDING:
|
|
return "disconnect", None
|
|
|
|
while True:
|
|
if self.driver_stop_event.is_set():
|
|
self._cancel_driver_receive(port, overlapped)
|
|
return "stop", None
|
|
|
|
wait_status = self.kernel32.WaitForSingleObject(event_handle, 20)
|
|
|
|
if wait_status == WAIT_TIMEOUT:
|
|
continue
|
|
|
|
if wait_status != WAIT_OBJECT_0:
|
|
self._cancel_driver_receive(port, overlapped)
|
|
return "error", None
|
|
|
|
transferred = ctypes.wintypes.DWORD(0)
|
|
|
|
if self.kernel32.GetOverlappedResult(
|
|
port, ctypes.byref(overlapped), ctypes.byref(transferred), False
|
|
):
|
|
return "message", message
|
|
|
|
error = ctypes.get_last_error()
|
|
|
|
if error == ERROR_OPERATION_ABORTED and self.driver_stop_event.is_set():
|
|
return "stop", None
|
|
|
|
return "disconnect", None
|
|
finally:
|
|
self.kernel32.CloseHandle(event_handle)
|
|
|
|
def pipe_server_thread(self):
|
|
owned_port = None
|
|
ready = False
|
|
|
|
try:
|
|
while self._driver_listener_should_run():
|
|
temp_port = self._connect_driver_port(asynchronous=True)
|
|
|
|
if not temp_port:
|
|
self.driver_stop_event.wait(0.02)
|
|
continue
|
|
|
|
if not self._driver_listener_should_run():
|
|
self.kernel32.CloseHandle(temp_port)
|
|
break
|
|
|
|
with self.lock_driver:
|
|
if self.driver_port:
|
|
accepted = False
|
|
else:
|
|
self.driver_port = temp_port
|
|
owned_port = temp_port
|
|
accepted = True
|
|
|
|
if not accepted:
|
|
self.kernel32.CloseHandle(temp_port)
|
|
self.driver_stop_event.wait(0.02)
|
|
continue
|
|
|
|
rule_files = []
|
|
|
|
if os.path.exists(self.path_rules):
|
|
rule_files = sorted(
|
|
os.path.join(self.path_rules, name)
|
|
for name in os.listdir(self.path_rules)
|
|
if name.lower().endswith(".json") and name.lower() != "rules_driver_p1.json"
|
|
)
|
|
|
|
load_failed = False
|
|
|
|
for rule_file in rule_files:
|
|
if self.driver_stop_event.is_set() or not self.load_driver_rule_file(rule_file):
|
|
load_failed = True
|
|
break
|
|
|
|
if load_failed:
|
|
self.driver_listener_failed_event.set()
|
|
break
|
|
|
|
with self.lock_config:
|
|
whitelist = list(self.pyas_config.get("white_list", []))
|
|
|
|
for item in whitelist:
|
|
if self.driver_stop_event.is_set():
|
|
break
|
|
|
|
if isinstance(item, dict) and item.get("file"):
|
|
self.sync_driver_whitelist(item["file"], True, item.get("is_dir"))
|
|
|
|
if self.driver_stop_event.is_set():
|
|
break
|
|
|
|
ready = True
|
|
self.driver_listener_ready_event.set()
|
|
|
|
while self._driver_listener_should_run():
|
|
with self.lock_driver:
|
|
current_port = self.driver_port
|
|
owns_current = self._driver_handle_value(
|
|
current_port
|
|
) == self._driver_handle_value(owned_port)
|
|
|
|
if not owns_current:
|
|
break
|
|
|
|
receive_state, message = self._receive_driver_message(owned_port)
|
|
|
|
if receive_state != "message":
|
|
break
|
|
|
|
code = message.Data.MessageCode
|
|
pid = message.Data.ProcessId
|
|
target = message.Data.Path
|
|
exe_info = self.get_exe_info(pid)
|
|
safe_source = exe_info[1] if exe_info and exe_info[1] else "Unknown"
|
|
|
|
if not self.is_in_whitelist(safe_source):
|
|
self.write_log(
|
|
"BLOCK",
|
|
"Driver Block",
|
|
detail="Driver protection event",
|
|
pid=pid,
|
|
source=safe_source,
|
|
target=target,
|
|
code=code,
|
|
file_hash=self.calc_file_hash(safe_source),
|
|
operate=None,
|
|
success=True,
|
|
)
|
|
|
|
self._close_driver_port(owned_port)
|
|
owned_port = None
|
|
ready = False
|
|
self.driver_listener_ready_event.clear()
|
|
|
|
if self.driver_stop_event.is_set():
|
|
break
|
|
|
|
self.driver_stop_event.wait(0.02)
|
|
|
|
except Exception as e:
|
|
log_exception("PYAS_Driver.DriverMixin.pipe_server_thread:928")
|
|
self.driver_listener_failed_event.set()
|
|
|
|
if not self.driver_stop_event.is_set():
|
|
self.write_log("WARN", "pipe_server_thread", detail=str(e), success=False)
|
|
|
|
finally:
|
|
if owned_port:
|
|
try:
|
|
self.kernel32.CancelIoEx(owned_port, None)
|
|
except Exception:
|
|
log_exception("PYAS_Driver.DriverMixin.pipe_server_thread:937")
|
|
pass
|
|
|
|
self._close_driver_port(owned_port)
|
|
|
|
if not ready and not self.driver_stop_event.is_set():
|
|
self.driver_listener_failed_event.set()
|
|
|
|
self.driver_listener_ready_event.clear()
|
|
|
|
with self.lock_driver:
|
|
if self.driver_listener_thread is threading.current_thread():
|
|
self.driver_listener_thread = None
|