Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
05e952c5ee | ||
|
|
8a0209a99a | ||
|
|
65bc5c7c2a | ||
|
|
07c64042be | ||
|
|
22c04063cf | ||
|
|
68a7ab657a | ||
|
|
630dc67058 | ||
|
|
591507512c | ||
|
|
ff49cafc74 | ||
|
|
b077ba06bd | ||
|
|
a2b66aec64 | ||
|
|
4b692b0c87 | ||
|
|
99d1b20d0f | ||
|
|
fae5edb57b | ||
|
|
6db91483a0 | ||
|
|
90383f3d09 | ||
|
|
966a5a3592 |
No files matched your search
@@ -191,7 +191,7 @@ def train_process(X, y, feature_names):
|
||||
n_neg = (y_train == 0).sum()
|
||||
|
||||
base_weight = n_neg / n_pos if n_pos > 0 else 1.0
|
||||
weight_ratio_target = 0.005
|
||||
weight_ratio_target = 0.01
|
||||
final_pos_weight = base_weight * weight_ratio_target
|
||||
|
||||
print(f"[*] Sample distribution: Safe={n_neg}, Malware={n_pos}")
|
||||
|
||||
@@ -366,7 +366,7 @@ class _MainMixin:
|
||||
self.lock_io = threading.RLock()
|
||||
|
||||
self.pyas_default = {
|
||||
"version": "3.6.6",
|
||||
"version": "3.6.9",
|
||||
"api_host": "https://pyas-security.com/",
|
||||
"api_key": "fBRZxYS1UxykM-qzNOlKOEl63WILzlvgNMn6QfsG6FXCAAIktCrOPTAfY5_hEyuZ",
|
||||
"suffix": [".exe", ".dll", ".sys", ".ocx", ".scr", ".efi", ".acm", ".ax", ".cpl", ".drv", ".com", ".mui", ".pyd", ".wfx", ".api", ".awx", ".rll", ".winmd"],
|
||||
@@ -461,6 +461,18 @@ class _MainMixin:
|
||||
self.pyas_config.update(data)
|
||||
|
||||
self.pyas_config["version"] = self.pyas_default["version"]
|
||||
whitelist_metadata_updated = False
|
||||
for item in self.pyas_config.get("white_list", []):
|
||||
if not isinstance(item, dict) or not item.get("file") or "is_dir" in item:
|
||||
continue
|
||||
|
||||
item_path = self.norm_path(item["file"], must_exist=False)
|
||||
if item_path and os.path.exists(item_path):
|
||||
item["is_dir"] = os.path.isdir(item_path)
|
||||
whitelist_metadata_updated = True
|
||||
|
||||
if whitelist_metadata_updated:
|
||||
self.save_config()
|
||||
except Exception as e:
|
||||
self.pyas_config = copy.deepcopy(self.pyas_default)
|
||||
self.write_log("WARN", "load_config", detail=str(e), success=False)
|
||||
@@ -513,7 +525,7 @@ class _MainMixin:
|
||||
elif key == "context_switch":
|
||||
self.register_context_menu(True)
|
||||
elif key == "autostart_switch":
|
||||
self.manage_autostart(True)
|
||||
success = self.manage_autostart(True)
|
||||
|
||||
except Exception as e:
|
||||
self.write_log("WARN", "Feature Start", detail=str(e), success=False)
|
||||
@@ -525,7 +537,7 @@ class _MainMixin:
|
||||
elif key == "context_switch":
|
||||
self.register_context_menu(False)
|
||||
elif key == "autostart_switch":
|
||||
self.manage_autostart(False)
|
||||
success = self.manage_autostart(False)
|
||||
|
||||
elif key == "document_switch":
|
||||
self._cancel_pending_file_scans()
|
||||
@@ -1110,9 +1122,17 @@ class _MainMixin:
|
||||
elif full_path.startswith("HKCC"):
|
||||
full_path = full_path.replace("HKCC", "HKEY_CURRENT_CONFIG", 1)
|
||||
|
||||
subprocess.run(["taskkill", "/F", "/IM", "regedit.exe"], stdout=subprocess.DEVNULL, stderr=subprocess.DEVNULL, creationflags=0x08000000)
|
||||
self._run_windows_tool(
|
||||
("taskkill.exe",),
|
||||
["/F", "/IM", "regedit.exe"],
|
||||
stdout=subprocess.DEVNULL,
|
||||
stderr=subprocess.DEVNULL
|
||||
)
|
||||
self._reg_write(winreg.HKEY_CURRENT_USER, r"Software\Microsoft\Windows\CurrentVersion\Applets\Regedit", "LastKey", winreg.REG_SZ, full_path)
|
||||
subprocess.Popen("regedit.exe")
|
||||
regedit = self._find_windows_tool("regedit.exe")
|
||||
if not regedit:
|
||||
return False
|
||||
subprocess.Popen([regedit])
|
||||
|
||||
return True
|
||||
except Exception:
|
||||
@@ -1122,7 +1142,10 @@ class _MainMixin:
|
||||
if os.path.exists(expanded_path):
|
||||
try:
|
||||
clean_path = os.path.normpath(expanded_path)
|
||||
subprocess.Popen(f'explorer /select,"{clean_path}"')
|
||||
explorer = self._find_windows_tool("explorer.exe")
|
||||
if not explorer:
|
||||
return False
|
||||
subprocess.Popen([explorer, "/select,", clean_path])
|
||||
return True
|
||||
|
||||
except Exception:
|
||||
|
||||
+34
-58
@@ -1,6 +1,6 @@
|
||||
#define AppId "{{a7d7bac3-93b8-4630-8308-c7a56bf7fdf4}"
|
||||
#define AppName "PYAS"
|
||||
#define AppVersion "3.6.5.0"
|
||||
#define AppVersion "3.6.9.0"
|
||||
#define AppPublisher "PYAS Security"
|
||||
#define AppURL "https://github.com/87owo/PYAS"
|
||||
#define AppExeName "PYAS.exe"
|
||||
@@ -52,9 +52,8 @@ Name: "arabic"; MessagesFile: "SetupResources\Arabic.isl"
|
||||
Name: "russian"; MessagesFile: "SetupResources\Russian.isl"
|
||||
Name: "slovenian"; MessagesFile: "SetupResources\Slovenian.isl"
|
||||
|
||||
[Tasks]
|
||||
Name: "desktopicon"; Description: "{cm:CreateDesktopIcon}"; GroupDescription: "{cm:AdditionalIcons}"
|
||||
Name: "autostart"; Description: "{cm:AutoStartTask}"; GroupDescription: "{cm:AdditionalIcons}"
|
||||
[Tasks]
|
||||
Name: "desktopicon"; Description: "{cm:CreateDesktopIcon}"; GroupDescription: "{cm:AdditionalIcons}"
|
||||
|
||||
[Files]
|
||||
Source: "Redist\VC_redist.x64.exe"; DestDir: "{tmp}\PYAS_Redist"; Flags: deleteafterinstall ignoreversion; AfterInstall: EnsureVCRedist
|
||||
@@ -65,9 +64,10 @@ Source: "Payload\License\*"; DestDir: "{app}\License"; Flags: ignoreversion recu
|
||||
Source: "Payload\PYAS.exe"; DestDir: "{app}"; Flags: ignoreversion; AfterInstall: FinalizeDriverRemoval
|
||||
Source: "Payload\Plugins\*"; DestDir: "{app}\Plugins"; Flags: ignoreversion recursesubdirs createallsubdirs
|
||||
|
||||
[Registry]
|
||||
Root: HKCU; Subkey: "Software\Classes\*\shell\PYAS_Scan"; Flags: uninsdeletekey dontcreatekey
|
||||
Root: HKCU; Subkey: "Software\Classes\Directory\shell\PYAS_Scan"; Flags: uninsdeletekey dontcreatekey
|
||||
[Registry]
|
||||
Root: HKCU; Subkey: "Software\Classes\*\shell\PYAS_Scan"; Flags: uninsdeletekey dontcreatekey
|
||||
Root: HKCU; Subkey: "Software\Classes\Directory\shell\PYAS_Scan"; Flags: uninsdeletekey dontcreatekey
|
||||
Root: HKCU; Subkey: "Software\Microsoft\Windows\CurrentVersion\Run"; ValueName: "PYAS_Security"; Flags: uninsdeletevalue dontcreatekey
|
||||
|
||||
[UninstallDelete]
|
||||
Type: filesandordirs; Name: "{commonappdata}\PYAS"
|
||||
@@ -87,9 +87,6 @@ chinesetraditional.InstallingVCRuntime=正在安裝 Microsoft Visual C++ 執行
|
||||
english.InstallingWebView2Runtime=Installing Microsoft Edge WebView2 Runtime...
|
||||
chinesesimplified.InstallingWebView2Runtime=正在安装 Microsoft Edge WebView2 Runtime...
|
||||
chinesetraditional.InstallingWebView2Runtime=正在安裝 Microsoft Edge WebView2 Runtime...
|
||||
english.AutoStartTask=Run PYAS automatically at system startup
|
||||
chinesesimplified.AutoStartTask=开机时自动运行 PYAS
|
||||
chinesetraditional.AutoStartTask=開機時自動執行 PYAS
|
||||
english.Dependencies=Dependencies:
|
||||
chinesesimplified.Dependencies=运行环境:
|
||||
chinesetraditional.Dependencies=執行環境:
|
||||
@@ -277,63 +274,42 @@ function NeedRestart: Boolean;
|
||||
begin
|
||||
Result := DependencyRestartRequired;
|
||||
end;
|
||||
procedure TryCreateStartupTask;
|
||||
var
|
||||
ResultCode: Integer;
|
||||
PSExe: string;
|
||||
PSCommand: string;
|
||||
AppPath: string;
|
||||
begin
|
||||
AppPath := ExpandConstant('{app}\{#AppExeName}');
|
||||
PSExe := ExpandConstant('{sys}\WindowsPowerShell\v1.0\powershell.exe');
|
||||
PSCommand := '-NoProfile -NonInteractive -ExecutionPolicy Bypass -WindowStyle Hidden -Command ' +
|
||||
'$Action = New-ScheduledTaskAction -Execute ''' + AppPath + ''' -Argument ''-hide''; ' +
|
||||
'$Trigger = New-ScheduledTaskTrigger -AtLogOn; ' +
|
||||
'$Settings = New-ScheduledTaskSettingsSet -AllowStartIfOnBatteries -DontStopIfGoingOnBatteries; ' +
|
||||
'Register-ScheduledTask -TaskName ''PYAS_Security_ATS'' -Action $Action -Trigger $Trigger -Settings $Settings -RunLevel Highest -Force';
|
||||
if not Exec(PSExe, PSCommand, '', SW_HIDE, ewWaitUntilTerminated, ResultCode) then
|
||||
begin
|
||||
Log(Format('Failed to create scheduled task, Exec error code: %d', [ResultCode]));
|
||||
end
|
||||
else if ResultCode <> 0 then
|
||||
begin
|
||||
Log(Format('powershell returned exit code %d while creating the scheduled task', [ResultCode]));
|
||||
end;
|
||||
end;
|
||||
|
||||
procedure TryDeleteStartupTask;
|
||||
var
|
||||
ResultCode: Integer;
|
||||
begin
|
||||
if not Exec(ExpandConstant('{sys}\schtasks.exe'), '/Delete /TN "PYAS_Security_ATS" /F', '', SW_HIDE, ewWaitUntilTerminated, ResultCode) then
|
||||
begin
|
||||
Log(Format('Failed to delete scheduled task, Exec error code: %d', [ResultCode]));
|
||||
end
|
||||
else if ResultCode <> 0 then
|
||||
begin
|
||||
Log(Format('schtasks returned exit code %d while deleting the scheduled task', [ResultCode]));
|
||||
end;
|
||||
end;
|
||||
procedure RemoveLegacyStartupTask;
|
||||
var
|
||||
TaskService: Variant;
|
||||
RootFolder: Variant;
|
||||
begin
|
||||
try
|
||||
TaskService := CreateOleObject('Schedule.Service');
|
||||
TaskService.Connect();
|
||||
RootFolder := TaskService.GetFolder('\');
|
||||
try
|
||||
RootFolder.DeleteTask('PYAS_Security_ATS', 0);
|
||||
Log('Removed legacy PYAS scheduled startup task');
|
||||
except
|
||||
Log('Legacy PYAS scheduled startup task was not present or could not be removed');
|
||||
end;
|
||||
except
|
||||
Log('Task Scheduler COM was unavailable while cleaning the legacy startup task');
|
||||
end;
|
||||
end;
|
||||
|
||||
procedure CurStepChanged(CurStep: TSetupStep);
|
||||
begin
|
||||
if CurStep = ssPostInstall then
|
||||
begin
|
||||
if WizardIsTaskSelected('autostart') then
|
||||
begin
|
||||
TryCreateStartupTask;
|
||||
end;
|
||||
end;
|
||||
if CurStep = ssPostInstall then
|
||||
begin
|
||||
RemoveLegacyStartupTask;
|
||||
end;
|
||||
end;
|
||||
|
||||
procedure CurUninstallStepChanged(CurUninstallStep: TUninstallStep);
|
||||
begin
|
||||
if CurUninstallStep = usUninstall then
|
||||
begin
|
||||
TryDeleteStartupTask;
|
||||
if CurUninstallStep = usUninstall then
|
||||
begin
|
||||
RemoveLegacyStartupTask;
|
||||
end
|
||||
else if CurUninstallStep = usPostUninstall then
|
||||
begin
|
||||
DelTree(ExpandConstant('{app}'), True, True, True);
|
||||
end;
|
||||
end;
|
||||
end;
|
||||
+42
-17
@@ -82,26 +82,49 @@ class ProtectMixin:
|
||||
def init_whitelist(self):
|
||||
self.manage_named_list("white_list", [self.file_pyas], action="add")
|
||||
|
||||
def sync_driver_whitelist(self, file_path, is_add=True):
|
||||
def _driver_whitelist_patterns(self, file_path, is_directory=None):
|
||||
normalized_path = self.norm_path(file_path, must_exist=False)
|
||||
if not normalized_path:
|
||||
return []
|
||||
|
||||
if is_directory is None:
|
||||
is_directory = os.path.isdir(normalized_path)
|
||||
|
||||
drive_letter = os.path.splitdrive(normalized_path)[0]
|
||||
driver_path = "*" + normalized_path[len(drive_letter):] if drive_letter else normalized_path
|
||||
patterns = [driver_path]
|
||||
|
||||
if is_directory:
|
||||
descendant_pattern = driver_path.rstrip("\\/") + "\\*"
|
||||
if descendant_pattern not in patterns:
|
||||
patterns.append(descendant_pattern)
|
||||
|
||||
return patterns
|
||||
|
||||
def sync_driver_whitelist(self, file_path, is_add=True, is_directory=None):
|
||||
with self.lock_driver:
|
||||
if not self.driver_port:
|
||||
return False
|
||||
|
||||
driver_path = str(file_path)
|
||||
drive_letter = os.path.splitdrive(driver_path)[0]
|
||||
if drive_letter:
|
||||
driver_path = "*" + driver_path[len(drive_letter):]
|
||||
|
||||
msg = PYAS_USER_MESSAGE()
|
||||
msg.Command = 1 if is_add else 2
|
||||
msg.Path = driver_path
|
||||
bytes_returned = ctypes.wintypes.DWORD(0)
|
||||
|
||||
try:
|
||||
return self.fltlib.FilterSendMessage(self.driver_port, ctypes.byref(msg), ctypes.sizeof(msg), None, 0, ctypes.byref(bytes_returned)) == 0
|
||||
except Exception:
|
||||
patterns = self._driver_whitelist_patterns(file_path, is_directory)
|
||||
if not patterns:
|
||||
return False
|
||||
|
||||
success = True
|
||||
for driver_path in patterns:
|
||||
msg = PYAS_USER_MESSAGE()
|
||||
msg.Command = 1 if is_add else 2
|
||||
bytes_returned = ctypes.wintypes.DWORD(0)
|
||||
|
||||
try:
|
||||
msg.Path = driver_path
|
||||
if self.fltlib.FilterSendMessage(self.driver_port, ctypes.byref(msg), ctypes.sizeof(msg), None, 0, ctypes.byref(bytes_returned)) != 0:
|
||||
success = False
|
||||
except Exception:
|
||||
success = False
|
||||
|
||||
return success
|
||||
|
||||
####################################################################################################
|
||||
|
||||
def _try_lock_file(self, file_path):
|
||||
@@ -891,7 +914,10 @@ class ProtectMixin:
|
||||
running = any(exe_name.lower() == "explorer.exe" for _, exe_name in self._enum_processes())
|
||||
|
||||
if not running:
|
||||
subprocess.Popen("explorer.exe", shell=True)
|
||||
explorer = self._find_windows_tool("explorer.exe")
|
||||
if not explorer:
|
||||
return
|
||||
subprocess.Popen([explorer])
|
||||
self.write_log("INFO", "System Restart", source="explorer.exe")
|
||||
|
||||
except Exception:
|
||||
@@ -1905,7 +1931,7 @@ class ProtectMixin:
|
||||
if self.driver_stop_event.is_set():
|
||||
break
|
||||
if isinstance(item, dict) and item.get("file"):
|
||||
self.sync_driver_whitelist(item["file"], True)
|
||||
self.sync_driver_whitelist(item["file"], True, item.get("is_dir"))
|
||||
|
||||
if self.driver_stop_event.is_set():
|
||||
break
|
||||
@@ -1972,4 +1998,3 @@ class ProtectMixin:
|
||||
with self.lock_driver:
|
||||
if self.driver_listener_thread is threading.current_thread():
|
||||
self.driver_listener_thread = None
|
||||
|
||||
+336
-43
@@ -1,4 +1,4 @@
|
||||
import os, re, io, csv, time, shutil, threading
|
||||
import os, re, io, csv, json, time, shutil, threading
|
||||
import pefile, hashlib, winreg, requests, subprocess
|
||||
import ctypes, ctypes.wintypes
|
||||
|
||||
@@ -167,6 +167,186 @@ class RECT(ctypes.Structure):
|
||||
####################################################################################################
|
||||
|
||||
class ToolsMixin:
|
||||
def _find_windows_tool(self, *names):
|
||||
windows_dir = os.environ.get("SystemRoot") or os.environ.get("WINDIR") or r"C:\Windows"
|
||||
search_dirs = [
|
||||
os.path.join(windows_dir, "Sysnative"),
|
||||
os.path.join(windows_dir, "System32"),
|
||||
windows_dir
|
||||
]
|
||||
|
||||
for name in names:
|
||||
executable_name = name if name.lower().endswith(".exe") else name + ".exe"
|
||||
for directory in search_dirs:
|
||||
candidate = os.path.join(directory, executable_name)
|
||||
if os.path.isfile(candidate):
|
||||
return candidate
|
||||
|
||||
candidate = shutil.which(name)
|
||||
if candidate:
|
||||
return candidate
|
||||
|
||||
return None
|
||||
|
||||
def _run_windows_tool(self, tool_names, arguments, **kwargs):
|
||||
executable = self._find_windows_tool(*tool_names)
|
||||
if not executable:
|
||||
return None
|
||||
|
||||
options = {"creationflags": 0x08000000}
|
||||
options.update(kwargs)
|
||||
try:
|
||||
return subprocess.run([executable, *arguments], **options)
|
||||
except (FileNotFoundError, OSError):
|
||||
return None
|
||||
|
||||
def _run_powershell(self, command, **kwargs):
|
||||
return self._run_windows_tool(
|
||||
("powershell.exe", "pwsh.exe"),
|
||||
["-NoProfile", "-NonInteractive", "-ExecutionPolicy", "Bypass", "-WindowStyle", "Hidden", "-Command", command],
|
||||
**kwargs
|
||||
)
|
||||
|
||||
def _set_registry_autostart(self, enable):
|
||||
run_key = r"Software\Microsoft\Windows\CurrentVersion\Run"
|
||||
value_name = "PYAS_Security"
|
||||
if enable:
|
||||
command = f'"{self.file_pyas}" -hide'
|
||||
return self._reg_write(winreg.HKEY_CURRENT_USER, run_key, value_name, winreg.REG_SZ, command)
|
||||
|
||||
try:
|
||||
with winreg.OpenKey(winreg.HKEY_CURRENT_USER, run_key, 0, winreg.KEY_SET_VALUE) as reg:
|
||||
try:
|
||||
winreg.DeleteValue(reg, value_name)
|
||||
except FileNotFoundError:
|
||||
pass
|
||||
return True
|
||||
except FileNotFoundError:
|
||||
return True
|
||||
except Exception:
|
||||
return False
|
||||
|
||||
def _clear_event_log(self, log_name):
|
||||
result = self._run_windows_tool(
|
||||
("wevtutil.exe",),
|
||||
["cl", log_name],
|
||||
stdout=subprocess.DEVNULL,
|
||||
stderr=subprocess.DEVNULL
|
||||
)
|
||||
if result is not None and result.returncode == 0:
|
||||
return True
|
||||
|
||||
try:
|
||||
wevtapi = ctypes.WinDLL("wevtapi", use_last_error=True)
|
||||
wevtapi.EvtClearLog.argtypes = [ctypes.wintypes.HANDLE, ctypes.wintypes.LPCWSTR, ctypes.wintypes.LPCWSTR, ctypes.wintypes.DWORD]
|
||||
wevtapi.EvtClearLog.restype = ctypes.wintypes.BOOL
|
||||
return bool(wevtapi.EvtClearLog(None, log_name, None, 0))
|
||||
except Exception:
|
||||
return False
|
||||
|
||||
def _manage_service_native(self, service_name, action):
|
||||
SC_MANAGER_CONNECT = 0x0001
|
||||
SERVICE_CHANGE_CONFIG = 0x0002
|
||||
DELETE = 0x00010000
|
||||
SERVICE_NO_CHANGE = 0xFFFFFFFF
|
||||
SERVICE_AUTO_START = 0x00000002
|
||||
SERVICE_DISABLED = 0x00000004
|
||||
|
||||
try:
|
||||
advapi32 = ctypes.WinDLL("advapi32", use_last_error=True)
|
||||
advapi32.OpenSCManagerW.argtypes = [ctypes.wintypes.LPCWSTR, ctypes.wintypes.LPCWSTR, ctypes.wintypes.DWORD]
|
||||
advapi32.OpenSCManagerW.restype = ctypes.wintypes.HANDLE
|
||||
advapi32.OpenServiceW.argtypes = [ctypes.wintypes.HANDLE, ctypes.wintypes.LPCWSTR, ctypes.wintypes.DWORD]
|
||||
advapi32.OpenServiceW.restype = ctypes.wintypes.HANDLE
|
||||
advapi32.CloseServiceHandle.argtypes = [ctypes.wintypes.HANDLE]
|
||||
advapi32.CloseServiceHandle.restype = ctypes.wintypes.BOOL
|
||||
|
||||
manager = advapi32.OpenSCManagerW(None, None, SC_MANAGER_CONNECT)
|
||||
if not manager:
|
||||
return False
|
||||
|
||||
service = None
|
||||
try:
|
||||
desired_access = DELETE if action == "delete" else SERVICE_CHANGE_CONFIG
|
||||
service = advapi32.OpenServiceW(manager, service_name, desired_access)
|
||||
if not service:
|
||||
return False
|
||||
|
||||
if action == "delete":
|
||||
advapi32.DeleteService.argtypes = [ctypes.wintypes.HANDLE]
|
||||
advapi32.DeleteService.restype = ctypes.wintypes.BOOL
|
||||
return bool(advapi32.DeleteService(service))
|
||||
|
||||
start_type = SERVICE_AUTO_START if action == "enable" else SERVICE_DISABLED
|
||||
advapi32.ChangeServiceConfigW.argtypes = [
|
||||
ctypes.wintypes.HANDLE,
|
||||
ctypes.wintypes.DWORD,
|
||||
ctypes.wintypes.DWORD,
|
||||
ctypes.wintypes.DWORD,
|
||||
ctypes.wintypes.LPCWSTR,
|
||||
ctypes.wintypes.LPCWSTR,
|
||||
ctypes.POINTER(ctypes.wintypes.DWORD),
|
||||
ctypes.wintypes.LPCWSTR,
|
||||
ctypes.wintypes.LPCWSTR,
|
||||
ctypes.wintypes.LPCWSTR,
|
||||
ctypes.wintypes.LPCWSTR
|
||||
]
|
||||
advapi32.ChangeServiceConfigW.restype = ctypes.wintypes.BOOL
|
||||
return bool(advapi32.ChangeServiceConfigW(
|
||||
service,
|
||||
SERVICE_NO_CHANGE,
|
||||
start_type,
|
||||
SERVICE_NO_CHANGE,
|
||||
None,
|
||||
None,
|
||||
None,
|
||||
None,
|
||||
None,
|
||||
None,
|
||||
None
|
||||
))
|
||||
finally:
|
||||
if service:
|
||||
advapi32.CloseServiceHandle(service)
|
||||
advapi32.CloseServiceHandle(manager)
|
||||
except Exception:
|
||||
return False
|
||||
|
||||
def _manage_scheduled_task(self, task_path, action):
|
||||
schtasks_action = "/delete" if action == "delete" else "/change"
|
||||
arguments = [schtasks_action, "/tn", task_path]
|
||||
if action == "delete":
|
||||
arguments.append("/f")
|
||||
else:
|
||||
arguments.append("/enable" if action == "enable" else "/disable")
|
||||
|
||||
result = self._run_windows_tool(
|
||||
("schtasks.exe",),
|
||||
arguments,
|
||||
capture_output=True,
|
||||
text=True
|
||||
)
|
||||
if result is not None and result.returncode == 0:
|
||||
return True
|
||||
|
||||
escaped_path = task_path.replace("'", "''")
|
||||
operation = {
|
||||
"delete": "Unregister-ScheduledTask -InputObject $Task -Confirm:$false",
|
||||
"enable": "Enable-ScheduledTask -InputObject $Task | Out-Null",
|
||||
"disable": "Disable-ScheduledTask -InputObject $Task | Out-Null"
|
||||
}.get(action)
|
||||
if not operation:
|
||||
return False
|
||||
|
||||
command = (
|
||||
f"$FullName = '{escaped_path}'; "
|
||||
"$Task = Get-ScheduledTask -ErrorAction SilentlyContinue | "
|
||||
"Where-Object { ($_.TaskPath + $_.TaskName) -eq $FullName } | Select-Object -First 1; "
|
||||
f"if ($Task) {{ {operation} }} else {{ exit 1 }}"
|
||||
)
|
||||
result = self._run_powershell(command, capture_output=True, text=True)
|
||||
return bool(result and result.returncode == 0)
|
||||
|
||||
def _reg_read(self, root, path, value_name):
|
||||
try:
|
||||
with winreg.OpenKey(root, path, 0, winreg.KEY_READ) as reg:
|
||||
@@ -203,18 +383,81 @@ class ToolsMixin:
|
||||
####################################################################################################
|
||||
|
||||
def manage_autostart(self, enable):
|
||||
try:
|
||||
task_name = "PYAS_Security_ATS"
|
||||
if enable:
|
||||
cmd = f"$Action = New-ScheduledTaskAction -Execute '{self.file_pyas}' -Argument '-hide'; $Trigger = New-ScheduledTaskTrigger -AtLogOn; $Settings = New-ScheduledTaskSettingsSet -AllowStartIfOnBatteries -DontStopIfGoingOnBatteries; Register-ScheduledTask -TaskName '{task_name}' -Action $Action -Trigger $Trigger -Settings $Settings -RunLevel Highest -Force"
|
||||
subprocess.run(["powershell", "-NoProfile", "-NonInteractive", "-ExecutionPolicy", "Bypass", "-WindowStyle", "Hidden", "-Command", cmd], creationflags=0x08000000)
|
||||
else:
|
||||
subprocess.run(["schtasks", "/Delete", "/TN", task_name, "/F"], creationflags=0x08000000)
|
||||
task_name = "PYAS_Security_ATS"
|
||||
executable = os.path.normpath(self.file_pyas)
|
||||
escaped_executable = executable.replace("'", "''")
|
||||
errors = []
|
||||
|
||||
return True
|
||||
except Exception as e:
|
||||
self.write_log("WARN", "manage_autostart", detail=str(e), success=False)
|
||||
return False
|
||||
if enable:
|
||||
task_command = f'"{executable}" -hide'
|
||||
result = self._run_windows_tool(
|
||||
("schtasks.exe",),
|
||||
["/Create", "/TN", task_name, "/TR", task_command, "/SC", "ONLOGON", "/RL", "HIGHEST", "/F"],
|
||||
capture_output=True,
|
||||
text=True
|
||||
)
|
||||
if result and result.returncode == 0:
|
||||
self._set_registry_autostart(False)
|
||||
return True
|
||||
if result:
|
||||
errors.append((result.stderr or result.stdout or f"schtasks exit {result.returncode}").strip())
|
||||
|
||||
command = (
|
||||
f"$Action = New-ScheduledTaskAction -Execute '{escaped_executable}' -Argument '-hide'; "
|
||||
"$Trigger = New-ScheduledTaskTrigger -AtLogOn; "
|
||||
"$Settings = New-ScheduledTaskSettingsSet -AllowStartIfOnBatteries -DontStopIfGoingOnBatteries; "
|
||||
f"Register-ScheduledTask -TaskName '{task_name}' -Action $Action -Trigger $Trigger -Settings $Settings -RunLevel Highest -Force | Out-Null"
|
||||
)
|
||||
result = self._run_powershell(command, capture_output=True, text=True)
|
||||
if result and result.returncode == 0:
|
||||
self._set_registry_autostart(False)
|
||||
return True
|
||||
if result:
|
||||
errors.append((result.stderr or result.stdout or f"PowerShell exit {result.returncode}").strip())
|
||||
|
||||
if self._set_registry_autostart(True):
|
||||
self.write_log("INFO", "manage_autostart", detail="Scheduled Task unavailable; using HKCU Run fallback", success=True)
|
||||
return True
|
||||
else:
|
||||
task_removed = False
|
||||
query_result = self._run_windows_tool(
|
||||
("schtasks.exe",),
|
||||
["/Query", "/TN", task_name],
|
||||
capture_output=True,
|
||||
text=True
|
||||
)
|
||||
result = None
|
||||
if query_result is not None and query_result.returncode != 0:
|
||||
task_removed = True
|
||||
elif query_result is not None:
|
||||
result = self._run_windows_tool(
|
||||
("schtasks.exe",),
|
||||
["/Delete", "/TN", task_name, "/F"],
|
||||
capture_output=True,
|
||||
text=True
|
||||
)
|
||||
task_removed = bool(result and result.returncode == 0)
|
||||
|
||||
if query_result is None or not task_removed:
|
||||
command = (
|
||||
f"$Task = Get-ScheduledTask -TaskName '{task_name}' -ErrorAction SilentlyContinue; "
|
||||
"if ($Task) { $Task | Unregister-ScheduledTask -Confirm:$false }"
|
||||
)
|
||||
powershell_result = self._run_powershell(command, capture_output=True, text=True)
|
||||
if powershell_result is not None:
|
||||
task_removed = powershell_result.returncode == 0
|
||||
result = powershell_result
|
||||
|
||||
if result and result.returncode != 0:
|
||||
errors.append((result.stderr or result.stdout or f"task removal exit {result.returncode}").strip())
|
||||
|
||||
registry_removed = self._set_registry_autostart(False)
|
||||
if registry_removed and (task_removed or query_result is None and result is None):
|
||||
return True
|
||||
|
||||
detail = "; ".join(error for error in errors if error) or "No supported autostart method was available"
|
||||
self.write_log("WARN", "manage_autostart", detail=detail, success=False)
|
||||
return False
|
||||
|
||||
####################################################################################################
|
||||
|
||||
@@ -292,10 +535,16 @@ class ToolsMixin:
|
||||
if lock_func:
|
||||
lock_func(path, True)
|
||||
|
||||
target_list.append({"file": path, "time": time.time()})
|
||||
new_item = {"file": path, "time": time.time()}
|
||||
is_directory = None
|
||||
if list_key == "white_list":
|
||||
is_directory = os.path.isdir(path)
|
||||
new_item["is_dir"] = is_directory
|
||||
|
||||
target_list.append(new_item)
|
||||
acted_items.append(path)
|
||||
if list_key == "white_list":
|
||||
self.sync_driver_whitelist(path, True)
|
||||
self.sync_driver_whitelist(path, True, is_directory)
|
||||
|
||||
elif action == "remove":
|
||||
norm_paths_case = {os.path.normcase(p) for p in norm_paths}
|
||||
@@ -311,7 +560,8 @@ class ToolsMixin:
|
||||
|
||||
acted_items.append(val)
|
||||
if list_key == "white_list":
|
||||
self.sync_driver_whitelist(val, False)
|
||||
is_directory = item.get("is_dir") if isinstance(item, dict) else None
|
||||
self.sync_driver_whitelist(val, False, is_directory)
|
||||
|
||||
continue
|
||||
new_list.append(item)
|
||||
@@ -356,7 +606,8 @@ class ToolsMixin:
|
||||
if np and os.path.normcase(np) in norm_paths_to_remove:
|
||||
removed_items.append(val)
|
||||
if list_key == "white_list":
|
||||
self.sync_driver_whitelist(val, False)
|
||||
is_directory = item.get("is_dir") if isinstance(item, dict) else None
|
||||
self.sync_driver_whitelist(val, False, is_directory)
|
||||
|
||||
continue
|
||||
new_list.append(item)
|
||||
@@ -888,7 +1139,7 @@ class ToolsMixin:
|
||||
log_path = os.path.join(self.path_system, "System32", "winevt", "Logs", f"{log_type}.evtx")
|
||||
|
||||
size = os.path.getsize(log_path) if os.path.exists(log_path) else 0
|
||||
if subprocess.run(["wevtutil", "cl", log_type], stdout=subprocess.DEVNULL, stderr=subprocess.DEVNULL, creationflags=0x08000000).returncode == 0:
|
||||
if self._clear_event_log(log_type):
|
||||
total_deleted += size
|
||||
|
||||
except Exception:
|
||||
@@ -1035,20 +1286,57 @@ class ToolsMixin:
|
||||
def get_tasks():
|
||||
res = []
|
||||
try:
|
||||
proc = subprocess.run(["schtasks", "/query", "/fo", "csv", "/v"], capture_output=True, text=True, creationflags=0x08000000)
|
||||
reader = csv.reader(io.StringIO(proc.stdout))
|
||||
next(reader, None)
|
||||
proc = self._run_windows_tool(
|
||||
("schtasks.exe",),
|
||||
["/query", "/fo", "csv", "/v"],
|
||||
capture_output=True,
|
||||
text=True
|
||||
)
|
||||
if proc is not None and proc.returncode == 0:
|
||||
reader = csv.reader(io.StringIO(proc.stdout))
|
||||
next(reader, None)
|
||||
|
||||
for row in reader:
|
||||
if len(row) > 8 and row[1].strip() and row[8].strip() and row[8] != "N/A":
|
||||
name = row[1].split('\\')[-1]
|
||||
path = row[8]
|
||||
status = row[3]
|
||||
for row in reader:
|
||||
if len(row) > 8 and row[1].strip() and row[8].strip() and row[8] != "N/A":
|
||||
name = row[1].split('\\')[-1]
|
||||
path = row[8]
|
||||
status = row[3]
|
||||
|
||||
if path and "windows" not in path.lower():
|
||||
fp = self.extract_paths_from_cmdline(path)
|
||||
fpp = fp[0] if fp else path
|
||||
res.append({"id": f"tsk|{row[1]}", "type": "type_tsk", "name": name, "status": "disabled" if status == "Disabled" else "enabled", "path": fpp})
|
||||
if path and "windows" not in path.lower():
|
||||
fp = self.extract_paths_from_cmdline(path)
|
||||
fpp = fp[0] if fp else path
|
||||
res.append({"id": f"tsk|{row[1]}", "type": "type_tsk", "name": name, "status": "disabled" if status.lower() == "disabled" else "enabled", "path": fpp})
|
||||
return res
|
||||
|
||||
command = (
|
||||
"[Console]::OutputEncoding = [Text.UTF8Encoding]::new(); "
|
||||
"Get-ScheduledTask -ErrorAction SilentlyContinue | ForEach-Object { "
|
||||
"$Action = @($_.Actions)[0]; "
|
||||
"[pscustomobject]@{Name=$_.TaskName; FullName=($_.TaskPath + $_.TaskName); "
|
||||
"State=[string]$_.State; Execute=$Action.Execute; Arguments=$Action.Arguments} "
|
||||
"} | ConvertTo-Json -Compress"
|
||||
)
|
||||
proc = self._run_powershell(command, capture_output=True, text=True, encoding="utf-8", errors="replace")
|
||||
if not proc or proc.returncode != 0 or not proc.stdout.strip():
|
||||
return res
|
||||
|
||||
task_data = json.loads(proc.stdout)
|
||||
if isinstance(task_data, dict):
|
||||
task_data = [task_data]
|
||||
|
||||
for task in task_data:
|
||||
name = str(task.get("Name") or "").strip()
|
||||
full_name = str(task.get("FullName") or name).strip()
|
||||
execute = str(task.get("Execute") or "").strip()
|
||||
arguments = str(task.get("Arguments") or "").strip()
|
||||
command_line = " ".join(part for part in (execute, arguments) if part)
|
||||
if not name or not command_line or "windows" in command_line.lower():
|
||||
continue
|
||||
|
||||
paths = self.extract_paths_from_cmdline(command_line)
|
||||
path = paths[0] if paths else execute or command_line
|
||||
status = "disabled" if str(task.get("State") or "").lower() == "disabled" else "enabled"
|
||||
res.append({"id": f"tsk|{full_name}", "type": "type_tsk", "name": name, "status": status, "path": path})
|
||||
|
||||
except Exception:
|
||||
pass
|
||||
@@ -1068,44 +1356,49 @@ class ToolsMixin:
|
||||
def manage_startup(self, items, action):
|
||||
def process_item(item_id):
|
||||
try:
|
||||
parts = item_id.split('|')
|
||||
parts = item_id.split('|', 3)
|
||||
stype = parts[0]
|
||||
|
||||
if stype == "reg":
|
||||
root, old_path, name = int(parts[1]), parts[2], parts[3]
|
||||
if action == "delete":
|
||||
self._reg_delete(root, old_path, name)
|
||||
return self._reg_delete(root, old_path, name)
|
||||
|
||||
else:
|
||||
new_path = old_path.replace("_Disabled", "") if action == "enable" else (old_path if "_Disabled" in old_path else old_path + "_Disabled")
|
||||
if old_path != new_path:
|
||||
val = self._reg_read(root, old_path, name)
|
||||
if val:
|
||||
self._reg_write(root, new_path, name, winreg.REG_SZ, val)
|
||||
self._reg_delete(root, old_path, name)
|
||||
return self._reg_write(root, new_path, name, winreg.REG_SZ, val) and self._reg_delete(root, old_path, name)
|
||||
return True
|
||||
|
||||
elif stype == "srv":
|
||||
name = parts[1]
|
||||
if action == "delete":
|
||||
subprocess.run(["sc", "delete", name], capture_output=True, creationflags=0x08000000)
|
||||
result = self._run_windows_tool(("sc.exe",), ["delete", name], capture_output=True)
|
||||
|
||||
else:
|
||||
mode = "auto" if action == "enable" else "disabled"
|
||||
subprocess.run(["sc", "config", name, f"start=", mode], capture_output=True, creationflags=0x08000000)
|
||||
result = self._run_windows_tool(("sc.exe",), ["config", name, "start=", mode], capture_output=True)
|
||||
|
||||
if result is not None and result.returncode == 0:
|
||||
return True
|
||||
return self._manage_service_native(name, action)
|
||||
|
||||
elif stype == "tsk":
|
||||
name = parts[1]
|
||||
if action == "delete":
|
||||
subprocess.run(["schtasks", "/delete", "/tn", name, "/f"], capture_output=True, creationflags=0x08000000)
|
||||
name = item_id.partition('|')[2]
|
||||
return self._manage_scheduled_task(name, action)
|
||||
|
||||
else:
|
||||
cmd = "/enable" if action == "enable" else "/disable"
|
||||
subprocess.run(["schtasks", "/change", "/tn", name, cmd], capture_output=True, creationflags=0x08000000)
|
||||
return False
|
||||
|
||||
except Exception as e:
|
||||
self.write_log("WARN", "manage_startup", detail=str(e), success=False)
|
||||
return False
|
||||
|
||||
with ThreadPoolExecutor(max_workers=5) as executor:
|
||||
list(executor.map(process_item, items))
|
||||
results = list(executor.map(process_item, items))
|
||||
|
||||
return True
|
||||
success = all(results) if results else True
|
||||
if not success:
|
||||
self.write_log("WARN", "manage_startup", detail="One or more startup items could not be updated", success=False)
|
||||
return success
|
||||
+1
-1
@@ -2,7 +2,7 @@ import pyinstaller_versionfile
|
||||
|
||||
pyinstaller_versionfile.create_versionfile(
|
||||
output_file="versionfile.txt",
|
||||
version='3.6.6.0',
|
||||
version='3.6.9.0',
|
||||
company_name="PYAS Security",
|
||||
file_description="PYAS Security Antivirus",
|
||||
internal_name="PYAS",
|
||||
|
||||
@@ -63,6 +63,9 @@
|
||||
"*\\PYAS.exe",
|
||||
"\\Device\\HarddiskVolume*\\Users\\*\\AppData\\Local\\Temp\\*\\PYAS_Setup.tmp",
|
||||
"\\Device\\HarddiskVolume*\\Users\\*\\AppData\\Local\\Temp\\*\\_unins.tmp",
|
||||
"\\Device\\HarddiskVolume*\\Users\\*\\AppData\\Local\\sogoupdf\\pdfpopcl.exe",
|
||||
"\\Device\\HarddiskVolume*\\Users\\*\\AppData\\Local\\WeGame\\*\\tiny_dl\\TinyDL.exe",
|
||||
"\\Device\\HarddiskVolume*\\Users\\*\\AppData\\Local\\Microsoft\\OneDrive\\OneDrive.exe",
|
||||
"\\Device\\HarddiskVolume*\\Windows\\explorer.exe",
|
||||
"\\Device\\HarddiskVolume*\\Windows\\System32\\csrss.exe",
|
||||
"\\Device\\HarddiskVolume*\\Windows\\System32\\lsass.exe",
|
||||
@@ -74,6 +77,8 @@
|
||||
"\\Device\\HarddiskVolume*\\Program Files (x86)\\*",
|
||||
"*\\games\\Arknights Game\\Arknights.exe",
|
||||
"*\\games\\EndField Game\\EndField.exe",
|
||||
"*\\cm\\FeverGames\\*\\FeverGamesInstaller.exe",
|
||||
"*\\Davinci\\Resolve.exe",
|
||||
],
|
||||
"Target": [
|
||||
"*\\PYAS.exe",
|
||||
@@ -241,13 +246,17 @@
|
||||
"InitiatorExclude": [
|
||||
"*\\PYAS.exe",
|
||||
"*\\Huorong\\Sysdiag\\bin\\sysclean.exe",
|
||||
"*\\Dell\\SARemediation\\agent\\DellSupportAssistRemedationService.exe",
|
||||
"\\Device\\HarddiskVolume*\\Windows\\explorer.exe",
|
||||
"\\Device\\HarddiskVolume*\\Windows\\System32\\SearchIndexer.exe",
|
||||
"\\Device\\HarddiskVolume*\\Windows\\System32\\lsass.exe",
|
||||
"\\Device\\HarddiskVolume*\\Windows\\System32\\svchost.exe",
|
||||
"\\Device\\HarddiskVolume*\\Windows\\System32\\services.exe",
|
||||
"\\Device\\HarddiskVolume*\\Windows\\System32\\taskhostw.exe",
|
||||
"\\Device\\HarddiskVolume*\\Windows\\System32\\TrustedInstaller.exe",
|
||||
"\\Device\\HarddiskVolume*\\Windows\\System32\\ReAgentc.exe",
|
||||
"\\Device\\HarddiskVolume*\\Windows\\WinSxS\\*\\TiWorker.exe",
|
||||
"\\Device\\HarddiskVolume*\\Windows\\UUS\\amd64\\wuaucltcore.exe",
|
||||
"\\Device\\HarddiskVolume*\\$WinREAgent\\Scratch\\*\\DismHost.exe",
|
||||
],
|
||||
"Target": [
|
||||
@@ -258,6 +267,10 @@
|
||||
"\\Device\\HarddiskVolume*\\Boot\\*",
|
||||
"\\Device\\HarddiskVolume*\\Recovery\\*",
|
||||
],
|
||||
"TargetExclude": [
|
||||
"\\Device\\HarddiskVolume*\\Windows\\System32\\Recovery\\ReAgent.xml",
|
||||
"\\Device\\HarddiskVolume*\\Recovery\\OEM\\Logs\\Versions.ini",
|
||||
],
|
||||
"Operations": [
|
||||
"Write",
|
||||
"Delete",
|
||||
@@ -452,11 +465,13 @@
|
||||
"*\\PYAS.exe",
|
||||
"*\\rufus-*.exe",
|
||||
"*\\DiskInfo*.exe",
|
||||
"\\Device\\HarddiskVolume*\\Program Files\\*\\launcher\\ca_launcher.exe",
|
||||
"\\Device\\HarddiskVolume*\\Windows\\System32\\diskpart.exe",
|
||||
"\\Device\\HarddiskVolume*\\Windows\\System32\\diskmgmt.msc",
|
||||
"\\Device\\HarddiskVolume*\\Windows\\System32\\taskhostw.exe",
|
||||
"\\Device\\HarddiskVolume*\\Windows\\System32\\vds.exe",
|
||||
"\\Device\\HarddiskVolume*\\Windows\\System32\\format.com",
|
||||
"\\Device\\HarddiskVolume*\\Windows\\System32\\WinSAT.exe",
|
||||
],
|
||||
"Operations": [
|
||||
"Ioctl",
|
||||
@@ -489,8 +504,6 @@
|
||||
"*\\DefaultPassword",
|
||||
"*\\AltDefaultUserName",
|
||||
"*\\AltDefaultPassword",
|
||||
"*\\LegalNoticeCaption",
|
||||
"*\\LegalNoticeText",
|
||||
"*\\AutoReboot",
|
||||
"*\\DisableAutomaticRestartOnFailure",
|
||||
"*\\SystemBiosVersion",
|
||||
@@ -645,6 +658,7 @@
|
||||
"*\\Sources\\SetupHost.exe",
|
||||
"*\\Work\\*\\DismHost.exe",
|
||||
"*\\Huorong\\Sysdiag\\bin\\HipsDaemon.exe",
|
||||
"*\\Huorong\\Sysdiag\\bin\\HRUpdate.exe",
|
||||
"*\\360Safe.exe",
|
||||
"*\\PYAS.exe",
|
||||
],
|
||||
@@ -656,6 +670,9 @@
|
||||
"\\REGISTRY\\MACHINE\\SYSTEM\\*ControlSet*\\Control\\SafeBoot\\Option",
|
||||
"\\REGISTRY\\MACHINE\\SYSTEM\\*ControlSet*\\Control\\SafeBoot\\Option\\*",
|
||||
],
|
||||
"TargetExclude": [
|
||||
"\\REGISTRY\\MACHINE\\SYSTEM\\ControlSet001\\Control\\SafeBoot\\Network\\HipsDaemon",
|
||||
],
|
||||
"Operations": [
|
||||
"Write",
|
||||
"Delete",
|
||||
@@ -669,15 +686,17 @@
|
||||
"Kill": false,
|
||||
"Category": "Registry",
|
||||
"InitiatorExclude": [
|
||||
"*\\PYAS.exe",
|
||||
"*\\Sources\\SetupHost.exe",
|
||||
"*\\Work\\*\\DismHost.exe",
|
||||
"*\\Windows\\UUS\\amd64\\MoUsoCoreWorker.exe",
|
||||
"*\\Windows\\System32\\MoUsoCoreWorker.exe",
|
||||
"*\\Windows\\System32\\Sysprep\\sysprep.exe",
|
||||
"*\\Windows\\System32\\oobe\\*",
|
||||
"*\\Windows\\System32\\svchost.exe",
|
||||
"*\\Windows\\explorer.exe",
|
||||
"*\\PYAS.exe",
|
||||
"\\Device\\HarddiskVolume*\\Windows\\UUS\\amd64\\MoUsoCoreWorker.exe",
|
||||
"\\Device\\HarddiskVolume*\\Windows\\UUS\\Packages\\Preview\\amd64\\MoUsoCoreWorker.exe",
|
||||
"\\Device\\HarddiskVolume*\\Windows\\System32\\MoUsoCoreWorker.exe",
|
||||
"\\Device\\HarddiskVolume*\\Windows\\System32\\Sysprep\\sysprep.exe",
|
||||
"\\Device\\HarddiskVolume*\\Windows\\System32\\oobe\\*",
|
||||
"\\Device\\HarddiskVolume*\\Windows\\System32\\svchost.exe",
|
||||
"\\Device\\HarddiskVolume*\\Windows\\System32\\taskhostw.exe",
|
||||
"\\Device\\HarddiskVolume*\\Windows\\explorer.exe",
|
||||
],
|
||||
"Target": [
|
||||
"\\REGISTRY\\MACHINE\\SYSTEM\\Setup\\OOBEInProgress",
|
||||
|
||||
@@ -134,6 +134,12 @@ The machine learning-based virus scanning engine training achieved extremely hig
|
||||
|
||||
<img width="895" height="1097" alt="train" src="https://github.com/user-attachments/assets/fa7cf0e2-01a1-4234-9358-718c2b8a2c9a" />
|
||||
|
||||
## Rule Editor
|
||||
|
||||
PYAS antivirus software driver protection rule editor, developed using Blockly. Included in the Releases.
|
||||
|
||||
<img width="2559" height="1271" alt="image" src="https://github.com/user-attachments/assets/29a816a8-3bf9-4e1c-b881-7336676ad7f9" />
|
||||
|
||||
## Support System
|
||||
|
||||
Please ensure your computer supports Microsoft Visual C++ 2015-2022 Redistributable and Edge Webview2.
|
||||
|
||||
Reference in new issue
Block a user