Files
PYAS/PYAS.py
T
2026-01-04 20:17:23 +08:00

2121 lines
92 KiB
Python

import os, gc, re, sys, time, json, uuid, queue
import ctypes, ctypes.wintypes, threading
import requests, webbrowser, winreg, msvcrt
import traceback, hashlib, subprocess, shutil
from PYAS_Config import *
from PYAS_Engine import *
from PYAS_Interface import *
from PySide6.QtWidgets import *
from PySide6.QtCore import *
from PySide6.QtGui import *
####################################################################################################
class PROCESSENTRY32W(ctypes.Structure):
_fields_ = [
("dwSize", ctypes.wintypes.DWORD),
("cntUsage", ctypes.wintypes.DWORD),
("th32ProcessID", ctypes.wintypes.DWORD),
("th32DefaultHeapID", ctypes.wintypes.LPVOID),
("th32ModuleID", ctypes.wintypes.DWORD),
("cntThreads", ctypes.wintypes.DWORD),
("th32ParentProcessID", ctypes.wintypes.DWORD),
("pcPriClassBase", ctypes.wintypes.LONG),
("dwFlags", ctypes.wintypes.DWORD),
("szExeFile", ctypes.wintypes.WCHAR * 260)]
class MIB_TCPROW_OWNER_PID(ctypes.Structure):
_fields_ = [
("dwState", ctypes.wintypes.DWORD),
("dwLocalAddr", ctypes.wintypes.DWORD),
("dwLocalPort", ctypes.wintypes.DWORD),
("dwRemoteAddr", ctypes.wintypes.DWORD),
("dwRemotePort", ctypes.wintypes.DWORD),
("dwOwningPid", ctypes.wintypes.DWORD)]
class MIB_TCPTABLE_OWNER_PID(ctypes.Structure):
_fields_ = [
("dwNumEntries", ctypes.wintypes.DWORD),
("table", MIB_TCPROW_OWNER_PID * 1)]
class FILE_NOTIFY_INFORMATION(ctypes.Structure):
_fields_ = [
("NextEntryOffset", ctypes.wintypes.DWORD),
("Action", ctypes.wintypes.DWORD),
("FileNameLength", ctypes.wintypes.DWORD),
("FileName", ctypes.wintypes.WCHAR * 1024)]
class SERVICE_STATUS(ctypes.Structure):
_fields_ = [
("dwServiceType", ctypes.wintypes.DWORD),
("dwCurrentState", ctypes.wintypes.DWORD),
("dwControlsAccepted", ctypes.wintypes.DWORD),
("dwWin32ExitCode", ctypes.wintypes.DWORD),
("dwServiceSpecificExitCode", ctypes.wintypes.DWORD),
("dwCheckPoint", ctypes.wintypes.DWORD),
("dwWaitHint", ctypes.wintypes.DWORD),]
class PROCESS_BASIC_INFORMATION(ctypes.Structure):
_fields_ = [
("Reserved1", ctypes.wintypes.LPVOID),
("PebBaseAddress", ctypes.wintypes.LPVOID),
("Reserved2", ctypes.wintypes.LPVOID * 2),
("UniqueProcessId", ctypes.wintypes.LPVOID),
("Reserved3", ctypes.wintypes.LPVOID)]
class UNICODE_STRING(ctypes.Structure):
_fields_ = [
("Length", ctypes.wintypes.USHORT),
("MaximumLength", ctypes.wintypes.USHORT),
("Buffer", ctypes.c_void_p)]
class FILTER_MESSAGE_HEADER(ctypes.Structure):
_fields_ = [
("ReplyLength", ctypes.wintypes.ULONG),
("MessageId", ctypes.c_uint64)]
class PYAS_MESSAGE(ctypes.Structure):
_fields_ = [
("MessageCode", ctypes.wintypes.ULONG),
("ProcessId", ctypes.wintypes.ULONG),
("Path", ctypes.wintypes.WCHAR * 1024)]
class PYAS_FULL_MESSAGE(ctypes.Structure):
_fields_ = [
("Header", FILTER_MESSAGE_HEADER),
("Data", PYAS_MESSAGE)]
####################################################################################################
class MainWindow_Controller(QMainWindow):
scan_progress_signal = Signal(str)
scan_add_virus_signal = Signal(str)
scan_result_signal = Signal(str)
scan_reset_signal = Signal()
scan_update_signal = Signal(str, str)
scan_remove_signal = Signal(str)
progress_title_signal = Signal(str)
init_log_signal = Signal(str)
def __init__(self):
super().__init__()
self.setAttribute(Qt.WA_TranslucentBackground)
self.setWindowFlags(Qt.FramelessWindowHint)
self.init_environ()
self.init_variable()
self.load_config()
self.init_interface()
self.init_windll()
self.show_startup()
####################################################################################################
def init_variable(self):
self.ui = Ui_main_window()
self.ui.setupUi(self)
self.python = sys.executable
self.process_timer = QTimer(self)
self.sign = sign_scanner()
self.sign.init_windll(["wintrust"])
self.model = model_scanner()
self.rule = rule_scanner()
self.cloud = cloud_scanner()
self.cloud_queue = queue.Queue()
self.init_cloud_worker()
self.mouse_flag = 0
self.mouse_pos = 0
self.current_opacity = 0
self.scan_running = False
self.virus_lock = {}
self.virus_results = []
self.scan_count = 0
self.mbr_backup = {}
self.theme_names = [
"white_switch", "red_switch", "yellow_switch",
"green_switch", "blue_switch", "black_switch",
]
self.lang_names = [
"traditional_switch", "simplified_switch", "english_switch",
]
self.pyas_default = {
"version": "3.3.8",
"api_host": "https://pyas-security.com/",
"api_key": "",
"suffix": [".com", ".dll", ".drv", ".exe", ".ocx", ".scr", ".sys", ".mui", ".cpl"],
"size": 256 * 1024 * 1024,
"sensitive": 95,
"language": "english_switch",
"theme": "white_switch",
"process_switch": True,
"document_switch": True,
"system_switch": True,
"driver_switch": True,
"network_switch": True,
"custom_rule": [],
}
self.pass_windows = [
{"exe": "System Idle Process", "class": "", "title": ""},
{"exe": "", "class": "Windows.UI.Core.CoreWindow", "title": ""},
{"exe": "", "class": "Qt691QWindowIcon", "title": "PYAS"},
{"exe": "explorer.exe", "class": "", "title": ""},
]
self.block_replace = {
"FILE_BLOCK": "檔案行為攔截",
"RANSOM_BLOCK": "勒索行為攔截",
"BOOT_BLOCK": "引導行為攔截",
"REG_BLOCK": "註冊表行為攔截",
"CLR_BLOCK": "執行環境攔截",
"SHELLCODE_BLOCK": "殼碼加載攔截",
"THREAD_BLOCK": "線程行為攔截",
"INJECT_BLOCK": "注入行為攔截",
"PROC_BLOCK": "進程行為攔截",
"REMOTE_BLOCK": "遠控行為攔截",
"SCREEN_BLOCK": "監控行為攔截",
"KILL_ATTEMPT": "終止行為攔截",
}
####################################################################################################
def init_environ(self):
self.file_pyas = self.norm_path(sys.argv[0])
self.args_pyas = sys.argv[1:]
self.path_pyas = os.path.dirname(self.file_pyas)
self.pid_pyas = int(os.getpid())
self.path_appdata = os.environ.get("APPDATA")
self.path_name = os.environ.get("USERNAME")
self.path_temp = os.environ.get("TEMP", f"C:\\Users\\{self.path_name}\\AppData\\Local\\Temp")
self.path_config = os.environ.get("ALLUSERSPROFILE", "C:\\ProgramData")
self.path_system = os.environ.get("SYSTEMROOT", "C:\\Windows")
self.path_user = os.environ.get("USERPROFILE", f"C:\\Users\\{self.path_name}")
self.path_systemp = os.path.join(self.path_system, "Temp")
self.file_config = os.path.join(self.path_config, "PYAS", "Config.json")
self.path_models = os.path.join(self.path_pyas, "Engine", "Models")
self.path_rules = os.path.join(self.path_pyas, "Engine", "Rules")
self.path_protect = os.path.join(self.path_pyas, "Plugins", "Filter")
self.path_drivers = os.path.join(self.path_protect, "PYAS_Driver.sys")
####################################################################################################
def init_windll(self):
for name in ["ntdll", "Psapi", "user32", "kernel32", "advapi32", "iphlpapi", "shell32", "fltlib"]:
try:
setattr(self, name.lower(), ctypes.WinDLL(name, use_last_error=True))
except Exception as e:
self.send_message(e, "warn", False)
self.advapi32.OpenSCManagerW.argtypes = [ctypes.c_wchar_p, ctypes.c_wchar_p, ctypes.wintypes.DWORD]
self.advapi32.OpenSCManagerW.restype = ctypes.wintypes.HANDLE
self.advapi32.CreateServiceW.argtypes = [
ctypes.wintypes.HANDLE, ctypes.c_wchar_p, ctypes.c_wchar_p, ctypes.wintypes.DWORD,
ctypes.wintypes.DWORD, ctypes.wintypes.DWORD, ctypes.wintypes.DWORD, ctypes.c_wchar_p,
ctypes.c_wchar_p, ctypes.POINTER(ctypes.wintypes.DWORD), ctypes.c_wchar_p, ctypes.c_wchar_p,
ctypes.c_wchar_p]
self.advapi32.CreateServiceW.restype = ctypes.wintypes.HANDLE
self.advapi32.OpenServiceW.argtypes = [ctypes.wintypes.HANDLE, ctypes.c_wchar_p, ctypes.wintypes.DWORD]
self.advapi32.OpenServiceW.restype = ctypes.wintypes.HANDLE
self.advapi32.StartServiceW.argtypes = [ctypes.wintypes.HANDLE, ctypes.wintypes.DWORD, ctypes.POINTER(ctypes.c_wchar_p)]
self.advapi32.StartServiceW.restype = ctypes.wintypes.BOOL
self.advapi32.ControlService.argtypes = [ctypes.wintypes.HANDLE, ctypes.wintypes.DWORD, ctypes.POINTER(SERVICE_STATUS)]
self.advapi32.ControlService.restype = ctypes.wintypes.BOOL
self.advapi32.DeleteService.argtypes = [ctypes.wintypes.HANDLE]
self.advapi32.DeleteService.restype = ctypes.wintypes.BOOL
self.advapi32.CloseServiceHandle.argtypes = [ctypes.wintypes.HANDLE]
self.advapi32.CloseServiceHandle.restype = ctypes.wintypes.BOOL
self.ntdll.NtQueryInformationProcess.argtypes = [ctypes.wintypes.HANDLE, ctypes.wintypes.ULONG, ctypes.c_void_p, ctypes.wintypes.ULONG, ctypes.POINTER(ctypes.wintypes.ULONG)]
self.ntdll.NtQueryInformationProcess.restype = ctypes.wintypes.ULONG
self.shell32.CommandLineToArgvW.argtypes = [ctypes.wintypes.LPCWSTR, ctypes.POINTER(ctypes.c_int)]
self.shell32.CommandLineToArgvW.restype = ctypes.POINTER(ctypes.wintypes.LPWSTR)
self.fltlib.FilterConnectCommunicationPort.argtypes = [ctypes.wintypes.LPCWSTR, ctypes.wintypes.DWORD, ctypes.c_void_p, ctypes.wintypes.DWORD, ctypes.c_void_p, ctypes.POINTER(ctypes.wintypes.HANDLE)]
self.fltlib.FilterConnectCommunicationPort.restype = ctypes.c_long
self.fltlib.FilterGetMessage.argtypes = [ctypes.wintypes.HANDLE, ctypes.c_void_p, ctypes.wintypes.DWORD, ctypes.c_void_p]
self.fltlib.FilterGetMessage.restype = ctypes.c_long
####################################################################################################
def save_config(self, file_path, config):
try:
filter_config = {}
for k, v in config.items():
if k.endswith("_button") and isinstance(v, bool) and k.replace("_button", "_window") in self.widgets:
continue
filter_config[k] = v
path = os.path.dirname(file_path)
if not os.path.exists(path):
os.makedirs(path)
with open(file_path, "w", encoding="utf-8", errors="ignore") as f:
f.write(json.dumps(filter_config, indent=4, ensure_ascii=False))
except Exception as e:
self.send_message(e, "warn", False)
def read_config(self, file_path, default_value):
try:
config_value = {}
if os.path.exists(file_path):
with open(file_path, "r", encoding="utf-8", errors="ignore") as f:
config_value = json.load(f)
for key, value in default_value.items():
config_value.setdefault(key, value)
return config_value
except Exception as e:
self.send_message(e, "warn", False)
return default_value.copy()
def load_config(self):
existed = os.path.exists(self.file_config)
self.pyas_config = self.read_config(self.file_config, self.pyas_default)
if existed:
dv = str(self.pyas_default.get("version", "")).strip()
cv = str(self.pyas_config.get("version", "")).strip()
if dv and dv != cv:
self.pyas_config["version"] = dv
self.save_config(self.file_config, self.pyas_config)
def config_list(self, key):
if key not in self.pyas_config:
self.pyas_config[key] = []
return self.pyas_config[key]
####################################################################################################
def init_engine_thread(self):
try:
self.rule.load_path(self.path_rules, callback=lambda x: self.init_log_signal.emit(os.path.basename(x)))
self.model.load_path(self.path_models, callback=lambda x: self.init_log_signal.emit(os.path.basename(x)))
self.init_log_signal.emit("引擎加載完成")
except Exception as e:
self.send_message(e, "warn", False)
####################################################################################################
def init_interface(self):
self.widgets = {k: v for k, v in self.ui.__dict__.items() if isinstance(v, QWidget)}
self.menu_window = [self.widgets.get(x) for x in ["options_window", "navigate_window"]]
self.sub_window = [self.widgets.get(x) for x in ["method_window", "solve_button"]]
self.blank_window = [self.widgets.get("blank_window")]
self.main_window = [self.widgets.get(x) for x in [
"about_window", "setting_window", "protect_window",
"taskmgr_window", "tool_window", "scan_window", "home_window"]]
for w in self.sub_window:
if w: w.hide()
for w in self.main_window + self.menu_window:
if w: w.raise_()
for w in self.menu_window + self.blank_window:
if w: w.setGraphicsEffect(self.create_shadow())
self.last_widget = self.widgets.get("home_window")
self.setup_theme_mapping()
self.setup_process_menu()
self.setup_scan_menu()
def create_shadow(self):
shadow = QGraphicsDropShadowEffect(self)
shadow.setOffset(0, 0)
shadow.setBlurRadius(15)
shadow.setColor(QColor(175, 175, 175))
return shadow
####################################################################################################
def init_connect(self):
self.tray_icon = QSystemTrayIcon(self)
self.tray_icon.setIcon(QIcon(r":/icon/logo_black.png"))
self.tray_icon.activated.connect(self.show_button)
self.tray_icon.show()
self.scan_progress_signal.connect(self.slot_scan_progress)
self.scan_add_virus_signal.connect(self.slot_scan_add_virus)
self.scan_result_signal.connect(self.slot_scan_result)
self.scan_reset_signal.connect(self.slot_scan_reset)
self.scan_update_signal.connect(self.slot_scan_update)
self.scan_remove_signal.connect(self.slot_scan_remove)
self.progress_title_signal.connect(self.slot_progress_title)
self.init_log_signal.connect(self.slot_init_log)
for name, widget in self.widgets.items():
if hasattr(widget, "setCheckable") and widget.isCheckable():
if name in self.lang_names:
widget.toggled.connect(lambda checked, ln=name: self.on_language_radio(ln, checked))
if self.pyas_config.get("language") == name:
widget.setChecked(True)
elif name in self.theme_names:
widget.toggled.connect(lambda checked, tn=name: self.on_theme_radio(tn, checked))
if self.pyas_config.get("theme") == name:
widget.setChecked(True)
elif "_switch" in name:
widget.toggled.connect(lambda checked, n=name: self.save_state(n, checked))
if name in self.pyas_config:
try:
widget.setChecked(self.pyas_config[name])
except Exception:
pass
if "_button" in name and hasattr(widget, "clicked"):
wn = name.replace("_button", "_window")
if self.widgets.get(wn):
widget.clicked.connect(lambda _, wn=wn: self.change_window(wn))
elif hasattr(self, name):
widget.clicked.connect(getattr(self, name))
def on_language_radio(self, ln, checked):
if checked:
self.pyas_config["language"] = ln
self.save_config(self.file_config, self.pyas_config)
self.apply_settings()
def on_theme_radio(self, tn, checked):
if checked:
self.pyas_config["theme"] = tn
self.save_config(self.file_config, self.pyas_config)
self.apply_settings()
####################################################################################################
def save_state(self, name, checked):
self.pyas_config[name] = bool(checked)
if name == "sensitive_switch":
self.pyas_config["sensitive"] = 0 if checked else 95
elif name == "extension_switch":
pass
elif name == "process_switch":
if checked:
self.start_daemon_thread(self.protect_proc_thread)
elif name == "document_switch":
if checked:
self.start_daemon_thread(self.protect_file_thread)
elif name == "system_switch":
if checked:
self.start_daemon_thread(self.protect_system_thread)
elif name == "driver_switch":
if not checked:
self.stop_system_driver()
elif self.install_system_driver():
self.start_daemon_thread(self.pipe_server_thread)
else:
self.send_message("驅動防護啟用失敗,請重啟裝置來修復錯誤", "warn", True)
elif name == "network_switch":
if checked:
self.start_daemon_thread(self.protect_net_thread)
else:
self.send_message(f"此功能不支持使用", "info", True)
self.save_config(self.file_config, self.pyas_config)
def init_thread(self):
self.start_daemon_thread(self.popup_intercept_thread)
def start_daemon_thread(self, target, *args, **kwargs):
t = threading.Thread(target=target, args=args, kwargs=kwargs, daemon=True)
t.start()
return t
####################################################################################################
def setup_theme_mapping(self):
for name, widget in self.widgets.items():
mapping = {}
base_name = name.replace("_button", "").replace("_window", "")
theme_icon_map = {
theme: f":/icon/{base_name}_{'white' if theme == 'black_switch' else 'black'}.png"
for theme in self.theme_names}
if hasattr(widget, "setPixmap"):
mapping["pixmap"] = theme_icon_map
if hasattr(widget, "setIcon"):
mapping["icon"] = theme_icon_map
if mapping:
widget._theme_mapping = mapping
####################################################################################################
def apply_settings(self):
lang = self.pyas_config.get("language", "english_switch")
theme = self.pyas_config.get("theme", "white_switch")
lang_map = translate_dict.get(lang, {})
theme_map = translate_dict.get(theme, {})
for name, widget in self.widgets.items():
if "_switch" in name and hasattr(widget, "setText") and hasattr(widget, "isChecked"):
if name not in self.lang_names + self.theme_names:
checked = widget.isChecked()
widget.setText(lang_map.get("開啟", "開啟") if checked else lang_map.get("關閉", "關閉"))
if not widget.property("_connected_toggle"):
widget.toggled.connect(lambda checked, b=widget: self.toggle_switch_text(b, checked))
widget.setProperty("_connected_toggle", True)
else:
if not hasattr(widget, "_origin_text"):
widget._origin_text = self.get_widget_text(widget)
widget.setText(self.trans(lang, widget._origin_text))
elif hasattr(widget, "setText"):
if name in ["log_text","license_text"]:
pass
else:
if not hasattr(widget, "_origin_text"):
widget._origin_text = self.get_widget_text(widget)
widget.setText(self.trans(lang, widget._origin_text))
if hasattr(widget, "setStyleSheet"):
if not hasattr(widget, "_origin_style"):
widget._origin_style = widget.styleSheet()
style = widget._origin_style
for k, v in theme_map.items():
style = style.replace(str(k), str(v))
widget.setStyleSheet(style)
mapping = getattr(widget, "_theme_mapping", None)
if isinstance(mapping, dict):
if "icon" in mapping and hasattr(widget, "setIcon"):
icon_path = mapping["icon"].get(theme)
if icon_path and (os.path.exists(icon_path) or icon_path.startswith(":")):
widget.setIcon(QIcon(icon_path))
widget.icon_path = icon_path
if "pixmap" in mapping and hasattr(widget, "setPixmap"):
pm_path = mapping["pixmap"].get(theme)
if pm_path and pm_path.startswith(":"):
pix = QPixmap(pm_path)
if not pix.isNull():
widget.setPixmap(pix)
widget.pixmap_path = pm_path
####################################################################################################
def toggle_switch_text(self, btn, checked):
lang = self.pyas_config.get("language", "traditional_switch")
switch_map = translate_dict.get(lang, {})
btn.setText(switch_map.get("開啟", "開啟") if checked else switch_map.get("關閉", "關閉"))
def get_widget_text(self, widget):
for method in ["text", "toPlainText", "currentText"]:
if hasattr(widget, method) and callable(getattr(widget, method)):
return getattr(widget, method)()
return ""
def trans(self, language, text):
for key, value in translate_dict.get(language, translate_dict).items():
text = str(text).replace(str(key), str(value))
return text.rstrip()
####################################################################################################
def change_opacity(self, widget, current, target, duration=200):
self.cleanup_animation(widget, "opacity_animation")
anim = QPropertyAnimation(widget, b"windowOpacity")
anim.setStartValue(current / 100.0)
anim.setEndValue(target / 100.0)
anim.setDuration(duration)
anim.setEasingCurve(QEasingCurve.Linear)
anim.valueChanged.connect(lambda v: self.update_opacity(widget, v))
anim.start()
widget.opacity_animation = anim
def update_opacity(self, widget, value):
widget.setWindowOpacity(value)
widget.current_opacity = value * 100
widget.setVisible(value > 0)
def cleanup_animation(self, widget, attr):
anim = getattr(widget, attr, None)
if anim:
anim.stop()
anim.deleteLater()
setattr(widget, attr, None)
####################################################################################################
def change_window(self, widget_name, duration=200):
widget = self.widgets.get(widget_name)
if widget and widget != self.last_widget:
widget.raise_()
widget.hide()
for window in self.menu_window:
window.raise_()
for window in self.sub_window:
window.hide()
self.animate_geometry(widget, duration)
self.last_widget = widget
if widget_name == "taskmgr_window":
self.start_process_timer()
else:
self.stop_process_timer()
def animate_geometry(self, widget, duration):
click = widget.mapFromGlobal(QCursor.pos())
w, h = widget.width(), widget.height()
final_radius = int(((max(click.x(), w - click.x())) ** 2 + (max(click.y(), h - click.y())) ** 2) ** 0.5)
anim = QVariantAnimation(widget)
anim.setStartValue(0)
anim.setEndValue(final_radius)
anim.setDuration(duration)
anim.valueChanged.connect(lambda val: self.update_geometry(widget, click, val))
anim.start()
def update_geometry(self, widget, click, value):
widget.setMask(QRegion(click.x() - value, click.y() - value, 2 * value, 2 * value, QRegion.Ellipse))
widget.setVisible(value > 0)
####################################################################################################
def mousePressEvent(self, event: QMouseEvent):
if event.button() == Qt.LeftButton:
self.mouse_flag = True
self.mouse_pos = event.globalPosition().toPoint() - self.pos()
self.change_opacity(self, self.current_opacity, 80, 100)
event.accept()
def mouseMoveEvent(self, event: QMouseEvent):
if self.mouse_flag:
new_top_left = event.globalPosition().toPoint() - self.mouse_pos
self.move(new_top_left)
event.accept()
def mouseReleaseEvent(self, event: QMouseEvent):
if event.button() == Qt.LeftButton and self.mouse_flag:
self.mouse_flag = False
self.setCursor(QCursor(Qt.ArrowCursor))
self.change_opacity(self, self.current_opacity, 100, 100)
event.accept()
####################################################################################################
def nativeEvent(self, eventType, message):
event = ctypes.wintypes.MSG.from_address(int(message))
if event.message in [0x0010, 0x0002, 0x0012, 0x0112, 0x0212]:
return True, 0
return super().nativeEvent(eventType, message)
def show_startup(self):
try:
if self.singleton_mutex("pyas_security"):
self.backup_mbr()
self.relock_file()
self.init_whitelist()
self.init_thread()
self.init_connect()
param = ""
if self.args_pyas:
param = self.args_pyas[0].replace("/", "-")
if "-h" not in param:
self.show_button()
self.apply_settings()
self.start_daemon_thread(self.init_engine_thread)
else:
sys.exit(0)
except Exception as e:
self.send_message(e, "warn", False)
def show_button(self):
self.show()
self.activateWindow()
self.repaint()
self.change_opacity(self, self.current_opacity, 100, 200)
def minimize_button(self):
self.change_opacity(self, self.current_opacity, 0, 200)
def close_button(self):
if self.send_message("您確定要退出所有防護嗎?", "quest", True):
self.pyas_config["process_switch"] = False
self.pyas_config["document_switch"] = False
self.pyas_config["system_switch"] = False
self.pyas_config["driver_switch"] = False
self.pyas_config["network_switch"] = False
self.stop_button()
self.stop_system_driver()
self.minimize_button()
while self.isVisible():
QApplication.processEvents()
if hasattr(self, "tray_icon"):
self.tray_icon.hide()
self.tray_icon.deleteLater()
QApplication.quit()
def reset_button(self):
if self.send_message("您確定要重置所有設定嗎?", "quest", True):
self.pyas_config = self.pyas_default.copy()
self.close_button()
def singleton_mutex(self, name):
try:
self.h_mutex = self.kernel32.CreateMutexW(None, False, name)
if self.kernel32.GetLastError() == 183:
return False
return True
except Exception:
return False
####################################################################################################
def send_message(self, message, mode, translate=True):
if QThread.currentThread() != QApplication.instance().thread():
QMetaObject.invokeMethod(self, "send_message_thread", Qt.QueuedConnection,
Q_ARG("QString", str(message)), Q_ARG("QString", mode), Q_ARG("bool", translate))
return False
return self.send_message_thread(message, mode, translate)
@Slot(str, str, bool)
def send_message_thread(self, message, mode, translate=True):
try:
yes, no, ok = QMessageBox.Yes, QMessageBox.No, QMessageBox.Ok
select = None
if not isinstance(message, str):
message = str(message)
if message and translate:
lang = self.pyas_config.get("language", "english_switch")
message = self.trans(lang, message)
m = mode.lower()
if m == "quest":
select = QMessageBox.question(self, "Quest", message, yes | no) == yes
elif m == "info":
select = QMessageBox.information(self, "Info", message, ok) == ok
elif m == "error":
select = QMessageBox.critical(self, "Error", message, ok) == ok
elif m == "notify":
now = time.strftime("%Y-%m-%d %H:%M:%S")
self.tray_icon.showMessage(now, message, QSystemTrayIcon.Information, 5000)
elif m == "files":
select = QFileDialog.getOpenFileNames(self, message)[0]
elif m == "file":
select = QFileDialog.getOpenFileName(self, message)[0]
elif m == "path":
select = QFileDialog.getExistingDirectory(self, message)
elif m == "save":
select = QFileDialog.getSaveFileName(self, message)[0]
select = select or None
now = time.strftime("%Y-%m-%d %H:%M:%S")
output = f"[{now}] | {mode} | {message} | {select}"
log_widget = self.widgets.get("log_text")
if log_widget:
log_widget.append(output)
return select
except Exception:
traceback.print_exc()
return False
####################################################################################################
def invoke_method(self, obj, method, *args):
q_args = []
for arg in args:
if isinstance(arg, int):
q_args.append(Q_ARG("int", arg))
elif isinstance(arg, QSystemTrayIcon.MessageIcon):
q_args.append(Q_ARG("QSystemTrayIcon::MessageIcon", arg))
else:
q_args.append(Q_ARG("QString", str(arg)))
QMetaObject.invokeMethod(obj, method, Qt.QueuedConnection, *q_args)
def norm_path(self, path, must_exist=True):
if isinstance(path, list):
return [p for p in (self.norm_path(x, must_exist) for x in path) if p]
if isinstance(path, tuple):
return tuple(p for p in (self.norm_path(x, must_exist) for x in path) if p)
if isinstance(path, set):
return {p for p in (self.norm_path(x, must_exist) for x in path) if p}
if isinstance(path, str):
ap = os.path.normpath(os.path.abspath(path))
return ap if (not must_exist or os.path.exists(ap)) else None
return path
def path_equal(self, a, b):
pa = self.norm_path(a, must_exist=False)
pb = self.norm_path(b, must_exist=False)
if not pa or not pb:
return False
return os.path.normcase(pa) == os.path.normcase(pb)
####################################################################################################
@Slot()
def slot_scan_reset(self):
count = len(self.virus_results)
self.widgets["solve_button"].setVisible(count > 0)
self.widgets["virus_list"].clear()
self.widgets["stop_button"].show()
self.widgets["method_button"].hide()
self.widgets["method_window"].hide()
@Slot(str)
def slot_progress_title(self, text: str):
self.widgets["progress_title"].setText(text)
@Slot(str)
def slot_scan_progress(self, text):
self.widgets["progress_text"].setText(text)
@Slot(str)
def slot_scan_add_virus(self, item_text):
item = QListWidgetItem(item_text)
item.setFlags(item.flags() | Qt.ItemIsUserCheckable)
item.setCheckState(Qt.Checked)
self.widgets["virus_list"].addItem(item)
@Slot(str)
def slot_scan_result(self, msg):
count = len(self.virus_results)
self.widgets["solve_button"].setVisible(count > 0)
self.widgets["progress_text"].setText(msg)
self.widgets["stop_button"].hide()
self.widgets["method_button"].show()
self.send_message(msg, "notify", True)
@Slot(str)
def slot_init_log(self, text):
self.send_message(text, "load", True)
@Slot(str, str)
def slot_scan_update(self, old_text, new_text):
items = self.widgets["virus_list"].findItems(old_text, Qt.MatchExactly)
if items:
items[0].setText(new_text)
@Slot(str)
def slot_scan_remove(self, text):
items = self.widgets["virus_list"].findItems(text, Qt.MatchExactly)
if items:
row = self.widgets["virus_list"].row(items[0])
self.widgets["virus_list"].takeItem(row)
####################################################################################################
def init_scan(self):
try:
self.scan_running = True
self.virus_results = []
self.scan_count = 0
self.scan_start = time.time()
self.last_widget = self.widgets.get("scan_window")
self.scan_reset_signal.emit()
lang = self.pyas_config.get("language", "english_switch")
self.scan_progress_signal.emit(self.trans(lang, "正在初始化中"))
except Exception as e:
self.send_message(e, "warn", False)
def file_button(self):
targets = self.send_message("檔案掃描", "files", True)
if targets:
self.init_scan()
self.start_daemon_thread(self.scan_worker, targets)
def path_button(self):
targets = self.send_message("路徑掃描", "path", True)
if targets:
self.init_scan()
self.start_daemon_thread(self.scan_worker, targets)
def full_button(self):
targets = [f"{chr(d)}:/" for d in range(65, 91) if os.path.exists(f"{chr(d)}:/")]
if targets:
self.init_scan()
self.start_daemon_thread(self.scan_worker, targets)
####################################################################################################
def scan_worker(self, targets):
try:
for file_path in self.yield_files(targets):
if not self.scan_running:
break
try:
norm_path = self.norm_path(file_path)
if self.is_in_whitelist(norm_path):
continue
self.scan_count += 1
suffix = self.pyas_config.get("suffix", [".com", ".dll", ".drv", ".exe", ".ocx", ".scr", ".sys", ".mui"])
ext = os.path.splitext(file_path)[-1].lower()
if ext not in suffix:
continue
result = self.scan_engine(norm_path)
if result:
self.virus_results.append(norm_path)
self.scan_add_virus_signal.emit(f"[{result}] > {norm_path}")
self.start_daemon_thread(self.cloud_check, norm_path)
lang = self.pyas_config.get("language", "english_switch")
self.progress_title_signal.emit(self.trans(lang, "正在掃描"))
self.scan_progress_signal.emit(norm_path)
gc.collect()
except Exception as e:
self.send_message(e, "warn", False)
finally:
count = len(self.virus_results)
elapsed = int(time.time() - self.scan_start)
result = (
f"發現 {count} 個病毒,共掃描 {self.scan_count} 檔案,耗時 {elapsed} 秒"
if count else f"未發現病毒,共掃描 {self.scan_count} 檔案,耗時 {elapsed} 秒")
lang = self.pyas_config.get("language", "english_switch")
self.progress_title_signal.emit(self.trans(lang, "病毒掃描"))
self.scan_result_signal.emit(self.trans(lang, result))
def yield_files(self, targets):
if isinstance(targets, str):
if os.path.isdir(targets):
for root, _, files in os.walk(targets):
for f in files:
yield self.norm_path(os.path.join(root, f))
elif os.path.isfile(targets):
yield self.norm_path(targets)
elif isinstance(targets, (list, tuple, set)):
for t in targets:
yield from self.yield_files(t)
def scan_engine(self, file_path):
try:
result = False
label, level = self.model.model_scan(file_path)
if label and level >= self.pyas_config.get("sensitive", 95):
if not self.sign.sign_verify(file_path):
result = f"{label}.{level}"
if self.pyas_config.get("extension_switch", False):
types, detail = self.rule.yara_scan(file_path)
if types and detail:
result = f"{types}.{detail}"
return result
except Exception as e:
self.send_message(e, "warn", False)
return False
####################################################################################################
def init_cloud_worker(self):
for _ in range(1):
t = threading.Thread(target=self.cloud_worker, daemon=True)
t.start()
def cloud_worker(self):
while True:
try:
file_path = self.cloud_queue.get()
self.perform_cloud_scan(file_path)
self.cloud_queue.task_done()
except Exception:
pass
def cloud_check(self, file_path, get_result=False):
if get_result:
return self.perform_cloud_scan(file_path, get_result=True)
self.cloud_queue.put(file_path)
def perform_cloud_scan(self, file_path, get_result=False):
was_locked = False
try:
if not os.path.exists(file_path):
return False
if file_path in self.virus_lock:
self.lock_file(file_path, False)
was_locked = True
size = os.path.getsize(file_path)
if size > self.pyas_config.get("size", 256 * 1024 * 1024):
if was_locked:
self.lock_file(file_path, True)
return False
api_host = self.pyas_config.get("api_host", "https://pyas-security.com/")
api_key = self.pyas_config.get("api_key", "")
success, sha256 = self.cloud.upload_file(file_path, api_host, api_key)
if was_locked:
self.lock_file(file_path, True)
was_locked = False
if success and sha256:
if get_result:
return self.cloud.get_result(sha256, api_host, api_key)
else:
self.send_message(f"雲端服務 | 通訊服務錯誤 | None | {file_path} | None", "warn", True)
except Exception as e:
self.send_message(e, "warn", False)
finally:
if 'was_locked' in locals() and was_locked:
try:
self.lock_file(file_path, True)
except Exception:
pass
return False
####################################################################################################
def solve_button(self):
deleted = 0
virus_list = self.widgets["virus_list"]
start_ts = time.time()
for i in reversed(range(virus_list.count())):
item = virus_list.item(i)
if item.checkState() == Qt.Checked:
file_path = self.norm_path(item.text().split(">")[1].strip())
lang = self.pyas_config.get("language", "english_switch")
self.progress_title_signal.emit(self.trans(lang, "正在刪除"))
self.scan_progress_signal.emit(file_path)
try:
os.remove(file_path)
deleted += 1
virus_list.takeItem(i)
self.virus_results.remove(file_path)
except Exception as e:
self.send_message(e, "warn", False)
QApplication.processEvents()
count = len(self.virus_results)
self.widgets["solve_button"].setVisible(count > 0)
elapsed = int(time.time() - start_ts)
result = (
f"剩餘 {count} 個病毒,共刪除 {deleted} 檔案,耗時 {elapsed} 秒"
if count else f"未剩餘病毒,共刪除 {deleted} 檔案,耗時 {elapsed} 秒")
lang = self.pyas_config.get("language", "english_switch")
self.progress_title_signal.emit(self.trans(lang, "病毒掃描"))
self.scan_result_signal.emit(self.trans(lang, result))
def stop_button(self):
self.scan_running = False
self.widgets["stop_button"].hide()
####################################################################################################
def setup_scan_menu(self):
widget = self.widgets.get("virus_list")
if widget:
widget.setContextMenuPolicy(Qt.CustomContextMenu)
widget.customContextMenuRequested.connect(self.show_scan_menu)
def show_scan_menu(self, pos):
widget = self.widgets.get("virus_list")
if not widget:
return
item = widget.itemAt(pos)
if not item:
return
lang = self.pyas_config.get("language", "english_switch")
menu = QMenu()
copy_action = menu.addAction(self.trans(lang, "複製路徑"))
del_action = menu.addAction(self.trans(lang, "刪除檔案"))
white_action = menu.addAction(self.trans(lang, "增加白名單"))
quarantine_action = menu.addAction(self.trans(lang, "增加隔離區"))
act = menu.exec(widget.viewport().mapToGlobal(pos))
text = item.text()
if ">" in text:
path = text.split(">", 1)[1].strip()
else:
path = text.strip()
if act == copy_action:
QGuiApplication.clipboard().setText(str(path or "").replace("/", "\\"))
elif act == del_action:
file_path = self.norm_path(path)
if file_path and self.send_message("您確定要刪除檔案嗎?", "quest"):
try:
os.remove(file_path)
widget.takeItem(widget.row(item))
self.virus_results.remove(file_path)
except Exception as e:
self.send_message(e, "warn", False)
elif act == white_action:
file_path = self.norm_path(path)
if file_path and self.send_message("您確定要增加到白名單嗎?", "quest"):
try:
n = self.manage_named_list("white_list", [file_path], action="add", with_hash=True)
self.send_message(f"成功增加到白名單,共 {n} 個檔案", "info", True)
widget.takeItem(widget.row(item))
self.virus_results.remove(file_path)
except Exception as e:
self.send_message(e, "warn", False)
elif act == quarantine_action:
file_path = self.norm_path(path)
if file_path and self.send_message("您確定要增加到隔離區嗎?", "quest"):
try:
n = self.manage_named_list("quarantine", [file_path], action="add", with_hash=True, lock_func=self.lock_file)
self.send_message(f"成功增加到隔離區,共 {n} 個檔案", "info", True)
widget.takeItem(widget.row(item))
self.virus_results.remove(file_path)
except Exception as e:
self.send_message(e, "warn", False)
count = len(self.virus_results)
self.widgets["solve_button"].setVisible(count > 0)
####################################################################################################
def list_process(self):
pe = self.get_process_entry()
snapshot = self.kernel32.CreateToolhelp32Snapshot(0x00000002, 0)
result, success = [], self.kernel32.Process32FirstW(snapshot, ctypes.byref(pe))
while success:
pid = pe.th32ProcessID
name, file_path = (None, None)
if pid > 4:
name, file_path = self.get_exe_info(pid)
if not name:
try:
name = pe.szExeFile
except Exception:
name = None
result.append((pid, name, file_path))
success = self.kernel32.Process32NextW(snapshot, ctypes.byref(pe))
self.kernel32.CloseHandle(snapshot)
return result
def refresh_process(self):
procs = self.list_process()
self.proc_pid_map = [pid for pid, _, _ in procs]
widget = self.widgets.get("manage_list")
info_text = self.widgets.get("info_text")
if widget:
model = QStringListModel([f"{name or 'None'} | {pid} | {file_path or 'None'}" for pid, name, file_path in procs])
widget.setModel(model)
if info_text:
info_text.setText(str(len(self.proc_pid_map)))
def device_path_to_drive(self, path):
if not path:
return ""
for d in range(65, 91):
drive = f"{chr(d)}:"
buf = ctypes.create_unicode_buffer(1024)
if self.kernel32.QueryDosDeviceW(drive, buf, 1024):
dev = buf.value
if path.startswith(dev):
return path.replace(dev, drive, 1)
return path
def start_process_timer(self):
if hasattr(self, "process_timer") and self.process_timer.isActive():
return
self.process_timer = QTimer(self)
self.process_timer.timeout.connect(self.refresh_process)
self.process_timer.start(0)
def stop_process_timer(self):
if hasattr(self, "process_timer"):
self.process_timer.stop()
def setup_process_menu(self):
widget = self.widgets.get("manage_list")
if widget:
widget.setContextMenuPolicy(Qt.CustomContextMenu)
widget.customContextMenuRequested.connect(self.show_process_menu)
def show_process_menu(self, pos):
widget = self.widgets.get("manage_list")
if not widget:
return
idx = widget.indexAt(pos)
if not idx.isValid() or idx.row() >= len(self.proc_pid_map):
return
lang = self.pyas_config.get("language", "english_switch")
menu = QMenu()
copy_action = menu.addAction(self.trans(lang, "複製路徑"))
kill_action = menu.addAction(self.trans(lang, "結束進程"))
white_action = menu.addAction(self.trans(lang, "增加白名單"))
quarantine_action = menu.addAction(self.trans(lang, "增加隔離區"))
act = menu.exec(widget.viewport().mapToGlobal(pos))
pid = self.proc_pid_map[idx.row()]
name, file_path = self.get_exe_info(pid)
if act == copy_action:
QGuiApplication.clipboard().setText(str(file_path or "").replace("/", "\\"))
elif act == kill_action:
self.kill_process(pid)
elif act == white_action:
target_path = self.norm_path(file_path)
if target_path and self.send_message("您確定要增加到白名單嗎?", "quest"):
try:
n = self.manage_named_list("white_list", [target_path], action="add", with_hash=True)
self.send_message(f"成功增加到白名單,共 {n} 個檔案", "info", True)
except Exception as e:
self.send_message(e, "warn", False)
elif act == quarantine_action:
target_path = self.norm_path(file_path)
if target_path and self.send_message("您確定要增加到隔離區嗎?", "quest"):
try:
n = self.manage_named_list("quarantine", [target_path], action="add", with_hash=True, lock_func=self.lock_file)
self.send_message(f"成功增加到隔離區,共 {n} 個檔案", "info", True)
except Exception as e:
self.send_message(e, "warn", False)
def kill_process(self, pid):
try:
h = self.kernel32.OpenProcess(0x1F0FFF, False, pid)
if h:
file_path = self.norm_path(self.get_process_file(h))
if self.path_equal(file_path, self.file_pyas):
self.close_button()
else:
self.kernel32.TerminateProcess(h, 0)
self.kernel32.CloseHandle(h)
except Exception as e:
self.send_message(e, "warn", False)
self.refresh_process()
####################################################################################################
def clean_button(self):
try:
if self.send_message("您確定要清理系統垃圾嗎?", "quest", True):
self.total_deleted_size = 0
for path in [self.path_temp, self.path_systemp]:
self.traverse_temp(path)
size_text = self.format_size(self.total_deleted_size)
self.send_message(f"成功清理了 {size_text} 系統垃圾", "info", True)
except Exception as e:
self.send_message(e, "warn", False)
def traverse_temp(self, path):
try:
if not os.path.exists(path):
return
for fd in os.listdir(path):
file = os.path.join(path, fd)
try:
QApplication.processEvents()
if os.path.isdir(file):
self.traverse_temp(file)
try:
os.rmdir(file)
except Exception:
pass
else:
file_size = os.path.getsize(file)
os.remove(file)
self.total_deleted_size += file_size
except Exception:
continue
except Exception as e:
self.send_message(e, "warn", False)
def format_size(self, size):
units = ["B", "KB", "MB", "GB", "TB", "PB", "EB", "ZB", "YB"]
for unit in units:
if size < 1024 or unit == units[-1]:
return f"{size:.2f} {unit}"
size /= 1024
####################################################################################################
def repair_button(self):
try:
if self.send_message("您確定要修復系統檔案嗎?", "quest", True):
self.repair_system_mbr()
self.repair_system_wallpaper()
self.repair_system_restrict()
self.repair_system_file_type()
self.repair_system_file_icon()
self.repair_system_image()
self.send_message("修復系統檔案成功", "info", True)
except Exception as e:
self.send_message(e, "warn", False)
def backup_mbr(self, max_drives=26):
self.mbr_backup = {}
for drive in range(max_drives):
drive_path = rf"\\.\PhysicalDrive{drive}"
try:
with open(drive_path, "rb") as f:
mbr = f.read(512)
if mbr[510:512] == b"\x55\xAA":
self.mbr_backup[drive] = mbr
except FileNotFoundError:
continue
except PermissionError as e:
continue
except Exception as e:
self.send_message(e, "warn", False)
def repair_system_mbr(self):
if not hasattr(self, "mbr_backup") or not self.mbr_backup:
return
for drive, mbr_value in self.mbr_backup.items():
drive_path = rf"\\.\PhysicalDrive{drive}"
try:
with open(drive_path, "rb+") as f:
current = f.read(512)
if current != mbr_value:
f.seek(0)
f.write(mbr_value)
except PermissionError as e:
continue
except Exception as e:
self.send_message(e, "warn", False)
def repair_system_wallpaper(self):
try:
wallpaper = os.path.join(self.path_system, "web", "wallpaper", "Windows", "img0.jpg")
if not os.path.exists(wallpaper):
return
key = r"Control Panel\\Desktop"
with winreg.OpenKey(winreg.HKEY_CURRENT_USER, key, 0, winreg.KEY_SET_VALUE) as reg:
winreg.SetValueEx(reg, "Wallpaper", 0, winreg.REG_SZ, wallpaper)
theme_dir = os.path.join(self.path_appdata, "Microsoft", "Windows", "Themes")
for fname in ["TranscodedWallpaper", "TranscodedWallpaper.tmp"]:
fpath = os.path.join(theme_dir, fname)
if os.path.exists(fpath):
try:
os.remove(fpath)
except Exception:
pass
cache_dir = os.path.join(theme_dir, "CachedFiles")
if os.path.exists(cache_dir):
try:
shutil.rmtree(cache_dir, ignore_errors=True)
except Exception:
pass
self.user32.SystemParametersInfoW(20, 0, wallpaper, 3)
except Exception as e:
self.send_message(e, "warn", False)
def repair_system_restrict(self):
try:
permissions = [
"NoControlPanel", "NoDrives", "NoFileMenu", "NoFind", "NoStartMenuPinnedList",
"NoSetFolders", "NoSetFolderOptions", "NoViewOnDrive", "NoClose", "NoDesktop",
"NoLogoff", "NoFolderOptions", "RestrictRun", "NoViewContextMenu", "HideClock",
"NoStartMenuMyGames", "NoStartMenuMyMusic", "DisableCMD", "NoAddingComponents",
"NoWinKeys", "NoStartMenuLogOff", "NoSimpleNetIDList", "NoLowDiskSpaceChecks",
"DisableLockWorkstation","Restrict_Run", "DisableTaskMgr", "DisableRegistryTools",
"DisableChangePassword", "Wallpaper", "NoComponents", "NoStartMenuMorePrograms",
"NoActiveDesktop", "NoSetActiveDesktop", "NoRecentDocsMenu", "NoWindowsUpdate",
"NoChangeStartMenu", "NoFavoritesMenu", "NoRecentDocsHistory", "NoSetTaskbar",
"NoSMHelp", "NoTrayContextMenu", "NoManageMyComputerVerb", "NoRealMode", "NoRun",
"ClearRecentDocsOnExit", "NoActiveDesktopChanges", "NoStartMenuNetworkPlaces"
]
restrict_paths = [
(winreg.HKEY_CURRENT_USER, r"SOFTWARE\Policies\Microsoft\MMC"),
(winreg.HKEY_CURRENT_USER, r"SOFTWARE\Policies\Microsoft\Windows\System"),
(winreg.HKEY_CURRENT_USER, r"SOFTWARE\Microsoft\Windows\CurrentVersion\Policies\Explorer"),
(winreg.HKEY_CURRENT_USER, r"SOFTWARE\Microsoft\Windows\CurrentVersion\Policies\System"),
(winreg.HKEY_LOCAL_MACHINE, r"SOFTWARE\Microsoft\Windows\CurrentVersion\Policies\Explorer"),
(winreg.HKEY_LOCAL_MACHINE, r"SOFTWARE\Microsoft\Windows\CurrentVersion\Policies\System"),
(winreg.HKEY_LOCAL_MACHINE, r"SOFTWARE\Microsoft\Windows NT\CurrentVersion\Winlogon"),
(winreg.HKEY_LOCAL_MACHINE, r"SOFTWARE\Policies\Microsoft\Windows\System"),
]
for hkey, path in restrict_paths:
try:
with winreg.OpenKey(hkey, path, 0, winreg.KEY_SET_VALUE | winreg.KEY_WRITE) as reg:
for value in permissions:
try:
winreg.DeleteValue(reg, value)
except FileNotFoundError:
continue
except FileNotFoundError:
continue
except Exception as e:
self.send_message(e, "warn", False)
def repair_system_file_type(self):
try:
file_types = [".exe", ".bat", ".cmd", ".com"]
for root in [winreg.HKEY_LOCAL_MACHINE, winreg.HKEY_CURRENT_USER]:
for ext in file_types:
try:
with winreg.CreateKey(root, rf"SOFTWARE\Classes\{ext}") as reg:
winreg.SetValue(reg, "", winreg.REG_SZ, "exefile" if ext == ".exe" else ext[1:]+"file")
except Exception:
continue
for shell_cmd in ["open", "runas"]:
try:
with winreg.CreateKey(root, r"SOFTWARE\Classes\exefile\shell\{}\command".format(shell_cmd)) as reg:
winreg.SetValue(reg, "", winreg.REG_SZ, '"%1" %*')
except Exception:
continue
except Exception as e:
self.send_message(e, "warn", False)
def repair_system_file_icon(self):
try:
for root in [winreg.HKEY_LOCAL_MACHINE, winreg.HKEY_CURRENT_USER]:
try:
with winreg.CreateKey(root, r"SOFTWARE\Classes\exefile\DefaultIcon") as reg:
winreg.SetValue(reg, "", winreg.REG_SZ, "%1")
except Exception:
continue
except Exception as e:
self.send_message(e, "warn", False)
def repair_system_image(self):
try:
key_path = r"SOFTWARE\Microsoft\Windows NT\CurrentVersion\Image File Execution Options"
with winreg.OpenKey(winreg.HKEY_LOCAL_MACHINE, key_path, 0, winreg.KEY_ALL_ACCESS) as reg:
i = 0
while True:
try:
subkey = winreg.EnumKey(reg, i)
subkey_path = key_path + "\\" + subkey
with winreg.OpenKey(reg, subkey, 0, winreg.KEY_ALL_ACCESS) as sub_reg:
for value in ["Debugger", "UseFilter", "GlobalFlag", "MitigationOptions"]:
try:
winreg.DeleteValue(sub_reg, value)
except FileNotFoundError:
continue
i += 1
except OSError:
break
except Exception as e:
self.send_message(e, "warn", False)
####################################################################################################
def check_process_survival(self):
target_map = {
"explorer.exe": "explorer.exe"
}
try:
running = set()
pe = self.get_process_entry()
snapshot = self.kernel32.CreateToolhelp32Snapshot(0x00000002, 0)
if self.kernel32.Process32FirstW(snapshot, ctypes.byref(pe)):
while True:
try:
name = pe.szExeFile.lower()
running.add(name)
except:
pass
if not self.kernel32.Process32NextW(snapshot, ctypes.byref(pe)):
break
self.kernel32.CloseHandle(snapshot)
for name, cmd in target_map.items():
if name.lower() not in running:
subprocess.Popen(cmd, shell=True)
self.send_message(f"系統防護 | 系統進程重啟 | None | None | {name}", "notify", True)
except Exception as e:
self.send_message(e, "warn", False)
####################################################################################################
def popup_button(self):
self.config_list("block_list")
self.block_window = False
if self.send_message("請選擇要攔截的軟體彈窗", "quest", True):
while True:
QApplication.processEvents()
title, class_name, proc_name = self.get_window_info(self.user32.GetForegroundWindow())
if proc_name and not any(self.window_rule_match(item, proc_name, class_name, title) for item in self.pass_windows):
if self.send_message(f"您確定要攔截 {proc_name} ({title}) 嗎?", "quest", True):
if self.add_window_rule(self.pyas_config["block_list"], proc_name, class_name, title):
self.send_message(f"成功增加到彈窗攔截: {proc_name} ({title})", "info", True)
else:
self.send_message("已存在彈窗攔截", "info", True)
break
self.start_daemon_thread(self.popup_intercept_thread)
def popup_button_2(self):
self.config_list("block_list")
self.block_window = False
if self.send_message("請選擇要取消攔截的軟體彈窗", "quest", True):
while True:
QApplication.processEvents()
title, class_name, proc_name = self.get_window_info(self.user32.GetForegroundWindow())
if proc_name and not any(self.window_rule_match(item, proc_name, class_name, title) for item in self.pass_windows):
if self.send_message(f"您確定要取消攔截 {proc_name} ({title}) 嗎?", "quest", True):
if self.remove_window_rule(self.pyas_config["block_list"], proc_name, class_name, title):
self.send_message(f"成功取消彈窗攔截: {proc_name} ({title})", "info", True)
else:
self.send_message("未找到彈窗攔截", "info", True)
break
self.start_daemon_thread(self.popup_intercept_thread)
def window_rule_match(self, item, proc_name, class_name, title):
for k, v in zip(["exe", "class", "title"], [proc_name, class_name, title]):
if item.get(k, "") and item.get(k, "") != v:
return False
return True
def add_window_rule(self, rule_list, proc_name, class_name, title):
if not any(self.window_rule_match(item, proc_name, class_name, title) for item in rule_list):
rule_list.append({"exe": proc_name, "class": class_name, "title": title})
self.save_config(self.file_config, self.pyas_config)
return True
return False
def remove_window_rule(self, rule_list, proc_name, class_name, title):
before = len(rule_list)
rule_list[:] = [item for item in rule_list if not self.window_rule_match(item, proc_name, class_name, title)]
if len(rule_list) < before:
self.save_config(self.file_config, self.pyas_config)
return True
return False
def popup_intercept_thread(self):
self.block_window = True
while self.block_window:
try:
time.sleep(0.2)
rules = self.pyas_config.get("block_list", [])
if not rules:
continue
for hWnd in self.get_all_windows():
title, class_name, proc_name = self.get_window_info(hWnd)
if (not any(self.window_rule_match(item, proc_name, class_name, title) for item in self.pass_windows) and
any(self.window_rule_match(item, proc_name, class_name, title) for item in rules)):
closed = False
for msg in [0x0010, 0x0002, 0x0012, 0x0112]:
if self.user32.SendMessageW(hWnd, msg, 0xF060, 0) != 0:
closed = True
break
if not closed and proc_name:
pid = ctypes.c_ulong()
self.user32.GetWindowThreadProcessId(hWnd, ctypes.byref(pid))
if pid.value:
h = self.kernel32.OpenProcess(0x1F0FFF, False, pid.value)
if h:
self.kernel32.TerminateProcess(h, 0)
self.kernel32.CloseHandle(h)
except Exception as e:
self.send_message(e, "warn", False)
####################################################################################################
def get_all_windows(self):
self.hwnd_list = []
WNDENUMPROC = ctypes.WINFUNCTYPE(ctypes.c_bool, ctypes.c_int, ctypes.c_int)
self.user32.EnumWindows(WNDENUMPROC(self.enum_windows_callback), 0)
return self.hwnd_list
def enum_windows_callback(self, hWnd, lParam):
self.hwnd_list.append(hWnd)
return True
def get_window_info(self, hWnd):
length = self.user32.GetWindowTextLengthW(hWnd)
title = ctypes.create_unicode_buffer(length + 1)
self.user32.GetWindowTextW(hWnd, title, length + 1)
class_name = ctypes.create_unicode_buffer(256)
self.user32.GetClassNameW(hWnd, class_name, 256)
pid = ctypes.c_ulong()
self.user32.GetWindowThreadProcessId(hWnd, ctypes.byref(pid))
proc_name = ""
try:
proc_name = self.get_process_name(pid.value)
except Exception:
pass
return str(title.value), str(class_name.value), proc_name
def get_process_name(self, pid):
name, file_path = self.get_exe_info(pid)
if name:
return name
snapshot = self.kernel32.CreateToolhelp32Snapshot(0x00000002, 0)
entry = PROCESSENTRY32W()
entry.dwSize = ctypes.sizeof(PROCESSENTRY32W)
result = ""
if self.kernel32.Process32FirstW(snapshot, ctypes.byref(entry)):
while True:
if entry.th32ProcessID == pid:
try:
result = entry.szExeFile
except Exception:
result = ""
break
if not self.kernel32.Process32NextW(snapshot, ctypes.byref(entry)):
break
self.kernel32.CloseHandle(snapshot)
return result
####################################################################################################
def whitelist_button(self):
self.config_list("white_list")
files = self.send_message("選擇檔案", "files", True)
if files and self.send_message("您確定要增加到白名單嗎?", "quest"):
n = self.manage_named_list("white_list", files, action="add", with_hash=True)
self.send_message(f"成功增加到白名單,共 {n} 個檔案", "info", True)
def whitelist_button_2(self):
self.config_list("white_list")
files = self.send_message("選擇檔案", "files", True)
if files and self.send_message("您確定要移除白名單嗎?", "quest"):
n = self.manage_named_list("white_list", files, action="remove", with_hash=True)
self.send_message(f"成功移除白名單,共 {n} 個檔案", "info", True)
def quarantine_button(self):
self.config_list("quarantine")
files = self.send_message("選擇檔案", "files", True)
if files and self.send_message("您確定要增加到隔離區嗎?", "quest"):
n = self.manage_named_list("quarantine", files, action="add", with_hash=True, lock_func=self.lock_file)
self.send_message(f"成功增加到隔離區,共 {n} 個檔案", "info", True)
def quarantine_button_2(self):
self.config_list("quarantine")
quarantine_dir = os.path.join(self.path_config, "quarantine")
files = self.send_message("選擇檔案", "files", True)
if files and self.send_message("您確定要移除隔離區嗎?", "quest"):
n = self.manage_named_list("quarantine", files, action="remove", with_hash=True, lock_func=self.lock_file)
self.send_message(f"成功移除隔離區,共 {n} 個檔案", "info", True)
def add_to_quarantine(self, files):
self.config_list("quarantine")
return self.manage_named_list("quarantine", files, action="add", with_hash=True, lock_func=self.lock_file)
def is_in_whitelist(self, file_path):
p = self.norm_path(file_path)
if not p:
return False
file_hash = self.calc_file_hash(p)
return any(os.path.normcase(item.get("file","")) == os.path.normcase(p) and
item.get("hash","") == file_hash for item in self.pyas_config.get("white_list", []))
def init_whitelist(self):
self.manage_named_list("white_list", [self.file_pyas], action="add", with_hash=True)
####################################################################################################
def manage_named_list(self, list_key, files, action="add", with_hash=True, lock_func=None):
norm_paths = self.norm_path(files or [], must_exist=True)
if isinstance(norm_paths, str):
norm_paths = [norm_paths] if norm_paths else []
n = 0
if action == "add":
for path in norm_paths:
file_hash = self.calc_file_hash(path) if with_hash else ""
if not any(self.norm_path(item["file"]) == path and
(not with_hash or item.get("hash", "") == file_hash)
for item in self.config_list(list_key)):
if lock_func:
try:
lock_func(path, True)
except Exception as e:
self.send_message(e, "warn", False)
self.config_list(list_key).append({"file": path, "hash": file_hash})
n += 1
elif action == "remove":
for item in [item for item in self.config_list(list_key)
if self.norm_path(item["file"]) in norm_paths]:
try:
if lock_func:
lock_func(item["file"], False)
self.config_list(list_key).remove(item)
n += 1
except Exception as e:
self.send_message(e, "warn", False)
if n:
self.save_config(self.file_config, self.pyas_config)
return n
def calc_file_hash(self, file_path, block_size=65536):
try:
h = hashlib.sha256()
with open(file_path, "rb") as f:
for chunk in iter(lambda: f.read(block_size), b""):
h.update(chunk)
return h.hexdigest()
except Exception:
return ""
####################################################################################################
def website_button(self):
try:
webbrowser.open("https://pyas-security.com/antivirus")
return True
except Exception as e:
self.send_message(e, "warn", False)
return False
def cloud_button(self):
try:
webbrowser.open("https://pyas-security.com/analyze")
return True
except Exception as e:
self.send_message(e, "warn", False)
return False
def update_button(self):
try:
current = str(self.pyas_config.get("version", "")).strip()
page = "https://github.com/87owo/PYAS/releases"
latest = ""
try:
j = requests.get("https://api.github.com/repos/87owo/PYAS/releases/latest", headers={"Accept": "application/vnd.github+json", "User-Agent": "PYAS"}, timeout=10).json()
latest = (j.get("tag_name") or j.get("name") or "").strip()
page = j.get("html_url") or page
except Exception:
try:
u = requests.get("https://github.com/87owo/PYAS/releases/latest", headers={"User-Agent": "PYAS"}, allow_redirects=True, timeout=10).url
page = u or page
m = re.search(r"/tag/([^/]+)$", str(u))
latest = m.group(1).strip() if m else ""
except Exception:
pass
rl = re.sub(r"^[vV]\s*", "", str(latest))
cl = re.sub(r"^[vV]\s*", "", str(current))
tr = tuple(int(x) for x in re.findall(r"\d+", rl))
tl = tuple(int(x) for x in re.findall(r"\d+", cl))
if tr and (not tl or tr > tl):
if self.send_message(f"發現新版本 {latest},您確定要前往更新嗎?", "quest", True):
webbrowser.open(page)
else:
if cl:
self.send_message(f"當前已是最新版本 {current}", "info", True)
else:
webbrowser.open(page)
return True
except Exception as e:
self.send_message(e, "warn", False)
return False
####################################################################################################
def lock_file(self, file, lock):
try:
if lock:
if file not in self.virus_lock:
self.virus_lock[file] = os.open(file, os.O_RDWR)
msvcrt.locking(self.virus_lock[file], msvcrt.LK_NBRLCK, os.path.getsize(file))
else:
if file in self.virus_lock:
msvcrt.locking(self.virus_lock[file], msvcrt.LK_UNLCK, os.path.getsize(file))
os.close(self.virus_lock[file])
del self.virus_lock[file]
except Exception as e:
self.send_message(e, "warn", False)
def relock_file(self):
quarantine_list = self.pyas_config.get("quarantine", [])
for item in quarantine_list:
file = item["file"]
if os.path.exists(file):
try:
self.lock_file(file, True)
except Exception as e:
self.send_message(e, "warn", False)
####################################################################################################
def protect_proc_thread(self):
self.exist_process = self.get_process_list()
while self.pyas_config.get("process_switch", False):
try:
time.sleep(0.2)
cur = self.get_process_list()
for pid in cur - self.exist_process:
self.handle_new_process(pid)
self.exist_process = cur
except Exception as e:
self.send_message(e, "warn", False)
def handle_new_process(self, pid):
try:
h = self.kernel32.OpenProcess(0x1F0FFF, False, pid)
if not h:
return
self.suspend_process(h, True)
cmdline = self.get_process_cmdline(h)
paths = []
for arg in self.split_commandline(cmdline):
paths.extend(self.extract_paths_from_arg(arg))
seen = set()
paths = [p for p in paths if p and (p not in seen and not seen.add(p))]
for p in paths:
file_path = self.norm_path(self.device_path_to_drive(p)) if p else ""
if not file_path:
continue
if self.is_in_whitelist(file_path):
continue
if self.scan_engine(file_path):
self.kernel32.TerminateProcess(h, 0)
self.send_message(f"進程防護 | 靜態掃描攔截 | {pid} | None | {file_path}", "notify", True)
self.start_daemon_thread(self.cloud_check, file_path)
self.suspend_process(h, False)
self.kernel32.CloseHandle(h)
except Exception as e:
self.send_message(e, "warn", False)
def suspend_process(self, h_process, suspend):
try:
if not hasattr(self.ntdll, "NtSuspendProcess"):
self.ntdll.NtSuspendProcess.argtypes = [ctypes.c_void_p]
self.ntdll.NtSuspendProcess.restype = ctypes.c_ulong
self.ntdll.NtResumeProcess.argtypes = [ctypes.c_void_p]
self.ntdll.NtResumeProcess.restype = ctypes.c_ulong
if suspend:
self.ntdll.NtSuspendProcess(h_process)
else:
self.ntdll.NtResumeProcess(h_process)
except Exception as e:
self.send_message(e, "warn", False)
####################################################################################################
def get_process_list(self):
exist_process, pe = set(), self.get_process_entry()
hSnapshot = self.kernel32.CreateToolhelp32Snapshot(0x00000002, 0)
if self.kernel32.Process32FirstW(hSnapshot, ctypes.byref(pe)):
while True:
exist_process.add(pe.th32ProcessID)
if not self.kernel32.Process32NextW(hSnapshot, ctypes.byref(pe)):
break
self.kernel32.CloseHandle(hSnapshot)
return exist_process
def get_process_file(self, h_process):
buf = ctypes.create_unicode_buffer(1024)
if self.psapi.GetProcessImageFileNameW(h_process, buf, 1024):
return self.norm_path(self.device_path_to_drive(buf.value))
return ""
def get_process_entry(self):
pe = PROCESSENTRY32W()
pe.dwSize = ctypes.sizeof(PROCESSENTRY32W)
return pe
def get_exe_info(self, pid):
name, file_path = None, None
h = self.kernel32.OpenProcess(0x1000, False, pid)
if h:
buf = ctypes.create_unicode_buffer(1024)
if self.psapi.GetProcessImageFileNameW(h, buf, 1024):
path = self.norm_path(self.device_path_to_drive(buf.value))
if path:
file_path = path
name = os.path.basename(path)
self.kernel32.CloseHandle(h)
return name, file_path
####################################################################################################
def get_process_cmdline(self, h):
pbi = PROCESS_BASIC_INFORMATION()
retlen = ctypes.wintypes.ULONG(0)
status = self.ntdll.NtQueryInformationProcess(h, 0, ctypes.byref(pbi), ctypes.sizeof(pbi), ctypes.byref(retlen))
if status != 0:
return ""
pointer_size = ctypes.sizeof(ctypes.c_void_p)
peb_address = int(pbi.PebBaseAddress) if pbi.PebBaseAddress else 0
read_buf = (ctypes.c_ubyte * pointer_size)()
lpNumberOfBytesRead = ctypes.c_size_t(0)
offset_process_parameters = 0x20 if pointer_size == 8 else 0x10
addr_pp = peb_address + offset_process_parameters
if not addr_pp:
return ""
if not self.kernel32.ReadProcessMemory(h, ctypes.c_void_p(addr_pp), read_buf, pointer_size, ctypes.byref(lpNumberOfBytesRead)):
return ""
proc_params_address = ctypes.c_void_p.from_buffer_copy(read_buf).value
if not proc_params_address:
return ""
offset_command_line = 0x70 if pointer_size == 8 else 0x40
us = UNICODE_STRING()
addr_cmd = int(proc_params_address) + offset_command_line
if not self.kernel32.ReadProcessMemory(h, ctypes.c_void_p(addr_cmd), ctypes.byref(us), ctypes.sizeof(us), ctypes.byref(lpNumberOfBytesRead)):
return ""
if not us.Buffer or us.Length == 0:
return ""
buf_len = int(us.Length // 2)
buf = (ctypes.c_wchar * buf_len)()
if not self.kernel32.ReadProcessMemory(h, ctypes.c_void_p(int(us.Buffer)), buf, us.Length, ctypes.byref(lpNumberOfBytesRead)):
return ""
return "".join(buf)
def split_commandline(self, cmdline):
if not cmdline:
return []
argc = ctypes.c_int(0)
argv = self.shell32.CommandLineToArgvW(cmdline, ctypes.byref(argc))
if not argv:
return []
args = [argv[i] for i in range(argc.value)]
self.kernel32.LocalFree(ctypes.cast(argv, ctypes.c_void_p))
return args
def extract_paths_from_arg(self, arg):
patterns = [
r'([A-Za-z]:\\[^"\']+)',
r'(\\\\[^"\']+)',
r'(\.\\[^"\']+)',
r'(\./[^"\']+)',
r'([A-Za-z]:/[^"\']+)',
r'([^\s]*\\[^\s]+)'
]
found = []
for p in patterns:
for m in re.finditer(p, arg):
val = m.group(1).strip('"').strip("'")
found.append(val)
return list(dict.fromkeys(found))
####################################################################################################
def protect_file_thread(self):
hDir = self.kernel32.CreateFileW(self.path_user, 0x0001, 0x00000007, None, 3, 0x02000000, None)
buffer = ctypes.create_string_buffer(65536)
while self.pyas_config.get("document_switch", False):
try:
bytes_returned = ctypes.wintypes.DWORD()
res = self.kernel32.ReadDirectoryChangesW(hDir, buffer, ctypes.sizeof(buffer), True,
0x0000001F, ctypes.byref(bytes_returned), None, None)
if not res:
continue
notify = FILE_NOTIFY_INFORMATION.from_buffer_copy(buffer)
raw_filename = notify.FileName[:notify.FileNameLength // 2]
if not raw_filename:
continue
file_path = self.norm_path(os.path.join(self.path_user, raw_filename), must_exist=True)
if file_path and notify.Action in [2, 3, 4] and not self.is_in_whitelist(file_path):
suffix = self.pyas_config.get("suffix", [".com", ".dll", ".drv", ".exe", ".ocx", ".scr", ".sys", ".mui"])
ext = os.path.splitext(file_path)[-1].lower()
if ext not in suffix:
continue
state = self.scan_engine(file_path)
if state:
self.add_to_quarantine([file_path])
self.send_message(f"檔案防護 | 靜態掃描攔截 | None | None | {file_path}", "notify", True)
self.start_daemon_thread(self.cloud_check, file_path)
except Exception as e:
self.send_message(e, "warn", False)
self.kernel32.CloseHandle(hDir)
####################################################################################################
def protect_net_thread(self):
while self.pyas_config.get("network_switch", False):
try:
time.sleep(0.5)
conns = self.get_connections_list()
if not hasattr(self, "exist_connections") or self.exist_connections is None:
self.exist_connections = set()
if conns is None:
conns = set()
for key in conns - self.exist_connections:
self.handle_new_connection(key)
self.exist_connections = conns
except Exception as e:
self.send_message(e, "warn", False)
self.exist_connections = set()
def get_connections_list(self):
try:
connections = set()
size = ctypes.wintypes.DWORD()
ret = self.iphlpapi.GetExtendedTcpTable(None, ctypes.byref(size), True, 2, 5, 0)
if ret != 122:
raise ctypes.WinError(ret)
buf = ctypes.create_string_buffer(size.value)
ret = self.iphlpapi.GetExtendedTcpTable(buf, ctypes.byref(size), True, 2, 5, 0)
if ret != 0:
raise ctypes.WinError(ret)
num_entries = ctypes.cast(buf, ctypes.POINTER(ctypes.wintypes.DWORD)).contents.value
row_size = ctypes.sizeof(MIB_TCPROW_OWNER_PID)
offset = ctypes.sizeof(ctypes.wintypes.DWORD)
for i in range(num_entries):
entry_addr = ctypes.addressof(buf) + offset + i * row_size
row = MIB_TCPROW_OWNER_PID.from_address(entry_addr)
connections.add((row.dwOwningPid, row.dwRemoteAddr, row.dwRemotePort))
return connections
except Exception as e:
self.send_message(e, "warn", False)
return set()
def handle_new_connection(self, key):
pid, remote_addr, remote_port = key
try:
h = self.kernel32.OpenProcess(0x1F0FFF, False, pid)
if not h:
return
remote_ip = f"{remote_addr & 0xFF}.{(remote_addr >> 8) & 0xFF}.{(remote_addr >> 16) & 0xFF}.{(remote_addr >> 24) & 0xFF}"
file_path = self.norm_path(self.get_process_file(h))
if file_path and not self.is_in_whitelist(file_path):
if hasattr(self.rule, "network") and remote_ip in self.rule.network:
self.kernel32.TerminateProcess(h, 0)
self.send_message(f"網路防護 | 規則列表攔截 | {pid} | {file_path} | {remote_ip}", "notify", True)
self.kernel32.CloseHandle(h)
except Exception as e:
self.send_message(e, "warn", False)
####################################################################################################
def protect_system_thread(self):
while self.pyas_config.get("system_switch", False):
try:
self.repair_system_mbr()
self.repair_system_restrict()
self.repair_system_file_type()
self.repair_system_file_icon()
self.repair_system_image()
self.check_process_survival()
time.sleep(0.5)
except Exception as e:
self.send_message(e, "warn", False)
####################################################################################################
def install_system_driver(self):
try:
service_name = "PYAS_Driver"
bin_path = self.path_drivers
cmd = [
"sc", "create", service_name,
f"binPath={bin_path}",
"type=kernel",
"start=demand",
"error=normal",
"depend=FltMgr",
"group=\"FSFilter Activity Monitor\""]
subprocess.run(cmd, stdout=subprocess.DEVNULL, stderr=subprocess.DEVNULL, shell=True)
key_path = r"SYSTEM\CurrentControlSet\Services\PYAS_Driver\Instances"
with winreg.CreateKey(winreg.HKEY_LOCAL_MACHINE, key_path) as key:
winreg.SetValueEx(key, "DefaultInstance", 0, winreg.REG_SZ, "PYAS Instance")
with winreg.CreateKey(winreg.HKEY_LOCAL_MACHINE, rf"{key_path}\PYAS Instance") as key:
winreg.SetValueEx(key, "Altitude", 0, winreg.REG_SZ, "320000")
winreg.SetValueEx(key, "Flags", 0, winreg.REG_DWORD, 0)
subprocess.run(["sc", "start", service_name], stdout=subprocess.DEVNULL, stderr=subprocess.DEVNULL, shell=True)
return True
except Exception as e:
self.send_message(e, "warn", False)
return False
def stop_system_driver(self):
try:
service_name = "PYAS_Driver"
subprocess.run(["sc", "stop", service_name], stdout=subprocess.DEVNULL, stderr=subprocess.DEVNULL, shell=True)
subprocess.run(["sc", "delete", service_name], stdout=subprocess.DEVNULL, stderr=subprocess.DEVNULL, shell=True)
return True
except Exception:
return False
####################################################################################################
def pipe_server_thread(self):
try:
port_name = "\\PYAS_Output_Pipe"
h_port = ctypes.wintypes.HANDLE()
while self.pyas_config.get("driver_switch", False):
hr = self.fltlib.FilterConnectCommunicationPort(port_name, 0, None, 0, None, ctypes.byref(h_port))
if hr == 0:
message = PYAS_FULL_MESSAGE()
while self.pyas_config.get("driver_switch", False):
hr_get = self.fltlib.FilterGetMessage(h_port, ctypes.byref(message), ctypes.sizeof(PYAS_FULL_MESSAGE), None)
if hr_get == 0:
code = message.Data.MessageCode
pid = message.Data.ProcessId
raw_path = self.get_exe_info(pid)[1]
target = message.Data.Path
rule_name = "Unknown"
if code == 2001:
rule_name = "FILE_BLOCK"
elif code == 3001:
rule_name = "REG_BLOCK"
elif code == 4001:
rule_name = "BOOT_BLOCK"
elif code == 5001:
rule_name = "RANSOM_BLOCK"
elif code == 6001:
rule_name = "INJECT_BLOCK"
display_rule = self.block_replace.get(rule_name, rule_name)
self.send_message(f"驅動防護 | {display_rule} | {pid} | {raw_path} | {target}", "notify", True)
if code in [4001, 5001]:
try:
h = self.kernel32.OpenProcess(0x1F0FFF, False, pid)
if h:
self.kernel32.TerminateProcess(h, 0)
self.kernel32.CloseHandle(h)
except:
pass
else:
break
self.kernel32.CloseHandle(h_port)
time.sleep(0.2)
except Exception as e:
self.send_message(e, "warn", False)
####################################################################################################
if __name__ == "__main__":
QGuiApplication.setHighDpiScaleFactorRoundingPolicy(Qt.HighDpiScaleFactorRoundingPolicy.PassThrough)
app = QApplication(sys.argv)
MainWindow_Controller()
sys.exit(app.exec())