Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
7b9474521d | ||
|
|
78c3f2c9b8 | ||
|
|
ad256abb7c | ||
|
|
86d160ae5f | ||
|
|
fde6afb39e | ||
|
|
7c3e78296a | ||
|
|
9cc06ff767 | ||
|
|
766b6e81d0 | ||
|
|
0349e01629 | ||
|
|
0bd9da2bbc | ||
|
|
f14c7c3f55 | ||
|
|
ef5793fb15 | ||
|
|
9f22fe6d97 | ||
|
|
3147d576ee | ||
|
|
2ceec2588c | ||
|
|
5b4d88b9c7 | ||
|
|
55043cca36 | ||
|
|
097ebe025f | ||
|
|
9f92307f64 |
No files matched your search
+100
-79
@@ -1,6 +1,6 @@
|
||||
import os, sys, math, time, datetime, pefile, hashlib
|
||||
import ctypes, ctypes.wintypes, multiprocessing
|
||||
import re, zlib
|
||||
import re, os, sys, math, time, zlib, datetime, hashlib
|
||||
import ctypes, ctypes.wintypes, pefile, multiprocessing
|
||||
import numpy as np
|
||||
|
||||
####################################################################################################
|
||||
|
||||
@@ -109,6 +109,17 @@ def verify_signature(file_path):
|
||||
|
||||
class FeatureExtractor:
|
||||
_STRING_PATTERN = re.compile(b'[\x20-\x7E]{5,}')
|
||||
|
||||
_BYTE_HIST_KEYS = [f'ByteHist_{i:02X}' for i in range(256)]
|
||||
_BYTE_ENT_KEYS = [f'ByteEnt_{i:02X}' for i in range(256)]
|
||||
_STRING_KEYS = [f'StringHist_{i:02d}' for i in range(95)]
|
||||
_SECTION_HASH_KEYS = [f'SectionHash_{i:02d}' for i in range(50)]
|
||||
_CHAR_FLAGS = [0x00000020, 0x00000040, 0x00000080, 0x02000000, 0x20000000, 0x40000000, 0x80000000]
|
||||
_CHAR_COUNT_KEYS = {flag: f'Char_{flag:08X}_Count' for flag in _CHAR_FLAGS}
|
||||
_CHAR_ENT_KEYS = {flag: f'Char_{flag:08X}_MeanEntropy' for flag in _CHAR_FLAGS}
|
||||
|
||||
_C_LOG_C_TABLE = np.zeros(2049, dtype=np.float64)
|
||||
_C_LOG_C_TABLE[1:] = np.arange(1, 2049) * np.log2(np.arange(1, 2049))
|
||||
|
||||
@staticmethod
|
||||
def _safe_float(val):
|
||||
@@ -121,90 +132,97 @@ class FeatureExtractor:
|
||||
@staticmethod
|
||||
def _calc_entropy(data):
|
||||
if not data: return 0.0
|
||||
import numpy as np
|
||||
arr = np.frombuffer(data, dtype=np.uint8)
|
||||
sz = len(arr)
|
||||
counts = np.bincount(arr, minlength=256)
|
||||
probs = counts[counts > 0] / len(arr)
|
||||
return float(-np.sum(probs * np.log2(probs)))
|
||||
counts = counts[counts > 0]
|
||||
return float(np.log2(sz) - np.sum(counts * np.log2(counts)) / sz)
|
||||
|
||||
@classmethod
|
||||
def _extract_strings(cls, file_bytes):
|
||||
res = {"StringCount": 0.0, "StringMeanLength": 0.0, "StringEntropy": 0.0}
|
||||
for i in range(95):
|
||||
res[f'StringHist_{i:02d}'] = 0.0
|
||||
res.update(dict.fromkeys(cls._STRING_KEYS, 0.0))
|
||||
|
||||
count = 0
|
||||
total_len = 0
|
||||
char_counts = [0] * 95
|
||||
combined_data = bytearray()
|
||||
|
||||
for match in cls._STRING_PATTERN.finditer(file_bytes):
|
||||
m = match.group()
|
||||
count += 1
|
||||
length = len(m)
|
||||
total_len += length
|
||||
combined_data.extend(m)
|
||||
for b in m:
|
||||
char_counts[b - 0x20] += 1
|
||||
|
||||
if count == 0:
|
||||
matches = cls._STRING_PATTERN.findall(file_bytes)
|
||||
if not matches:
|
||||
return res
|
||||
|
||||
combined_data = b''.join(matches)
|
||||
count = len(matches)
|
||||
total_len = len(combined_data)
|
||||
|
||||
res["StringCount"] = float(count)
|
||||
res["StringMeanLength"] = float(total_len) / count
|
||||
res["StringEntropy"] = cls._calc_entropy(combined_data)
|
||||
|
||||
for i in range(95):
|
||||
res[f'StringHist_{i:02d}'] = float(char_counts[i]) / total_len
|
||||
arr = np.frombuffer(combined_data, dtype=np.uint8)
|
||||
counts = np.bincount(arr, minlength=127)
|
||||
char_counts = counts[0x20:0x7F]
|
||||
|
||||
res.update(dict(zip(cls._STRING_KEYS, char_counts.astype(np.float64) / total_len)))
|
||||
return res
|
||||
|
||||
@classmethod
|
||||
def _extract_histograms(cls, file_bytes):
|
||||
import numpy as np
|
||||
arr = np.frombuffer(file_bytes, dtype=np.uint8)
|
||||
sz = len(arr)
|
||||
res = {}
|
||||
|
||||
if sz == 0:
|
||||
for i in range(256):
|
||||
res[f'ByteHist_{i:02X}'] = 0.0
|
||||
res[f'ByteEnt_{i:02X}'] = 0.0
|
||||
res.update(dict.fromkeys(cls._BYTE_HIST_KEYS, 0.0))
|
||||
res.update(dict.fromkeys(cls._BYTE_ENT_KEYS, 0.0))
|
||||
return res
|
||||
|
||||
byte_counts = np.bincount(arr, minlength=256)
|
||||
byte_hist = byte_counts / sz
|
||||
for i, v in enumerate(byte_hist):
|
||||
res[f'ByteHist_{i:02X}'] = float(v)
|
||||
byte_hist = byte_counts.astype(np.float64) / sz
|
||||
res.update(dict(zip(cls._BYTE_HIST_KEYS, byte_hist)))
|
||||
|
||||
ent_hist = np.zeros(256, dtype=np.float64)
|
||||
window = 2048
|
||||
step = 1024
|
||||
num_windows = (sz - window) // step + 1 if sz >= window else 1
|
||||
|
||||
for i in range(num_windows):
|
||||
w = arr if sz < window else arr[i*step : i*step+window]
|
||||
w_counts = np.bincount(w, minlength=256)
|
||||
|
||||
p = w_counts[w_counts > 0] / len(w)
|
||||
entropy = -np.sum(p * np.log2(p))
|
||||
|
||||
ent_bin = int(entropy * 2.0)
|
||||
if ent_bin > 15:
|
||||
ent_bin = 15
|
||||
|
||||
if sz < window:
|
||||
w_counts = byte_counts
|
||||
p = w_counts[w_counts > 0] / float(sz)
|
||||
entropy = float(-np.sum(p * np.log2(p)))
|
||||
ent_bin = min(int(entropy * 2.0), 15)
|
||||
byte_bins = w_counts.reshape(16, 16).sum(axis=1)
|
||||
|
||||
idx_start = ent_bin * 16
|
||||
ent_hist[idx_start : idx_start+16] += byte_bins
|
||||
else:
|
||||
num_windows = (sz - window) // step + 1
|
||||
batch_windows = 10000
|
||||
|
||||
for w_start in range(0, num_windows, batch_windows):
|
||||
w_end = min(w_start + batch_windows, num_windows)
|
||||
b_num = w_end - w_start
|
||||
|
||||
start_byte = w_start * step
|
||||
end_byte = start_byte + b_num * step + step
|
||||
b_arr = arr[start_byte:end_byte]
|
||||
|
||||
arr_2d = b_arr.reshape(b_num + 1, step)
|
||||
offsets = (np.arange(b_num + 1, dtype=np.int32)[:, None] * 256)
|
||||
flat_idx = (arr_2d.astype(np.int32) + offsets).ravel()
|
||||
|
||||
chunk_counts = np.bincount(flat_idx, minlength=(b_num + 1) * 256).reshape(b_num + 1, 256)
|
||||
window_counts = chunk_counts[:-1] + chunk_counts[1:]
|
||||
|
||||
sum_c_log_c = np.sum(cls._C_LOG_C_TABLE[window_counts], axis=1)
|
||||
entropies = 11.0 - sum_c_log_c / 2048.0
|
||||
|
||||
ent_bins = (entropies * 2.0).astype(np.int32)
|
||||
np.clip(ent_bins, 0, 15, out=ent_bins)
|
||||
|
||||
byte_bins = window_counts.reshape(b_num, 16, 16).sum(axis=2)
|
||||
flat_ent_bins = (ent_bins[:, None] * 16 + np.arange(16)).ravel()
|
||||
np.add.at(ent_hist, flat_ent_bins, byte_bins.ravel())
|
||||
|
||||
ent_sum = np.sum(ent_hist)
|
||||
if ent_sum > 0:
|
||||
ent_hist /= ent_sum
|
||||
|
||||
for i, v in enumerate(ent_hist):
|
||||
res[f'ByteEnt_{i:02X}'] = float(v)
|
||||
|
||||
res.update(dict(zip(cls._BYTE_ENT_KEYS, ent_hist)))
|
||||
return res
|
||||
|
||||
@classmethod
|
||||
@@ -373,9 +391,9 @@ class FeatureExtractor:
|
||||
except Exception:
|
||||
pass
|
||||
|
||||
base['TrustSigned'] = verify_signature(file_path)
|
||||
base['TrustSigned'] = float(verify_signature(file_path))
|
||||
base['FileEntropy'] = cls._calc_entropy(file_bytes)
|
||||
base['FileSize'] = fsize
|
||||
base['FileSize'] = float(fsize)
|
||||
|
||||
fh = pe.FILE_HEADER
|
||||
base['Machine'] = getattr(fh, 'Machine', 0)
|
||||
@@ -410,13 +428,9 @@ class FeatureExtractor:
|
||||
if len(op.DATA_DIRECTORY) > exc_idx and op.DATA_DIRECTORY[exc_idx].Size > 0:
|
||||
base['ExceptionCount'] = op.DATA_DIRECTORY[exc_idx].Size // 12 if base['Machine'] in (0x8664, 0xAA64) else 0.0
|
||||
|
||||
char_flags = [0x00000020, 0x00000040, 0x00000080, 0x02000000, 0x20000000, 0x40000000, 0x80000000]
|
||||
for flag in char_flags:
|
||||
base[f'Char_{flag:08X}_Count'] = 0.0
|
||||
base[f'Char_{flag:08X}_MeanEntropy'] = 0.0
|
||||
|
||||
for i in range(50):
|
||||
base[f'SectionHash_{i:02d}'] = 0.0
|
||||
res_flags_count = dict.fromkeys(cls._CHAR_COUNT_KEYS.values(), 0.0)
|
||||
res_flags_ent = dict.fromkeys(cls._CHAR_ENT_KEYS.values(), 0.0)
|
||||
res_sec_hash = dict.fromkeys(cls._SECTION_HASH_KEYS, 0.0)
|
||||
|
||||
if hasattr(pe, 'sections'):
|
||||
base['SectionCount'] = len(pe.sections)
|
||||
@@ -426,7 +440,7 @@ class FeatureExtractor:
|
||||
for section in pe.sections:
|
||||
sec_name = section.Name.rstrip(b'\x00')
|
||||
hash_val = zlib.crc32(sec_name) % 50
|
||||
base[f'SectionHash_{hash_val:02d}'] += 1.0
|
||||
res_sec_hash[cls._SECTION_HASH_KEYS[hash_val]] += 1.0
|
||||
|
||||
try:
|
||||
s_data = section.get_data()
|
||||
@@ -443,10 +457,10 @@ class FeatureExtractor:
|
||||
if section.Characteristics & 0x40000000: read_sec += 1
|
||||
if section.SizeOfRawData + section.PointerToRawData > fsize: sec_exc = 1
|
||||
|
||||
for flag in char_flags:
|
||||
for flag in cls._CHAR_FLAGS:
|
||||
if section.Characteristics & flag:
|
||||
base[f'Char_{flag:08X}_Count'] += 1.0
|
||||
base[f'Char_{flag:08X}_MeanEntropy'] += s_entropy
|
||||
res_flags_count[cls._CHAR_COUNT_KEYS[flag]] += 1.0
|
||||
res_flags_ent[cls._CHAR_ENT_KEYS[flag]] += s_entropy
|
||||
|
||||
base['SectionMaxEntropy'] = max(entropies) if entropies else 0.0
|
||||
base['SectionMinEntropy'] = min(entropies) if entropies else 0.0
|
||||
@@ -457,14 +471,19 @@ class FeatureExtractor:
|
||||
base['SectionMaxVSize'] = max(v_sizes) if v_sizes else 0.0
|
||||
base['SectionMinVSize'] = min(v_sizes) if v_sizes else 0.0
|
||||
base['SectionMeanVSize'] = sum(v_sizes) / len(v_sizes) if v_sizes else 0.0
|
||||
base['ExecutableSections'] = exec_sec
|
||||
base['WritableSections'] = write_sec
|
||||
base['ReadableSections'] = read_sec
|
||||
base['ExecutableSections'] = float(exec_sec)
|
||||
base['WritableSections'] = float(write_sec)
|
||||
base['ReadableSections'] = float(read_sec)
|
||||
base['SectionException'] = float(sec_exc)
|
||||
|
||||
for flag in char_flags:
|
||||
if base[f'Char_{flag:08X}_Count'] > 0:
|
||||
base[f'Char_{flag:08X}_MeanEntropy'] = base[f'Char_{flag:08X}_MeanEntropy'] / base[f'Char_{flag:08X}_Count']
|
||||
for flag in cls._CHAR_FLAGS:
|
||||
cnt_key = cls._CHAR_COUNT_KEYS[flag]
|
||||
if res_flags_count[cnt_key] > 0:
|
||||
res_flags_ent[cls._CHAR_ENT_KEYS[flag]] /= res_flags_count[cnt_key]
|
||||
|
||||
base.update(res_flags_count)
|
||||
base.update(res_flags_ent)
|
||||
base.update(res_sec_hash)
|
||||
|
||||
string_keys = ['FileDescription', 'FileVersion', 'ProductName', 'ProductVersion', 'CompanyName', 'LegalCopyright', 'Comments', 'InternalName', 'LegalTrademarks', 'SpecialBuild', 'PrivateBuild']
|
||||
for key in string_keys:
|
||||
@@ -496,7 +515,7 @@ class FeatureExtractor:
|
||||
base['IsSpecialBuild'] = 1.0 if flags & 0x20 else 0.0
|
||||
|
||||
if hasattr(pe, 'DIRECTORY_ENTRY_IMPORT'):
|
||||
base['ImportCount'] = len(pe.DIRECTORY_ENTRY_IMPORT)
|
||||
base['ImportCount'] = float(len(pe.DIRECTORY_ENTRY_IMPORT))
|
||||
func_count = 0
|
||||
for entry in pe.DIRECTORY_ENTRY_IMPORT:
|
||||
if getattr(entry, 'dll', None):
|
||||
@@ -507,20 +526,20 @@ class FeatureExtractor:
|
||||
if getattr(imp, 'name', None):
|
||||
try: apis.add(imp.name.decode('ascii', 'ignore'))
|
||||
except Exception: pass
|
||||
base['ImportFunctionCount'] = func_count
|
||||
base['ImportFunctionCount'] = float(func_count)
|
||||
|
||||
base['ExportCount'] = len(pe.DIRECTORY_ENTRY_EXPORT.symbols) if (hasattr(pe, 'DIRECTORY_ENTRY_EXPORT') and hasattr(pe.DIRECTORY_ENTRY_EXPORT, 'symbols')) else 0.0
|
||||
base['ExportCount'] = float(len(pe.DIRECTORY_ENTRY_EXPORT.symbols)) if (hasattr(pe, 'DIRECTORY_ENTRY_EXPORT') and hasattr(pe.DIRECTORY_ENTRY_EXPORT, 'symbols')) else 0.0
|
||||
|
||||
if hasattr(pe, 'DIRECTORY_ENTRY_RESOURCE'):
|
||||
icon_count = 0
|
||||
for entry in pe.DIRECTORY_ENTRY_RESOURCE.entries:
|
||||
if getattr(entry, 'id', None) == 3 and hasattr(entry, 'directory'):
|
||||
icon_count += len(getattr(entry.directory, 'entries', []))
|
||||
base['IconCount'] = icon_count
|
||||
base['IconCount'] = float(icon_count)
|
||||
else:
|
||||
base['IconCount'] = 0.0
|
||||
|
||||
base['DebugCount'] = len(pe.DIRECTORY_ENTRY_DEBUG) if hasattr(pe, 'DIRECTORY_ENTRY_DEBUG') else 0.0
|
||||
base['DebugCount'] = float(len(pe.DIRECTORY_ENTRY_DEBUG)) if hasattr(pe, 'DIRECTORY_ENTRY_DEBUG') else 0.0
|
||||
|
||||
base.update(cls._extract_strings(file_bytes))
|
||||
base.update(cls._extract_histograms(file_bytes))
|
||||
@@ -542,13 +561,15 @@ class FeatureExtractor:
|
||||
|
||||
def load_existing_hashes():
|
||||
hashes = set()
|
||||
if os.path.exists(JSONL_PATH):
|
||||
with open(JSONL_PATH, 'r', encoding='utf-8') as f:
|
||||
for line in f:
|
||||
try:
|
||||
data = JSON_LOADS(line)
|
||||
if 'FileHash' in data: hashes.add(data['FileHash'])
|
||||
except Exception: pass
|
||||
if not os.path.exists(JSONL_PATH):
|
||||
return hashes
|
||||
|
||||
pattern = re.compile(rb'"FileHash"\s*:\s*"([a-fA-F0-9]{64})"')
|
||||
with open(JSONL_PATH, 'rb') as f:
|
||||
for line in f:
|
||||
match = pattern.search(line)
|
||||
if match:
|
||||
hashes.add(match.group(1).decode('ascii'))
|
||||
return hashes
|
||||
|
||||
GLOBAL_HASHES = set()
|
||||
|
||||
@@ -1,8 +1,7 @@
|
||||
import os, sys, math, json, pefile, datetime
|
||||
import os, re, sys, math, json, zlib, pefile, datetime
|
||||
import ctypes, ctypes.wintypes
|
||||
import numpy as np
|
||||
import onnxruntime as ort
|
||||
import ctypes, ctypes.wintypes
|
||||
import re, zlib
|
||||
|
||||
####################################################################################################
|
||||
|
||||
@@ -100,6 +99,17 @@ class WinTrust:
|
||||
|
||||
class FeatureExtractor:
|
||||
_STRING_PATTERN = re.compile(b'[\x20-\x7E]{5,}')
|
||||
|
||||
_BYTE_HIST_KEYS = [f'ByteHist_{i:02X}' for i in range(256)]
|
||||
_BYTE_ENT_KEYS = [f'ByteEnt_{i:02X}' for i in range(256)]
|
||||
_STRING_KEYS = [f'StringHist_{i:02d}' for i in range(95)]
|
||||
_SECTION_HASH_KEYS = [f'SectionHash_{i:02d}' for i in range(50)]
|
||||
_CHAR_FLAGS = [0x00000020, 0x00000040, 0x00000080, 0x02000000, 0x20000000, 0x40000000, 0x80000000]
|
||||
_CHAR_COUNT_KEYS = {flag: f'Char_{flag:08X}_Count' for flag in _CHAR_FLAGS}
|
||||
_CHAR_ENT_KEYS = {flag: f'Char_{flag:08X}_MeanEntropy' for flag in _CHAR_FLAGS}
|
||||
|
||||
_C_LOG_C_TABLE = np.zeros(2049, dtype=np.float64)
|
||||
_C_LOG_C_TABLE[1:] = np.arange(1, 2049) * np.log2(np.arange(1, 2049))
|
||||
|
||||
@staticmethod
|
||||
def _safe_float(val):
|
||||
@@ -112,90 +122,97 @@ class FeatureExtractor:
|
||||
@staticmethod
|
||||
def _calc_entropy(data):
|
||||
if not data: return 0.0
|
||||
import numpy as np
|
||||
arr = np.frombuffer(data, dtype=np.uint8)
|
||||
sz = len(arr)
|
||||
counts = np.bincount(arr, minlength=256)
|
||||
probs = counts[counts > 0] / len(arr)
|
||||
return float(-np.sum(probs * np.log2(probs)))
|
||||
counts = counts[counts > 0]
|
||||
return float(np.log2(sz) - np.sum(counts * np.log2(counts)) / sz)
|
||||
|
||||
@classmethod
|
||||
def _extract_strings(cls, file_bytes):
|
||||
res = {"StringCount": 0.0, "StringMeanLength": 0.0, "StringEntropy": 0.0}
|
||||
for i in range(95):
|
||||
res[f'StringHist_{i:02d}'] = 0.0
|
||||
res.update(dict.fromkeys(cls._STRING_KEYS, 0.0))
|
||||
|
||||
count = 0
|
||||
total_len = 0
|
||||
char_counts = [0] * 95
|
||||
combined_data = bytearray()
|
||||
|
||||
for match in cls._STRING_PATTERN.finditer(file_bytes):
|
||||
m = match.group()
|
||||
count += 1
|
||||
length = len(m)
|
||||
total_len += length
|
||||
combined_data.extend(m)
|
||||
for b in m:
|
||||
char_counts[b - 0x20] += 1
|
||||
|
||||
if count == 0:
|
||||
matches = cls._STRING_PATTERN.findall(file_bytes)
|
||||
if not matches:
|
||||
return res
|
||||
|
||||
combined_data = b''.join(matches)
|
||||
count = len(matches)
|
||||
total_len = len(combined_data)
|
||||
|
||||
res["StringCount"] = float(count)
|
||||
res["StringMeanLength"] = float(total_len) / count
|
||||
res["StringEntropy"] = cls._calc_entropy(combined_data)
|
||||
|
||||
for i in range(95):
|
||||
res[f'StringHist_{i:02d}'] = float(char_counts[i]) / total_len
|
||||
arr = np.frombuffer(combined_data, dtype=np.uint8)
|
||||
counts = np.bincount(arr, minlength=127)
|
||||
char_counts = counts[0x20:0x7F]
|
||||
|
||||
res.update(dict(zip(cls._STRING_KEYS, char_counts.astype(np.float64) / total_len)))
|
||||
return res
|
||||
|
||||
@classmethod
|
||||
def _extract_histograms(cls, file_bytes):
|
||||
import numpy as np
|
||||
arr = np.frombuffer(file_bytes, dtype=np.uint8)
|
||||
sz = len(arr)
|
||||
res = {}
|
||||
|
||||
if sz == 0:
|
||||
for i in range(256):
|
||||
res[f'ByteHist_{i:02X}'] = 0.0
|
||||
res[f'ByteEnt_{i:02X}'] = 0.0
|
||||
res.update(dict.fromkeys(cls._BYTE_HIST_KEYS, 0.0))
|
||||
res.update(dict.fromkeys(cls._BYTE_ENT_KEYS, 0.0))
|
||||
return res
|
||||
|
||||
byte_counts = np.bincount(arr, minlength=256)
|
||||
byte_hist = byte_counts / sz
|
||||
for i, v in enumerate(byte_hist):
|
||||
res[f'ByteHist_{i:02X}'] = float(v)
|
||||
byte_hist = byte_counts.astype(np.float64) / sz
|
||||
res.update(dict(zip(cls._BYTE_HIST_KEYS, byte_hist)))
|
||||
|
||||
ent_hist = np.zeros(256, dtype=np.float64)
|
||||
window = 2048
|
||||
step = 1024
|
||||
num_windows = (sz - window) // step + 1 if sz >= window else 1
|
||||
|
||||
for i in range(num_windows):
|
||||
w = arr if sz < window else arr[i*step : i*step+window]
|
||||
w_counts = np.bincount(w, minlength=256)
|
||||
|
||||
p = w_counts[w_counts > 0] / len(w)
|
||||
entropy = -np.sum(p * np.log2(p))
|
||||
|
||||
ent_bin = int(entropy * 2.0)
|
||||
if ent_bin > 15:
|
||||
ent_bin = 15
|
||||
|
||||
if sz < window:
|
||||
w_counts = byte_counts
|
||||
p = w_counts[w_counts > 0] / float(sz)
|
||||
entropy = float(-np.sum(p * np.log2(p)))
|
||||
ent_bin = min(int(entropy * 2.0), 15)
|
||||
byte_bins = w_counts.reshape(16, 16).sum(axis=1)
|
||||
|
||||
idx_start = ent_bin * 16
|
||||
ent_hist[idx_start : idx_start+16] += byte_bins
|
||||
else:
|
||||
num_windows = (sz - window) // step + 1
|
||||
batch_windows = 10000
|
||||
|
||||
for w_start in range(0, num_windows, batch_windows):
|
||||
w_end = min(w_start + batch_windows, num_windows)
|
||||
b_num = w_end - w_start
|
||||
|
||||
start_byte = w_start * step
|
||||
end_byte = start_byte + b_num * step + step
|
||||
b_arr = arr[start_byte:end_byte]
|
||||
|
||||
arr_2d = b_arr.reshape(b_num + 1, step)
|
||||
offsets = (np.arange(b_num + 1, dtype=np.int32)[:, None] * 256)
|
||||
flat_idx = (arr_2d.astype(np.int32) + offsets).ravel()
|
||||
|
||||
chunk_counts = np.bincount(flat_idx, minlength=(b_num + 1) * 256).reshape(b_num + 1, 256)
|
||||
window_counts = chunk_counts[:-1] + chunk_counts[1:]
|
||||
|
||||
sum_c_log_c = np.sum(cls._C_LOG_C_TABLE[window_counts], axis=1)
|
||||
entropies = 11.0 - sum_c_log_c / 2048.0
|
||||
|
||||
ent_bins = (entropies * 2.0).astype(np.int32)
|
||||
np.clip(ent_bins, 0, 15, out=ent_bins)
|
||||
|
||||
byte_bins = window_counts.reshape(b_num, 16, 16).sum(axis=2)
|
||||
flat_ent_bins = (ent_bins[:, None] * 16 + np.arange(16)).ravel()
|
||||
np.add.at(ent_hist, flat_ent_bins, byte_bins.ravel())
|
||||
|
||||
ent_sum = np.sum(ent_hist)
|
||||
if ent_sum > 0:
|
||||
ent_hist /= ent_sum
|
||||
|
||||
for i, v in enumerate(ent_hist):
|
||||
res[f'ByteEnt_{i:02X}'] = float(v)
|
||||
|
||||
res.update(dict(zip(cls._BYTE_ENT_KEYS, ent_hist)))
|
||||
return res
|
||||
|
||||
@classmethod
|
||||
@@ -407,13 +424,9 @@ class FeatureExtractor:
|
||||
if len(op.DATA_DIRECTORY) > exc_idx and op.DATA_DIRECTORY[exc_idx].Size > 0:
|
||||
base['ExceptionCount'] = op.DATA_DIRECTORY[exc_idx].Size // 12 if base['Machine'] in (0x8664, 0xAA64) else 0.0
|
||||
|
||||
char_flags = [0x00000020, 0x00000040, 0x00000080, 0x02000000, 0x20000000, 0x40000000, 0x80000000]
|
||||
for flag in char_flags:
|
||||
base[f'Char_{flag:08X}_Count'] = 0.0
|
||||
base[f'Char_{flag:08X}_MeanEntropy'] = 0.0
|
||||
|
||||
for i in range(50):
|
||||
base[f'SectionHash_{i:02d}'] = 0.0
|
||||
res_flags_count = dict.fromkeys(cls._CHAR_COUNT_KEYS.values(), 0.0)
|
||||
res_flags_ent = dict.fromkeys(cls._CHAR_ENT_KEYS.values(), 0.0)
|
||||
res_sec_hash = dict.fromkeys(cls._SECTION_HASH_KEYS, 0.0)
|
||||
|
||||
if hasattr(pe, 'sections'):
|
||||
base['SectionCount'] = len(pe.sections)
|
||||
@@ -423,7 +436,7 @@ class FeatureExtractor:
|
||||
for section in pe.sections:
|
||||
sec_name = section.Name.rstrip(b'\x00')
|
||||
hash_val = zlib.crc32(sec_name) % 50
|
||||
base[f'SectionHash_{hash_val:02d}'] += 1.0
|
||||
res_sec_hash[cls._SECTION_HASH_KEYS[hash_val]] += 1.0
|
||||
|
||||
try:
|
||||
s_data = section.get_data()
|
||||
@@ -440,10 +453,10 @@ class FeatureExtractor:
|
||||
if section.Characteristics & 0x40000000: read_sec += 1
|
||||
if section.SizeOfRawData + section.PointerToRawData > fsize: sec_exc = 1
|
||||
|
||||
for flag in char_flags:
|
||||
for flag in cls._CHAR_FLAGS:
|
||||
if section.Characteristics & flag:
|
||||
base[f'Char_{flag:08X}_Count'] += 1.0
|
||||
base[f'Char_{flag:08X}_MeanEntropy'] += s_entropy
|
||||
res_flags_count[cls._CHAR_COUNT_KEYS[flag]] += 1.0
|
||||
res_flags_ent[cls._CHAR_ENT_KEYS[flag]] += s_entropy
|
||||
|
||||
base['SectionMaxEntropy'] = max(entropies) if entropies else 0.0
|
||||
base['SectionMinEntropy'] = min(entropies) if entropies else 0.0
|
||||
@@ -454,14 +467,19 @@ class FeatureExtractor:
|
||||
base['SectionMaxVSize'] = max(v_sizes) if v_sizes else 0.0
|
||||
base['SectionMinVSize'] = min(v_sizes) if v_sizes else 0.0
|
||||
base['SectionMeanVSize'] = sum(v_sizes) / len(v_sizes) if v_sizes else 0.0
|
||||
base['ExecutableSections'] = exec_sec
|
||||
base['WritableSections'] = write_sec
|
||||
base['ReadableSections'] = read_sec
|
||||
base['ExecutableSections'] = float(exec_sec)
|
||||
base['WritableSections'] = float(write_sec)
|
||||
base['ReadableSections'] = float(read_sec)
|
||||
base['SectionException'] = float(sec_exc)
|
||||
|
||||
for flag in char_flags:
|
||||
if base[f'Char_{flag:08X}_Count'] > 0:
|
||||
base[f'Char_{flag:08X}_MeanEntropy'] = base[f'Char_{flag:08X}_MeanEntropy'] / base[f'Char_{flag:08X}_Count']
|
||||
for flag in cls._CHAR_FLAGS:
|
||||
cnt_key = cls._CHAR_COUNT_KEYS[flag]
|
||||
if res_flags_count[cnt_key] > 0:
|
||||
res_flags_ent[cls._CHAR_ENT_KEYS[flag]] /= res_flags_count[cnt_key]
|
||||
|
||||
base.update(res_flags_count)
|
||||
base.update(res_flags_ent)
|
||||
base.update(res_sec_hash)
|
||||
|
||||
string_keys = ['FileDescription', 'FileVersion', 'ProductName', 'ProductVersion', 'CompanyName', 'LegalCopyright', 'Comments', 'InternalName', 'LegalTrademarks', 'SpecialBuild', 'PrivateBuild']
|
||||
for key in string_keys:
|
||||
@@ -493,7 +511,7 @@ class FeatureExtractor:
|
||||
base['IsSpecialBuild'] = 1.0 if flags & 0x20 else 0.0
|
||||
|
||||
if hasattr(pe, 'DIRECTORY_ENTRY_IMPORT'):
|
||||
base['ImportCount'] = len(pe.DIRECTORY_ENTRY_IMPORT)
|
||||
base['ImportCount'] = float(len(pe.DIRECTORY_ENTRY_IMPORT))
|
||||
func_count = 0
|
||||
for entry in pe.DIRECTORY_ENTRY_IMPORT:
|
||||
if getattr(entry, 'dll', None):
|
||||
@@ -504,20 +522,20 @@ class FeatureExtractor:
|
||||
if getattr(imp, 'name', None):
|
||||
try: apis.add(imp.name.decode('ascii', 'ignore'))
|
||||
except Exception: pass
|
||||
base['ImportFunctionCount'] = func_count
|
||||
base['ImportFunctionCount'] = float(func_count)
|
||||
|
||||
base['ExportCount'] = len(pe.DIRECTORY_ENTRY_EXPORT.symbols) if (hasattr(pe, 'DIRECTORY_ENTRY_EXPORT') and hasattr(pe.DIRECTORY_ENTRY_EXPORT, 'symbols')) else 0.0
|
||||
base['ExportCount'] = float(len(pe.DIRECTORY_ENTRY_EXPORT.symbols)) if (hasattr(pe, 'DIRECTORY_ENTRY_EXPORT') and hasattr(pe.DIRECTORY_ENTRY_EXPORT, 'symbols')) else 0.0
|
||||
|
||||
if hasattr(pe, 'DIRECTORY_ENTRY_RESOURCE'):
|
||||
icon_count = 0
|
||||
for entry in pe.DIRECTORY_ENTRY_RESOURCE.entries:
|
||||
if getattr(entry, 'id', None) == 3 and hasattr(entry, 'directory'):
|
||||
icon_count += len(getattr(entry.directory, 'entries', []))
|
||||
base['IconCount'] = icon_count
|
||||
base['IconCount'] = float(icon_count)
|
||||
else:
|
||||
base['IconCount'] = 0.0
|
||||
|
||||
base['DebugCount'] = len(pe.DIRECTORY_ENTRY_DEBUG) if hasattr(pe, 'DIRECTORY_ENTRY_DEBUG') else 0.0
|
||||
base['DebugCount'] = float(len(pe.DIRECTORY_ENTRY_DEBUG)) if hasattr(pe, 'DIRECTORY_ENTRY_DEBUG') else 0.0
|
||||
|
||||
base.update(cls._extract_strings(file_bytes))
|
||||
base.update(cls._extract_histograms(file_bytes))
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import os, sys, time, json, onnxmltools, zlib
|
||||
import os, sys, time, json, zlib, array, onnxmltools, multiprocessing
|
||||
import numpy as np
|
||||
import pandas as pd
|
||||
import lightgbm as lgb
|
||||
@@ -55,14 +55,15 @@ def analyze_schema(jsonl_path):
|
||||
|
||||
with open(jsonl_path, 'rb') as f:
|
||||
for line in f:
|
||||
try:
|
||||
data = JSON_LOADS(line)
|
||||
base_data = data.get('Base')
|
||||
if base_data:
|
||||
base_keys.update(base_data.keys())
|
||||
num_lines += 1
|
||||
except Exception:
|
||||
continue
|
||||
num_lines += 1
|
||||
if not base_keys:
|
||||
try:
|
||||
data = JSON_LOADS(line)
|
||||
base_data = data.get('Base')
|
||||
if base_data:
|
||||
base_keys.update(base_data.keys())
|
||||
except Exception:
|
||||
pass
|
||||
|
||||
base_schema = sorted([k for k in base_keys if k.lower().strip() not in EXCLUDE_FEATURES])
|
||||
dll_schema = [f"DllHash_{i:03d}" for i in range(DLL_HASH_DIM)]
|
||||
@@ -73,28 +74,69 @@ def analyze_schema(jsonl_path):
|
||||
####################################################################################################
|
||||
|
||||
def load_data_efficiently(jsonl_path, base_schema, dll_schema, api_schema, num_lines):
|
||||
print(f"[*] Stage 2: Building sparse feature matrix dynamically (Expected samples: {num_lines})...")
|
||||
print(f"[*] Stage 2: Building sparse feature matrix dynamically via Multiprocessing...")
|
||||
final_features = base_schema + dll_schema + api_schema
|
||||
num_features = len(final_features)
|
||||
|
||||
base_idx = {feat: i for i, feat in enumerate(base_schema)}
|
||||
offset_dll = len(base_schema)
|
||||
offset_api = offset_dll + DLL_HASH_DIM
|
||||
|
||||
rows, cols, vals = [], [], []
|
||||
y = np.zeros(num_lines, dtype=np.int8)
|
||||
file_size = os.path.getsize(jsonl_path)
|
||||
workers = min(os.cpu_count() or 4, 16)
|
||||
chunk_size = file_size // workers
|
||||
|
||||
tasks = []
|
||||
for i in range(workers):
|
||||
start = i * chunk_size
|
||||
end = file_size if i == workers - 1 else (i + 1) * chunk_size
|
||||
tasks.append((jsonl_path, start, end, base_schema, num_features, offset_dll, offset_api))
|
||||
|
||||
with multiprocessing.Pool(workers) as pool:
|
||||
results = pool.map(_chunk_worker, tasks)
|
||||
|
||||
Xs = [r[0] for r in results if r[0].shape[0] > 0]
|
||||
ys = [r[1] for r in results if r[1].shape[0] > 0]
|
||||
|
||||
X_final = sp.vstack(Xs, format='csr') if Xs else sp.csr_matrix((0, num_features), dtype=np.float32)
|
||||
y_final = np.concatenate(ys) if ys else np.array([], dtype=np.int8)
|
||||
|
||||
return X_final, pd.Series(y_final), final_features
|
||||
|
||||
with open(jsonl_path, 'rb') as f:
|
||||
def _chunk_worker(args):
|
||||
filepath, start_byte, end_byte, base_schema, num_features, offset_dll, offset_api = args
|
||||
|
||||
base_idx = {feat: i for i, feat in enumerate(base_schema)}
|
||||
base_idx_get = base_idx.get
|
||||
|
||||
rows = array.array('i')
|
||||
cols = array.array('i')
|
||||
vals = array.array('f')
|
||||
y_list = array.array('b')
|
||||
|
||||
with open(filepath, 'rb') as f:
|
||||
if start_byte > 0:
|
||||
f.seek(start_byte - 1)
|
||||
if f.read(1) != b'\n':
|
||||
f.seek(start_byte)
|
||||
f.readline()
|
||||
else:
|
||||
f.seek(start_byte)
|
||||
else:
|
||||
f.seek(0)
|
||||
|
||||
row_idx = 0
|
||||
for line in f:
|
||||
while f.tell() < end_byte:
|
||||
line = f.readline()
|
||||
if not line:
|
||||
break
|
||||
|
||||
try:
|
||||
data = JSON_LOADS(line)
|
||||
y[row_idx] = data.get('Label', 0)
|
||||
y_list.append(data.get('Label', 0))
|
||||
|
||||
base_data = data.get('Base')
|
||||
if base_data:
|
||||
for k, v in base_data.items():
|
||||
idx = base_idx.get(k)
|
||||
idx = base_idx_get(k)
|
||||
if idx is not None:
|
||||
val = float(v)
|
||||
if val != 0.0:
|
||||
@@ -121,11 +163,15 @@ def load_data_efficiently(jsonl_path, base_schema, dll_schema, api_schema, num_l
|
||||
row_idx += 1
|
||||
except Exception:
|
||||
continue
|
||||
|
||||
y = y[:row_idx]
|
||||
X = sp.csr_matrix((vals, (rows, cols)), shape=(row_idx, num_features), dtype=np.float32)
|
||||
|
||||
y_arr = np.frombuffer(y_list, dtype=np.int8)
|
||||
np_rows = np.frombuffer(rows, dtype=np.int32)
|
||||
np_cols = np.frombuffer(cols, dtype=np.int32)
|
||||
np_vals = np.frombuffer(vals, dtype=np.float32)
|
||||
|
||||
return X, pd.Series(y), final_features
|
||||
X_local = sp.csr_matrix((np_vals, (np_rows, np_cols)), shape=(row_idx, num_features), dtype=np.float32)
|
||||
|
||||
return X_local, y_arr
|
||||
|
||||
####################################################################################################
|
||||
|
||||
@@ -233,6 +279,7 @@ def evaluate_and_save(model, X_test, y_test):
|
||||
####################################################################################################
|
||||
|
||||
if __name__ == "__main__":
|
||||
multiprocessing.freeze_support()
|
||||
print("\n---------------- PE Malware Trainer v4.0 ----------------\n")
|
||||
|
||||
if not os.path.exists(JSONL_PATH):
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import os, sys, time, json, onnxmltools, zlib
|
||||
import os, sys, time, json, zlib, array, onnxmltools, multiprocessing
|
||||
import numpy as np
|
||||
import pandas as pd
|
||||
import lightgbm as lgb
|
||||
@@ -55,14 +55,15 @@ def analyze_schema(jsonl_path):
|
||||
|
||||
with open(jsonl_path, 'rb') as f:
|
||||
for line in f:
|
||||
try:
|
||||
data = JSON_LOADS(line)
|
||||
base_data = data.get('Base')
|
||||
if base_data:
|
||||
base_keys.update(base_data.keys())
|
||||
num_lines += 1
|
||||
except Exception:
|
||||
continue
|
||||
num_lines += 1
|
||||
if not base_keys:
|
||||
try:
|
||||
data = JSON_LOADS(line)
|
||||
base_data = data.get('Base')
|
||||
if base_data:
|
||||
base_keys.update(base_data.keys())
|
||||
except Exception:
|
||||
pass
|
||||
|
||||
base_schema = sorted([k for k in base_keys if k.lower().strip() not in EXCLUDE_FEATURES])
|
||||
dll_schema = [f"DllHash_{i:03d}" for i in range(DLL_HASH_DIM)]
|
||||
@@ -73,28 +74,69 @@ def analyze_schema(jsonl_path):
|
||||
####################################################################################################
|
||||
|
||||
def load_data_efficiently(jsonl_path, base_schema, dll_schema, api_schema, num_lines):
|
||||
print(f"[*] Stage 2: Building sparse feature matrix dynamically (Expected samples: {num_lines})...")
|
||||
print(f"[*] Stage 2: Building sparse feature matrix dynamically via Multiprocessing...")
|
||||
final_features = base_schema + dll_schema + api_schema
|
||||
num_features = len(final_features)
|
||||
|
||||
base_idx = {feat: i for i, feat in enumerate(base_schema)}
|
||||
offset_dll = len(base_schema)
|
||||
offset_api = offset_dll + DLL_HASH_DIM
|
||||
|
||||
rows, cols, vals = [], [], []
|
||||
y = np.zeros(num_lines, dtype=np.int8)
|
||||
file_size = os.path.getsize(jsonl_path)
|
||||
workers = min(os.cpu_count() or 4, 16)
|
||||
chunk_size = file_size // workers
|
||||
|
||||
tasks = []
|
||||
for i in range(workers):
|
||||
start = i * chunk_size
|
||||
end = file_size if i == workers - 1 else (i + 1) * chunk_size
|
||||
tasks.append((jsonl_path, start, end, base_schema, num_features, offset_dll, offset_api))
|
||||
|
||||
with multiprocessing.Pool(workers) as pool:
|
||||
results = pool.map(_chunk_worker, tasks)
|
||||
|
||||
Xs = [r[0] for r in results if r[0].shape[0] > 0]
|
||||
ys = [r[1] for r in results if r[1].shape[0] > 0]
|
||||
|
||||
X_final = sp.vstack(Xs, format='csr') if Xs else sp.csr_matrix((0, num_features), dtype=np.float32)
|
||||
y_final = np.concatenate(ys) if ys else np.array([], dtype=np.int8)
|
||||
|
||||
return X_final, pd.Series(y_final), final_features
|
||||
|
||||
with open(jsonl_path, 'rb') as f:
|
||||
def _chunk_worker(args):
|
||||
filepath, start_byte, end_byte, base_schema, num_features, offset_dll, offset_api = args
|
||||
|
||||
base_idx = {feat: i for i, feat in enumerate(base_schema)}
|
||||
base_idx_get = base_idx.get
|
||||
|
||||
rows = array.array('i')
|
||||
cols = array.array('i')
|
||||
vals = array.array('f')
|
||||
y_list = array.array('b')
|
||||
|
||||
with open(filepath, 'rb') as f:
|
||||
if start_byte > 0:
|
||||
f.seek(start_byte - 1)
|
||||
if f.read(1) != b'\n':
|
||||
f.seek(start_byte)
|
||||
f.readline()
|
||||
else:
|
||||
f.seek(start_byte)
|
||||
else:
|
||||
f.seek(0)
|
||||
|
||||
row_idx = 0
|
||||
for line in f:
|
||||
while f.tell() < end_byte:
|
||||
line = f.readline()
|
||||
if not line:
|
||||
break
|
||||
|
||||
try:
|
||||
data = JSON_LOADS(line)
|
||||
y[row_idx] = data.get('Label', 0)
|
||||
y_list.append(data.get('Label', 0))
|
||||
|
||||
base_data = data.get('Base')
|
||||
if base_data:
|
||||
for k, v in base_data.items():
|
||||
idx = base_idx.get(k)
|
||||
idx = base_idx_get(k)
|
||||
if idx is not None:
|
||||
val = float(v)
|
||||
if val != 0.0:
|
||||
@@ -121,11 +163,15 @@ def load_data_efficiently(jsonl_path, base_schema, dll_schema, api_schema, num_l
|
||||
row_idx += 1
|
||||
except Exception:
|
||||
continue
|
||||
|
||||
y = y[:row_idx]
|
||||
X = sp.csr_matrix((vals, (rows, cols)), shape=(row_idx, num_features), dtype=np.float32)
|
||||
|
||||
y_arr = np.frombuffer(y_list, dtype=np.int8)
|
||||
np_rows = np.frombuffer(rows, dtype=np.int32)
|
||||
np_cols = np.frombuffer(cols, dtype=np.int32)
|
||||
np_vals = np.frombuffer(vals, dtype=np.float32)
|
||||
|
||||
return X, pd.Series(y), final_features
|
||||
X_local = sp.csr_matrix((np_vals, (np_rows, np_cols)), shape=(row_idx, num_features), dtype=np.float32)
|
||||
|
||||
return X_local, y_arr
|
||||
|
||||
####################################################################################################
|
||||
|
||||
@@ -233,6 +279,7 @@ def evaluate_and_save(model, X_test, y_test):
|
||||
####################################################################################################
|
||||
|
||||
if __name__ == "__main__":
|
||||
multiprocessing.freeze_support()
|
||||
print("\n---------------- PE Malware Trainer v4.0 ----------------\n")
|
||||
|
||||
if not os.path.exists(JSONL_PATH):
|
||||
|
||||
+324
-71
@@ -49,6 +49,7 @@ window.AppI18n = {
|
||||
"scan_context_desc": "此選項可以啟用右鍵掃描功能",
|
||||
"theme_color": "顯色主題",
|
||||
"theme_color_desc": "此選項可以選擇主題顏色",
|
||||
"theme_system": "跟隨系統",
|
||||
"theme_white": "白色主題",
|
||||
"theme_red": "紅色主題",
|
||||
"theme_yellow": "黃色主題",
|
||||
@@ -67,18 +68,14 @@ window.AppI18n = {
|
||||
"update_check": "檢查更新",
|
||||
"update_check_desc": "此選項可以檢查可用更新",
|
||||
"search_proc": "搜尋進程...",
|
||||
"btn_view": "查看",
|
||||
"btn_end": "結束",
|
||||
"manage_white": "白名單管理",
|
||||
"search_file": "搜尋檔案...",
|
||||
"btn_add": "加入",
|
||||
"btn_remove": "移除",
|
||||
"manage_quarantine": "隔離區管理",
|
||||
"btn_scan": "掃描",
|
||||
"search_window": "搜尋窗口...",
|
||||
"search_rule": "搜尋規則...",
|
||||
"search_log": "搜尋日誌...",
|
||||
"btn_export": "導出",
|
||||
"btn_export": "導出項目",
|
||||
"msg_init": "正在初始化中",
|
||||
"msg_update_fail": "檢查更新失敗",
|
||||
"msg_new_version": "發現新版本",
|
||||
@@ -97,7 +94,6 @@ window.AppI18n = {
|
||||
"msg_click_target": "請在5秒內點擊目標視窗...",
|
||||
"msg_confirm_block": "確認攔截",
|
||||
"msg_repaired": "已修復選取項目",
|
||||
"btn_repair": "修復",
|
||||
"title_prompt": "提示",
|
||||
"title_error": "錯誤",
|
||||
"path_unknown": "未知路徑",
|
||||
@@ -134,7 +130,34 @@ window.AppI18n = {
|
||||
"res_net_block": "網路攔截",
|
||||
"res_drv_block": "驅動攔截",
|
||||
"res_sys_repair": "系統修復",
|
||||
"res_cfg_update": "設定更新"
|
||||
"res_cfg_update": "設定更新",
|
||||
"manage_startup": "啟動項管理",
|
||||
"manage_startup_desc": "此選項可以管理各應用啟動項",
|
||||
"col_status": "狀態",
|
||||
"status_enabled": "已啟用",
|
||||
"status_disabled": "已禁用",
|
||||
"search_item": "搜尋項目...",
|
||||
"type_reg": "登錄檔",
|
||||
"type_srv": "服務",
|
||||
"type_tsk": "排程任務",
|
||||
"btn_view_item": "查看項目",
|
||||
"btn_end": "結束進程",
|
||||
"btn_enable_item": "啟用項目",
|
||||
"btn_disable_item": "禁用項目",
|
||||
"btn_delete_item": "刪除項目",
|
||||
"btn_repair_item": "修復項目",
|
||||
"btn_add_item": "新增項目",
|
||||
"btn_remove_item": "移除項目",
|
||||
"btn_add_file": "新增檔案",
|
||||
"btn_add_path": "新增路徑",
|
||||
"manage_netmon": "流量監控",
|
||||
"manage_netmon_desc": "此選項可以實時監控網路流量",
|
||||
"col_down_speed": "下載速度",
|
||||
"col_up_speed": "上傳速度",
|
||||
"col_conn_count": "連接數",
|
||||
"btn_extract_item": "提取項目",
|
||||
"msg_extract_success": "成功提取選取項目",
|
||||
"msg_key_activated": "產品金鑰已啟用"
|
||||
},
|
||||
"simplified_switch": {
|
||||
"app_name": "PYAS Security",
|
||||
@@ -185,6 +208,7 @@ window.AppI18n = {
|
||||
"scan_context_desc": "此选项可以启用右键扫描功能",
|
||||
"theme_color": "显色主题",
|
||||
"theme_color_desc": "此选项可以选择主题颜色",
|
||||
"theme_system": "跟随系统",
|
||||
"theme_white": "白色主题",
|
||||
"theme_red": "红色主题",
|
||||
"theme_yellow": "黄色主题",
|
||||
@@ -203,18 +227,14 @@ window.AppI18n = {
|
||||
"update_check": "检查更新",
|
||||
"update_check_desc": "此选项可以检查可用更新",
|
||||
"search_proc": "搜索进程...",
|
||||
"btn_view": "查看",
|
||||
"btn_end": "结束",
|
||||
"manage_white": "白名单管理",
|
||||
"search_file": "搜索文件...",
|
||||
"btn_add": "加入",
|
||||
"btn_remove": "移除",
|
||||
"manage_quarantine": "隔离区管理",
|
||||
"btn_scan": "扫描",
|
||||
"search_window": "搜索窗口...",
|
||||
"search_rule": "搜索规则...",
|
||||
"search_log": "搜索日志...",
|
||||
"btn_export": "导出",
|
||||
"btn_export": "导出项目",
|
||||
"msg_init": "正在初始化中",
|
||||
"msg_update_fail": "检查更新失败",
|
||||
"msg_new_version": "发现新版本",
|
||||
@@ -233,7 +253,6 @@ window.AppI18n = {
|
||||
"msg_click_target": "请在5秒内点击目标窗口...",
|
||||
"msg_confirm_block": "确认拦截",
|
||||
"msg_repaired": "已修复选取项目",
|
||||
"btn_repair": "修复",
|
||||
"title_prompt": "提示",
|
||||
"title_error": "错误",
|
||||
"path_unknown": "未知路径",
|
||||
@@ -270,7 +289,34 @@ window.AppI18n = {
|
||||
"res_net_block": "网络拦截",
|
||||
"res_drv_block": "驱动拦截",
|
||||
"res_sys_repair": "系统修复",
|
||||
"res_cfg_update": "设置更新"
|
||||
"res_cfg_update": "设置更新",
|
||||
"manage_startup": "启动项管理",
|
||||
"manage_startup_desc": "此选项可以管理各应用启动项",
|
||||
"col_status": "状态",
|
||||
"status_enabled": "已启用",
|
||||
"status_disabled": "已禁用",
|
||||
"search_item": "搜索项目...",
|
||||
"type_reg": "注册表",
|
||||
"type_srv": "服务",
|
||||
"type_tsk": "计划任务",
|
||||
"btn_view_item": "查看项目",
|
||||
"btn_end": "结束进程",
|
||||
"btn_enable_item": "启用项目",
|
||||
"btn_disable_item": "禁用项目",
|
||||
"btn_delete_item": "删除项目",
|
||||
"btn_repair_item": "修复项目",
|
||||
"btn_add_item": "新增项目",
|
||||
"btn_remove_item": "移除项目",
|
||||
"btn_add_file": "新增文件",
|
||||
"btn_add_path": "新增路径",
|
||||
"manage_netmon": "流量监控",
|
||||
"manage_netmon_desc": "此选项可以实时监控网络流量",
|
||||
"col_down_speed": "下载速度",
|
||||
"col_up_speed": "上传速度",
|
||||
"col_conn_count": "连接数",
|
||||
"btn_extract_item": "提取项目",
|
||||
"msg_extract_success": "成功提取选取项目",
|
||||
"msg_key_activated": "产品密钥已启用"
|
||||
},
|
||||
"english_switch": {
|
||||
"app_name": "PYAS Security",
|
||||
@@ -321,6 +367,7 @@ window.AppI18n = {
|
||||
"scan_context_desc": "Enable right-click scan functionality",
|
||||
"theme_color": "Color Theme",
|
||||
"theme_color_desc": "Select application color theme",
|
||||
"theme_system": "System Theme",
|
||||
"theme_white": "Light Mode",
|
||||
"theme_red": "Red Theme",
|
||||
"theme_yellow": "Yellow Theme",
|
||||
@@ -339,18 +386,14 @@ window.AppI18n = {
|
||||
"update_check": "Check for Updates",
|
||||
"update_check_desc": "Check for the latest updates",
|
||||
"search_proc": "Search processes...",
|
||||
"btn_view": "View",
|
||||
"btn_end": "End Task",
|
||||
"manage_white": "Whitelist Manager",
|
||||
"search_file": "Search files...",
|
||||
"btn_add": "Add",
|
||||
"btn_remove": "Remove",
|
||||
"manage_quarantine": "Quarantine Manager",
|
||||
"btn_scan": "Scan",
|
||||
"search_window": "Search windows...",
|
||||
"search_rule": "Search rules...",
|
||||
"search_log": "Search logs...",
|
||||
"btn_export": "Export",
|
||||
"btn_export": "Export Items",
|
||||
"msg_init": "Initializing...",
|
||||
"msg_update_fail": "Update check failed",
|
||||
"msg_new_version": "New version found",
|
||||
@@ -369,7 +412,6 @@ window.AppI18n = {
|
||||
"msg_click_target": "Click the target window within 5s...",
|
||||
"msg_confirm_block": "Confirm Block",
|
||||
"msg_repaired": "Selected items repaired",
|
||||
"btn_repair": "Repair",
|
||||
"title_prompt": "Prompt",
|
||||
"title_error": "Error",
|
||||
"path_unknown": "Unknown Path",
|
||||
@@ -406,7 +448,34 @@ window.AppI18n = {
|
||||
"res_net_block": "Network Blocked",
|
||||
"res_drv_block": "Driver Blocked",
|
||||
"res_sys_repair": "System Repair",
|
||||
"res_cfg_update": "Config Update"
|
||||
"res_cfg_update": "Config Update",
|
||||
"manage_startup": "Startup Manager",
|
||||
"manage_startup_desc": "Manage startup applications and services",
|
||||
"col_status": "Status",
|
||||
"status_enabled": "Enabled",
|
||||
"status_disabled": "Disabled",
|
||||
"search_item": "Search items...",
|
||||
"type_reg": "Registry",
|
||||
"type_srv": "Service",
|
||||
"type_tsk": "Task",
|
||||
"btn_view_item": "View Item",
|
||||
"btn_end": "End Process",
|
||||
"btn_enable_item": "Enable Item",
|
||||
"btn_disable_item": "Disable Item",
|
||||
"btn_delete_item": "Delete Item",
|
||||
"btn_repair_item": "Repair Item",
|
||||
"btn_add_item": "Add Item",
|
||||
"btn_remove_item": "Remove Item",
|
||||
"btn_add_file": "Add File",
|
||||
"btn_add_path": "Add Path",
|
||||
"manage_netmon": "Traffic Monitor",
|
||||
"manage_netmon_desc": "Monitor network traffic in real-time",
|
||||
"col_down_speed": "Download Speed",
|
||||
"col_up_speed": "Upload Speed",
|
||||
"col_conn_count": "Connections",
|
||||
"btn_extract_item": "Extract Item",
|
||||
"msg_extract_success": "Successfully extracted selected items",
|
||||
"msg_key_activated": "Product key activated"
|
||||
},
|
||||
"japanese_switch": {
|
||||
"app_name": "PYAS Security",
|
||||
@@ -457,6 +526,7 @@ window.AppI18n = {
|
||||
"scan_context_desc": "右クリックでスキャン機能を有効にします",
|
||||
"theme_color": "カラーテーマ",
|
||||
"theme_color_desc": "アプリケーションのテーマカラーを選択します",
|
||||
"theme_system": "システムに従う",
|
||||
"theme_white": "ライトモード",
|
||||
"theme_red": "レッドテーマ",
|
||||
"theme_yellow": "イエローテーマ",
|
||||
@@ -475,12 +545,8 @@ window.AppI18n = {
|
||||
"update_check": "アップデートの確認",
|
||||
"update_check_desc": "利用可能なアップデートを確認します",
|
||||
"search_proc": "プロセスを検索...",
|
||||
"btn_view": "表示",
|
||||
"btn_end": "タスクの終了",
|
||||
"manage_white": "ホワイトリスト管理",
|
||||
"search_file": "ファイルを検索...",
|
||||
"btn_add": "追加",
|
||||
"btn_remove": "削除",
|
||||
"manage_quarantine": "隔離管理",
|
||||
"btn_scan": "スキャン",
|
||||
"search_window": "ウィンドウを検索...",
|
||||
@@ -505,7 +571,6 @@ window.AppI18n = {
|
||||
"msg_click_target": "5秒以内にターゲットウィンドウをクリックしてください...",
|
||||
"msg_confirm_block": "ブロックの確認",
|
||||
"msg_repaired": "選択された項目が修復されました",
|
||||
"btn_repair": "修復",
|
||||
"title_prompt": "プロンプト",
|
||||
"title_error": "エラー",
|
||||
"path_unknown": "不明なパス",
|
||||
@@ -542,7 +607,34 @@ window.AppI18n = {
|
||||
"res_net_block": "ネットワークブロック",
|
||||
"res_drv_block": "ドライバーブロック",
|
||||
"res_sys_repair": "システム修復",
|
||||
"res_cfg_update": "設定の更新"
|
||||
"res_cfg_update": "設定の更新",
|
||||
"manage_startup": "起動項管理",
|
||||
"manage_startup_desc": "アプリケーションの起動項目を管理します",
|
||||
"col_status": "ステータス",
|
||||
"status_enabled": "有効",
|
||||
"status_disabled": "無効",
|
||||
"search_item": "項目を検索...",
|
||||
"type_reg": "レジストリ",
|
||||
"type_srv": "サービス",
|
||||
"type_tsk": "タスク",
|
||||
"btn_view_item": "項目を表示",
|
||||
"btn_end": "プロセスを終了",
|
||||
"btn_enable_item": "項目を有効化",
|
||||
"btn_disable_item": "項目を無効化",
|
||||
"btn_delete_item": "項目を削除",
|
||||
"btn_repair_item": "項目を修復",
|
||||
"btn_add_item": "項目を追加",
|
||||
"btn_remove_item": "項目を削除",
|
||||
"btn_add_file": "ファイルを追加",
|
||||
"btn_add_path": "パスを追加",
|
||||
"manage_netmon": "トラフィック監視",
|
||||
"manage_netmon_desc": "ネットワークトラフィックをリアルタイムで監視します",
|
||||
"col_down_speed": "ダウンロード速度",
|
||||
"col_up_speed": "アップロード速度",
|
||||
"col_conn_count": "接続数",
|
||||
"btn_extract_item": "アイテムを抽出",
|
||||
"msg_extract_success": "選択したアイテムを正常に抽出しました",
|
||||
"msg_key_activated": "プロダクトキーが有効化されました"
|
||||
},
|
||||
"korean_switch": {
|
||||
"app_name": "PYAS Security",
|
||||
@@ -593,6 +685,7 @@ window.AppI18n = {
|
||||
"scan_context_desc": "우클릭 스캔 기능을 활성화합니다",
|
||||
"theme_color": "색상 테마",
|
||||
"theme_color_desc": "응용 프로그램 테마 색상을 선택합니다",
|
||||
"theme_system": "시스템 테마",
|
||||
"theme_white": "라이트 모드",
|
||||
"theme_red": "레드 테마",
|
||||
"theme_yellow": "옐로우 테마",
|
||||
@@ -611,12 +704,8 @@ window.AppI18n = {
|
||||
"update_check": "업데이트 확인",
|
||||
"update_check_desc": "사용 가능한 최신 업데이트를 확인합니다",
|
||||
"search_proc": "프로세스 검색...",
|
||||
"btn_view": "보기",
|
||||
"btn_end": "작업 끝내기",
|
||||
"manage_white": "화이트리스트 관리",
|
||||
"search_file": "파일 검색...",
|
||||
"btn_add": "추가",
|
||||
"btn_remove": "제거",
|
||||
"manage_quarantine": "격리소 관리",
|
||||
"btn_scan": "스캔",
|
||||
"search_window": "창 검색...",
|
||||
@@ -641,7 +730,6 @@ window.AppI18n = {
|
||||
"msg_click_target": "5초 이내에 대상 창을 클릭하십시오...",
|
||||
"msg_confirm_block": "차단 확인",
|
||||
"msg_repaired": "선택한 항목이 복구되었습니다",
|
||||
"btn_repair": "복구",
|
||||
"title_prompt": "프롬프트",
|
||||
"title_error": "오류",
|
||||
"path_unknown": "알 수 없는 경로",
|
||||
@@ -678,7 +766,34 @@ window.AppI18n = {
|
||||
"res_net_block": "네트워크 차단",
|
||||
"res_drv_block": "드라이버 차단",
|
||||
"res_sys_repair": "시스템 복구",
|
||||
"res_cfg_update": "설정 업데이트"
|
||||
"res_cfg_update": "설정 업데이트",
|
||||
"manage_startup": "시작 항목 관리",
|
||||
"manage_startup_desc": "애플리케이션 시작 항목을 관리합니다",
|
||||
"col_status": "상태",
|
||||
"status_enabled": "사용",
|
||||
"status_disabled": "사용 안 함",
|
||||
"search_item": "항목 검색...",
|
||||
"type_reg": "레지스트리",
|
||||
"type_srv": "서비스",
|
||||
"type_tsk": "작업",
|
||||
"btn_view_item": "항목 보기",
|
||||
"btn_end": "프로세스 종료",
|
||||
"btn_enable_item": "항목 사용",
|
||||
"btn_disable_item": "항목 사용 안 함",
|
||||
"btn_delete_item": "항목 삭제",
|
||||
"btn_repair_item": "항목 복구",
|
||||
"btn_add_item": "항목 추가",
|
||||
"btn_remove_item": "항목 제거",
|
||||
"btn_add_file": "파일 추가",
|
||||
"btn_add_path": "경로 추가",
|
||||
"manage_netmon": "트래픽 모니터링",
|
||||
"manage_netmon_desc": "실시간으로 네트워크 트래픽을 모니터링합니다",
|
||||
"col_down_speed": "다운로드 속도",
|
||||
"col_up_speed": "업로드 속도",
|
||||
"col_conn_count": "연결 수",
|
||||
"btn_extract_item": "항목 추출",
|
||||
"msg_extract_success": "선택한 항목을 성공적으로 추출했습니다",
|
||||
"msg_key_activated": "제품 키가 활성화되었습니다"
|
||||
},
|
||||
"french_switch": {
|
||||
"app_name": "PYAS Security",
|
||||
@@ -729,6 +844,7 @@ window.AppI18n = {
|
||||
"scan_context_desc": "Activer la fonctionnalité d'analyse par clic droit",
|
||||
"theme_color": "Thème de couleur",
|
||||
"theme_color_desc": "Sélectionnez le thème de couleur de l'application",
|
||||
"theme_system": "Thème du système",
|
||||
"theme_white": "Mode clair",
|
||||
"theme_red": "Thème rouge",
|
||||
"theme_yellow": "Thème jaune",
|
||||
@@ -747,12 +863,8 @@ window.AppI18n = {
|
||||
"update_check": "Vérifier les mises à jour",
|
||||
"update_check_desc": "Vérifier les dernières mises à jour",
|
||||
"search_proc": "Rechercher des processus...",
|
||||
"btn_view": "Voir",
|
||||
"btn_end": "Fin de tâche",
|
||||
"manage_white": "Gestionnaire de liste blanche",
|
||||
"search_file": "Rechercher des fichiers...",
|
||||
"btn_add": "Ajouter",
|
||||
"btn_remove": "Retirer",
|
||||
"manage_quarantine": "Gestionnaire de quarantaine",
|
||||
"btn_scan": "Analyser",
|
||||
"search_window": "Rechercher des fenêtres...",
|
||||
@@ -777,7 +889,6 @@ window.AppI18n = {
|
||||
"msg_click_target": "Cliquez sur la fenêtre cible dans les 5s...",
|
||||
"msg_confirm_block": "Confirmer le blocage",
|
||||
"msg_repaired": "Éléments sélectionnés réparés",
|
||||
"btn_repair": "Réparer",
|
||||
"title_prompt": "Indication",
|
||||
"title_error": "Erreur",
|
||||
"path_unknown": "Chemin inconnu",
|
||||
@@ -814,7 +925,34 @@ window.AppI18n = {
|
||||
"res_net_block": "Réseau bloqué",
|
||||
"res_drv_block": "Pilote bloqué",
|
||||
"res_sys_repair": "Réparation du système",
|
||||
"res_cfg_update": "Mise à jour de la configuration"
|
||||
"res_cfg_update": "Mise à jour de la configuration",
|
||||
"manage_startup": "Gestionnaire de démarrage",
|
||||
"manage_startup_desc": "Gérer les applications et services de démarrage",
|
||||
"col_status": "Statut",
|
||||
"status_enabled": "Activé",
|
||||
"status_disabled": "Désactivé",
|
||||
"search_item": "Rechercher des éléments...",
|
||||
"type_reg": "Registre",
|
||||
"type_srv": "Service",
|
||||
"type_tsk": "Tâche",
|
||||
"btn_view_item": "Afficher l'élément",
|
||||
"btn_end": "Terminer le processus",
|
||||
"btn_enable_item": "Activer l'élément",
|
||||
"btn_disable_item": "Désactiver l'élément",
|
||||
"btn_delete_item": "Supprimer l'élément",
|
||||
"btn_repair_item": "Réparer l'élément",
|
||||
"btn_add_item": "Ajouter l'élément",
|
||||
"btn_remove_item": "Retirer l'élément",
|
||||
"btn_add_file": "Ajouter un fichier",
|
||||
"btn_add_path": "Ajouter un chemin",
|
||||
"manage_netmon": "Moniteur de trafic",
|
||||
"manage_netmon_desc": "Surveille le trafic réseau en temps réel",
|
||||
"col_down_speed": "Vitesse de téléchargement",
|
||||
"col_up_speed": "Vitesse de téléversement",
|
||||
"col_conn_count": "Connexions",
|
||||
"btn_extract_item": "Extraire l'élément",
|
||||
"msg_extract_success": "Éléments sélectionnés extraits avec succès",
|
||||
"msg_key_activated": "Clé de produit activée"
|
||||
},
|
||||
"spanish_switch": {
|
||||
"app_name": "PYAS Security",
|
||||
@@ -822,7 +960,7 @@ window.AppI18n = {
|
||||
"scan_smart": "Escaneo inteligente",
|
||||
"scan_smart_desc": "Escanear rápidamente la seguridad del dispositivo",
|
||||
"log_export": "Exportar registros",
|
||||
"log_export_desc": "Filtrar registros y exportar a un archivo",
|
||||
"log_export_desc": "Filtrer registros y exportar a un archivo",
|
||||
"scan_virus": "Escaneo de virus",
|
||||
"scan_virus_desc": "Seleccione un método de escaneo avanzado",
|
||||
"btn_delete": "Eliminar",
|
||||
@@ -865,6 +1003,7 @@ window.AppI18n = {
|
||||
"scan_context_desc": "Habilitar la función de escaneo con el botón derecho",
|
||||
"theme_color": "Tema de color",
|
||||
"theme_color_desc": "Seleccione el tema de color de la aplicación",
|
||||
"theme_system": "Tema del sistema",
|
||||
"theme_white": "Modo claro",
|
||||
"theme_red": "Tema rojo",
|
||||
"theme_yellow": "Tema amarillo",
|
||||
@@ -883,12 +1022,8 @@ window.AppI18n = {
|
||||
"update_check": "Buscar actualizaciones",
|
||||
"update_check_desc": "Comprobar las últimas actualizaciones",
|
||||
"search_proc": "Buscar procesos...",
|
||||
"btn_view": "Ver",
|
||||
"btn_end": "Finalizar tarea",
|
||||
"manage_white": "Gestor de lista blanca",
|
||||
"search_file": "Buscar archivos...",
|
||||
"btn_add": "Añadir",
|
||||
"btn_remove": "Eliminar",
|
||||
"manage_quarantine": "Gestor de cuarentena",
|
||||
"btn_scan": "Escanear",
|
||||
"search_window": "Buscar ventanas...",
|
||||
@@ -913,7 +1048,6 @@ window.AppI18n = {
|
||||
"msg_click_target": "Haga clic en la ventana objetivo en 5s...",
|
||||
"msg_confirm_block": "Confirmar bloqueo",
|
||||
"msg_repaired": "Elementos seleccionados reparados",
|
||||
"btn_repair": "Reparar",
|
||||
"title_prompt": "Aviso",
|
||||
"title_error": "Error",
|
||||
"path_unknown": "Ruta desconocida",
|
||||
@@ -950,7 +1084,34 @@ window.AppI18n = {
|
||||
"res_net_block": "Red bloqueada",
|
||||
"res_drv_block": "Controlador bloqueado",
|
||||
"res_sys_repair": "Reparación del sistema",
|
||||
"res_cfg_update": "Actualización de configuración"
|
||||
"res_cfg_update": "Actualización de configuración",
|
||||
"manage_startup": "Gestor de inicio",
|
||||
"manage_startup_desc": "Administre aplicaciones y servicios de inicio",
|
||||
"col_status": "Estado",
|
||||
"status_enabled": "Habilitado",
|
||||
"status_disabled": "Deshabilitado",
|
||||
"search_item": "Buscar elementos...",
|
||||
"type_reg": "Registro",
|
||||
"type_srv": "Servicio",
|
||||
"type_tsk": "Tarea",
|
||||
"btn_view_item": "Ver elemento",
|
||||
"btn_end": "Terminar proceso",
|
||||
"btn_enable_item": "Habilitar elemento",
|
||||
"btn_disable_item": "Deshabilitar elemento",
|
||||
"btn_delete_item": "Eliminar elemento",
|
||||
"btn_repair_item": "Reparar elemento",
|
||||
"btn_add_item": "Añadir elemento",
|
||||
"btn_remove_item": "Eliminar elemento",
|
||||
"btn_add_file": "Añadir archivo",
|
||||
"btn_add_path": "Añadir ruta",
|
||||
"manage_netmon": "Monitor de tráfico",
|
||||
"manage_netmon_desc": "Monitorea el tráfico de red en tiempo real",
|
||||
"col_down_speed": "Velocidad de descarga",
|
||||
"col_up_speed": "Velocidad de subida",
|
||||
"col_conn_count": "Conexiones",
|
||||
"btn_extract_item": "Extraer elemento",
|
||||
"msg_extract_success": "Elementos seleccionados extraídos con éxito",
|
||||
"msg_key_activated": "Clave de producto activada"
|
||||
},
|
||||
"hindi_switch": {
|
||||
"app_name": "PYAS Security",
|
||||
@@ -1001,6 +1162,7 @@ window.AppI18n = {
|
||||
"scan_context_desc": "राइट-क्लिक स्कैन कार्यक्षमता सक्षम करें",
|
||||
"theme_color": "रंग थीम",
|
||||
"theme_color_desc": "एप्लिकेशन का रंग थीम चुनें",
|
||||
"theme_system": "सिस्टम थीम",
|
||||
"theme_white": "लाइट मोड",
|
||||
"theme_red": "लाल थीम",
|
||||
"theme_yellow": "पीला थीम",
|
||||
@@ -1019,12 +1181,8 @@ window.AppI18n = {
|
||||
"update_check": "अद्यतन जाँचे",
|
||||
"update_check_desc": "नवीनतम अपडेट के लिए जाँचें",
|
||||
"search_proc": "प्रक्रियाएँ खोजें...",
|
||||
"btn_view": "देखें",
|
||||
"btn_end": "कार्य समाप्त करें",
|
||||
"manage_white": "श्वेतसूची प्रबंधक",
|
||||
"search_file": "फ़ाइलें खोजें...",
|
||||
"btn_add": "जोड़ें",
|
||||
"btn_remove": "निकालें",
|
||||
"manage_quarantine": "संगरोध प्रबंधक",
|
||||
"btn_scan": "स्कैन",
|
||||
"search_window": "विंडोज़ खोजें...",
|
||||
@@ -1049,7 +1207,6 @@ window.AppI18n = {
|
||||
"msg_click_target": "5 सेकंड के भीतर लक्ष्य विंडो पर क्लिक करें...",
|
||||
"msg_confirm_block": "ब्लॉक की पुष्टि करें",
|
||||
"msg_repaired": "चयनित आइटम मरम्मत किए गए",
|
||||
"btn_repair": "मरम्मत",
|
||||
"title_prompt": "सुझाव",
|
||||
"title_error": "त्रुटि",
|
||||
"path_unknown": "अज्ञात पथ",
|
||||
@@ -1086,7 +1243,34 @@ window.AppI18n = {
|
||||
"res_net_block": "नेटवर्क अवरुद्ध",
|
||||
"res_drv_block": "ड्राइवर अवरुद्ध",
|
||||
"res_sys_repair": "सिस्टम मरम्मत",
|
||||
"res_cfg_update": "कॉन्फ़िगरेशन अपडेट"
|
||||
"res_cfg_update": "कॉन्फ़िगरेशन अपडेट",
|
||||
"manage_startup": "स्टार्टअप प्रबंधक",
|
||||
"manage_startup_desc": "स्टार्टअप एप्लिकेशन और सेवाएं प्रबंधित करें",
|
||||
"col_status": "स्थिति",
|
||||
"status_enabled": "सक्षम",
|
||||
"status_disabled": "अक्षम",
|
||||
"search_item": "आइटम खोजें...",
|
||||
"type_reg": "रजिस्ट्री",
|
||||
"type_srv": "सेवा",
|
||||
"type_tsk": "कार्य",
|
||||
"btn_view_item": "आइटम देखें",
|
||||
"btn_end": "प्रक्रिया समाप्त करें",
|
||||
"btn_enable_item": "आइटम सक्षम करें",
|
||||
"btn_disable_item": "आइटम अक्षम करें",
|
||||
"btn_delete_item": "आइटम हटाएं",
|
||||
"btn_repair_item": "आइटम की मरम्मत करें",
|
||||
"btn_add_item": "आइटम जोड़ें",
|
||||
"btn_remove_item": "आइटम निकालें",
|
||||
"btn_add_file": "फ़ाइल जोड़ें",
|
||||
"btn_add_path": "पथ जोड़ें",
|
||||
"manage_netmon": "ट्रैफ़िक मॉनिटर",
|
||||
"manage_netmon_desc": "रीयल-टाइम में नेटवर्क ट्रैफ़िक की निगरानी करें",
|
||||
"col_down_speed": "डाउनलोड गति",
|
||||
"col_up_speed": "अपलोड गति",
|
||||
"col_conn_count": "कनेक्शन",
|
||||
"btn_extract_item": "आइटम निकालें",
|
||||
"msg_extract_success": "चयनित आइटम सफलतापूर्वक निकाले गए",
|
||||
"msg_key_activated": "उत्पाद कुंजी सक्रिय हो गई"
|
||||
},
|
||||
"arabic_switch": {
|
||||
"app_name": "PYAS Security",
|
||||
@@ -1137,6 +1321,7 @@ window.AppI18n = {
|
||||
"scan_context_desc": "تمكين ميزة الفحص بزر الماوس الأيمن",
|
||||
"theme_color": "سمة اللون",
|
||||
"theme_color_desc": "تحديد سمة ألوان التطبيق",
|
||||
"theme_system": "سمة النظام",
|
||||
"theme_white": "الوضع الفاتح",
|
||||
"theme_red": "سمة حمراء",
|
||||
"theme_yellow": "سمة صفراء",
|
||||
@@ -1155,12 +1340,8 @@ window.AppI18n = {
|
||||
"update_check": "التحقق من التحديثات",
|
||||
"update_check_desc": "التحقق من أحدث التحديثات",
|
||||
"search_proc": "البحث في العمليات...",
|
||||
"btn_view": "عرض",
|
||||
"btn_end": "إنهاء المهمة",
|
||||
"manage_white": "مدير القائمة البيضاء",
|
||||
"search_file": "البحث عن الملفات...",
|
||||
"btn_add": "إضافة",
|
||||
"btn_remove": "إزالة",
|
||||
"manage_quarantine": "مدير الحجر الصحي",
|
||||
"btn_scan": "فحص",
|
||||
"search_window": "البحث عن النوافذ...",
|
||||
@@ -1185,7 +1366,6 @@ window.AppI18n = {
|
||||
"msg_click_target": "انقر على النافذة المستهدفة خلال 5 ثوانٍ...",
|
||||
"msg_confirm_block": "تأكيد الحظر",
|
||||
"msg_repaired": "تم إصلاح العناصر المحددة",
|
||||
"btn_repair": "إصلاح",
|
||||
"title_prompt": "تلميح",
|
||||
"title_error": "خطأ",
|
||||
"path_unknown": "مسار غير معروف",
|
||||
@@ -1222,7 +1402,34 @@ window.AppI18n = {
|
||||
"res_net_block": "حظر الشبكة",
|
||||
"res_drv_block": "حظر برنامج التشغيل",
|
||||
"res_sys_repair": "إصلاح النظام",
|
||||
"res_cfg_update": "تحديث التكوين"
|
||||
"res_cfg_update": "تحديث التكوين",
|
||||
"manage_startup": "مدير بدء التشغيل",
|
||||
"manage_startup_desc": "إدارة تطبيقات وخدمات بدء التشغيل",
|
||||
"col_status": "الحالة",
|
||||
"status_enabled": "مُمكّن",
|
||||
"status_disabled": "مُعطّل",
|
||||
"search_item": "البحث في العناصر...",
|
||||
"type_reg": "التسجيل",
|
||||
"type_srv": "خدمة",
|
||||
"type_tsk": "مهمة",
|
||||
"btn_view_item": "عرض العنصر",
|
||||
"btn_end": "إنهاء العملية",
|
||||
"btn_enable_item": "تمكين العنصر",
|
||||
"btn_disable_item": "تعطيل العنصر",
|
||||
"btn_delete_item": "حذف العنصر",
|
||||
"btn_repair_item": "إصلاح العنصر",
|
||||
"btn_add_item": "إضافة عنصر",
|
||||
"btn_remove_item": "إزالة العنصر",
|
||||
"btn_add_file": "إضافة ملف",
|
||||
"btn_add_path": "إضافة مسار",
|
||||
"manage_netmon": "مراقبة حركة المرور",
|
||||
"manage_netmon_desc": "مراقبة حركة مرور الشبكة في الوقت الفعلي",
|
||||
"col_down_speed": "سرعة التنزيل",
|
||||
"col_up_speed": "سرعة الرفع",
|
||||
"col_conn_count": "الاتصالات",
|
||||
"btn_extract_item": "استخراج العنصر",
|
||||
"msg_extract_success": "تم استخراج العناصر المحددة بنجاح",
|
||||
"msg_key_activated": "تم تنشيط مفتاح المنتج"
|
||||
},
|
||||
"russian_switch": {
|
||||
"app_name": "PYAS Security",
|
||||
@@ -1272,7 +1479,8 @@ window.AppI18n = {
|
||||
"scan_context": "Сканирование из контекстного меню",
|
||||
"scan_context_desc": "Включить функцию сканирования по правому клику",
|
||||
"theme_color": "Цветовая тема",
|
||||
"theme_color_desc": "Выберите цветовую тему приложения",
|
||||
"theme_color_desc": "Выберите цветовой тему приложения",
|
||||
"theme_system": "Системная тема",
|
||||
"theme_white": "Светлый режим",
|
||||
"theme_red": "Красная тема",
|
||||
"theme_yellow": "Желтая тема",
|
||||
@@ -1291,12 +1499,8 @@ window.AppI18n = {
|
||||
"update_check": "Проверить обновления",
|
||||
"update_check_desc": "Проверить наличие последних обновлений",
|
||||
"search_proc": "Поиск процессов...",
|
||||
"btn_view": "Просмотр",
|
||||
"btn_end": "Завершить",
|
||||
"manage_white": "Управление белым списком",
|
||||
"search_file": "Поиск файлов...",
|
||||
"btn_add": "Добавить",
|
||||
"btn_remove": "Удалить",
|
||||
"manage_quarantine": "Управление карантином",
|
||||
"btn_scan": "Сканировать",
|
||||
"search_window": "Поиск окон...",
|
||||
@@ -1321,7 +1525,6 @@ window.AppI18n = {
|
||||
"msg_click_target": "Нажмите на целевое окно в течение 5 сек...",
|
||||
"msg_confirm_block": "Подтвердить блокировку",
|
||||
"msg_repaired": "Выбранные элементы восстановлены",
|
||||
"btn_repair": "Восстановить",
|
||||
"title_prompt": "Подсказка",
|
||||
"title_error": "Ошибка",
|
||||
"path_unknown": "Неизвестный путь",
|
||||
@@ -1358,7 +1561,34 @@ window.AppI18n = {
|
||||
"res_net_block": "Сеть заблокирована",
|
||||
"res_drv_block": "Драйвер заблокирован",
|
||||
"res_sys_repair": "Восстановление системы",
|
||||
"res_cfg_update": "Обновление конфигурации"
|
||||
"res_cfg_update": "Обновление конфигурации",
|
||||
"manage_startup": "Диспетчер автозагрузки",
|
||||
"manage_startup_desc": "Управление приложениями и службами автозагрузки",
|
||||
"col_status": "Статус",
|
||||
"status_enabled": "Включено",
|
||||
"status_disabled": "Отключено",
|
||||
"search_item": "Поиск элементов...",
|
||||
"type_reg": "Реестр",
|
||||
"type_srv": "Служба",
|
||||
"type_tsk": "Задача",
|
||||
"btn_view_item": "Просмотр элемента",
|
||||
"btn_end": "Завершить процесс",
|
||||
"btn_enable_item": "Включить элемент",
|
||||
"btn_disable_item": "Отключить элемент",
|
||||
"btn_delete_item": "Удалить элемент",
|
||||
"btn_repair_item": "Восстановить элемент",
|
||||
"btn_add_item": "Добавить элемент",
|
||||
"btn_remove_item": "Удалить элемент",
|
||||
"btn_add_file": "Добавить файл",
|
||||
"btn_add_path": "Добавить путь",
|
||||
"manage_netmon": "Монитор трафика",
|
||||
"manage_netmon_desc": "Мониторинг сетевого трафика в реальном времени",
|
||||
"col_down_speed": "Скорость скачивания",
|
||||
"col_up_speed": "Скорость загрузки",
|
||||
"col_conn_count": "Соединения",
|
||||
"btn_extract_item": "Извлечь элемент",
|
||||
"msg_extract_success": "Выбранные элементы успешно извлечены",
|
||||
"msg_key_activated": "Ключ продукта активирован"
|
||||
},
|
||||
"slovenian_switch": {
|
||||
"app_name": "PYAS Security",
|
||||
@@ -1409,6 +1639,7 @@ window.AppI18n = {
|
||||
"scan_context_desc": "Omogoči funkcijo skeniranja z desnim klikom",
|
||||
"theme_color": "Barvna tema",
|
||||
"theme_color_desc": "Izberite barvno temo aplikacije",
|
||||
"theme_system": "Sistemska tema",
|
||||
"theme_white": "Svetel način",
|
||||
"theme_red": "Rdeča tema",
|
||||
"theme_yellow": "Rumena tema",
|
||||
@@ -1427,12 +1658,8 @@ window.AppI18n = {
|
||||
"update_check": "Preveri posodobitve",
|
||||
"update_check_desc": "Preveri zadnje posodobitve",
|
||||
"search_proc": "Išči procese...",
|
||||
"btn_view": "Poglej",
|
||||
"btn_end": "Končaj opravilo",
|
||||
"manage_white": "Upravitelj seznama dovoljenih",
|
||||
"search_file": "Išči datoteke...",
|
||||
"btn_add": "Dodaj",
|
||||
"btn_remove": "Odstrani",
|
||||
"manage_quarantine": "Upravitelj karantene",
|
||||
"btn_scan": "Skeniraj",
|
||||
"search_window": "Išči okna...",
|
||||
@@ -1457,7 +1684,6 @@ window.AppI18n = {
|
||||
"msg_click_target": "Kliknite na ciljno okno v 5s...",
|
||||
"msg_confirm_block": "Potrdi blokado",
|
||||
"msg_repaired": "Izbrani predmeti popravljeni",
|
||||
"btn_repair": "Popravi",
|
||||
"title_prompt": "Namig",
|
||||
"title_error": "Napaka",
|
||||
"path_unknown": "Neznana pot",
|
||||
@@ -1494,7 +1720,34 @@ window.AppI18n = {
|
||||
"res_net_block": "Omrežje blokirano",
|
||||
"res_drv_block": "Gonilnik blokiran",
|
||||
"res_sys_repair": "Popravilo sistema",
|
||||
"res_cfg_update": "Posodobitev konfiguracije"
|
||||
"res_cfg_update": "Posodobitev konfiguracije",
|
||||
"manage_startup": "Upravitelj zagona",
|
||||
"manage_startup_desc": "Upravljanje zagon aplikacij in storitev",
|
||||
"col_status": "Stanje",
|
||||
"status_enabled": "Omogočeno",
|
||||
"status_disabled": "Onemogočeno",
|
||||
"search_item": "Išči elemente...",
|
||||
"type_reg": "Register",
|
||||
"type_srv": "Storitev",
|
||||
"type_tsk": "Opravilo",
|
||||
"btn_view_item": "Ogled elementa",
|
||||
"btn_end": "Končaj proces",
|
||||
"btn_enable_item": "Omogoči element",
|
||||
"btn_disable_item": "Onemogoči element",
|
||||
"btn_delete_item": "Izbriši element",
|
||||
"btn_repair_item": "Popravi element",
|
||||
"btn_add_item": "Dodaj element",
|
||||
"btn_remove_item": "Odstrani element",
|
||||
"btn_add_file": "Dodaj datoteko",
|
||||
"btn_add_path": "Dodaj pot",
|
||||
"manage_netmon": "Nadzor prometa",
|
||||
"manage_netmon_desc": "Spremlja omrežni promet v realnem času",
|
||||
"col_down_speed": "Hitrost prenosa",
|
||||
"col_up_speed": "Hitrost nalaganja",
|
||||
"col_conn_count": "Povezave",
|
||||
"btn_extract_item": "Izvleči element",
|
||||
"msg_extract_success": "Izbrani elementi so bili uspešno izvlečeni",
|
||||
"msg_key_activated": "Ključ izdelka je aktiviran"
|
||||
}
|
||||
},
|
||||
langMap: {
|
||||
|
||||
+270
-90
@@ -9,10 +9,13 @@ document.addEventListener('DOMContentLoaded', () => {
|
||||
|
||||
const appState = {
|
||||
lang: "english_switch",
|
||||
theme: "system_switch",
|
||||
scanning: false,
|
||||
firstLaunch: false,
|
||||
taskmgrTimer: null,
|
||||
taskmgrActive: false,
|
||||
netmonTimer: null,
|
||||
netmonActive: false,
|
||||
virusMap: new Map(),
|
||||
virusResults: [],
|
||||
junkList: []
|
||||
@@ -79,11 +82,13 @@ document.addEventListener('DOMContentLoaded', () => {
|
||||
const cols = {
|
||||
log: [{key: 'time_str', label: 'col_date', flex: 1.5}, {key: 'level', label: 'col_type', flex: 0.8}, {key: 'action', label: 'col_func', flex: 1.2, isI18n: true}, {key: 'source', label: 'col_path', flex: 3}],
|
||||
process: [{key: 'name', label: 'col_name', flex: 1.5}, {key: 'pid', label: 'col_pid', flex: 0.5}, {key: 'path', label: 'col_path', flex: 3}],
|
||||
startup: [{key: 'type', label: 'col_type', flex: 0.5, isI18n: true}, {key: 'name', label: 'col_name', flex: 1.5}, {key: 'status', label: 'col_status', flex: 0.5, isI18n: true}, {key: 'path', label: 'col_path', flex: 3}],
|
||||
virus: [{key: 'label', label: 'col_type', flex: 1, isI18n: true}, {key: 'path', label: 'col_path', flex: 3}],
|
||||
pathOnly: [{key: 'path', label: 'col_path', flex: 1}],
|
||||
junk: [{key: 'path', label: 'col_path', flex: 3}, {key: 'sizeStr', label: 'col_size', flex: 1}],
|
||||
popup: [{key: 'exe', label: 'col_prog', flex: 1}, {key: 'class', label: 'col_class', flex: 1}, {key: 'title', label: 'col_title', flex: 2}],
|
||||
repair: [{key: 'display', label: 'col_repair', flex: 1, isI18n: true}]
|
||||
repair: [{key: 'display', label: 'col_repair', flex: 1, isI18n: true}, {key: 'path', label: 'col_path', flex: 3}],
|
||||
netmon: [{key: 'name', label: 'col_name', flex: 1.5}, {key: 'pid', label: 'col_pid', flex: 0.5}, {key: 'downStr', label: 'col_down_speed', flex: 1}, {key: 'upStr', label: 'col_up_speed', flex: 1}, {key: 'connCount', label: 'col_conn_count', flex: 0.8}]
|
||||
};
|
||||
|
||||
const buildCustomSelectElement = (select) => {
|
||||
@@ -198,7 +203,12 @@ document.addEventListener('DOMContentLoaded', () => {
|
||||
};
|
||||
|
||||
const applyTheme = (theme) => {
|
||||
const themeVars = themes[theme];
|
||||
appState.theme = theme;
|
||||
const actualTheme = theme === "system_switch"
|
||||
? (window.matchMedia('(prefers-color-scheme: dark)').matches ? "black_switch" : "white_switch")
|
||||
: theme;
|
||||
|
||||
const themeVars = themes[actualTheme];
|
||||
if (themeVars) {
|
||||
for (const [key, value] of Object.entries(themeVars)) {
|
||||
document.documentElement.style.setProperty(key, value.startsWith("rgba") ? value : `rgb(${value})`);
|
||||
@@ -206,6 +216,10 @@ document.addEventListener('DOMContentLoaded', () => {
|
||||
}
|
||||
};
|
||||
|
||||
window.matchMedia('(prefers-color-scheme: dark)').addEventListener('change', () => {
|
||||
if (appState.theme === "system_switch") applyTheme("system_switch");
|
||||
});
|
||||
|
||||
const renderDataGrid = (containerSelector, dataList, columns, valKey, defaultChecked = false) => {
|
||||
const widget = document.querySelector(`${containerSelector} .manage-widget`) || document.querySelector(`${containerSelector} .virus-widget`);
|
||||
const listUl = document.querySelector(`${containerSelector} .manage-list`) || document.querySelector(`${containerSelector} .virus-list`);
|
||||
@@ -331,7 +345,13 @@ document.addEventListener('DOMContentLoaded', () => {
|
||||
|
||||
displayData.sort((a, b) => {
|
||||
const valA = a[state.sortKey] ?? '', valB = b[state.sortKey] ?? '';
|
||||
if (state.sortKey === 'sizeStr' && 'size' in a && 'size' in b) return state.sortAsc ? a.size - b.size : b.size - a.size;
|
||||
|
||||
const rawKeyMap = { 'sizeStr': 'size', 'downStr': 'down', 'upStr': 'up' };
|
||||
if (rawKeyMap[state.sortKey] && rawKeyMap[state.sortKey] in a && rawKeyMap[state.sortKey] in b) {
|
||||
const rawKey = rawKeyMap[state.sortKey];
|
||||
return state.sortAsc ? a[rawKey] - b[rawKey] : b[rawKey] - a[rawKey];
|
||||
}
|
||||
|
||||
const numA = parseFloat(valA), numB = parseFloat(valB);
|
||||
if (!isNaN(numA) && !isNaN(numB) && String(valA).trim() !== '' && String(valB).trim() !== '') return state.sortAsc ? numA - numB : numB - numA;
|
||||
return state.sortAsc ? String(valA).localeCompare(String(valB)) : String(valB).localeCompare(String(valA));
|
||||
@@ -496,12 +516,72 @@ document.addEventListener('DOMContentLoaded', () => {
|
||||
window.pywebview.api.get_process_list().then(procs => {
|
||||
const mapped = procs.map(p => ({ name: p.name, pid: p.pid, path: (p.path && p.path !== "None" && p.path !== "Unknown") ? p.path : "path_unknown" }));
|
||||
renderDataGrid('#taskmgr_window', mapped, cols.process, 'pid', false);
|
||||
if (appState.taskmgrActive) appState.taskmgrTimer = setTimeout(fetchProcs, 2000);
|
||||
}).catch(() => { if (appState.taskmgrActive) appState.taskmgrTimer = setTimeout(fetchProcs, 2000); });
|
||||
if (appState.taskmgrActive) appState.taskmgrTimer = setTimeout(fetchProcs, 1000);
|
||||
}).catch(() => { if (appState.taskmgrActive) appState.taskmgrTimer = setTimeout(fetchProcs, 1000); });
|
||||
} else {
|
||||
if (appState.taskmgrActive) appState.taskmgrTimer = setTimeout(fetchProcs, 2000);
|
||||
if (appState.taskmgrActive) appState.taskmgrTimer = setTimeout(fetchProcs, 1000);
|
||||
}
|
||||
};
|
||||
|
||||
const fetchNetmon = () => {
|
||||
if (!appState.netmonActive || !window.pywebview) return;
|
||||
const widget = document.querySelector('#netmon_window .manage-widget');
|
||||
const blockFetch = widget && (widget.gridState?.checkedSet.size > 0 || widget.matches(':hover')) || document.querySelector('.custom-context-menu.show') || document.body.classList.contains('resizing-active');
|
||||
|
||||
if (!blockFetch) {
|
||||
window.pywebview.api.get_traffic_list().then(list => {
|
||||
const mapped = list.map(item => ({
|
||||
name: item.name,
|
||||
pid: item.pid,
|
||||
downStr: formatSize(item.down) + '/s',
|
||||
down: item.down,
|
||||
upStr: formatSize(item.up) + '/s',
|
||||
up: item.up,
|
||||
connCount: item.conn,
|
||||
path: (item.path && item.path !== "None" && item.path !== "Unknown") ? item.path : "path_unknown"
|
||||
}));
|
||||
renderDataGrid('#netmon_window', mapped, cols.netmon, 'pid', false);
|
||||
if (appState.netmonActive) appState.netmonTimer = setTimeout(fetchNetmon, 1000);
|
||||
}).catch(() => { if (appState.netmonActive) appState.netmonTimer = setTimeout(fetchNetmon, 1000); });
|
||||
} else {
|
||||
if (appState.netmonActive) appState.netmonTimer = setTimeout(fetchNetmon, 1000);
|
||||
}
|
||||
};
|
||||
|
||||
const fetchStartup = () => {
|
||||
if (!window.pywebview) return;
|
||||
const listUl = document.querySelector('#startup_window .manage-list');
|
||||
if (listUl && !listUl.children.length) {
|
||||
listUl.innerHTML = `<li class="manage-list-item" style="pointer-events: none; justify-content: center; height: 100%; border: none;"><div style="color: var(--text-secondary); padding: 40px; font-size: 15px;">${getMsg('msg_init')}</div></li>`;
|
||||
}
|
||||
window.pywebview.api.get_startup_list().then(items => {
|
||||
const mapped = items.map(item => ({ id: item.id, type: item.type, name: item.name, status: item.status === 'enabled' ? 'status_enabled' : 'status_disabled', path: item.path || 'path_unknown' }));
|
||||
renderDataGrid('#startup_window', mapped, cols.startup, 'id', false);
|
||||
});
|
||||
};
|
||||
|
||||
const fetchJunk = () => {
|
||||
if (!window.pywebview) return;
|
||||
const widget = document.querySelector('#junk_window .manage-widget');
|
||||
if (widget?.gridState) widget.gridState.checkedSet.clear();
|
||||
const listUl = document.querySelector('#junk_window .manage-list');
|
||||
if (listUl && !listUl.children.length) {
|
||||
listUl.innerHTML = `<li class="manage-list-item" style="pointer-events: none; justify-content: center; height: 100%; border: none;"><div style="color: var(--text-secondary); padding: 40px; font-size: 15px;">${getMsg('msg_init')}</div></li>`;
|
||||
}
|
||||
window.pywebview.api.scan_system_junk().then(list => {
|
||||
appState.junkList = list.map(item => ({ path: item.path, sizeStr: formatSize(item.size), size: item.size }));
|
||||
renderDataGrid('#junk_window', appState.junkList, cols.junk, 'path', true);
|
||||
});
|
||||
};
|
||||
|
||||
const fetchRepair = () => {
|
||||
if (!window.pywebview) return;
|
||||
const listUl = document.querySelector('#repair_window .manage-list');
|
||||
if (listUl && !listUl.children.length) {
|
||||
listUl.innerHTML = `<li class="manage-list-item" style="pointer-events: none; justify-content: center; height: 100%; border: none;"><div style="color: var(--text-secondary); padding: 40px; font-size: 15px;">${getMsg('msg_init')}</div></li>`;
|
||||
}
|
||||
window.pywebview.api.scan_system_repair().then(list => renderDataGrid('#repair_window', list, cols.repair, 'id', true));
|
||||
};
|
||||
|
||||
const refreshConfigLists = () => {
|
||||
if (!window.pywebview) return;
|
||||
@@ -523,7 +603,7 @@ document.addEventListener('DOMContentLoaded', () => {
|
||||
const input = document.querySelector('#junk_window .search-box input');
|
||||
if (input) input.value = '';
|
||||
} else if (oldActive.id === 'repair_window') {
|
||||
renderDataGrid('#repair_window', [], cols.repair, 'value', true);
|
||||
renderDataGrid('#repair_window', [], cols.repair, 'id', true);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -535,6 +615,14 @@ document.addEventListener('DOMContentLoaded', () => {
|
||||
if (appState.taskmgrActive) fetchProcs();
|
||||
else if (appState.taskmgrTimer) { clearTimeout(appState.taskmgrTimer); appState.taskmgrTimer = null; }
|
||||
|
||||
appState.netmonActive = targetId === 'netmon_window';
|
||||
if (appState.netmonActive) fetchNetmon();
|
||||
else if (appState.netmonTimer) { clearTimeout(appState.netmonTimer); appState.netmonTimer = null; }
|
||||
|
||||
if (targetId === 'startup_window') fetchStartup();
|
||||
if (targetId === 'junk_window') fetchJunk();
|
||||
if (targetId === 'repair_window') fetchRepair();
|
||||
|
||||
if (['whitelist_window', 'quarantine_window', 'popup_window', 'custom_protect_window'].includes(targetId)) refreshConfigLists();
|
||||
|
||||
if (targetId === 'log_export_window' && window.pywebview) {
|
||||
@@ -683,41 +771,97 @@ document.addEventListener('DOMContentLoaded', () => {
|
||||
});
|
||||
});
|
||||
|
||||
document.querySelector('#junk_window .primary-btn')?.addEventListener('click', (e) => {
|
||||
if (!window.pywebview) return;
|
||||
e.target.setAttribute('data-i18n', 'msg_init'); e.target.textContent = getMsg('msg_init');
|
||||
const widget = document.querySelector('#junk_window .manage-widget');
|
||||
if (widget?.gridState) widget.gridState.checkedSet.clear();
|
||||
|
||||
window.pywebview.api.scan_system_junk().then(list => {
|
||||
appState.junkList = list.map(item => ({ path: item.path, sizeStr: formatSize(item.size), size: item.size }));
|
||||
renderDataGrid('#junk_window', appState.junkList, cols.junk, 'path', true);
|
||||
e.target.setAttribute('data-i18n', 'btn_scan'); e.target.textContent = getMsg('btn_scan');
|
||||
});
|
||||
document.getElementById('taskmgr_select')?.addEventListener('change', (e) => {
|
||||
const val = e.target.value;
|
||||
const checkedCbs = document.querySelectorAll('#taskmgr_window .manage-list-item input[type="checkbox"]:checked');
|
||||
if (checkedCbs.length === 0 || !window.pywebview) return updateCustomSelectUI('taskmgr_select', 'none');
|
||||
|
||||
if (val === 'view') {
|
||||
checkedCbs.forEach(cb => {
|
||||
if (cb.dataset.path && cb.dataset.path !== "path_unknown") window.pywebview.api.open_file_location(cb.dataset.path);
|
||||
});
|
||||
} else if (val === 'end') {
|
||||
window.pywebview.api.show_confirm(getMsg("msg_end_proc_title"), getMsg("msg_end_proc_confirm")).then(res => {
|
||||
if (res) Promise.all(Array.from(checkedCbs).map(cb => window.pywebview.api.kill_process(parseInt(cb.value)))).then(() => fetchProcs());
|
||||
});
|
||||
} else if (val === 'quarantine' || val === 'whitelist') {
|
||||
const paths = Array.from(checkedCbs).map(cb => cb.dataset.path).filter(p => p && p !== "path_unknown");
|
||||
if (paths.length > 0) {
|
||||
const listKey = val === 'quarantine' ? 'quarantine' : 'white_list';
|
||||
window.pywebview.api.manage_named_list(listKey, paths, 'add').then(() => {
|
||||
Promise.all(Array.from(checkedCbs).map(cb => window.pywebview.api.kill_process(parseInt(cb.value)))).then(() => fetchProcs());
|
||||
});
|
||||
}
|
||||
}
|
||||
updateCustomSelectUI('taskmgr_select', 'none');
|
||||
});
|
||||
|
||||
document.getElementById('netmon_select')?.addEventListener('change', (e) => {
|
||||
const val = e.target.value;
|
||||
const checkedCbs = document.querySelectorAll('#netmon_window .manage-list-item input[type="checkbox"]:checked');
|
||||
if (checkedCbs.length === 0 || !window.pywebview) return updateCustomSelectUI('netmon_select', 'none');
|
||||
|
||||
if (val === 'view') {
|
||||
checkedCbs.forEach(cb => {
|
||||
if (cb.dataset.path && cb.dataset.path !== "path_unknown") window.pywebview.api.open_file_location(cb.dataset.path);
|
||||
});
|
||||
} else if (val === 'end') {
|
||||
window.pywebview.api.show_confirm(getMsg("msg_end_proc_title"), getMsg("msg_end_proc_confirm")).then(res => {
|
||||
if (res) Promise.all(Array.from(checkedCbs).map(cb => window.pywebview.api.kill_process(parseInt(cb.value)))).then(() => fetchNetmon());
|
||||
});
|
||||
}
|
||||
updateCustomSelectUI('netmon_select', 'none');
|
||||
});
|
||||
|
||||
document.querySelector('#junk_window .danger-btn')?.addEventListener('click', () => {
|
||||
document.getElementById('startup_select')?.addEventListener('change', (e) => {
|
||||
const val = e.target.value;
|
||||
const checked = getCheckedValues('#startup_window');
|
||||
if (checked.length > 0 && window.pywebview) {
|
||||
if (val === 'enable' || val === 'disable') {
|
||||
const widget = document.querySelector('#startup_window .manage-widget');
|
||||
if (widget && widget.gridState && widget.gridState.lastDisplayData) {
|
||||
widget.gridState.lastDisplayData.forEach(item => {
|
||||
if (checked.includes(item.id)) item.status = val === 'enable' ? 'status_enabled' : 'status_disabled';
|
||||
});
|
||||
widget.renderData();
|
||||
}
|
||||
}
|
||||
window.pywebview.api.manage_startup(checked, val).then(fetchStartup);
|
||||
}
|
||||
updateCustomSelectUI('startup_select', 'none');
|
||||
});
|
||||
|
||||
document.getElementById('junk_select')?.addEventListener('change', (e) => {
|
||||
const val = e.target.value;
|
||||
const checked = getCheckedValues('#junk_window');
|
||||
if (checked.length === 0 || !window.pywebview) return;
|
||||
window.pywebview.api.clean_system_junk(checked).then(size => {
|
||||
window.pywebview.api.show_alert(getMsg("title_prompt"), `${getMsg("msg_cleaned")}${formatSize(size)}`, "info");
|
||||
appState.junkList = appState.junkList.filter(item => !checked.includes(item.path));
|
||||
renderDataGrid('#junk_window', appState.junkList, cols.junk, 'path', true);
|
||||
});
|
||||
});
|
||||
|
||||
document.querySelector('#repair_window .primary-btn')?.addEventListener('click', () => {
|
||||
if (!window.pywebview) return;
|
||||
window.pywebview.api.scan_system_repair().then(list => renderDataGrid('#repair_window', list, cols.repair, 'value', true));
|
||||
|
||||
if (val === 'view') {
|
||||
checked.forEach(path => window.pywebview.api.open_file_location(path));
|
||||
} else if (val === 'delete') {
|
||||
if (checked.length > 0) {
|
||||
window.pywebview.api.clean_system_junk(checked).then(size => {
|
||||
window.pywebview.api.show_alert(getMsg("title_prompt"), `${getMsg("msg_cleaned")}${formatSize(size)}`, "info");
|
||||
appState.junkList = appState.junkList.filter(item => !checked.includes(item.path));
|
||||
renderDataGrid('#junk_window', appState.junkList, cols.junk, 'path', true);
|
||||
});
|
||||
}
|
||||
}
|
||||
updateCustomSelectUI('junk_select', 'none');
|
||||
});
|
||||
|
||||
document.querySelector('#repair_window .danger-btn')?.addEventListener('click', () => {
|
||||
const checked = getCheckedValues('#repair_window');
|
||||
if (checked.length === 0 || !window.pywebview) return;
|
||||
window.pywebview.api.execute_system_repair(checked).then(() => {
|
||||
window.pywebview.api.show_alert(getMsg("title_prompt"), getMsg("msg_repaired"), "info");
|
||||
renderDataGrid('#repair_window', [], cols.repair, 'value');
|
||||
});
|
||||
document.getElementById('repair_select')?.addEventListener('change', (e) => {
|
||||
const val = e.target.value;
|
||||
if (val === 'repair') {
|
||||
const checked = getCheckedValues('#repair_window');
|
||||
if (checked.length > 0 && window.pywebview) {
|
||||
window.pywebview.api.execute_system_repair(checked).then(() => {
|
||||
window.pywebview.api.show_alert(getMsg("title_prompt"), getMsg("msg_repaired"), "info");
|
||||
renderDataGrid('#repair_window', [], cols.repair, 'id', true);
|
||||
});
|
||||
}
|
||||
}
|
||||
updateCustomSelectUI('repair_select', 'none');
|
||||
});
|
||||
|
||||
['whitelist_window', 'quarantine_window', 'popup_window', 'custom_protect_window'].forEach(id => {
|
||||
@@ -728,65 +872,87 @@ document.addEventListener('DOMContentLoaded', () => {
|
||||
'quarantine_window': 'quarantine'
|
||||
};
|
||||
const listKey = listKeyMap[id];
|
||||
const selectId = id.replace('_window', '_select');
|
||||
|
||||
document.querySelector(`#${id} .modern-btn:not(.primary-btn)`)?.addEventListener('click', () => {
|
||||
const checked = getCheckedValues(`#${id}`);
|
||||
if (checked.length > 0 && window.pywebview) window.pywebview.api.remove_list_items(listKey, checked).then(refreshConfigLists);
|
||||
});
|
||||
|
||||
if (id !== 'popup_window') {
|
||||
document.querySelector(`#${id} .primary-btn`)?.addEventListener('click', () => {
|
||||
window.pywebview?.api.select_files().then(files => {
|
||||
document.getElementById(selectId)?.addEventListener('change', (e) => {
|
||||
const val = e.target.value;
|
||||
if (!window.pywebview) return;
|
||||
|
||||
if (val === 'add') {
|
||||
if (id === 'popup_window') {
|
||||
const wrapper = document.getElementById(selectId).nextElementSibling;
|
||||
const triggerText = wrapper.querySelector('.custom-select-text');
|
||||
const originalI18n = triggerText.getAttribute('data-i18n');
|
||||
const originalText = triggerText.textContent;
|
||||
|
||||
triggerText.setAttribute('data-i18n', 'msg_click_target');
|
||||
triggerText.textContent = getMsg('msg_click_target');
|
||||
|
||||
window.pywebview.api.capture_popup_window().then(rule => {
|
||||
triggerText.setAttribute('data-i18n', originalI18n || 'btn_select');
|
||||
triggerText.textContent = originalText;
|
||||
if (rule) window.pywebview.api.add_popup_rule(rule).then(ok => ok && refreshConfigLists());
|
||||
});
|
||||
} else {
|
||||
window.pywebview.api.select_files().then(files => {
|
||||
if (files?.length > 0) window.pywebview.api.manage_named_list(listKey, files, 'add').then(refreshConfigLists);
|
||||
});
|
||||
}
|
||||
} else if (val === 'add_file') {
|
||||
window.pywebview.api.select_files().then(files => {
|
||||
if (files?.length > 0) window.pywebview.api.manage_named_list(listKey, files, 'add').then(refreshConfigLists);
|
||||
});
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
document.querySelector('#popup_window .primary-btn')?.addEventListener('click', (e) => {
|
||||
if (!window.pywebview) return;
|
||||
e.target.setAttribute('data-i18n', 'msg_click_target'); e.target.textContent = getMsg('msg_click_target'); e.target.disabled = true;
|
||||
window.pywebview.api.capture_popup_window().then(rule => {
|
||||
e.target.setAttribute('data-i18n', 'btn_add'); e.target.textContent = getMsg('btn_add'); e.target.disabled = false;
|
||||
if (rule) window.pywebview.api.add_popup_rule(rule).then(ok => ok && refreshConfigLists());
|
||||
});
|
||||
});
|
||||
|
||||
document.querySelector('#taskmgr_window .primary-btn')?.addEventListener('click', () => {
|
||||
if (!window.pywebview) return;
|
||||
document.querySelectorAll('#taskmgr_window .manage-list-item input[type="checkbox"]:checked').forEach(cb => {
|
||||
if (cb.dataset.path && cb.dataset.path !== "path_unknown") window.pywebview.api.open_file_location(cb.dataset.path);
|
||||
});
|
||||
});
|
||||
|
||||
document.querySelector('#taskmgr_window .danger-btn')?.addEventListener('click', () => {
|
||||
const cbs = document.querySelectorAll('#taskmgr_window .manage-list-item input[type="checkbox"]:checked');
|
||||
if (cbs.length > 0 && window.pywebview) {
|
||||
window.pywebview.api.show_confirm(getMsg("msg_end_proc_title"), getMsg("msg_end_proc_confirm")).then(res => {
|
||||
if (res) Promise.all(Array.from(cbs).map(cb => window.pywebview.api.kill_process(parseInt(cb.value)))).then(() => {
|
||||
window.pywebview.api.get_process_list().then(procs => renderDataGrid('#taskmgr_window', procs.map(p => ({ name: p.name, pid: p.pid, path: (p.path && p.path !== "None" && p.path !== "Unknown") ? p.path : "path_unknown" })), cols.process, 'pid', false));
|
||||
} else if (val === 'add_path') {
|
||||
window.pywebview.api.select_folder().then(folder => {
|
||||
if (folder?.length > 0) window.pywebview.api.manage_named_list(listKey, folder, 'add').then(refreshConfigLists);
|
||||
});
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
document.querySelector('#log_export_window .primary-btn')?.addEventListener('click', () => {
|
||||
const checked = getCheckedValues('#log_export_window');
|
||||
if (checked.length === 0) return window.pywebview?.api.show_alert(getMsg("title_prompt"), getMsg("msg_sel_log_export"), "warning");
|
||||
window.pywebview?.api.export_logs(checked).then(res => res && window.pywebview.api.show_alert(getMsg("title_prompt"), getMsg("msg_export_success"), "info"));
|
||||
});
|
||||
|
||||
document.querySelector('#log_export_window .danger-btn')?.addEventListener('click', () => {
|
||||
const checked = getCheckedValues('#log_export_window');
|
||||
if (checked.length === 0 || !window.pywebview) return;
|
||||
window.pywebview.api.show_confirm(getMsg("title_prompt"), getMsg("msg_del_log_confirm")).then(res => {
|
||||
if (res) window.pywebview.api.clear_logs(checked).then(() => {
|
||||
window.pywebview.api.get_logs().then(logs => renderDataGrid('#log_export_window', logs.reverse().map(log => ({ time_str: `[${log.time_str}]`, level: log.level, action: log.action, source: log.source || '-', id: log.id })), cols.log, 'id', true));
|
||||
window.pywebview.api.show_alert(getMsg("title_prompt"), getMsg("msg_log_deleted"), "info");
|
||||
});
|
||||
} else if (val === 'extract') {
|
||||
const checked = getCheckedValues(`#${id}`);
|
||||
if (checked.length > 0) {
|
||||
window.pywebview.api.select_folder().then(folder => {
|
||||
if (folder?.length > 0) {
|
||||
window.pywebview.api.extract_list_items(checked, folder[0]).then(success => {
|
||||
if (success) window.pywebview.api.show_alert(getMsg("title_prompt"), getMsg("msg_extract_success"), "info");
|
||||
});
|
||||
}
|
||||
});
|
||||
}
|
||||
} else if (val === 'remove') {
|
||||
const checked = getCheckedValues(`#${id}`);
|
||||
if (checked.length > 0) window.pywebview.api.remove_list_items(listKey, checked).then(refreshConfigLists);
|
||||
} else if (val === 'quarantine' || val === 'whitelist') {
|
||||
const checked = getCheckedValues(`#${id}`);
|
||||
if (checked.length > 0) {
|
||||
const targetKey = val === 'quarantine' ? 'quarantine' : 'white_list';
|
||||
window.pywebview.api.manage_named_list(targetKey, checked, 'add').then(() => refreshConfigLists());
|
||||
}
|
||||
}
|
||||
updateCustomSelectUI(selectId, 'none');
|
||||
});
|
||||
});
|
||||
|
||||
document.getElementById('log_export_select')?.addEventListener('change', (e) => {
|
||||
const val = e.target.value;
|
||||
const checked = getCheckedValues('#log_export_window');
|
||||
if (val === 'export') {
|
||||
if (checked.length === 0) {
|
||||
window.pywebview?.api.show_alert(getMsg("title_prompt"), getMsg("msg_sel_log_export"), "warning");
|
||||
} else {
|
||||
window.pywebview?.api.export_logs(checked).then(res => res && window.pywebview.api.show_alert(getMsg("title_prompt"), getMsg("msg_export_success"), "info"));
|
||||
}
|
||||
} else if (val === 'delete') {
|
||||
if (checked.length > 0 && window.pywebview) {
|
||||
window.pywebview.api.show_confirm(getMsg("title_prompt"), getMsg("msg_del_log_confirm")).then(res => {
|
||||
if (res) window.pywebview.api.clear_logs(checked).then(() => {
|
||||
window.pywebview.api.get_logs().then(logs => renderDataGrid('#log_export_window', logs.reverse().map(log => ({ time_str: `[${log.time_str}]`, level: log.level, action: log.action, source: log.source || '-', id: log.id })), cols.log, 'id', true));
|
||||
window.pywebview.api.show_alert(getMsg("title_prompt"), getMsg("msg_log_deleted"), "info");
|
||||
});
|
||||
});
|
||||
}
|
||||
}
|
||||
updateCustomSelectUI('log_export_select', 'none');
|
||||
});
|
||||
|
||||
document.getElementById('minimize_button')?.addEventListener('click', () => window.pywebview?.api.minimize());
|
||||
document.getElementById('close_button')?.addEventListener('click', () => window.pywebview?.api.hide_window());
|
||||
|
||||
@@ -844,7 +1010,18 @@ document.addEventListener('DOMContentLoaded', () => {
|
||||
}
|
||||
};
|
||||
|
||||
bindSettingAction('key_product', async () => {
|
||||
if (!window.pywebview) return;
|
||||
try {
|
||||
const cfg = await window.pywebview.api.get_config();
|
||||
if (cfg && cfg.api_key && cfg.api_key.trim() !== "") {
|
||||
window.pywebview.api.show_alert(getMsg("title_prompt"), getMsg("msg_key_activated"), "info");
|
||||
}
|
||||
} catch (e) {}
|
||||
});
|
||||
|
||||
bindSettingAction('web_official', () => window.pywebview?.api.open_website());
|
||||
|
||||
bindSettingAction('update_check', () => {
|
||||
window.pywebview?.api.check_update().then(res => {
|
||||
if (res.error) window.pywebview.api.show_alert(getMsg("title_error"), getMsg("msg_update_fail"), "error");
|
||||
@@ -852,6 +1029,7 @@ document.addEventListener('DOMContentLoaded', () => {
|
||||
else window.pywebview.api.show_alert(getMsg("title_prompt"), `${getMsg("msg_latest_version")} ${res.current}`, "info");
|
||||
});
|
||||
});
|
||||
|
||||
bindSettingAction('opt_reset', () => {
|
||||
window.pywebview?.api.show_confirm(getMsg("title_prompt"), getMsg("opt_reset_desc")).then(res => {
|
||||
if (res) window.pywebview.api.reset_config().then(() => window.pywebview.api.show_alert(getMsg("title_prompt"), getMsg("msg_reset_success"), "info"));
|
||||
@@ -861,13 +1039,15 @@ document.addEventListener('DOMContentLoaded', () => {
|
||||
document.querySelectorAll('select.modern-select').forEach(buildCustomSelectElement);
|
||||
renderDataGrid('#scan_window', appState.virusResults, cols.virus, 'path', true);
|
||||
renderDataGrid('#taskmgr_window', [], cols.process, 'pid', false);
|
||||
renderDataGrid('#startup_window', [], cols.startup, 'id', false);
|
||||
renderDataGrid('#junk_window', [], cols.junk, 'path', true);
|
||||
renderDataGrid('#repair_window', [], cols.repair, 'value', true);
|
||||
renderDataGrid('#repair_window', [], cols.repair, 'id', true);
|
||||
renderDataGrid('#whitelist_window', [], cols.pathOnly, 'path', false);
|
||||
renderDataGrid('#quarantine_window', [], cols.pathOnly, 'path', false);
|
||||
renderDataGrid('#custom_protect_window', [], cols.pathOnly, 'path', false);
|
||||
renderDataGrid('#popup_window', [], cols.popup, 'value', false);
|
||||
renderDataGrid('#log_export_window', [], cols.log, 'id', true);
|
||||
renderDataGrid('#netmon_window', [], cols.netmon, 'pid', false);
|
||||
|
||||
window.addEventListener('pywebviewready', () => {
|
||||
window.pywebview.api.get_config().then(cfg => {
|
||||
@@ -913,7 +1093,7 @@ document.addEventListener('DOMContentLoaded', () => {
|
||||
if (res !== undefined && res !== null) toggle.checked = res;
|
||||
} catch { toggle.checked = false; }
|
||||
finally { toggle.disabled = false; }
|
||||
await new Promise(r => setTimeout(r, 200));
|
||||
await new Promise(r => setTimeout(r, 100));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
+117
-16
@@ -150,6 +150,12 @@
|
||||
</div>
|
||||
<button class="icon-action-btn"><svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><polyline points="9 18 15 12 9 6"></polyline></svg></button>
|
||||
</div>
|
||||
<div class="list-item" style="cursor: pointer;" data-target="startup_window">
|
||||
<div class="item-info">
|
||||
<h2 data-i18n>啟動項管理</h2><p data-i18n>此選項可以管理各應用啟動項</p>
|
||||
</div>
|
||||
<button class="icon-action-btn"><svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><polyline points="9 18 15 12 9 6"></polyline></svg></button>
|
||||
</div>
|
||||
<div class="list-item" style="cursor: pointer;" data-target="junk_window">
|
||||
<div class="item-info">
|
||||
<h2 data-i18n>垃圾清理</h2><p data-i18n>此選項可以清理暫存垃圾檔案</p>
|
||||
@@ -168,6 +174,12 @@
|
||||
</div>
|
||||
<button class="icon-action-btn"><svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><polyline points="9 18 15 12 9 6"></polyline></svg></button>
|
||||
</div>
|
||||
<div class="list-item" style="cursor: pointer;" data-target="netmon_window">
|
||||
<div class="item-info">
|
||||
<h2 data-i18n>流量監控</h2><p data-i18n>此選項可以實時監控網路流量</p>
|
||||
</div>
|
||||
<button class="icon-action-btn"><svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><polyline points="9 18 15 12 9 6"></polyline></svg></button>
|
||||
</div>
|
||||
<div class="list-item" style="cursor: pointer;" data-target="whitelist_window">
|
||||
<div class="item-info">
|
||||
<h2 data-i18n>白名單</h2><p data-i18n>此選項可以查看並管理白名單</p>
|
||||
@@ -214,6 +226,7 @@
|
||||
<h2 data-i18n>顯色主題</h2><p data-i18n>此選項可以選擇主題顏色</p>
|
||||
</div>
|
||||
<select id="theme_select" class="modern-select">
|
||||
<option value="system_switch" selected data-i18n>跟隨系統</option>
|
||||
<option value="white_switch" data-i18n>白色主題</option>
|
||||
<option value="red_switch" data-i18n>紅色主題</option>
|
||||
<option value="yellow_switch" data-i18n>黃色主題</option>
|
||||
@@ -314,8 +327,36 @@ Last updated: 2025-12-19
|
||||
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><circle cx="11" cy="11" r="8"></circle><line x1="21" y1="21" x2="16.65" y2="16.65"></line></svg>
|
||||
<input type="text" data-i18n-placeholder="搜尋進程..." placeholder="搜尋進程...">
|
||||
</div>
|
||||
<button class="action-btn modern-btn primary-btn" data-i18n>查看</button>
|
||||
<button class="action-btn modern-btn danger-btn" data-i18n>結束</button>
|
||||
<div class="action-group modern-action-group">
|
||||
<select id="taskmgr_select" class="modern-select">
|
||||
<option value="none" selected disabled data-i18n="btn_select">選擇</option>
|
||||
<option value="view" data-i18n="btn_view_item">查看項目</option>
|
||||
<option value="end" data-i18n="btn_end">結束進程</option>
|
||||
<option value="quarantine" data-i18n="act_quarantine">加入隔離區</option>
|
||||
<option value="whitelist" data-i18n="act_white">加入白名單</option>
|
||||
</select>
|
||||
</div>
|
||||
</div>
|
||||
<div class="manage-widget flex-grow"><ul class="manage-list"></ul></div>
|
||||
</section>
|
||||
|
||||
<section id="startup_window" class="page sub-page">
|
||||
<div class="sub-header-toolbar">
|
||||
<button class="icon-btn" data-target="tool_window"><svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><polyline points="15 18 9 12 15 6"></polyline></svg></button>
|
||||
<h2 data-i18n>啟動項管理</h2>
|
||||
<div class="spacer"></div>
|
||||
<div class="search-box">
|
||||
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><circle cx="11" cy="11" r="8"></circle><line x1="21" y1="21" x2="16.65" y2="16.65"></line></svg>
|
||||
<input type="text" data-i18n-placeholder="搜尋項目..." placeholder="搜尋項目...">
|
||||
</div>
|
||||
<div class="action-group modern-action-group">
|
||||
<select id="startup_select" class="modern-select">
|
||||
<option value="none" selected disabled data-i18n="btn_select">選擇</option>
|
||||
<option value="enable" data-i18n="btn_enable_item">啟用項目</option>
|
||||
<option value="disable" data-i18n="btn_disable_item">禁用項目</option>
|
||||
<option value="delete" data-i18n="btn_delete_item">刪除項目</option>
|
||||
</select>
|
||||
</div>
|
||||
</div>
|
||||
<div class="manage-widget flex-grow"><ul class="manage-list"></ul></div>
|
||||
</section>
|
||||
@@ -329,8 +370,16 @@ Last updated: 2025-12-19
|
||||
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><circle cx="11" cy="11" r="8"></circle><line x1="21" y1="21" x2="16.65" y2="16.65"></line></svg>
|
||||
<input type="text" data-i18n-placeholder="搜尋檔案..." placeholder="搜尋檔案...">
|
||||
</div>
|
||||
<button class="action-btn modern-btn primary-btn" data-i18n>加入</button>
|
||||
<button class="action-btn modern-btn" data-i18n>移除</button>
|
||||
<div class="action-group modern-action-group">
|
||||
<select id="whitelist_select" class="modern-select">
|
||||
<option value="none" selected disabled data-i18n="btn_select">選擇</option>
|
||||
<option value="add_file" data-i18n="btn_add_file">新增檔案</option>
|
||||
<option value="add_path" data-i18n="btn_add_path">新增路徑</option>
|
||||
<option value="extract" data-i18n="btn_extract_item">提取項目</option>
|
||||
<option value="remove" data-i18n="btn_remove_item">移除項目</option>
|
||||
<option value="quarantine" data-i18n="act_quarantine">加入隔離區</option>
|
||||
</select>
|
||||
</div>
|
||||
</div>
|
||||
<div class="manage-widget flex-grow"><ul class="manage-list"></ul></div>
|
||||
</section>
|
||||
@@ -344,8 +393,16 @@ Last updated: 2025-12-19
|
||||
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><circle cx="11" cy="11" r="8"></circle><line x1="21" y1="21" x2="16.65" y2="16.65"></line></svg>
|
||||
<input type="text" data-i18n-placeholder="搜尋檔案..." placeholder="搜尋檔案...">
|
||||
</div>
|
||||
<button class="action-btn modern-btn primary-btn" data-i18n>加入</button>
|
||||
<button class="action-btn modern-btn" data-i18n>移除</button>
|
||||
<div class="action-group modern-action-group">
|
||||
<select id="quarantine_select" class="modern-select">
|
||||
<option value="none" selected disabled data-i18n="btn_select">選擇</option>
|
||||
<option value="add_file" data-i18n="btn_add_file">新增檔案</option>
|
||||
<option value="add_path" data-i18n="btn_add_path">新增路徑</option>
|
||||
<option value="extract" data-i18n="btn_extract_item">提取項目</option>
|
||||
<option value="remove" data-i18n="btn_remove_item">移除項目</option>
|
||||
<option value="whitelist" data-i18n="act_white">加入白名單</option>
|
||||
</select>
|
||||
</div>
|
||||
</div>
|
||||
<div class="manage-widget flex-grow"><ul class="manage-list"></ul></div>
|
||||
</section>
|
||||
@@ -359,8 +416,13 @@ Last updated: 2025-12-19
|
||||
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><circle cx="11" cy="11" r="8"></circle><line x1="21" y1="21" x2="16.65" y2="16.65"></line></svg>
|
||||
<input type="text" data-i18n-placeholder="搜尋檔案..." placeholder="搜尋檔案...">
|
||||
</div>
|
||||
<button class="action-btn modern-btn primary-btn" data-i18n>掃描</button>
|
||||
<button class="action-btn modern-btn danger-btn" data-i18n>刪除</button>
|
||||
<div class="action-group modern-action-group">
|
||||
<select id="junk_select" class="modern-select">
|
||||
<option value="none" selected disabled data-i18n="btn_select">選擇</option>
|
||||
<option value="view" data-i18n="btn_view_item">查看項目</option>
|
||||
<option value="delete" data-i18n="btn_delete_item">刪除項目</option>
|
||||
</select>
|
||||
</div>
|
||||
</div>
|
||||
<div class="manage-widget flex-grow"><ul class="manage-list"></ul></div>
|
||||
</section>
|
||||
@@ -370,8 +432,12 @@ Last updated: 2025-12-19
|
||||
<button class="icon-btn" data-target="tool_window"><svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><polyline points="15 18 9 12 15 6"></polyline></svg></button>
|
||||
<h2 data-i18n>系統修復</h2>
|
||||
<div class="spacer"></div>
|
||||
<button class="action-btn modern-btn primary-btn" data-i18n>掃描</button>
|
||||
<button class="action-btn modern-btn danger-btn" data-i18n>修復</button>
|
||||
<div class="action-group modern-action-group">
|
||||
<select id="repair_select" class="modern-select">
|
||||
<option value="none" selected disabled data-i18n="btn_select">選擇</option>
|
||||
<option value="repair" data-i18n="btn_repair_item">修復項目</option>
|
||||
</select>
|
||||
</div>
|
||||
</div>
|
||||
<div class="manage-widget flex-grow"><ul class="manage-list"></ul></div>
|
||||
</section>
|
||||
@@ -385,8 +451,33 @@ Last updated: 2025-12-19
|
||||
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><circle cx="11" cy="11" r="8"></circle><line x1="21" y1="21" x2="16.65" y2="16.65"></line></svg>
|
||||
<input type="text" data-i18n-placeholder="搜尋窗口..." placeholder="搜尋窗口...">
|
||||
</div>
|
||||
<button class="action-btn modern-btn primary-btn" data-i18n>加入</button>
|
||||
<button class="action-btn modern-btn" data-i18n>移除</button>
|
||||
<div class="action-group modern-action-group">
|
||||
<select id="popup_select" class="modern-select">
|
||||
<option value="none" selected disabled data-i18n="btn_select">選擇</option>
|
||||
<option value="add" data-i18n="btn_add_item">新增項目</option>
|
||||
<option value="remove" data-i18n="btn_remove_item">移除項目</option>
|
||||
</select>
|
||||
</div>
|
||||
</div>
|
||||
<div class="manage-widget flex-grow"><ul class="manage-list"></ul></div>
|
||||
</section>
|
||||
|
||||
<section id="netmon_window" class="page sub-page">
|
||||
<div class="sub-header-toolbar">
|
||||
<button class="icon-btn" data-target="tool_window"><svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><polyline points="15 18 9 12 15 6"></polyline></svg></button>
|
||||
<h2 data-i18n>流量監控</h2>
|
||||
<div class="spacer"></div>
|
||||
<div class="search-box">
|
||||
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><circle cx="11" cy="11" r="8"></circle><line x1="21" y1="21" x2="16.65" y2="16.65"></line></svg>
|
||||
<input type="text" data-i18n-placeholder="搜尋進程..." placeholder="搜尋進程...">
|
||||
</div>
|
||||
<div class="action-group modern-action-group">
|
||||
<select id="netmon_select" class="modern-select">
|
||||
<option value="none" selected disabled data-i18n="btn_select">選擇</option>
|
||||
<option value="view" data-i18n="btn_view_item">查看項目</option>
|
||||
<option value="end" data-i18n="btn_end">結束進程</option>
|
||||
</select>
|
||||
</div>
|
||||
</div>
|
||||
<div class="manage-widget flex-grow"><ul class="manage-list"></ul></div>
|
||||
</section>
|
||||
@@ -400,8 +491,13 @@ Last updated: 2025-12-19
|
||||
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><circle cx="11" cy="11" r="8"></circle><line x1="21" y1="21" x2="16.65" y2="16.65"></line></svg>
|
||||
<input type="text" data-i18n-placeholder="搜尋規則..." placeholder="搜尋規則...">
|
||||
</div>
|
||||
<button class="action-btn modern-btn primary-btn" data-i18n>加入</button>
|
||||
<button class="action-btn modern-btn" data-i18n>移除</button>
|
||||
<div class="action-group modern-action-group">
|
||||
<select id="custom_protect_select" class="modern-select">
|
||||
<option value="none" selected disabled data-i18n="btn_select">選擇</option>
|
||||
<option value="add" data-i18n="btn_add_item">新增項目</option>
|
||||
<option value="remove" data-i18n="btn_remove_item">移除項目</option>
|
||||
</select>
|
||||
</div>
|
||||
</div>
|
||||
<div class="manage-widget flex-grow"><ul class="manage-list"></ul></div>
|
||||
</section>
|
||||
@@ -415,8 +511,13 @@ Last updated: 2025-12-19
|
||||
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><circle cx="11" cy="11" r="8"></circle><line x1="21" y1="21" x2="16.65" y2="16.65"></line></svg>
|
||||
<input type="text" data-i18n-placeholder="搜尋日誌..." placeholder="搜尋日誌...">
|
||||
</div>
|
||||
<button class="action-btn modern-btn primary-btn" data-i18n>導出</button>
|
||||
<button class="action-btn modern-btn danger-btn" data-i18n>刪除</button>
|
||||
<div class="action-group modern-action-group">
|
||||
<select id="log_export_select" class="modern-select">
|
||||
<option value="none" selected disabled data-i18n="btn_select">選擇</option>
|
||||
<option value="export" data-i18n="btn_export">導出項目</option>
|
||||
<option value="delete" data-i18n="btn_remove_item">移除項目</option>
|
||||
</select>
|
||||
</div>
|
||||
</div>
|
||||
<div class="manage-widget flex-grow"><ul class="manage-list"></ul></div>
|
||||
</section>
|
||||
|
||||
@@ -115,6 +115,16 @@ class COPYDATASTRUCT(ctypes.Structure):
|
||||
("lpData", ctypes.c_void_p)
|
||||
]
|
||||
|
||||
class IO_COUNTERS(ctypes.Structure):
|
||||
_fields_ = [
|
||||
("ReadOperationCount", ctypes.c_ulonglong),
|
||||
("WriteOperationCount", ctypes.c_ulonglong),
|
||||
("OtherOperationCount", ctypes.c_ulonglong),
|
||||
("ReadTransferCount", ctypes.c_ulonglong),
|
||||
("WriteTransferCount", ctypes.c_ulonglong),
|
||||
("OtherTransferCount", ctypes.c_ulonglong)
|
||||
]
|
||||
|
||||
####################################################################################################
|
||||
|
||||
class WindowAPI:
|
||||
@@ -285,6 +295,8 @@ class WindowAPI:
|
||||
self.kernel32.ReadProcessMemory.argtypes = [ctypes.wintypes.HANDLE, ctypes.c_void_p, ctypes.c_void_p, ctypes.c_size_t, ctypes.POINTER(ctypes.c_size_t)]
|
||||
self.kernel32.QueryFullProcessImageNameW.restype = ctypes.wintypes.BOOL
|
||||
self.kernel32.QueryFullProcessImageNameW.argtypes = [ctypes.wintypes.HANDLE, ctypes.wintypes.DWORD, ctypes.c_wchar_p, ctypes.POINTER(ctypes.wintypes.DWORD)]
|
||||
self.kernel32.GetProcessIoCounters.argtypes = [ctypes.wintypes.HANDLE, ctypes.POINTER(IO_COUNTERS)]
|
||||
self.kernel32.GetProcessIoCounters.restype = ctypes.wintypes.BOOL
|
||||
|
||||
def init_variables(self):
|
||||
self.python = sys.executable
|
||||
@@ -295,6 +307,9 @@ class WindowAPI:
|
||||
self.cloud = cloud_scanner()
|
||||
self.cloud_queue = queue.Queue()
|
||||
|
||||
self.ui_queue = queue.Queue()
|
||||
self.start_daemon_thread(self.ui_dispatcher_thread)
|
||||
|
||||
self.lock_driver = threading.RLock()
|
||||
self.lock_update = threading.RLock()
|
||||
self.driver_port = None
|
||||
@@ -312,20 +327,23 @@ class WindowAPI:
|
||||
self.lock_proc = threading.RLock()
|
||||
self.lock_net = threading.RLock()
|
||||
self.lock_file_ops = threading.RLock()
|
||||
self.lock_io = threading.RLock()
|
||||
self.last_io_counters = {}
|
||||
self.last_io_time = time.time()
|
||||
|
||||
self.scan_events = {}
|
||||
self.scan_cache = {}
|
||||
self.cloud_pending = set()
|
||||
|
||||
self.pyas_default = {
|
||||
"version": "3.5.6",
|
||||
"version": "3.5.8",
|
||||
"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", ".bin", ".rll", ".winmd", ".ax"],
|
||||
"suffix": [".exe", ".dll", ".sys", ".ocx", ".scr", ".efi", ".acm", ".ax", ".cpl", ".drv", ".com", ".mui", ".pyd", ".wfx", ".api", ".awx", ".rll", ".winmd", ".ax"],
|
||||
"block": [2001, 3001, 5001, 6001],
|
||||
"size": 100 * 1024 * 1024,
|
||||
"language": "english_switch",
|
||||
"theme": "white_switch",
|
||||
"theme": "system_switch",
|
||||
"first_launch": True,
|
||||
"process_switch": False,
|
||||
"document_switch": False,
|
||||
@@ -355,6 +373,35 @@ class WindowAPI:
|
||||
for _ in range(2):
|
||||
self.start_daemon_thread(self.cloud_worker)
|
||||
|
||||
def ui_dispatcher_thread(self):
|
||||
batch = []
|
||||
while True:
|
||||
try:
|
||||
task = self.ui_queue.get(timeout=0.1)
|
||||
batch.append(task)
|
||||
|
||||
while not self.ui_queue.empty() and len(batch) < 50:
|
||||
try:
|
||||
batch.append(self.ui_queue.get_nowait())
|
||||
except queue.Empty:
|
||||
break
|
||||
|
||||
if self._window and batch:
|
||||
js_script = "".join(batch)
|
||||
try:
|
||||
self._window.evaluate_js(js_script)
|
||||
except Exception:
|
||||
pass
|
||||
|
||||
for _ in batch:
|
||||
self.ui_queue.task_done()
|
||||
batch.clear()
|
||||
|
||||
except queue.Empty:
|
||||
continue
|
||||
except Exception:
|
||||
batch.clear()
|
||||
|
||||
def _loc(self, text_dict):
|
||||
with self.lock_config:
|
||||
lang = self.pyas_config.get("language", "traditional_switch") if hasattr(self, 'pyas_config') else "traditional_switch"
|
||||
@@ -515,7 +562,8 @@ class WindowAPI:
|
||||
self.logs_dirty = True
|
||||
|
||||
if self._window:
|
||||
self._window.evaluate_js(f"if(window.updateLogs) window.updateLogs({json.dumps(entry)});")
|
||||
js_cmd = f"if(window.updateLogs) window.updateLogs({json.dumps(entry)});"
|
||||
self.ui_queue.put(js_cmd)
|
||||
|
||||
if level == "BLOCK" and self.tray_icon:
|
||||
self.trigger_block_notification(action, source, target, code)
|
||||
@@ -563,7 +611,7 @@ class WindowAPI:
|
||||
if list_key == "quarantine" and lock_func is None:
|
||||
lock_func = self.lock_file
|
||||
|
||||
norm_paths = self.norm_path(files or [], must_exist=True)
|
||||
norm_paths = self.norm_path(files or [], must_exist=False)
|
||||
if isinstance(norm_paths, str):
|
||||
norm_paths = [norm_paths] if norm_paths else []
|
||||
|
||||
@@ -582,7 +630,15 @@ class WindowAPI:
|
||||
|
||||
if action == "add":
|
||||
for path in norm_paths:
|
||||
exists = any(self.norm_path(item.get("file", "")) == path for item in target_list if isinstance(item, dict))
|
||||
path_case = os.path.normcase(path)
|
||||
exists = False
|
||||
for item in target_list:
|
||||
if isinstance(item, dict):
|
||||
val = item.get("file", "")
|
||||
np = self.norm_path(val, must_exist=False)
|
||||
if np and os.path.normcase(np) == path_case:
|
||||
exists = True
|
||||
break
|
||||
if not exists:
|
||||
if lock_func:
|
||||
lock_func(path, True)
|
||||
@@ -591,15 +647,21 @@ class WindowAPI:
|
||||
if list_key == "white_list":
|
||||
self.sync_driver_whitelist(path, True)
|
||||
elif action == "remove":
|
||||
for item in list(target_list):
|
||||
file_path = self.norm_path(item.get("file", ""))
|
||||
if isinstance(item, dict) and file_path in norm_paths:
|
||||
if lock_func:
|
||||
lock_func(file_path, False)
|
||||
target_list.remove(item)
|
||||
acted_items.append(file_path)
|
||||
if list_key == "white_list":
|
||||
self.sync_driver_whitelist(file_path, False)
|
||||
norm_paths_case = {os.path.normcase(p) for p in norm_paths}
|
||||
new_list = []
|
||||
for item in target_list:
|
||||
val = item.get("file", "") if isinstance(item, dict) else item
|
||||
if val:
|
||||
np = self.norm_path(val, must_exist=False)
|
||||
if np and os.path.normcase(np) in norm_paths_case:
|
||||
if lock_func:
|
||||
lock_func(val, False)
|
||||
acted_items.append(val)
|
||||
if list_key == "white_list":
|
||||
self.sync_driver_whitelist(val, False)
|
||||
continue
|
||||
new_list.append(item)
|
||||
target_list[:] = new_list
|
||||
|
||||
if acted_items:
|
||||
self.write_log("INFO", "Config Update", detail=f"List [{list_key}] {action}: {acted_items}")
|
||||
@@ -611,29 +673,32 @@ class WindowAPI:
|
||||
target_list = self.pyas_config.get(list_key, [])
|
||||
original_len = len(target_list)
|
||||
|
||||
norm_paths_to_remove = set()
|
||||
for p in paths_to_remove:
|
||||
np = self.norm_path(p, must_exist=False)
|
||||
if np:
|
||||
norm_paths_to_remove.add(os.path.normcase(np))
|
||||
|
||||
if list_key == "quarantine":
|
||||
for item in target_list:
|
||||
if isinstance(item, dict) and item.get("file") in paths_to_remove:
|
||||
self.lock_file(item["file"], False)
|
||||
val = item.get("file") if isinstance(item, dict) else item
|
||||
if val:
|
||||
np = self.norm_path(val, must_exist=False)
|
||||
if np and os.path.normcase(np) in norm_paths_to_remove:
|
||||
self.lock_file(val, False)
|
||||
|
||||
new_list = []
|
||||
removed_items = []
|
||||
for item in target_list:
|
||||
if isinstance(item, dict):
|
||||
val = item.get("file") or item.get("exe") or item.get("title")
|
||||
if val not in paths_to_remove:
|
||||
new_list.append(item)
|
||||
else:
|
||||
val = item.get("file") or item.get("exe") or item.get("title") if isinstance(item, dict) else item
|
||||
if val:
|
||||
np = self.norm_path(val, must_exist=False)
|
||||
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)
|
||||
else:
|
||||
if item not in paths_to_remove:
|
||||
new_list.append(item)
|
||||
else:
|
||||
removed_items.append(item)
|
||||
if list_key == "white_list":
|
||||
self.sync_driver_whitelist(item, False)
|
||||
continue
|
||||
new_list.append(item)
|
||||
|
||||
self.pyas_config[list_key] = new_list
|
||||
|
||||
@@ -643,6 +708,35 @@ class WindowAPI:
|
||||
return True
|
||||
return False
|
||||
|
||||
def extract_list_items(self, paths, dest_dir):
|
||||
if not dest_dir or not os.path.exists(dest_dir):
|
||||
return False
|
||||
extracted_count = 0
|
||||
for raw_path in paths:
|
||||
src = self.norm_path(raw_path, must_exist=True)
|
||||
if not src:
|
||||
continue
|
||||
base_name = os.path.basename(src)
|
||||
target_path = os.path.join(dest_dir, base_name)
|
||||
|
||||
if os.path.exists(target_path):
|
||||
name, ext = os.path.splitext(base_name)
|
||||
counter = 1
|
||||
while os.path.exists(target_path):
|
||||
target_path = os.path.join(dest_dir, f"{name} ({counter}){ext}")
|
||||
counter += 1
|
||||
|
||||
try:
|
||||
if os.path.isdir(src):
|
||||
shutil.copytree(src, target_path)
|
||||
else:
|
||||
shutil.copy2(src, target_path)
|
||||
extracted_count += 1
|
||||
self.write_log("INFO", "File Extract", source=src, target=target_path, operate=True)
|
||||
except Exception as e:
|
||||
self.write_log("WARN", "extract_list_items", source=src, detail=str(e), success=False)
|
||||
return extracted_count > 0
|
||||
|
||||
####################################################################################################
|
||||
|
||||
def set_window(self, window):
|
||||
@@ -676,6 +770,12 @@ class WindowAPI:
|
||||
"spanish_switch": "Abrir PYAS", "hindi_switch": "PYAS खोलें", "arabic_switch": "فتح PYAS",
|
||||
"russian_switch": "Открыть PYAS", "slovenian_switch": "Odpri PYAS"
|
||||
},
|
||||
"check_update": {
|
||||
"traditional_switch": "檢查更新", "simplified_switch": "检查更新", "english_switch": "Check Update",
|
||||
"japanese_switch": "更新を確認", "korean_switch": "업데이트 확인", "french_switch": "Vérifier la mise à jour",
|
||||
"spanish_switch": "Buscar actualizaciones", "hindi_switch": "अद्यतन जाँचे", "arabic_switch": "التحقق من التحديثات",
|
||||
"russian_switch": "Проверить обновления", "slovenian_switch": "Preveri posodobitve"
|
||||
},
|
||||
"exit_app": {
|
||||
"traditional_switch": "退出防護", "simplified_switch": "退出防护", "english_switch": "Exit Security",
|
||||
"japanese_switch": "保護を終了", "korean_switch": "보호 종료", "french_switch": "Quitter la sécurité",
|
||||
@@ -690,11 +790,34 @@ class WindowAPI:
|
||||
return
|
||||
menu = pystray.Menu(
|
||||
pystray.MenuItem(lambda item: self.get_tray_text("open_ui"), self.restore_from_tray, default=True),
|
||||
pystray.MenuItem(lambda item: self.get_tray_text("check_update"), self.tray_check_update),
|
||||
pystray.MenuItem(lambda item: self.get_tray_text("exit_app"), self.close)
|
||||
)
|
||||
self.tray_icon = pystray.Icon("PYAS", self.get_app_icon(), "PYAS Security", menu)
|
||||
self.tray_icon.run_detached()
|
||||
|
||||
def tray_check_update(self, icon=None, item=None):
|
||||
def _check():
|
||||
res = self.check_update()
|
||||
|
||||
title_error = self._loc({"traditional_switch": "錯誤", "simplified_switch": "错误", "english_switch": "Error", "japanese_switch": "エラー", "korean_switch": "오류", "french_switch": "Erreur", "spanish_switch": "Error", "hindi_switch": "त्रुटि", "arabic_switch": "خطأ", "russian_switch": "Ошибка", "slovenian_switch": "Napaka"})
|
||||
title_prompt = self._loc({"traditional_switch": "提示", "simplified_switch": "提示", "english_switch": "Prompt", "japanese_switch": "プロンプト", "korean_switch": "프롬프트", "french_switch": "Indication", "spanish_switch": "Aviso", "hindi_switch": "सुझाव", "arabic_switch": "تلميح", "russian_switch": "Подсказка", "slovenian_switch": "Namig"})
|
||||
msg_fail = self._loc({"traditional_switch": "檢查更新失敗", "simplified_switch": "检查更新失败", "english_switch": "Update check failed", "japanese_switch": "アップデートの確認に失敗しました", "korean_switch": "업데이트 확인 실패", "french_switch": "Échec de la vérification des mises à jour", "spanish_switch": "Fallo al buscar actualizaciones", "hindi_switch": "अपडेट की जाँच विफल रही", "arabic_switch": "فشل التحقق من التحديثات", "russian_switch": "Ошибка проверки обновлений", "slovenian_switch": "Preverjanje posodobitev ni uspelo"})
|
||||
msg_new = self._loc({"traditional_switch": "發現新版本", "simplified_switch": "发现新版本", "english_switch": "New version found", "japanese_switch": "新しいバージョンが見つかりました", "korean_switch": "새 버전을 찾았습니다", "french_switch": "Nouvelle version trouvée", "spanish_switch": "Nueva versión encontrada", "hindi_switch": "नया संस्करण मिला", "arabic_switch": "تم العثور على إصدار جديد", "russian_switch": "Найдена новая версия", "slovenian_switch": "Najdena nova različica"})
|
||||
msg_latest = self._loc({"traditional_switch": "當前已是最新版本", "simplified_switch": "当前已是最新版本", "english_switch": "Currently at latest version", "japanese_switch": "現在は最新バージョンです", "korean_switch": "현재 최신 버전입니다", "french_switch": "Actuellement à la dernière version", "spanish_switch": "Actualmente en la última versión", "hindi_switch": "वर्तमान में नवीनतम संस्करण है", "arabic_switch": "أنت تستخدم أحدث إصدار حاليًا", "russian_switch": "Установлена последняя версия", "slovenian_switch": "Trenutno imate najnovejšo različico"})
|
||||
|
||||
if res.get("error"):
|
||||
self.show_alert(title_error, msg_fail, "error")
|
||||
elif res.get("has_update"):
|
||||
msg = f"{msg_new} {res.get('latest')}\n({res.get('current')} -> {res.get('latest')})"
|
||||
if self.show_confirm(title_prompt, msg):
|
||||
self.open_url(res.get("url"))
|
||||
else:
|
||||
msg = f"{msg_latest} {res.get('current')}"
|
||||
self.show_alert(title_prompt, msg, "info")
|
||||
|
||||
threading.Thread(target=_check, daemon=True).start()
|
||||
|
||||
def restore_from_tray(self, icon=None, item=None):
|
||||
if self._window:
|
||||
self._window.restore()
|
||||
@@ -825,21 +948,25 @@ class WindowAPI:
|
||||
threading.Thread(target=_process_drop, daemon=True).start()
|
||||
|
||||
def select_files(self):
|
||||
if self._window: return self._window.create_file_dialog(webview.FileDialog.OPEN, allow_multiple=True) or []
|
||||
if self._window:
|
||||
return self._window.create_file_dialog(getattr(webview, 'OPEN_DIALOG', 10), allow_multiple=True) or []
|
||||
return []
|
||||
|
||||
def select_folder(self):
|
||||
if self._window: return self._window.create_file_dialog(webview.FileDialog.FOLDER) or []
|
||||
if self._window:
|
||||
return self._window.create_file_dialog(getattr(webview, 'FOLDER_DIALOG', 20)) or []
|
||||
return []
|
||||
|
||||
def open_file_location(self, file_path):
|
||||
if file_path and os.path.exists(file_path):
|
||||
try:
|
||||
clean_path = os.path.normpath(file_path).replace('"', '')
|
||||
subprocess.Popen(f'explorer /select,"{clean_path}"')
|
||||
return True
|
||||
except Exception:
|
||||
pass
|
||||
if file_path:
|
||||
expanded_path = os.path.expandvars(file_path).strip('"').strip("'")
|
||||
if os.path.exists(expanded_path):
|
||||
try:
|
||||
clean_path = os.path.normpath(expanded_path)
|
||||
subprocess.Popen(f'explorer /select,"{clean_path}"')
|
||||
return True
|
||||
except Exception:
|
||||
pass
|
||||
return False
|
||||
|
||||
def open_website(self):
|
||||
@@ -971,17 +1098,25 @@ class WindowAPI:
|
||||
|
||||
def extract_paths_from_cmdline(self, cmdline):
|
||||
if not cmdline: return []
|
||||
argc = ctypes.c_int(0)
|
||||
argv = self.shell32.CommandLineToArgvW(cmdline, ctypes.byref(argc))
|
||||
if not argv: return []
|
||||
args = [argv[i] for i in range(argc.value)]
|
||||
self.kernel32.LocalFree(ctypes.cast(argv, ctypes.c_void_p))
|
||||
found = []
|
||||
patterns = [r'([A-Za-z]:\\[^"\']+)', r'(\\\\[^"\']+)', r'(\.\\[^"\']+)', r'(\./[^"\']+)', r'([A-Za-z]:/[^"\']+)', r'([^\s]*\\[^\s]+)']
|
||||
for arg in args:
|
||||
for p in patterns:
|
||||
for m in re.finditer(p, arg): found.append(m.group(1).strip('"').strip("'"))
|
||||
return list(dict.fromkeys(found))
|
||||
for m in re.finditer(r'"([^"]+)"|\'([^\']+)\'', cmdline):
|
||||
path = m.group(1) or m.group(2)
|
||||
if re.match(r'^[A-Za-z]:\\|^\\\\', path):
|
||||
found.append(path)
|
||||
if not found:
|
||||
m = re.search(r'([A-Za-z]:\\[^\*?"<>\|]+\.(?:exe|dll|bat|cmd|vbs|sys|com|pif))', cmdline, re.IGNORECASE)
|
||||
if m: found.append(m.group(1))
|
||||
if not found:
|
||||
argc = ctypes.c_int(0)
|
||||
argv = self.shell32.CommandLineToArgvW(cmdline, ctypes.byref(argc))
|
||||
if argv:
|
||||
args = [argv[i] for i in range(argc.value)]
|
||||
self.kernel32.LocalFree(ctypes.cast(argv, ctypes.c_void_p))
|
||||
patterns = [r'([A-Za-z]:\\[^"\']+)', r'(\\\\[^"\']+)', r'(\.\\[^"\']+)', r'(\./[^"\']+)', r'([A-Za-z]:/[^"\']+)', r'([^\s]*\\[^\s]+)']
|
||||
for arg in args:
|
||||
for p in patterns:
|
||||
for match in re.finditer(p, arg): found.append(match.group(1).strip('"').strip("'"))
|
||||
return list(dict.fromkeys([p.strip('"').strip("'") for p in found]))
|
||||
|
||||
def get_process_list(self):
|
||||
pe = PROCESSENTRY32W()
|
||||
@@ -1044,6 +1179,59 @@ class WindowAPI:
|
||||
except Exception: pass
|
||||
return connections
|
||||
|
||||
def get_traffic_list(self):
|
||||
conns = self.get_connections_list()
|
||||
conn_map = {}
|
||||
for pid, _, _ in conns:
|
||||
conn_map[pid] = conn_map.get(pid, 0) + 1
|
||||
|
||||
current_io = {}
|
||||
current_time = time.time()
|
||||
|
||||
with self.lock_io:
|
||||
time_diff = current_time - self.last_io_time
|
||||
if time_diff <= 0: time_diff = 1
|
||||
self.last_io_time = current_time
|
||||
old_counters = self.last_io_counters.copy()
|
||||
|
||||
result = []
|
||||
exist_process = self.get_process_list_pids()
|
||||
|
||||
for pid in exist_process:
|
||||
name, file_path = "Unknown", ""
|
||||
if pid > 4: name, file_path = self.get_exe_info(pid)
|
||||
else: name = "System"
|
||||
|
||||
down_speed, up_speed = 0, 0
|
||||
h = self.kernel32.OpenProcess(0x1000, False, pid)
|
||||
if h:
|
||||
try:
|
||||
io = IO_COUNTERS()
|
||||
if self.kernel32.GetProcessIoCounters(h, ctypes.byref(io)):
|
||||
current_io[pid] = (io.ReadTransferCount, io.WriteTransferCount)
|
||||
if pid in old_counters:
|
||||
old_read, old_write = old_counters[pid]
|
||||
down_speed = max(0, (io.ReadTransferCount - old_read) / time_diff)
|
||||
up_speed = max(0, (io.WriteTransferCount - old_write) / time_diff)
|
||||
finally:
|
||||
self.kernel32.CloseHandle(h)
|
||||
|
||||
count = conn_map.get(pid, 0)
|
||||
if count > 0 or down_speed > 0 or up_speed > 0:
|
||||
result.append({
|
||||
"pid": pid,
|
||||
"name": name,
|
||||
"path": file_path or "None",
|
||||
"down": int(down_speed),
|
||||
"up": int(up_speed),
|
||||
"conn": count
|
||||
})
|
||||
|
||||
with self.lock_io:
|
||||
self.last_io_counters = current_io
|
||||
|
||||
return result
|
||||
|
||||
def _traverse_delete(self, path):
|
||||
deleted = 0
|
||||
if not os.path.exists(path): return deleted
|
||||
@@ -1147,6 +1335,126 @@ class WindowAPI:
|
||||
self.write_log("WARN", "clean_system_junk", detail=str(e), operate=True, success=False)
|
||||
return 0
|
||||
|
||||
def get_startup_list(self):
|
||||
items = []
|
||||
|
||||
def get_reg_startup():
|
||||
res = []
|
||||
paths = [
|
||||
(winreg.HKEY_LOCAL_MACHINE, r"SOFTWARE\Microsoft\Windows\CurrentVersion\Run", "enabled"),
|
||||
(winreg.HKEY_CURRENT_USER, r"SOFTWARE\Microsoft\Windows\CurrentVersion\Run", "enabled"),
|
||||
(winreg.HKEY_LOCAL_MACHINE, r"SOFTWARE\Microsoft\Windows\CurrentVersion\Run_Disabled", "disabled"),
|
||||
(winreg.HKEY_CURRENT_USER, r"SOFTWARE\Microsoft\Windows\CurrentVersion\Run_Disabled", "disabled")
|
||||
]
|
||||
for root, path, status in paths:
|
||||
try:
|
||||
with winreg.OpenKey(root, path, 0, winreg.KEY_READ) as reg:
|
||||
i = 0
|
||||
while True:
|
||||
try:
|
||||
name, val, _ = winreg.EnumValue(reg, i)
|
||||
file_path = self.extract_paths_from_cmdline(val)
|
||||
fp = file_path[0] if file_path else val
|
||||
res.append({"id": f"reg|{root}|{path}|{name}", "type": "type_reg", "name": name, "status": status, "path": fp})
|
||||
i += 1
|
||||
except OSError: break
|
||||
except Exception: pass
|
||||
return res
|
||||
|
||||
def get_services():
|
||||
res = []
|
||||
try:
|
||||
with winreg.OpenKey(winreg.HKEY_LOCAL_MACHINE, r"SYSTEM\CurrentControlSet\Services", 0, winreg.KEY_READ) as reg:
|
||||
i = 0
|
||||
while True:
|
||||
try:
|
||||
name = winreg.EnumKey(reg, i)
|
||||
i += 1
|
||||
try:
|
||||
with winreg.OpenKey(reg, name, 0, winreg.KEY_READ) as sub:
|
||||
stype, _ = winreg.QueryValueEx(sub, "Type")
|
||||
if stype not in (16, 32, 256): continue
|
||||
start, _ = winreg.QueryValueEx(sub, "Start")
|
||||
if start not in (2, 3, 4): continue
|
||||
path, _ = winreg.QueryValueEx(sub, "ImagePath")
|
||||
|
||||
if path and "svchost" not in path.lower() and "windows" not in path.lower():
|
||||
fp = self.extract_paths_from_cmdline(path)
|
||||
fpp = fp[0] if fp else path
|
||||
status = "enabled" if start == 2 else "disabled"
|
||||
res.append({"id": f"srv|{name}", "type": "type_srv", "name": name, "status": status, "path": fpp})
|
||||
except Exception: pass
|
||||
except OSError: break
|
||||
except Exception: pass
|
||||
return res
|
||||
|
||||
def get_tasks():
|
||||
res = []
|
||||
try:
|
||||
proc = subprocess.run(["schtasks", "/query", "/fo", "csv", "/v"], capture_output=True, text=True, creationflags=0x08000000)
|
||||
import csv, io
|
||||
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]
|
||||
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})
|
||||
except Exception: pass
|
||||
return res
|
||||
|
||||
with ThreadPoolExecutor(max_workers=3) as executor:
|
||||
f1 = executor.submit(get_reg_startup)
|
||||
f2 = executor.submit(get_services)
|
||||
f3 = executor.submit(get_tasks)
|
||||
items.extend(f1.result())
|
||||
items.extend(f2.result())
|
||||
items.extend(f3.result())
|
||||
|
||||
return items
|
||||
|
||||
def manage_startup(self, items, action):
|
||||
def process_item(item_id):
|
||||
try:
|
||||
parts = item_id.split('|')
|
||||
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)
|
||||
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)
|
||||
elif stype == "srv":
|
||||
name = parts[1]
|
||||
if action == "delete":
|
||||
subprocess.run(["sc", "delete", name], capture_output=True, creationflags=0x08000000)
|
||||
else:
|
||||
mode = "auto" if action == "enable" else "disabled"
|
||||
subprocess.run(["sc", "config", name, f"start=", mode], capture_output=True, creationflags=0x08000000)
|
||||
elif stype == "tsk":
|
||||
name = parts[1]
|
||||
if action == "delete":
|
||||
subprocess.run(["schtasks", "/delete", "/tn", name, "/f"], capture_output=True, creationflags=0x08000000)
|
||||
else:
|
||||
cmd = "/enable" if action == "enable" else "/disable"
|
||||
subprocess.run(["schtasks", "/change", "/tn", name, cmd], capture_output=True, creationflags=0x08000000)
|
||||
except Exception as e:
|
||||
self.write_log("WARN", "manage_startup", detail=str(e), success=False)
|
||||
|
||||
with ThreadPoolExecutor(max_workers=5) as executor:
|
||||
list(executor.map(process_item, items))
|
||||
|
||||
return True
|
||||
|
||||
####################################################################################################
|
||||
|
||||
def init_engine_thread(self):
|
||||
@@ -1277,7 +1585,8 @@ class WindowAPI:
|
||||
current_time = time.time()
|
||||
if current_time - last_update >= 0.05:
|
||||
if self._window:
|
||||
self._window.evaluate_js(f"if(window.updateScanProgress) window.updateScanProgress({json.dumps(norm_path.replace(os.sep, '/'))});")
|
||||
js_cmd = f"if(window.updateScanProgress) window.updateScanProgress({json.dumps(norm_path.replace(os.sep, '/'))});"
|
||||
self.ui_queue.put(js_cmd)
|
||||
last_update = current_time
|
||||
|
||||
was_locked = False
|
||||
@@ -1297,7 +1606,9 @@ class WindowAPI:
|
||||
result = self.safe_scan_engine(norm_path)
|
||||
if result:
|
||||
with self.lock_virus: self.virus_results.append(norm_path)
|
||||
if self._window: self._window.evaluate_js(f"if(window.addVirusResult) window.addVirusResult({json.dumps(result)}, {json.dumps(norm_path.replace(os.sep, '/'))});")
|
||||
if self._window:
|
||||
js_cmd = f"if(window.addVirusResult) window.addVirusResult({json.dumps(result)}, {json.dumps(norm_path.replace(os.sep, '/'))});"
|
||||
self.ui_queue.put(js_cmd)
|
||||
self.write_log("SCAN", "Virus Detected", source=norm_path, file_hash=self.calc_file_hash(norm_path))
|
||||
|
||||
self.cloud_check(norm_path)
|
||||
@@ -1324,7 +1635,9 @@ class WindowAPI:
|
||||
"russian_switch": f"Найдено {count} вирусов, проверено {scanned} файлов, время {elapsed}с",
|
||||
"slovenian_switch": f"Najdenih {count} virusov, skeniranih {scanned} datotek, čas {elapsed}s"
|
||||
}
|
||||
if self._window: self._window.evaluate_js(f"if(window.finishScan) window.finishScan({json.dumps(messages)}, {count});")
|
||||
if self._window:
|
||||
js_cmd = f"if(window.finishScan) window.finishScan({json.dumps(messages)}, {count});"
|
||||
self.ui_queue.put(js_cmd)
|
||||
self.write_log("INFO", "Scan Completed", detail=f"Found {count} viruses, scanned {scanned} files, time {elapsed}s")
|
||||
|
||||
def stop_scan(self):
|
||||
@@ -1475,11 +1788,34 @@ class WindowAPI:
|
||||
####################################################################################################
|
||||
|
||||
def is_in_whitelist(self, file_path):
|
||||
p = self.norm_path(file_path)
|
||||
if not p: return False
|
||||
with self.lock_config: whitelist = self.pyas_config.get("white_list", [])
|
||||
p = self.norm_path(file_path, must_exist=False)
|
||||
if not p:
|
||||
return False
|
||||
|
||||
p_norm = os.path.normcase(p)
|
||||
return any(os.path.normcase(item.get("file", "")) == p_norm for item in whitelist if isinstance(item, dict))
|
||||
|
||||
with self.lock_config:
|
||||
whitelist = self.pyas_config.get("white_list", [])
|
||||
|
||||
for item in whitelist:
|
||||
if isinstance(item, dict):
|
||||
wl_path = item.get("file", "")
|
||||
wl_norm = self.norm_path(wl_path, must_exist=False)
|
||||
if not wl_norm:
|
||||
continue
|
||||
|
||||
wl_norm = os.path.normcase(wl_norm)
|
||||
|
||||
if p_norm == wl_norm:
|
||||
return True
|
||||
|
||||
if not wl_norm.endswith(os.sep):
|
||||
wl_norm += os.sep
|
||||
|
||||
if p_norm.startswith(wl_norm):
|
||||
return True
|
||||
|
||||
return False
|
||||
|
||||
def init_whitelist(self):
|
||||
self.manage_named_list("white_list", [self.file_pyas], action="add")
|
||||
@@ -1497,30 +1833,60 @@ class WindowAPI:
|
||||
try: return self.fltlib.FilterSendMessage(self.driver_port, ctypes.byref(msg), ctypes.sizeof(msg), None, 0, ctypes.byref(bytes_returned)) == 0
|
||||
except Exception: return False
|
||||
|
||||
def lock_file(self, file, lock):
|
||||
def lock_file(self, target_path, lock):
|
||||
with self.lock_file_ops:
|
||||
try:
|
||||
if lock:
|
||||
if file not in self.virus_lock:
|
||||
fd = os.open(file, os.O_RDWR | os.O_BINARY)
|
||||
if lock:
|
||||
if not os.path.exists(target_path):
|
||||
return
|
||||
|
||||
paths_to_lock = []
|
||||
if os.path.isdir(target_path):
|
||||
for root, _, files in os.walk(target_path):
|
||||
for f in files:
|
||||
paths_to_lock.append(os.path.join(root, f))
|
||||
else:
|
||||
paths_to_lock.append(target_path)
|
||||
|
||||
for file_path in paths_to_lock:
|
||||
if file_path in self.virus_lock:
|
||||
continue
|
||||
try:
|
||||
fd = os.open(file_path, os.O_RDWR | os.O_BINARY)
|
||||
try:
|
||||
try: size = os.path.getsize(file)
|
||||
except Exception: size = 1
|
||||
try:
|
||||
size = os.path.getsize(file_path)
|
||||
except Exception:
|
||||
size = 1
|
||||
lock_size = size if size > 0 else 1
|
||||
msvcrt.locking(fd, msvcrt.LK_NBRLCK, lock_size)
|
||||
self.virus_lock[file] = (fd, lock_size)
|
||||
self.virus_lock[file_path] = (fd, lock_size)
|
||||
except Exception:
|
||||
os.close(fd)
|
||||
raise
|
||||
else:
|
||||
if file in self.virus_lock:
|
||||
fd, lock_size = self.virus_lock[file]
|
||||
try: msvcrt.locking(fd, msvcrt.LK_UNLCK, lock_size)
|
||||
finally:
|
||||
except Exception as e:
|
||||
self.write_log("WARN", "lock_file", source=file_path, detail=str(e), success=False)
|
||||
else:
|
||||
target_norm = os.path.normcase(target_path)
|
||||
target_dir = target_norm + os.sep
|
||||
keys_to_unlock = []
|
||||
|
||||
for locked_path in self.virus_lock:
|
||||
locked_norm = os.path.normcase(locked_path)
|
||||
if locked_norm == target_norm or locked_norm.startswith(target_dir):
|
||||
keys_to_unlock.append(locked_path)
|
||||
|
||||
for file_path in keys_to_unlock:
|
||||
fd, lock_size = self.virus_lock[file_path]
|
||||
try:
|
||||
msvcrt.locking(fd, msvcrt.LK_UNLCK, lock_size)
|
||||
except Exception as e:
|
||||
self.write_log("WARN", "unlock_file", source=file_path, detail=str(e), success=False)
|
||||
finally:
|
||||
try:
|
||||
os.close(fd)
|
||||
del self.virus_lock[file]
|
||||
except Exception as e:
|
||||
self.write_log("WARN", "lock_file", source=file, detail=str(e), success=False)
|
||||
except Exception:
|
||||
pass
|
||||
del self.virus_lock[file_path]
|
||||
|
||||
def relock_file(self):
|
||||
with self.lock_config: quarantine_list = self.pyas_config.get("quarantine", [])
|
||||
@@ -1592,12 +1958,56 @@ class WindowAPI:
|
||||
|
||||
def scan_system_repair(self):
|
||||
items = []
|
||||
if self.check_system_mbr(): items.append({"display": "修復系統 MBR", "value": "mbr"})
|
||||
if self.check_system_restrict(): items.append({"display": "修復系統限制", "value": "restrict"})
|
||||
if self.check_system_file_type(): items.append({"display": "修復檔案關聯", "value": "file_type"})
|
||||
if self.check_system_file_icon(): items.append({"display": "修復檔案圖標", "value": "file_icon"})
|
||||
if self.check_system_image(): items.append({"display": "修復映像劫持", "value": "image"})
|
||||
if self.check_system_wallpaper(): items.append({"display": "修復桌面壁紙", "value": "wallpaper"})
|
||||
|
||||
for drive, mbr_value in self.mbr_backup.items():
|
||||
drive_path = rf"\\.\PhysicalDrive{drive}"
|
||||
try:
|
||||
with open(drive_path, "rb") as f:
|
||||
if f.read(512) != mbr_value:
|
||||
items.append({"id": f"mbr|{drive}", "display": "repair_mbr", "path": drive_path})
|
||||
except Exception: pass
|
||||
|
||||
permissions, paths = self._get_restrict_lists()
|
||||
for hkey, path in paths:
|
||||
for val in permissions:
|
||||
if self._reg_read(hkey, path, val) is not None:
|
||||
root = "HKLM" if hkey == winreg.HKEY_LOCAL_MACHINE else "HKCU"
|
||||
items.append({"id": f"restrict|{root}\\{path}\\{val}", "display": "repair_limit", "path": rf"{root}\{path}\{val}"})
|
||||
|
||||
for root in [winreg.HKEY_LOCAL_MACHINE, winreg.HKEY_CURRENT_USER]:
|
||||
root_str = "HKLM" if root == winreg.HKEY_LOCAL_MACHINE else "HKCU"
|
||||
for ext in [".exe", ".bat", ".cmd", ".com"]:
|
||||
expected = "exefile" if ext == ".exe" else ext[1:] + "file"
|
||||
if self._reg_read(root, rf"SOFTWARE\Classes\{ext}", "") != expected:
|
||||
items.append({"id": f"file_type|{root_str}\\{ext}", "display": "repair_assoc", "path": rf"{root_str}\SOFTWARE\Classes\{ext}"})
|
||||
for cmd in ["open", "runas"]:
|
||||
if self._reg_read(root, rf"SOFTWARE\Classes\exefile\shell\{cmd}\command", "") != '"%1" %*':
|
||||
items.append({"id": f"file_type|{root_str}\\exefile\\{cmd}", "display": "repair_assoc", "path": rf"{root_str}\SOFTWARE\Classes\exefile\shell\{cmd}\command"})
|
||||
if self._reg_read(root, r"SOFTWARE\Classes\exefile\DefaultIcon", "") != "%1":
|
||||
items.append({"id": f"file_icon|{root_str}", "display": "repair_icon", "path": rf"{root_str}\SOFTWARE\Classes\exefile\DefaultIcon"})
|
||||
|
||||
try:
|
||||
with winreg.OpenKey(winreg.HKEY_LOCAL_MACHINE, r"SOFTWARE\Microsoft\Windows NT\CurrentVersion\Image File Execution Options", 0, winreg.KEY_READ) as reg:
|
||||
i = 0
|
||||
while True:
|
||||
try:
|
||||
subkey = winreg.EnumKey(reg, i)
|
||||
with winreg.OpenKey(reg, subkey, 0, winreg.KEY_READ) as sub_reg:
|
||||
for val in ["Debugger", "UseFilter", "GlobalFlag", "MitigationOptions"]:
|
||||
try:
|
||||
winreg.QueryValueEx(sub_reg, val)
|
||||
items.append({"id": f"image|{subkey}", "display": "repair_hijack", "path": rf"HKLM\SOFTWARE\Microsoft\Windows NT\CurrentVersion\Image File Execution Options\{subkey}"})
|
||||
break
|
||||
except FileNotFoundError: pass
|
||||
i += 1
|
||||
except OSError: break
|
||||
except Exception: pass
|
||||
|
||||
wp = self._reg_read(winreg.HKEY_CURRENT_USER, r"Control Panel\Desktop", "Wallpaper")
|
||||
expected_wp = os.path.join(self.path_system, "web", "wallpaper", "Windows", "img0.jpg")
|
||||
if wp != expected_wp:
|
||||
items.append({"id": "wallpaper|0", "display": "repair_wallpaper", "path": wp if wp else r"HKCU\Control Panel\Desktop\Wallpaper"})
|
||||
|
||||
return items
|
||||
|
||||
def repair_system_mbr(self):
|
||||
@@ -1653,12 +2063,13 @@ class WindowAPI:
|
||||
|
||||
def execute_system_repair(self, items):
|
||||
try:
|
||||
if "mbr" in items: self.repair_system_mbr()
|
||||
if "restrict" in items: self.repair_system_restrict()
|
||||
if "file_type" in items: self.repair_system_file_type()
|
||||
if "file_icon" in items: self.repair_system_file_icon()
|
||||
if "image" in items: self.repair_system_image()
|
||||
if "wallpaper" in items: self.repair_system_wallpaper()
|
||||
types = set(item.split('|')[0] for item in items)
|
||||
if "mbr" in types: self.repair_system_mbr()
|
||||
if "restrict" in types: self.repair_system_restrict()
|
||||
if "file_type" in types: self.repair_system_file_type()
|
||||
if "file_icon" in types: self.repair_system_file_icon()
|
||||
if "image" in types: self.repair_system_image()
|
||||
if "wallpaper" in types: self.repair_system_wallpaper()
|
||||
self.write_log("INFO", "System Repair", detail=f"Repaired {len(items)} items", operate=True)
|
||||
return True
|
||||
except Exception as e:
|
||||
@@ -1853,12 +2264,13 @@ class WindowAPI:
|
||||
return True
|
||||
|
||||
def popup_intercept_thread(self):
|
||||
WNDENUMPROC = ctypes.WINFUNCTYPE(ctypes.c_bool, ctypes.c_int, ctypes.c_int)
|
||||
hwnd_list = []
|
||||
def enum_windows_callback(hWnd, lParam):
|
||||
if self.user32.IsWindowVisible(hWnd): hwnd_list.append(hWnd)
|
||||
return True
|
||||
enum_cb = WNDENUMPROC(enum_windows_callback)
|
||||
|
||||
WNDENUMPROC = ctypes.WINFUNCTYPE(ctypes.c_bool, ctypes.c_int, ctypes.c_int)
|
||||
self._enum_cb_keepalive = WNDENUMPROC(enum_windows_callback)
|
||||
|
||||
while True:
|
||||
try:
|
||||
@@ -1867,7 +2279,8 @@ class WindowAPI:
|
||||
if not rules: continue
|
||||
|
||||
hwnd_list.clear()
|
||||
self.user32.EnumWindows(enum_cb, 0)
|
||||
self.user32.EnumWindows(self._enum_cb_keepalive, 0)
|
||||
|
||||
for hWnd in list(hwnd_list):
|
||||
pid = ctypes.c_ulong(0)
|
||||
self.user32.GetWindowThreadProcessId(hWnd, ctypes.byref(pid))
|
||||
|
||||
+80
-61
@@ -1,4 +1,4 @@
|
||||
import os, re, yara, time, math, json, numpy, base64, datetime, requests, zlib
|
||||
import os, re, yara, time, math, json, zlib, numpy, base64, requests, datetime
|
||||
import ctypes, ctypes.wintypes, hashlib, pefile, threading, onnxruntime
|
||||
|
||||
####################################################################################################
|
||||
@@ -179,6 +179,17 @@ class rule_scanner:
|
||||
class pe_scanner:
|
||||
_STRING_PATTERN = re.compile(b'[\x20-\x7E]{5,}')
|
||||
|
||||
_BYTE_HIST_KEYS = [f'ByteHist_{i:02X}' for i in range(256)]
|
||||
_BYTE_ENT_KEYS = [f'ByteEnt_{i:02X}' for i in range(256)]
|
||||
_STRING_KEYS = [f'StringHist_{i:02d}' for i in range(95)]
|
||||
_SECTION_HASH_KEYS = [f'SectionHash_{i:02d}' for i in range(50)]
|
||||
_CHAR_FLAGS = [0x00000020, 0x00000040, 0x00000080, 0x02000000, 0x20000000, 0x40000000, 0x80000000]
|
||||
_CHAR_COUNT_KEYS = {flag: f'Char_{flag:08X}_Count' for flag in _CHAR_FLAGS}
|
||||
_CHAR_ENT_KEYS = {flag: f'Char_{flag:08X}_MeanEntropy' for flag in _CHAR_FLAGS}
|
||||
|
||||
_C_LOG_C_TABLE = numpy.zeros(2049, dtype=numpy.float64)
|
||||
_C_LOG_C_TABLE[1:] = numpy.arange(1, 2049) * numpy.log2(numpy.arange(1, 2049))
|
||||
|
||||
def __init__(self):
|
||||
self.model = None
|
||||
self.input_name = None
|
||||
@@ -221,39 +232,32 @@ class pe_scanner:
|
||||
if not data:
|
||||
return 0.0
|
||||
arr = numpy.frombuffer(data, dtype=numpy.uint8)
|
||||
sz = len(arr)
|
||||
counts = numpy.bincount(arr, minlength=256)
|
||||
probs = counts[counts > 0] / len(arr)
|
||||
return float(-numpy.sum(probs * numpy.log2(probs)))
|
||||
counts = counts[counts > 0]
|
||||
return float(numpy.log2(sz) - numpy.sum(counts * numpy.log2(counts)) / sz)
|
||||
|
||||
def _extract_strings(self, file_bytes):
|
||||
res = {"StringCount": 0.0, "StringMeanLength": 0.0, "StringEntropy": 0.0}
|
||||
for i in range(95):
|
||||
res[f'StringHist_{i:02d}'] = 0.0
|
||||
res.update(dict.fromkeys(self._STRING_KEYS, 0.0))
|
||||
|
||||
count = 0
|
||||
total_len = 0
|
||||
char_counts = [0] * 95
|
||||
combined_data = bytearray()
|
||||
|
||||
for match in self._STRING_PATTERN.finditer(file_bytes):
|
||||
m = match.group()
|
||||
count += 1
|
||||
length = len(m)
|
||||
total_len += length
|
||||
combined_data.extend(m)
|
||||
for b in m:
|
||||
char_counts[b - 0x20] += 1
|
||||
|
||||
if count == 0:
|
||||
matches = self._STRING_PATTERN.findall(file_bytes)
|
||||
if not matches:
|
||||
return res
|
||||
|
||||
combined_data = b''.join(matches)
|
||||
count = len(matches)
|
||||
total_len = len(combined_data)
|
||||
|
||||
res["StringCount"] = float(count)
|
||||
res["StringMeanLength"] = float(total_len) / count
|
||||
res["StringEntropy"] = self._calc_entropy(combined_data)
|
||||
|
||||
for i in range(95):
|
||||
res[f'StringHist_{i:02d}'] = float(char_counts[i]) / total_len
|
||||
arr = numpy.frombuffer(combined_data, dtype=numpy.uint8)
|
||||
counts = numpy.bincount(arr, minlength=127)
|
||||
char_counts = counts[0x20:0x7F]
|
||||
|
||||
res.update(dict(zip(self._STRING_KEYS, char_counts.astype(numpy.float64) / total_len)))
|
||||
return res
|
||||
|
||||
def _extract_histograms(self, file_bytes):
|
||||
@@ -262,43 +266,60 @@ class pe_scanner:
|
||||
res = {}
|
||||
|
||||
if sz == 0:
|
||||
for i in range(256):
|
||||
res[f'ByteHist_{i:02X}'] = 0.0
|
||||
res[f'ByteEnt_{i:02X}'] = 0.0
|
||||
res.update(dict.fromkeys(self._BYTE_HIST_KEYS, 0.0))
|
||||
res.update(dict.fromkeys(self._BYTE_ENT_KEYS, 0.0))
|
||||
return res
|
||||
|
||||
byte_counts = numpy.bincount(arr, minlength=256)
|
||||
byte_hist = byte_counts / sz
|
||||
for i, v in enumerate(byte_hist):
|
||||
res[f'ByteHist_{i:02X}'] = float(v)
|
||||
byte_hist = byte_counts.astype(numpy.float64) / sz
|
||||
res.update(dict(zip(self._BYTE_HIST_KEYS, byte_hist)))
|
||||
|
||||
ent_hist = numpy.zeros(256, dtype=numpy.float64)
|
||||
window = 2048
|
||||
step = 1024
|
||||
num_windows = (sz - window) // step + 1 if sz >= window else 1
|
||||
|
||||
for i in range(num_windows):
|
||||
w = arr if sz < window else arr[i*step : i*step+window]
|
||||
w_counts = numpy.bincount(w, minlength=256)
|
||||
|
||||
p = w_counts[w_counts > 0] / len(w)
|
||||
entropy = -numpy.sum(p * numpy.log2(p))
|
||||
|
||||
ent_bin = int(entropy * 2.0)
|
||||
if ent_bin > 15:
|
||||
ent_bin = 15
|
||||
|
||||
if sz < window:
|
||||
w_counts = byte_counts
|
||||
p = w_counts[w_counts > 0] / float(sz)
|
||||
entropy = float(-numpy.sum(p * numpy.log2(p)))
|
||||
ent_bin = min(int(entropy * 2.0), 15)
|
||||
byte_bins = w_counts.reshape(16, 16).sum(axis=1)
|
||||
idx_start = ent_bin * 16
|
||||
ent_hist[idx_start : idx_start+16] += byte_bins
|
||||
else:
|
||||
num_windows = (sz - window) // step + 1
|
||||
batch_windows = 10000
|
||||
|
||||
for w_start in range(0, num_windows, batch_windows):
|
||||
w_end = min(w_start + batch_windows, num_windows)
|
||||
b_num = w_end - w_start
|
||||
|
||||
start_byte = w_start * step
|
||||
end_byte = start_byte + b_num * step + step
|
||||
b_arr = arr[start_byte:end_byte]
|
||||
|
||||
arr_2d = b_arr.reshape(b_num + 1, step)
|
||||
offsets = (numpy.arange(b_num + 1, dtype=numpy.int32)[:, None] * 256)
|
||||
flat_idx = (arr_2d.astype(numpy.int32) + offsets).ravel()
|
||||
|
||||
chunk_counts = numpy.bincount(flat_idx, minlength=(b_num + 1) * 256).reshape(b_num + 1, 256)
|
||||
window_counts = chunk_counts[:-1] + chunk_counts[1:]
|
||||
|
||||
sum_c_log_c = numpy.sum(self._C_LOG_C_TABLE[window_counts], axis=1)
|
||||
entropies = 11.0 - sum_c_log_c / 2048.0
|
||||
|
||||
ent_bins = (entropies * 2.0).astype(numpy.int32)
|
||||
numpy.clip(ent_bins, 0, 15, out=ent_bins)
|
||||
|
||||
byte_bins = window_counts.reshape(b_num, 16, 16).sum(axis=2)
|
||||
flat_ent_bins = (ent_bins[:, None] * 16 + numpy.arange(16)).ravel()
|
||||
numpy.add.at(ent_hist, flat_ent_bins, byte_bins.ravel())
|
||||
|
||||
ent_sum = numpy.sum(ent_hist)
|
||||
if ent_sum > 0:
|
||||
ent_hist /= ent_sum
|
||||
|
||||
for i, v in enumerate(ent_hist):
|
||||
res[f'ByteEnt_{i:02X}'] = float(v)
|
||||
|
||||
res.update(dict(zip(self._BYTE_ENT_KEYS, ent_hist)))
|
||||
return res
|
||||
|
||||
def _extract_overlay_features(self, pe, fsize):
|
||||
@@ -516,28 +537,21 @@ class pe_scanner:
|
||||
if len(op.DATA_DIRECTORY) > exc_idx and op.DATA_DIRECTORY[exc_idx].Size > 0:
|
||||
base['ExceptionCount'] = op.DATA_DIRECTORY[exc_idx].Size // 12 if base['Machine'] in (0x8664, 0xAA64) else 0.0
|
||||
|
||||
char_flags = [0x00000020, 0x00000040, 0x00000080, 0x02000000, 0x20000000, 0x40000000, 0x80000000]
|
||||
for flag in char_flags:
|
||||
base[f'Char_{flag:08X}_Count'] = 0.0
|
||||
base[f'Char_{flag:08X}_MeanEntropy'] = 0.0
|
||||
|
||||
for i in range(50):
|
||||
base[f'SectionHash_{i:02d}'] = 0.0
|
||||
res_flags_count = dict.fromkeys(self._CHAR_COUNT_KEYS.values(), 0.0)
|
||||
res_flags_ent = dict.fromkeys(self._CHAR_ENT_KEYS.values(), 0.0)
|
||||
res_sec_hash = dict.fromkeys(self._SECTION_HASH_KEYS, 0.0)
|
||||
|
||||
if hasattr(pe, 'sections'):
|
||||
base['SectionCount'] = len(pe.sections)
|
||||
entropies = []
|
||||
raw_sizes = []
|
||||
v_sizes = []
|
||||
exec_sec = 0
|
||||
write_sec = 0
|
||||
read_sec = 0
|
||||
sec_exc = 0
|
||||
exec_sec = write_sec = read_sec = sec_exc = 0
|
||||
|
||||
for section in pe.sections:
|
||||
sec_name = section.Name.rstrip(b'\x00')
|
||||
hash_val = zlib.crc32(sec_name) % 50
|
||||
base[f'SectionHash_{hash_val:02d}'] += 1.0
|
||||
res_sec_hash[self._SECTION_HASH_KEYS[hash_val]] += 1.0
|
||||
|
||||
try:
|
||||
s_data = section.get_data()
|
||||
@@ -554,10 +568,10 @@ class pe_scanner:
|
||||
if section.Characteristics & 0x40000000: read_sec += 1
|
||||
if section.SizeOfRawData + section.PointerToRawData > fsize: sec_exc = 1
|
||||
|
||||
for flag in char_flags:
|
||||
for flag in self._CHAR_FLAGS:
|
||||
if section.Characteristics & flag:
|
||||
base[f'Char_{flag:08X}_Count'] += 1.0
|
||||
base[f'Char_{flag:08X}_MeanEntropy'] += s_entropy
|
||||
res_flags_count[self._CHAR_COUNT_KEYS[flag]] += 1.0
|
||||
res_flags_ent[self._CHAR_ENT_KEYS[flag]] += s_entropy
|
||||
|
||||
base['SectionMaxEntropy'] = max(entropies) if entropies else 0.0
|
||||
base['SectionMinEntropy'] = min(entropies) if entropies else 0.0
|
||||
@@ -573,9 +587,14 @@ class pe_scanner:
|
||||
base['ReadableSections'] = float(read_sec)
|
||||
base['SectionException'] = float(sec_exc)
|
||||
|
||||
for flag in char_flags:
|
||||
if base[f'Char_{flag:08X}_Count'] > 0:
|
||||
base[f'Char_{flag:08X}_MeanEntropy'] = base[f'Char_{flag:08X}_MeanEntropy'] / base[f'Char_{flag:08X}_Count']
|
||||
for flag in self._CHAR_FLAGS:
|
||||
cnt_key = self._CHAR_COUNT_KEYS[flag]
|
||||
if res_flags_count[cnt_key] > 0:
|
||||
res_flags_ent[self._CHAR_ENT_KEYS[flag]] /= res_flags_count[cnt_key]
|
||||
|
||||
base.update(res_flags_count)
|
||||
base.update(res_flags_ent)
|
||||
base.update(res_sec_hash)
|
||||
|
||||
string_keys = ['FileDescription', 'FileVersion', 'ProductName', 'ProductVersion', 'CompanyName', 'LegalCopyright', 'Comments', 'InternalName', 'LegalTrademarks', 'SpecialBuild', 'PrivateBuild']
|
||||
for key in string_keys:
|
||||
|
||||
+1
-1
@@ -2,7 +2,7 @@ import pyinstaller_versionfile
|
||||
|
||||
pyinstaller_versionfile.create_versionfile(
|
||||
output_file="versionfile.txt",
|
||||
version='3.5.6.0',
|
||||
version='3.5.8.0',
|
||||
company_name="PYAS Security",
|
||||
file_description="PYAS Security Antivirus",
|
||||
internal_name="PYAS",
|
||||
|
||||
+231
-224
@@ -1,226 +1,233 @@
|
||||
{
|
||||
"Rule_Registry_BlockList": [
|
||||
"\\REGISTRY\\MACHINE\\BCD00000000",
|
||||
"\\REGISTRY\\MACHINE\\BCD00000000\\*",
|
||||
|
||||
"*\\Classes\\.exe",
|
||||
"*\\Classes\\.bat",
|
||||
"*\\Classes\\.cmd",
|
||||
"*\\Classes\\.com",
|
||||
"*\\Classes\\exefile\\*",
|
||||
"*\\Classes\\batfile\\*",
|
||||
"*\\Classes\\cmdfile\\*",
|
||||
"*\\Classes\\comfile\\*",
|
||||
|
||||
"*\\mouclass",
|
||||
"*\\kbdclass",
|
||||
"*\\DisableAntiSpyware",
|
||||
"*\\DisableWindowsUpdateAccess",
|
||||
"*\\EnableLUA",
|
||||
"*\\ConsentPromptBehaviorAdmin",
|
||||
"*\\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",
|
||||
"*\\NoComponents",
|
||||
"*\\NoStartMenuMorePrograms",
|
||||
"*\\NoActiveDesktop",
|
||||
"*\\NoSetActiveDesktop",
|
||||
"*\\NoRecentDocsMenu",
|
||||
"*\\NoWindowsUpdate",
|
||||
"*\\NoChangeStartMenu",
|
||||
"*\\NoFavoritesMenu",
|
||||
"*\\NoRecentDocsHistory",
|
||||
"*\\NoSetTaskbar",
|
||||
"*\\NoSMHelp",
|
||||
"*\\NoTrayContextMenu",
|
||||
"*\\NoManageMyComputerVerb",
|
||||
"*\\NoRealMode",
|
||||
"*\\NoRun",
|
||||
"*\\ClearRecentDocsOnExit",
|
||||
"*\\NoActiveDesktopChanges",
|
||||
"*\\NoStartMenuNetworkPlaces",
|
||||
"*\\NoLockScreen",
|
||||
"*\\HideFastUserSwitching",
|
||||
"*\\ConsentPromptBehaviorUser",
|
||||
"*\\EnableSecureUIAPaths",
|
||||
"*\\NoSRM",
|
||||
"*\\NoInteractiveServices",
|
||||
"*\\ShutdownWithoutLogon",
|
||||
"*\\AutoAdminLogon",
|
||||
"*\\BootExecute",
|
||||
"*\\SubSystems",
|
||||
"*\\FirmwareBootDevice",
|
||||
"*\\BootDriverFlags",
|
||||
"*\\SystemStartOptions",
|
||||
"*\\DisableAutomaticRestartOnFailure",
|
||||
"*\\SystemBiosVersion",
|
||||
"*\\NoTheme",
|
||||
"*\\SystemSetupInProgress",
|
||||
"*\\SetupType",
|
||||
"*\\AutoReboot",
|
||||
"*\\Userinit",
|
||||
"*\\UIHost",
|
||||
"*\\Debugger",
|
||||
"*\\DefaultUserName",
|
||||
"*\\DefaultPassword",
|
||||
"*\\AltDefaultUserName",
|
||||
"*\\AltDefaultPassword",
|
||||
"*\\DisableCAD",
|
||||
"*\\LegalNoticeCaption",
|
||||
"*\\LegalNoticeText",
|
||||
],
|
||||
"Rule_Registry_TrustedList": [
|
||||
],
|
||||
"Rule_Process_TrustedPaths": [
|
||||
"\\Device\\HarddiskVolume*\\Windows\\*",
|
||||
"\\Device\\HarddiskVolume*\\Program Files\\*",
|
||||
"\\Device\\HarddiskVolume*\\Program Files (x86)\\*",
|
||||
|
||||
"\\Device\\HarddiskVolume*\\ProgramData\\Microsoft\\Windows Defender\\*",
|
||||
"\\Device\\HarddiskVolume*\\ProgramData\\Microsoft\\Windows Defender Advanced Threat Protection\\*",
|
||||
"*\\Sources\\SetupHost.exe",
|
||||
"*\\Work\\*\\DismHost.exe",
|
||||
|
||||
"*\\Google\\Chrome\\Application\\chrome.exe",
|
||||
"*\\Microsoft\\Edge\\Application\\msedge.exe",
|
||||
"*\\Mozilla Firefox\\firefox.exe",
|
||||
"*\\Thorium\\Application\\thorium.exe",
|
||||
|
||||
"*\\Steam\\steam.exe",
|
||||
"*\\steam\\*\\steam.exe",
|
||||
"*\\Battle.net\\*\\Battle.net.exe",
|
||||
"*\\Epic Games\\Launcher\\Portal\\Binaries\\*\\EpicGamesLauncher.exe",
|
||||
|
||||
"*\\resources\\bin\\SDK\\DownloadSDKServer.exe",
|
||||
"*\\ThunderEv.exe",
|
||||
"*\\ThunderStart.exe",
|
||||
"*\\KuGou.exe",
|
||||
"*\\XLGame.exe",
|
||||
"*\\XLLiveUD.exe",
|
||||
|
||||
"*\\hrkill-*.exe",
|
||||
"*\\bin\\HipsDaemon.exe",
|
||||
"*\\360Safe.exe",
|
||||
"*\\bilibili\\bilibili.exe",
|
||||
|
||||
"*\\Kingsoft\\WPS Office\\ksolaunch.exe",
|
||||
"*\\Kingsoft\\WPS Office\\*\\wps.exe",
|
||||
|
||||
"*\\PC4399_WPFLauncher.exe",
|
||||
"*\\bin\\APlayer.exe",
|
||||
"*\\Neverness To Everness\\NTELauncher.exe",
|
||||
"*\\Application\\AndrowsLauncher.exe",
|
||||
],
|
||||
"Rule_Process_ExploitableBlacklist": [
|
||||
"*\\cmd.exe",
|
||||
"*\\powershell.exe",
|
||||
"*\\pwsh.exe",
|
||||
"*\\wscript.exe",
|
||||
"*\\cscript.exe",
|
||||
"*\\mshta.exe",
|
||||
"*\\regsvr32.exe",
|
||||
"*\\certutil.exe",
|
||||
"*\\bitsadmin.exe",
|
||||
"*\\wmic.exe",
|
||||
"*\\hh.exe",
|
||||
"*\\cmstp.exe",
|
||||
"*\\installutil.exe",
|
||||
"*\\regasm.exe",
|
||||
"*\\regsvcs.exe",
|
||||
"*\\msbuild.exe",
|
||||
"*\\csc.exe",
|
||||
"*\\vbc.exe",
|
||||
"*\\jsc.exe",
|
||||
"*\\bash.exe",
|
||||
"*\\scrcons.exe",
|
||||
"*\\schtasks.exe",
|
||||
"*\\at.exe",
|
||||
"*\\reg.exe",
|
||||
"*\\sc.exe",
|
||||
"*\\net.exe",
|
||||
"*\\net1.exe",
|
||||
"*\\whoami.exe",
|
||||
"*\\ipconfig.exe",
|
||||
"*\\taskkill.exe",
|
||||
"*\\ProcessHacker.exe",
|
||||
"*\\procmon.exe",
|
||||
"*\\procexp.exe",
|
||||
],
|
||||
"Rule_File_ProtectedPaths": [
|
||||
"*\\PYAS.exe",
|
||||
"*\\Plugins\\Filter\\PYAS_Driver.sys",
|
||||
"*\\Plugins\\Rules\\Rules_Driver_P1.json",
|
||||
"*\\ProgramData\\PYAS\\*.json",
|
||||
"*\\Engine\\Pattern\\*.onnx",
|
||||
"*\\Engine\\Properties\\*.onnx",
|
||||
|
||||
"\\Device\\HarddiskVolume*\\bootmgr",
|
||||
"\\Device\\HarddiskVolume*\\boot.ini",
|
||||
"\\Device\\HarddiskVolume*\\BOOTNXT",
|
||||
"\\Device\\HarddiskVolume*\\EFI\\*",
|
||||
"\\Device\\HarddiskVolume*\\Boot\\*",
|
||||
"\\Device\\HarddiskVolume*\\Recovery\\*",
|
||||
"\\Device\\HarddiskVolume*\\System Volume Information\\*",
|
||||
"\\Device\\HarddiskVolume*\\Windows\\System32\\config\\*",
|
||||
|
||||
"*\\CON",
|
||||
"*\\CON\\*",
|
||||
"*\\PRN",
|
||||
"*\\PRN\\*",
|
||||
"*\\AUX",
|
||||
"*\\AUX\\*",
|
||||
"*\\NUL",
|
||||
"*\\NUL\\*",
|
||||
"*\\COM?",
|
||||
"*\\COM?\\*",
|
||||
"*\\LPT?",
|
||||
"*\\LPT?\\*",
|
||||
"*\\OSDATA",
|
||||
"*\\OSDATA\\*",
|
||||
"*\\*<*",
|
||||
"*\\*>*",
|
||||
],
|
||||
"Rule_File_ExceptionPaths": [
|
||||
"*\\Users\\*\\steam\\*",
|
||||
"*\\AppData\\*",
|
||||
"*\\Temp\\*",
|
||||
"*\\Local\\Temp\\*",
|
||||
"*\\ventoy\\Boot\\*",
|
||||
"*\\AdminRegionConfig.*",
|
||||
"*\\nt_qq\\nt_*\\*",
|
||||
"*\\cache\\*",
|
||||
],
|
||||
"Rule_File_RansomwareExtensions": [
|
||||
".zip", ".7z", ".rar", ".tar", ".gz",
|
||||
".ppt", ".pptx", ".wps", ".txt", ".rtf",
|
||||
".pdf", ".xls", ".xlsx", ".doc", ".docx",
|
||||
".jpg", ".jpeg", ".png", ".webp", ".gif",
|
||||
".mp3", ".wav", ".aac", ".ogg", ".flac",
|
||||
".mp4", ".avi", ".mov", ".wmv", ".mkv",
|
||||
]
|
||||
"Rule_Registry_BlockList": [
|
||||
"\\REGISTRY\\MACHINE\\BCD00000000",
|
||||
"\\REGISTRY\\MACHINE\\BCD00000000\\*",
|
||||
|
||||
"*\\Classes\\.exe",
|
||||
"*\\Classes\\.bat",
|
||||
"*\\Classes\\.cmd",
|
||||
"*\\Classes\\.com",
|
||||
"*\\Classes\\exefile\\*",
|
||||
"*\\Classes\\batfile\\*",
|
||||
"*\\Classes\\cmdfile\\*",
|
||||
"*\\Classes\\comfile\\*",
|
||||
|
||||
"*\\mouclass",
|
||||
"*\\kbdclass",
|
||||
"*\\DisableAntiSpyware",
|
||||
"*\\DisableWindowsUpdateAccess",
|
||||
"*\\EnableLUA",
|
||||
"*\\ConsentPromptBehaviorAdmin",
|
||||
"*\\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",
|
||||
"*\\NoComponents",
|
||||
"*\\NoStartMenuMorePrograms",
|
||||
"*\\NoActiveDesktop",
|
||||
"*\\NoSetActiveDesktop",
|
||||
"*\\NoRecentDocsMenu",
|
||||
"*\\NoWindowsUpdate",
|
||||
"*\\NoChangeStartMenu",
|
||||
"*\\NoFavoritesMenu",
|
||||
"*\\NoRecentDocsHistory",
|
||||
"*\\NoSetTaskbar",
|
||||
"*\\NoSMHelp",
|
||||
"*\\NoTrayContextMenu",
|
||||
"*\\NoManageMyComputerVerb",
|
||||
"*\\NoRealMode",
|
||||
"*\\NoRun",
|
||||
"*\\ClearRecentDocsOnExit",
|
||||
"*\\NoActiveDesktopChanges",
|
||||
"*\\NoStartMenuNetworkPlaces",
|
||||
"*\\NoLockScreen",
|
||||
"*\\HideFastUserSwitching",
|
||||
"*\\ConsentPromptBehaviorUser",
|
||||
"*\\EnableSecureUIAPaths",
|
||||
"*\\NoSRM",
|
||||
"*\\NoInteractiveServices",
|
||||
"*\\ShutdownWithoutLogon",
|
||||
"*\\AutoAdminLogon",
|
||||
"*\\BootExecute",
|
||||
"*\\SubSystems",
|
||||
"*\\FirmwareBootDevice",
|
||||
"*\\BootDriverFlags",
|
||||
"*\\SystemStartOptions",
|
||||
"*\\DisableAutomaticRestartOnFailure",
|
||||
"*\\SystemBiosVersion",
|
||||
"*\\NoTheme",
|
||||
"*\\SystemSetupInProgress",
|
||||
"*\\SetupType",
|
||||
"*\\AutoReboot",
|
||||
"*\\Userinit",
|
||||
"*\\UIHost",
|
||||
"*\\Debugger",
|
||||
"*\\DefaultUserName",
|
||||
"*\\DefaultPassword",
|
||||
"*\\AltDefaultUserName",
|
||||
"*\\AltDefaultPassword",
|
||||
"*\\DisableCAD",
|
||||
"*\\LegalNoticeCaption",
|
||||
"*\\LegalNoticeText",
|
||||
],
|
||||
"Rule_Registry_TrustedList": [
|
||||
],
|
||||
"Rule_Process_TrustedPaths": [
|
||||
"\\Device\\HarddiskVolume*\\Windows\\*",
|
||||
"\\Device\\HarddiskVolume*\\Program Files\\*",
|
||||
"\\Device\\HarddiskVolume*\\Program Files (x86)\\*",
|
||||
|
||||
"\\Device\\HarddiskVolume*\\ProgramData\\Microsoft\\Windows Defender\\*",
|
||||
"\\Device\\HarddiskVolume*\\ProgramData\\Microsoft\\Windows Defender Advanced Threat Protection\\*",
|
||||
"*\\Sources\\SetupHost.exe",
|
||||
"*\\Work\\*\\DismHost.exe",
|
||||
|
||||
"*\\Google\\Chrome\\Application\\chrome.exe",
|
||||
"*\\Microsoft\\Edge\\Application\\msedge.exe",
|
||||
"*\\Mozilla Firefox\\firefox.exe",
|
||||
"*\\Thorium\\Application\\thorium.exe",
|
||||
|
||||
"*\\Steam\\steam.exe",
|
||||
"*\\steam\\*\\steam.exe",
|
||||
"*\\Battle.net\\*\\Battle.net.exe",
|
||||
"*\\Epic Games\\Launcher\\Portal\\Binaries\\*\\EpicGamesLauncher.exe",
|
||||
|
||||
"*\\resources\\bin\\SDK\\DownloadSDKServer.exe",
|
||||
"*\\ThunderEv.exe",
|
||||
"*\\ThunderStart.exe",
|
||||
"*\\KuGou.exe",
|
||||
"*\\XLGame.exe",
|
||||
"*\\XLLiveUD.exe",
|
||||
|
||||
"*\\hrkill-*.exe",
|
||||
"*\\bin\\HipsDaemon.exe",
|
||||
"*\\360Safe.exe",
|
||||
"*\\bilibili\\bilibili.exe",
|
||||
|
||||
"*\\Kingsoft\\WPS Office\\ksolaunch.exe",
|
||||
"*\\Kingsoft\\WPS Office\\*\\wps.exe",
|
||||
"*\\Kingsoft\\WPS Office\\*\\*.exe",
|
||||
|
||||
"*\\PC4399_WPFLauncher.exe",
|
||||
"*\\bin\\APlayer.exe",
|
||||
"*\\Neverness To Everness\\NTELauncher.exe",
|
||||
"*\\Application\\AndrowsLauncher.exe",
|
||||
"*\\bin\\*\\codex.exe",
|
||||
"*\\bin\\codex.exe",
|
||||
],
|
||||
"Rule_Process_ExploitableBlacklist": [
|
||||
"*\\cmd.exe",
|
||||
"*\\powershell.exe",
|
||||
"*\\pwsh.exe",
|
||||
"*\\wscript.exe",
|
||||
"*\\cscript.exe",
|
||||
"*\\mshta.exe",
|
||||
"*\\regsvr32.exe",
|
||||
"*\\certutil.exe",
|
||||
"*\\bitsadmin.exe",
|
||||
"*\\wmic.exe",
|
||||
"*\\hh.exe",
|
||||
"*\\cmstp.exe",
|
||||
"*\\installutil.exe",
|
||||
"*\\regasm.exe",
|
||||
"*\\regsvcs.exe",
|
||||
"*\\msbuild.exe",
|
||||
"*\\csc.exe",
|
||||
"*\\vbc.exe",
|
||||
"*\\jsc.exe",
|
||||
"*\\bash.exe",
|
||||
"*\\scrcons.exe",
|
||||
"*\\schtasks.exe",
|
||||
"*\\at.exe",
|
||||
"*\\reg.exe",
|
||||
"*\\sc.exe",
|
||||
"*\\net.exe",
|
||||
"*\\net1.exe",
|
||||
"*\\whoami.exe",
|
||||
"*\\ipconfig.exe",
|
||||
"*\\taskkill.exe",
|
||||
"*\\ProcessHacker.exe",
|
||||
"*\\procmon.exe",
|
||||
"*\\procexp.exe",
|
||||
],
|
||||
"Rule_File_ProtectedPaths": [
|
||||
"*\\PYAS.exe",
|
||||
"*\\Plugins\\Filter\\PYAS_Driver.sys",
|
||||
"*\\Plugins\\Rules\\Rules_Driver_P1.json",
|
||||
"*\\ProgramData\\PYAS\\*.json",
|
||||
"*\\Engine\\Pattern\\*.onnx",
|
||||
"*\\Engine\\Properties\\*.onnx",
|
||||
|
||||
"\\Device\\HarddiskVolume*\\bootmgr",
|
||||
"\\Device\\HarddiskVolume*\\boot.ini",
|
||||
"\\Device\\HarddiskVolume*\\BOOTNXT",
|
||||
"\\Device\\HarddiskVolume*\\EFI\\*",
|
||||
"\\Device\\HarddiskVolume*\\Boot\\*",
|
||||
"\\Device\\HarddiskVolume*\\Recovery\\*",
|
||||
"\\Device\\HarddiskVolume*\\System Volume Information\\*",
|
||||
"\\Device\\HarddiskVolume*\\Windows\\System32\\config\\*",
|
||||
|
||||
"*\\CON",
|
||||
"*\\CON\\*",
|
||||
"*\\PRN",
|
||||
"*\\PRN\\*",
|
||||
"*\\AUX",
|
||||
"*\\AUX\\*",
|
||||
"*\\NUL",
|
||||
"*\\NUL\\*",
|
||||
"*\\COM?",
|
||||
"*\\COM?\\*",
|
||||
"*\\LPT?",
|
||||
"*\\LPT?\\*",
|
||||
"*\\OSDATA",
|
||||
"*\\OSDATA\\*",
|
||||
"*\\*<*",
|
||||
"*\\*>*",
|
||||
],
|
||||
"Rule_File_ExceptionPaths": [
|
||||
"*\\Users\\*\\steam\\*",
|
||||
"*\\AppData\\*",
|
||||
"*\\Temp\\*",
|
||||
"*\\Local\\Temp\\*",
|
||||
"*\\ventoy\\Boot\\*",
|
||||
"*\\AdminRegionConfig.*",
|
||||
"*\\nt_qq\\nt_*\\*",
|
||||
"*\\cache\\*",
|
||||
"*\\Documents\\My Games\\*",
|
||||
"*\\Documents\\Tencent Files\\*",
|
||||
"*\\Documents\\WeChat Files\\*",
|
||||
"*\\Documents\\WPS Cloud Files\\*"
|
||||
],
|
||||
"Rule_File_RansomwareExtensions": [
|
||||
".zip", ".7z", ".rar", ".tar", ".gz",
|
||||
".ppt", ".pptx", ".wps", ".txt", ".rtf",
|
||||
".pdf", ".xls", ".xlsx", ".doc", ".docx",
|
||||
".jpg", ".jpeg", ".png", ".webp", ".gif",
|
||||
".mp3", ".wav", ".aac", ".ogg", ".flac",
|
||||
".mp4", ".avi", ".mov", ".wmv", ".mkv",
|
||||
]
|
||||
}
|
||||
Reference in new issue
Block a user