1669 lines
73 KiB
Python
1669 lines
73 KiB
Python
import os, gc, re, sys, time, json, base64
|
|
import ctypes, ctypes.wintypes, threading
|
|
import requests, webbrowser, winreg, shutil
|
|
import traceback, hashlib, msvcrt, pyperclip
|
|
|
|
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 PROCESSENTRY32(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),
|
|
("dwFlags", ctypes.wintypes.DWORD),
|
|
("szExeFile", ctypes.wintypes.CHAR * 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 MainWindow_Controller(QMainWindow):
|
|
scan_progress_signal = Signal(str)
|
|
scan_add_virus_signal = Signal(str)
|
|
scan_result_signal = Signal(str)
|
|
scan_reset_signal = Signal()
|
|
progress_title_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.backup_mbr()
|
|
self.relock_file()
|
|
self.init_whitelist()
|
|
self.init_thread()
|
|
self.init_connect()
|
|
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.model = model_scanner()
|
|
self.model.load_path(self.path_models)
|
|
self.rule = rule_scanner()
|
|
self.rule.load_path(self.path_rules)
|
|
|
|
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.4",
|
|
"product": "00000-00000-00000-00000-00000",
|
|
"language": "english_switch",
|
|
"theme": "white_switch",
|
|
"sensitive": 95,
|
|
"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 = {
|
|
"BOOT_BLOCK": "引導行為攔截",
|
|
"REG_BLOCK": "註冊表行為攔截",
|
|
"FILE_BLOCK": "檔案行為攔截",
|
|
"RANSOM_BLOCK": "勒索行為攔截",
|
|
"CLR_BLOCK": "托管行為攔截",
|
|
"SHELLCODE_BLOCK": "殼碼行為攔截",
|
|
"THREAD_BLOCK": "線程行為攔截",
|
|
"INJECT_BLOCK": "注入行為攔截",
|
|
"PROC_BLOCK": "進程行為攔截",
|
|
}
|
|
|
|
####################################################################################################
|
|
|
|
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"]:
|
|
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
|
|
|
|
####################################################################################################
|
|
|
|
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_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()
|
|
|
|
def create_shadow(self):
|
|
shadow = QGraphicsDropShadowEffect(self)
|
|
shadow.setOffset(0, 0)
|
|
shadow.setBlurRadius(20)
|
|
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.progress_title_signal.connect(self.slot_progress_title)
|
|
|
|
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"):
|
|
param = ""
|
|
if self.args_pyas:
|
|
param = self.args_pyas[0].replace("/", "-")
|
|
if "-h" not in param:
|
|
self.show_button()
|
|
self.apply_settings()
|
|
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_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.restart_button()
|
|
|
|
def restart_button(self):
|
|
self.save_config(self.file_config, self.pyas_config)
|
|
self.minimize_button()
|
|
while self.isVisible():
|
|
QApplication.processEvents()
|
|
os.execl(self.python, self.python, *sys.argv)
|
|
|
|
def singleton_mutex(self, name):
|
|
mutex = self.kernel32.CreateMutexW(None, False, name)
|
|
if self.kernel32.GetLastError() == 183:
|
|
return False
|
|
return True
|
|
|
|
####################################################################################################
|
|
|
|
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:
|
|
message = self.trans(self.pyas_config["language"], 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 False
|
|
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):
|
|
if isinstance(path, (list, tuple, set)):
|
|
t = type(path)
|
|
return t(self.norm_path(p) for p in path)
|
|
if isinstance(path, str):
|
|
return os.path.normpath(os.path.abspath(path))
|
|
return path
|
|
|
|
####################################################################################################
|
|
|
|
@Slot()
|
|
def slot_scan_reset(self):
|
|
self.widgets["virus_list"].clear()
|
|
self.widgets["solve_button"].hide()
|
|
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):
|
|
self.widgets["progress_text"].setText(msg)
|
|
count = len(self.virus_results)
|
|
self.widgets["solve_button"].setVisible(count > 0)
|
|
self.widgets["stop_button"].hide()
|
|
self.widgets["method_button"].show()
|
|
self.send_message(msg, "notify", True)
|
|
|
|
####################################################################################################
|
|
|
|
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()
|
|
self.progress_title_signal.emit(self.trans(self.pyas_config["language"], "正在掃描"))
|
|
self.scan_progress_signal.emit(self.trans(self.pyas_config["language"], "正在初始化中"))
|
|
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
|
|
state = self.scan_engine(norm_path)
|
|
if state:
|
|
self.virus_results.append(norm_path)
|
|
self.scan_add_virus_signal.emit(f"[{state}] > {norm_path}")
|
|
self.scan_progress_signal.emit(norm_path)
|
|
except Exception as e:
|
|
self.send_message(e, "warn", False)
|
|
finally:
|
|
elapsed = int(time.time() - self.scan_start)
|
|
count = len(self.virus_results)
|
|
result = (
|
|
f"發現 {count} 個病毒,耗時 {elapsed} 秒,共掃描 {self.scan_count} 檔案"
|
|
if count else f"未發現病毒,耗時 {elapsed} 秒,共掃描 {self.scan_count} 檔案")
|
|
self.progress_title_signal.emit(self.trans(self.pyas_config["language"], "病毒掃描"))
|
|
self.scan_result_signal.emit(self.trans(self.pyas_config["language"], 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:
|
|
label, level = self.model.model_scan(file_path)
|
|
if label and level >= self.pyas_config.get("sensitive", 95):
|
|
return f"{label}.{level}"
|
|
|
|
if self.pyas_config.get("extension_switch", False):
|
|
label, level = self.rule.yara_scan(file_path)
|
|
if label and level:
|
|
return f"{label}.{level}"
|
|
except Exception as e:
|
|
self.send_message(e, "warn", False)
|
|
return None
|
|
|
|
####################################################################################################
|
|
|
|
def solve_button(self):
|
|
deleted = 0
|
|
virus_list = self.widgets["virus_list"]
|
|
for i in reversed(range(virus_list.count())):
|
|
item = virus_list.item(i)
|
|
if item.checkState() == Qt.Checked:
|
|
try:
|
|
file_path = self.norm_path(item.text().split(">")[1].strip())
|
|
os.remove(file_path)
|
|
deleted += 1
|
|
except Exception as e:
|
|
self.send_message(e, "warn", False)
|
|
virus_list.takeItem(i)
|
|
self.widgets["solve_button"].hide()
|
|
msg = f"已清理 {deleted} 個病毒檔案" if deleted else "無檔案被刪除"
|
|
self.widgets["progress_text"].setText(self.trans(self.pyas_config["language"], msg))
|
|
|
|
def stop_button(self):
|
|
self.scan_running = False
|
|
self.widgets["stop_button"].hide()
|
|
|
|
####################################################################################################
|
|
|
|
def list_process(self):
|
|
pe = self.get_process_entry()
|
|
snapshot = self.kernel32.CreateToolhelp32Snapshot(0x00000002, 0)
|
|
result, success = [], self.kernel32.Process32First(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:
|
|
raw = bytes(pe.szExeFile).split(b"\x00", 1)[0]
|
|
if raw:
|
|
try:
|
|
name = raw.decode("mbcs", errors="replace") or None
|
|
except Exception:
|
|
name = None
|
|
result.append((pid, name, file_path))
|
|
success = self.kernel32.Process32Next(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
|
|
pid = self.proc_pid_map[idx.row()]
|
|
menu = QMenu()
|
|
kill_action = menu.addAction("結束進程")
|
|
if menu.exec(widget.viewport().mapToGlobal(pos)) == kill_action:
|
|
self.kill_process(pid)
|
|
|
|
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 hasattr(self, "file_pyas") and file_path == getattr(self, "file_pyas", None):
|
|
self.close()
|
|
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 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, process_name = self.get_window_info(self.user32.GetForegroundWindow())
|
|
if process_name and not any(self.window_rule_match(item, process_name, class_name, title) for item in self.pass_windows):
|
|
if self.send_message(f"您確定要攔截 {process_name} ({title}) 嗎?", "quest", True):
|
|
if self.add_window_rule(self.pyas_config["block_list"], process_name, class_name, title):
|
|
self.send_message(f"成功增加到彈窗攔截: {process_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, process_name = self.get_window_info(self.user32.GetForegroundWindow())
|
|
if process_name and not any(self.window_rule_match(item, process_name, class_name, title) for item in self.pass_windows):
|
|
if self.send_message(f"您確定要取消攔截 {process_name} ({title}) 嗎?", "quest", True):
|
|
if self.remove_window_rule(self.pyas_config["block_list"], process_name, class_name, title):
|
|
self.send_message(f"成功取消彈窗攔截: {process_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, process_name, class_name, title):
|
|
for k, v in zip(["exe", "class", "title"], [process_name, class_name, title]):
|
|
if item.get(k, "") and item.get(k, "") != v:
|
|
return False
|
|
return True
|
|
|
|
def add_window_rule(self, rule_list, process_name, class_name, title):
|
|
if not any(self.window_rule_match(item, process_name, class_name, title) for item in rule_list):
|
|
rule_list.append({"exe": process_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, process_name, class_name, title):
|
|
before = len(rule_list)
|
|
rule_list[:] = [item for item in rule_list if not self.window_rule_match(item, process_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, process_name = self.get_window_info(hWnd)
|
|
if (not any(self.window_rule_match(item, process_name, class_name, title) for item in self.pass_windows) and
|
|
any(self.window_rule_match(item, process_name, class_name, title) for item in rules)):
|
|
for msg in [0x0010, 0x0002, 0x0012, 0x0112]:
|
|
self.user32.SendMessageW(hWnd, msg, 0xF060, 0)
|
|
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))
|
|
process_name = ""
|
|
try:
|
|
process_name = self.get_process_name_by_pid(pid.value)
|
|
except Exception:
|
|
pass
|
|
return str(title.value), str(class_name.value), process_name
|
|
|
|
def get_process_name_by_pid(self, pid):
|
|
snapshot = self.kernel32.CreateToolhelp32Snapshot(0x00000002, 0)
|
|
entry = PROCESSENTRY32()
|
|
entry.dwSize = ctypes.sizeof(PROCESSENTRY32)
|
|
name = ""
|
|
if self.kernel32.Process32First(snapshot, ctypes.byref(entry)):
|
|
while True:
|
|
if entry.th32ProcessID == pid:
|
|
name = entry.szExeFile.decode("mbcs").rstrip("\x00")
|
|
break
|
|
if not self.kernel32.Process32Next(snapshot, ctypes.byref(entry)):
|
|
break
|
|
self.kernel32.CloseHandle(snapshot)
|
|
return name
|
|
|
|
####################################################################################################
|
|
|
|
def whitelist_button(self):
|
|
self.config_list("white_list")
|
|
files = self.send_message("選擇檔案", "files", True)
|
|
if files:
|
|
if self.send_message("您確定要增加到白名單嗎?", "quest"):
|
|
n = self.manage_named_list("white_list", files, action="add", with_hash=True)
|
|
self.send_message(f"成功增加到白名單,共 {n} 個檔案", "info")
|
|
|
|
def whitelist_button_2(self):
|
|
self.config_list("white_list")
|
|
files = self.send_message("選擇檔案", "files", True)
|
|
if files:
|
|
if self.send_message("您確定要移除白名單嗎?", "quest"):
|
|
n = self.manage_named_list("white_list", files, action="remove", with_hash=True)
|
|
self.send_message(f"成功移除白名單,共 {n} 個檔案", "info")
|
|
|
|
def quarantine_button(self):
|
|
self.config_list("quarantine")
|
|
files = self.send_message("選擇檔案", "files", True)
|
|
if files:
|
|
if self.send_message("您確定要增加到隔離區嗎?", "quest"):
|
|
n = self.manage_named_list("quarantine", files, action="add", with_hash=True, lock_func=self.lock_file)
|
|
if n > 0:
|
|
self.send_message(f"成功增加到隔離區,共 {n} 個檔案", "info")
|
|
|
|
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:
|
|
if 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")
|
|
|
|
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):
|
|
norm_path = self.norm_path(file_path)
|
|
file_hash = self.calc_file_hash(norm_path)
|
|
return any(item["file"] == norm_path and item["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):
|
|
target_list = self.config_list(list_key)
|
|
files = files or []
|
|
norm_paths = self.norm_path(files)
|
|
if isinstance(norm_paths, str):
|
|
norm_paths = [norm_paths]
|
|
|
|
n = 0
|
|
if action == "add":
|
|
for path in norm_paths:
|
|
QApplication.processEvents()
|
|
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 target_list):
|
|
if lock_func:
|
|
try:
|
|
lock_func(path, True)
|
|
except Exception as e:
|
|
self.send_message(e, "warn", False)
|
|
target_list.append({"file": path, "hash": file_hash})
|
|
n += 1
|
|
|
|
elif action == "remove":
|
|
remove_list = [item for item in target_list if item["file"] in norm_paths]
|
|
for item in remove_list:
|
|
try:
|
|
if lock_func:
|
|
lock_func(item["file"], False)
|
|
target_list.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://github.com/87owo/PYAS")
|
|
return True
|
|
except Exception as e:
|
|
self.send_message(e, "warn", False)
|
|
return False
|
|
|
|
def update_button(self):
|
|
try:
|
|
webbrowser.open("https://github.com/87owo/PYAS/releases")
|
|
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 get_process_list(self):
|
|
exist_process, pe = set(), self.get_process_entry()
|
|
hSnapshot = self.kernel32.CreateToolhelp32Snapshot(0x00000002, 0)
|
|
if self.kernel32.Process32First(hSnapshot, ctypes.byref(pe)):
|
|
while True:
|
|
exist_process.add(pe.th32ProcessID)
|
|
if not self.kernel32.Process32Next(hSnapshot, ctypes.byref(pe)):
|
|
break
|
|
self.kernel32.CloseHandle(hSnapshot)
|
|
return exist_process
|
|
|
|
def handle_new_process(self, pid):
|
|
try:
|
|
h = self.kernel32.OpenProcess(0x1F0FFF, False, pid)
|
|
if not h:
|
|
return
|
|
file_path = self.norm_path(self.get_process_file(h))
|
|
if file_path and os.path.exists(file_path) and not self.is_in_whitelist(file_path):
|
|
self.suspend_process(h, True)
|
|
state = self.scan_engine(file_path)
|
|
if state:
|
|
self.kernel32.TerminateProcess(h, 0)
|
|
self.send_message(f"進程防護 | 靜態掃描攔截 | {pid} | {file_path} | None", "notify", True)
|
|
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_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 = PROCESSENTRY32()
|
|
pe.dwSize = ctypes.sizeof(PROCESSENTRY32)
|
|
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 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 res:
|
|
notify = FILE_NOTIFY_INFORMATION.from_buffer_copy(buffer)
|
|
raw_filename = notify.FileName[:notify.FileNameLength // 2]
|
|
file_path = self.norm_path(os.path.join(self.path_user, raw_filename))
|
|
|
|
if notify.Action in [2, 3, 4] and not self.is_in_whitelist(file_path):
|
|
state = self.scan_engine(file_path)
|
|
if state:
|
|
self.add_to_quarantine([file_path])
|
|
self.send_message(f"檔案防護 | 靜態掃描攔截 | None | {file_path} | None", "notify", True)
|
|
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_process = self.kernel32.OpenProcess(0x1F0FFF, False, pid)
|
|
if not h_process:
|
|
return
|
|
file_path = self.norm_path(self.get_process_file(h_process))
|
|
remote_ip = f"{remote_addr & 0xFF}.{(remote_addr >> 8) & 0xFF}.{(remote_addr >> 16) & 0xFF}.{(remote_addr >> 24) & 0xFF}"
|
|
|
|
if file_path and os.path.exists(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_process, 0)
|
|
self.send_message(f"網路防護 | 規則列表攔截 | {pid} | {file_path} | {remote_ip}", "notify", True)
|
|
self.kernel32.CloseHandle(h_process)
|
|
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()
|
|
time.sleep(0.2)
|
|
except Exception as e:
|
|
self.send_message(e, "warn", False)
|
|
|
|
####################################################################################################
|
|
|
|
def install_system_driver(self):
|
|
service_name = "PYAS_Driver"
|
|
scm = self.advapi32.OpenSCManagerW(None, None, 0xF003F)
|
|
if not scm:
|
|
return False
|
|
|
|
svc = self.advapi32.CreateServiceW(scm, service_name, service_name, 0xF01FF,
|
|
0x00000001, 0x00000003, 0x00000001, self.path_drivers, None, None, None, None, None)
|
|
if not svc:
|
|
svc = self.advapi32.OpenServiceW(scm, service_name, 0xF01FF)
|
|
if not svc:
|
|
self.advapi32.CloseServiceHandle(scm)
|
|
return False
|
|
|
|
res = self.advapi32.StartServiceW(svc,0,None)
|
|
self.advapi32.CloseServiceHandle(svc)
|
|
self.advapi32.CloseServiceHandle(scm)
|
|
return bool(res)
|
|
|
|
def stop_system_driver(self):
|
|
service_name = "PYAS_Driver"
|
|
scm = self.advapi32.OpenSCManagerW(None, None, 0xF003F)
|
|
if not scm:
|
|
return False
|
|
|
|
svc = self.advapi32.OpenServiceW(scm, service_name, 0xF01FF)
|
|
if not svc:
|
|
self.advapi32.CloseServiceHandle(scm)
|
|
return False
|
|
|
|
status = SERVICE_STATUS()
|
|
self.advapi32.ControlService(svc, 0x00000001, ctypes.byref(status))
|
|
self.advapi32.DeleteService(svc)
|
|
self.advapi32.CloseServiceHandle(svc)
|
|
self.advapi32.CloseServiceHandle(scm)
|
|
return True
|
|
|
|
####################################################################################################
|
|
|
|
def pipe_server_thread(self):
|
|
pipe_path = r"\\.\pipe\PYAS_Output_Pipe"
|
|
PIPE_BUF_SIZE = 65536
|
|
|
|
while self.pyas_config.get("driver_switch", False):
|
|
try:
|
|
hPipe = self.kernel32.CreateNamedPipeW(pipe_path, 0x00000001, 0x00000004 | 0x00000002, 1, PIPE_BUF_SIZE, PIPE_BUF_SIZE, 0, None)
|
|
if hPipe == ctypes.c_void_p(-1).value:
|
|
time.sleep(0.2)
|
|
continue
|
|
|
|
while self.pyas_config.get("driver_switch", False):
|
|
if not self.kernel32.ConnectNamedPipe(hPipe, None) and self.kernel32.GetLastError() != 535:
|
|
time.sleep(0.2)
|
|
continue
|
|
|
|
buf = ctypes.create_string_buffer(PIPE_BUF_SIZE)
|
|
bytes_read = ctypes.c_ulong(0)
|
|
if not self.kernel32.ReadFile(hPipe, buf, PIPE_BUF_SIZE, ctypes.byref(bytes_read), None) or bytes_read.value == 0:
|
|
self.kernel32.DisconnectNamedPipe(hPipe)
|
|
time.sleep(0.2)
|
|
continue
|
|
|
|
msg = buf.raw[:bytes_read.value].decode("utf-8", errors="ignore")
|
|
parts = [p.strip() for p in msg.split("|", 3)]
|
|
if len(parts) == 4:
|
|
rules, pid, raw_path, target = parts
|
|
for old, new in self.block_replace.items():
|
|
rules = rules.replace(old, new)
|
|
self.send_message(f"驅動防護 | {rules} | {pid} | {raw_path} | {target}", "notify", True)
|
|
|
|
self.kernel32.DisconnectNamedPipe(hPipe)
|
|
self.kernel32.CloseHandle(hPipe)
|
|
except Exception as e:
|
|
self.send_message(e, "warn", False)
|
|
time.sleep(0.5)
|
|
|
|
####################################################################################################
|
|
|
|
if __name__ == "__main__":
|
|
QGuiApplication.setHighDpiScaleFactorRoundingPolicy(Qt.HighDpiScaleFactorRoundingPolicy.PassThrough)
|
|
app = QApplication(sys.argv)
|
|
MainWindow_Controller()
|
|
sys.exit(app.exec())
|