Add files via upload

This commit is contained in:
87owo authored and GitHub committed 2026-04-26 02:13:58 +08:00
1 parent b1b77b73ac
commit d6bd383822
3 files changed
+262 -66

No files matched your search

+69 -32
View File
@@ -110,7 +110,9 @@ document.addEventListener('DOMContentLoaded', () => {
"修復映像劫持": "修復映像劫持",
"修復桌面壁紙": "修復桌面壁紙",
"正在掃描": "正在掃描",
"正在刪除": "正在刪除"
"正在刪除": "正在刪除",
"選單掃描": "選單掃描",
"此選項可以加入右鍵掃描": "此選項可以加入右鍵掃描"
},
"simplified_switch": {
"PYAS Security": "PYAS Security",
@@ -219,7 +221,9 @@ document.addEventListener('DOMContentLoaded', () => {
"修復映像劫持": "修复映像劫持",
"修復桌面壁紙": "修复桌面壁纸",
"正在掃描": "正在扫描",
"正在刪除": "正在删除"
"正在刪除": "正在删除",
"選單掃描": "菜单扫描",
"此選項可以加入右鍵掃描": "此选项可以加入右键扫描菜单"
},
"english_switch": {
"PYAS Security": "PYAS Security",
@@ -328,7 +332,9 @@ document.addEventListener('DOMContentLoaded', () => {
"修復映像劫持": "Repair Image Hijacking",
"修復桌面壁紙": "Repair Desktop Wallpaper",
"正在掃描": "Scanning...",
"正在刪除": "Deleting..."
"正在刪除": "Deleting...",
"選單掃描": "Context Menu Scan",
"此選項可以加入右鍵掃描": "Add scan option to right-click menu"
},
"japanese_switch": {
"PYAS Security": "PYAS Security",
@@ -437,7 +443,9 @@ document.addEventListener('DOMContentLoaded', () => {
"修復映像劫持": "イメージハイジャックの修復",
"修復桌面壁紙": "デスクトップの壁紙を修復",
"正在掃描": "スキャン中...",
"正在刪除": "削除中..."
"正在刪除": "削除中...",
"選單掃描": "コンテキストメニュースキャン",
"此選項可以加入右鍵掃描": "右クリックメニューにスキャンオプションを追加します"
},
"korean_switch": {
"PYAS Security": "PYAS Security",
@@ -546,7 +554,9 @@ document.addEventListener('DOMContentLoaded', () => {
"修復映像劫持": "이미지 하이재킹 복구",
"修復桌面壁紙": "바탕 화면 배경 무늬 복구",
"正在掃描": "스캔 중...",
"正在刪除": "삭제 중..."
"正在刪除": "삭제 중...",
"選單掃描": "컨텍스트 메뉴 스캔",
"此選項可以加入右鍵掃描": "마우스 우클릭 메뉴에 스캔 옵션을 추가합니다"
},
"french_switch": {
"PYAS Security": "PYAS Security",
@@ -655,7 +665,9 @@ document.addEventListener('DOMContentLoaded', () => {
"修復映像劫持": "Réparer le détournement d'image",
"修復桌面壁紙": "Réparer le fond d'écran du bureau",
"正在掃描": "Analyse en cours...",
"正在刪除": "Suppression en cours..."
"正在刪除": "Suppression en cours...",
"選單掃描": "Analyse du menu contextuel",
"此選項可以加入右鍵掃描": "Ajouter l'option d'analyse au menu contextuel"
},
"spanish_switch": {
"PYAS Security": "PYAS Security",
@@ -764,7 +776,9 @@ document.addEventListener('DOMContentLoaded', () => {
"修復映像劫持": "Reparar secuestro de imagen",
"修復桌面壁紙": "Reparar fondo de escritorio",
"正在掃描": "Escaneando...",
"正在刪除": "Eliminando..."
"正在刪除": "Eliminando...",
"選單掃描": "Escaneo de menú contextual",
"此選項可以加入右鍵掃描": "Añadir opción de escaneo al menú contextual"
},
"hindi_switch": {
"PYAS Security": "PYAS Security",
@@ -873,7 +887,9 @@ document.addEventListener('DOMContentLoaded', () => {
"修復映像劫持": "छवि अपहरण की मरम्मत",
"修復桌面壁紙": "डेस्कटॉप वॉलपेपर की मरम्मत",
"正在掃描": "स्कैन कर रहा है...",
"正在刪除": "हटा रहा है..."
"正在刪除": "हटा रहा है...",
"選單掃描": "संदर्भ मेनू स्कैन",
"此選項可以加入右鍵掃描": "राइट-क्लिक मेनू में स्कैन विकल्प जोड़ें"
},
"arabic_switch": {
"PYAS Security": "PYAS Security",
@@ -982,7 +998,9 @@ document.addEventListener('DOMContentLoaded', () => {
"修復映像劫持": "إصلاح اختطاف الصور",
"修復桌面壁紙": "إصلاح خلفية سطح المكتب",
"正在掃描": "جاري الفحص...",
"正在刪除": "جاري الحذف..."
"正在刪除": "جاري الحذف...",
"選單掃描": "فحص قائمة السياق",
"此選項可以加入右鍵掃描": "إضافة خيار الفحص إلى قائمة النقر الأيمن"
},
"russian_switch": {
"PYAS Security": "PYAS Security",
@@ -1091,7 +1109,9 @@ document.addEventListener('DOMContentLoaded', () => {
"修復映像劫持": "Восстановление Image Hijacking",
"修復桌面壁紙": "Восстановление обоев рабочего стола",
"正在掃描": "Сканирование...",
"正在刪除": "Удаление..."
"正在刪除": "Удаление...",
"選單掃描": "Сканирование из контекстного меню",
"此選項可以加入右鍵掃描": "Добавить опцию сканирования в контекстное меню"
},
"slovenian_switch": {
"PYAS Security": "PYAS Security",
@@ -1200,7 +1220,9 @@ document.addEventListener('DOMContentLoaded', () => {
"修復映像劫持": "Popravilo ugrabitve slike",
"修復桌面壁紙": "Popravilo ozadja namizja",
"正在掃描": "Skeniranje...",
"正在刪除": "Brisanje..."
"正在刪除": "Brisanje...",
"選單掃描": "Skeniranje iz kontekstnega menija",
"此選項可以加入右鍵掃描": "Dodaj možnost skeniranja v kontekstni meni"
}
};
@@ -1521,7 +1543,7 @@ document.addEventListener('DOMContentLoaded', () => {
});
window.revertSwitch = (switchKey) => {
const switchMap = ["process_switch", "document_switch", "system_switch", "driver_switch", "network_switch", "sensitive_switch", "extension_switch", "cloud_switch"];
const switchMap = ["process_switch", "document_switch", "system_switch", "driver_switch", "network_switch", "sensitive_switch", "extension_switch", "cloud_switch", "context_switch"];
const index = switchMap.indexOf(switchKey);
if (index !== -1) {
const toggle = document.querySelectorAll('.toggle-switch input')[index];
@@ -1533,13 +1555,47 @@ document.addEventListener('DOMContentLoaded', () => {
document.querySelectorAll('.toggle-switch input').forEach((toggle, index) => {
toggle.addEventListener('change', (e) => {
const switchMap = ["process_switch", "document_switch", "system_switch", "driver_switch", "network_switch", "sensitive_switch", "extension_switch", "cloud_switch"];
const switchMap = ["process_switch", "document_switch", "system_switch", "driver_switch", "network_switch", "sensitive_switch", "extension_switch", "cloud_switch", "context_switch"];
if (window.pywebview && switchMap[index]) {
window.pywebview.api.update_config(switchMap[index], e.target.checked);
}
});
});
window.triggerContextScan = (path) => {
if (isScanning) return;
switchPage('scan_window');
if (window.pywebview) {
isScanning = true;
virusList.innerHTML = '';
scanMethodSelect.classList.add('hidden');
stopBtn.classList.remove('hidden');
solveBtn.classList.add('hidden');
if (progressTitle) progressTitle.textContent = getMsg("正在掃描");
progressText.textContent = getMsg("正在初始化中");
window.pywebview.api.start_scan([path]);
}
};
window.addEventListener('pywebviewready', () => {
window.pywebview.api.get_config().then(cfg => {
const theme = cfg.theme || "white_switch";
const lang = cfg.language || "english_switch";
applyTheme(theme);
translateText(lang);
updateCustomSelectUI('theme_select', theme);
updateCustomSelectUI('lang_select', lang);
const switchMap = ["process_switch", "document_switch", "system_switch", "driver_switch", "network_switch", "sensitive_switch", "extension_switch", "cloud_switch", "context_switch"];
document.querySelectorAll('.toggle-switch input').forEach((toggle, index) => {
if (switchMap[index]) {
toggle.checked = !!cfg[switchMap[index]];
}
});
});
window.pywebview.api.init_ui_ready();
});
function renderManageList(containerSelector, dataList, displayKey, valKey, defaultChecked = false) {
const container = document.querySelector(`${containerSelector} .manage-list`);
const searchInput = document.querySelector(`${containerSelector} .search-box input`);
@@ -1918,23 +1974,4 @@ document.addEventListener('DOMContentLoaded', () => {
});
}
});
window.addEventListener('pywebviewready', () => {
window.pywebview.api.get_config().then(cfg => {
const theme = cfg.theme || "white_switch";
const lang = cfg.language || "english_switch";
applyTheme(theme);
translateText(lang);
updateCustomSelectUI('theme_select', theme);
updateCustomSelectUI('lang_select', lang);
const switchMap = ["process_switch", "document_switch", "system_switch", "driver_switch", "network_switch", "sensitive_switch", "extension_switch", "cloud_switch"];
document.querySelectorAll('.toggle-switch input').forEach((toggle, index) => {
if (switchMap[index]) {
toggle.checked = !!cfg[switchMap[index]];
}
});
});
window.pywebview.api.init_ui_ready();
});
});
+6
View File
@@ -203,6 +203,12 @@
</div>
<label class="toggle-switch"><input type="checkbox" checked><span class="slider"></span></label>
</div>
<div class="list-item">
<div class="item-info">
<h2 data-i18n>選單掃描</h2><p data-i18n>此選項可以加入右鍵掃描</p>
</div>
<label class="toggle-switch"><input type="checkbox"><span class="slider"></span></label>
</div>
<div class="list-item">
<div class="item-info">
<h2 data-i18n>顯色主題</h2><p data-i18n>此選項可以選擇主題顏色</p>
+187 -34
View File
@@ -1,5 +1,5 @@
import os, re, sys, time, json, uuid, queue, msvcrt, winreg, pystray
import shutil, hashlib, platform, threading, subprocess, pefile
import os, re, sys, time, json, uuid, stat, queue, msvcrt, winreg
import shutil, hashlib, platform, threading, subprocess, pefile, pystray
import socket, requests, webview, webbrowser, ctypes, ctypes.wintypes
from PIL import Image, ImageDraw
@@ -101,6 +101,13 @@ class PYAS_USER_MESSAGE(ctypes.Structure):
("Path", ctypes.wintypes.WCHAR * 1024)
]
class COPYDATASTRUCT(ctypes.Structure):
_fields_ = [
("dwData", ctypes.c_size_t),
("cbData", ctypes.wintypes.DWORD),
("lpData", ctypes.c_void_p)
]
####################################################################################################
class WindowAPI:
@@ -117,7 +124,28 @@ class WindowAPI:
if not self.check_singleton("PYAS_Security_Mutex"):
hwnd = self.user32.FindWindowW(None, "PYAS Security")
if hwnd:
self.user32.ShowWindow(hwnd, 9)
if "-scan" in self.args_pyas:
try:
idx = self.args_pyas.index("-scan")
target = self.args_pyas[idx+1]
cds = COPYDATASTRUCT()
cds.dwData = 1
encoded = target.encode('utf-8')
cds.cbData = len(encoded) + 1
buffer = ctypes.create_string_buffer(encoded)
cds.lpData = ctypes.cast(buffer, ctypes.c_void_p)
self.user32.SendMessageTimeoutW(hwnd, 0x004A, 0, ctypes.byref(cds), 0x0002, 3000, None)
except Exception:
pass
else:
try:
cds = COPYDATASTRUCT()
cds.dwData = 2
cds.cbData = 0
cds.lpData = None
self.user32.SendMessageTimeoutW(hwnd, 0x004A, 0, ctypes.byref(cds), 0x0002, 3000, None)
except Exception:
pass
self.user32.SetForegroundWindow(hwnd)
os._exit(0)
@@ -135,6 +163,11 @@ class WindowAPI:
return False
def init_ui_ready(self):
with self.lock_config:
if self.engine_initialized:
return
self.engine_initialized = True
self.start_daemon_thread(self.init_engine_thread)
def set_window(self, window):
@@ -206,25 +239,42 @@ class WindowAPI:
def restore_from_tray(self, icon=None, item=None):
if self._window:
self._window.restore()
self._window.show()
hwnd = self.user32.FindWindowW(None, "PYAS Security")
if hwnd:
self.user32.ShowWindow(hwnd, 9)
self.user32.SetForegroundWindow(hwnd)
def close(self):
with self.lock_config:
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
def close(self, *args, **kwargs):
if self.tray_icon:
try:
self.tray_icon.stop()
except Exception:
pass
self.tray_icon = None
self.stop_system_driver()
if self._window:
self._window.destroy()
os._exit(0)
try:
self._window.hide()
except Exception:
pass
def _cleanup_and_exit():
with self.lock_config:
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()
if self._window:
self._window.destroy()
os._exit(0)
threading.Thread(target=_cleanup_and_exit, daemon=True).start()
def get_file_version(self, file_path):
try:
@@ -257,7 +307,12 @@ class WindowAPI:
####################################################################################################
def init_environ(self):
self.file_pyas = self.norm_path(sys.argv[0])
self.python = sys.executable
if getattr(sys, 'frozen', False):
self.file_pyas = self.norm_path(sys.executable)
else:
self.file_pyas = self.norm_path(os.path.abspath(sys.argv[0]))
self.args_pyas = sys.argv[1:]
self.path_pyas = os.path.dirname(self.file_pyas)
self.pid_pyas = int(os.getpid())
@@ -365,6 +420,7 @@ class WindowAPI:
self.mbr_backup = {}
self.logs_data = []
self.tray_icon = None
self.engine_initialized = False
self.pyas_default = {
"version": "3.5.0",
@@ -383,6 +439,7 @@ class WindowAPI:
"extension_switch": False,
"sensitive_switch": False,
"cloud_switch": True,
"context_switch": False,
"custom_rule": [],
"white_list": [],
"quarantine": [],
@@ -441,6 +498,8 @@ class WindowAPI:
self.start_daemon_thread(self.protect_system_thread)
elif key == "network_switch" and value:
self.start_daemon_thread(self.protect_net_thread)
elif key == "context_switch":
self.register_context_menu(value)
elif key == "driver_switch":
if value:
if self.install_system_driver():
@@ -458,6 +517,40 @@ class WindowAPI:
with self.lock_config:
return self.pyas_config.copy()
####################################################################################################
def register_context_menu(self, enable):
paths = [
r"Software\Classes\*\shell\PYAS_Scan",
r"Software\Classes\Directory\shell\PYAS_Scan"
]
if getattr(sys, 'frozen', False):
cmd_path = f'"{self.file_pyas}"'
else:
cmd_path = f'"{self.python}" "{self.file_pyas}"'
try:
for path in paths:
if enable:
with winreg.CreateKey(winreg.HKEY_CURRENT_USER, path) as key:
winreg.SetValue(key, "", winreg.REG_SZ, "PYAS Security Scan")
winreg.SetValueEx(key, "Icon", 0, winreg.REG_SZ, f'{cmd_path},0')
with winreg.CreateKey(winreg.HKEY_CURRENT_USER, rf"{path}\command") as key:
winreg.SetValue(key, "", winreg.REG_SZ, f'{cmd_path} -scan "%1"')
else:
try:
winreg.DeleteKey(winreg.HKEY_CURRENT_USER, rf"{path}\command")
winreg.DeleteKey(winreg.HKEY_CURRENT_USER, path)
except FileNotFoundError:
pass
except Exception as e:
self.write_log("WARN", "register_context_menu", detail=str(e), success=False)
def trigger_context_scan(self, target):
if self._window:
self._window.evaluate_js(f"if(window.triggerContextScan) window.triggerContextScan({json.dumps(target.replace(os.sep, '/'))});")
####################################################################################################
def show_notification(self, title, message):
@@ -711,6 +804,14 @@ class WindowAPI:
self.properties.load_path(self.path_properties, callback=log_callback)
self.write_log("INFO", "System", detail="Engine Initialization Complete")
if "-scan" in self.args_pyas:
try:
idx = self.args_pyas.index("-scan")
target = self.args_pyas[idx+1]
self.trigger_context_scan(target)
except Exception:
pass
with self.lock_config:
if self.pyas_config.get("process_switch"): self.start_daemon_thread(self.protect_proc_thread)
if self.pyas_config.get("document_switch"): self.start_daemon_thread(self.protect_file_thread)
@@ -725,7 +826,6 @@ class WindowAPI:
except Exception as e:
self.write_log("WARN", "init_engine_thread", detail=str(e), success=False)
def backup_mbr(self, max_drives=26):
self.mbr_backup = {}
mbr_signature = b"\x55\xAA"
@@ -898,6 +998,8 @@ class WindowAPI:
def solve_scan(self, file_paths):
deleted_paths = []
running_procs = self.get_process_list()
with self.lock_virus:
for path in file_paths:
if self._window:
@@ -907,9 +1009,18 @@ class WindowAPI:
if path in self.virus_lock:
self.lock_file(path, False)
os.remove(path)
deleted_paths.append(path)
for proc in running_procs:
if self.path_equal(proc["path"], path):
self.kill_process(proc["pid"])
try:
os.chmod(path, stat.S_IWRITE)
except Exception:
pass
os.remove(path)
deleted_paths.append(path)
if path in self.virus_results:
self.virus_results.remove(path)
@@ -1634,6 +1745,10 @@ class WindowAPI:
self.ntdll.NtSuspendProcess(h)
try:
cmdline = self.get_process_cmdline(h)
if "-scan" in cmdline and self.path_equal(self.get_process_file(h), self.file_pyas):
return
paths = self.extract_paths_from_cmdline(cmdline)
with self.lock_config:
suffix = self.pyas_config.get("suffix", [])
@@ -1724,6 +1839,10 @@ class WindowAPI:
hDir = self.kernel32.CreateFileW(self.path_user, 0x0001, 0x00000007, None, 3, 0x02000000, None)
buffer = ctypes.create_string_buffer(65536)
temp_prefix = os.path.normcase(self.path_temp)
if not temp_prefix.endswith(os.sep):
temp_prefix += os.sep
while True:
with self.lock_config:
if not self.pyas_config.get("document_switch", False):
@@ -1741,6 +1860,13 @@ class WindowAPI:
file_path = self.norm_path(os.path.join(self.path_user, raw_filename), must_exist=True)
if file_path and notify.Action in [2, 3, 4] and not self.is_in_whitelist(file_path):
norm_path = os.path.normcase(file_path)
if norm_path.startswith(temp_prefix):
sub_path = norm_path[len(temp_prefix):]
if sub_path.startswith("_mei"):
continue
with self.lock_config:
suffix = self.pyas_config.get("suffix", [])
@@ -1766,7 +1892,7 @@ class WindowAPI:
if not self.pyas_config.get("network_switch", False):
break
try:
time.sleep(0.2)
time.sleep(0.5)
conns = self.get_connections_list()
for key in conns - self.exist_connections:
@@ -1851,7 +1977,7 @@ class WindowAPI:
while time.time() - start_time < 5:
hwnd = self.user32.GetForegroundWindow()
if not hwnd:
time.sleep(0.2)
time.sleep(0.5)
continue
pid = ctypes.c_ulong(0)
@@ -1870,7 +1996,7 @@ class WindowAPI:
if proc_name and not any(item.get("exe") == proc_name or item.get("class") == c_str for item in self.pass_windows):
return {"exe": proc_name, "class": c_str, "title": t_str}
time.sleep(0.2)
time.sleep(0.5)
except Exception as e:
self.write_log("WARN", "capture_popup_window", detail=str(e), success=False)
return None
@@ -2138,24 +2264,20 @@ class WindowAPI:
WNDENUMPROC = ctypes.WINFUNCTYPE(ctypes.c_bool, ctypes.c_int, ctypes.c_int)
while True:
try:
time.sleep(0.2)
time.sleep(0.5)
with self.lock_config:
rules = self.pyas_config.get("block_list", [])
cur_ver = self.pyas_config.get("version", "0.0.0")
hwnd_list = []
def enum_windows_callback(hWnd, lParam):
hwnd_list.append(hWnd)
if self.user32.IsWindowVisible(hWnd):
hwnd_list.append(hWnd)
return True
self.user32.EnumWindows(WNDENUMPROC(enum_windows_callback), 0)
for hWnd in hwnd_list:
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(0)
self.user32.GetWindowThreadProcessId(hWnd, ctypes.byref(pid))
@@ -2163,22 +2285,32 @@ class WindowAPI:
continue
proc_name, file_path = self.get_exe_info(pid.value)
if proc_name != "PYAS_Setup.exe" and not rules:
continue
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)
t_str = str(title.value)
c_str = str(class_name.value)
if proc_name == "PYAS_Setup.exe" and c_str == "WindowClass_0" and t_str == "PYAS Setup":
if file_path:
setup_ver = self.get_file_version(file_path)
if self.compare_versions(setup_ver, cur_ver):
self.close()
if not rules:
continue
is_pass = any(item.get("exe") == proc_name or item.get("class") == c_str for item in self.pass_windows)
if is_pass:
continue
if not rules:
continue
is_block = any(item.get("exe") == proc_name or item.get("class") == c_str or item.get("title") == t_str for item in rules)
if is_block:
for msg in [0x0010, 0x0002, 0x0012, 0x0112]:
@@ -2231,11 +2363,13 @@ class NoCacheRequestHandler(SimpleHTTPRequestHandler):
####################################################################################################
class WindowHook:
def __init__(self, title):
def __init__(self, title, api_ref=None):
self.title = title
self.api_ref = api_ref
self.old_wndproc = None
self.WM_DPICHANGED = 0x02E0
self.WM_NCHITTEST = 0x0084
self.WM_COPYDATA = 0x004A
self.HTCAPTION = 2
self.GWLP_WNDPROC = -4
@@ -2275,6 +2409,25 @@ class WindowHook:
if msg == 0x0112 and (wparam & 0xFFF0) == 0xF060:
return 0
if msg == self.WM_COPYDATA:
try:
cds = ctypes.cast(lparam, ctypes.POINTER(COPYDATASTRUCT)).contents
if cds.dwData == 1:
buffer = ctypes.string_at(cds.lpData, cds.cbData)
path = buffer.decode('utf-8').strip('\x00')
if self.api_ref:
if self.api_ref._window:
self.api_ref._window.restore()
self.api_ref._window.show()
threading.Thread(target=self.api_ref.trigger_context_scan, args=(path,), daemon=True).start()
elif cds.dwData == 2:
if self.api_ref and self.api_ref._window:
self.api_ref._window.restore()
self.api_ref._window.show()
except Exception:
pass
return 1
if msg == self.WM_NCHITTEST:
x = lparam & 0xFFFF
@@ -2335,7 +2488,7 @@ if __name__ == "__main__":
background_color='#e0e0e0', hidden=hide_on_start)
if platform.system() == "Windows":
window_hook = WindowHook("PYAS Security")
window_hook = WindowHook("PYAS Security", js_api)
window.events.shown += window_hook.hook
js_api.set_window(window)