Compare commits

...
19 Commits
Author SHA1 Message Date
87owo 7b9474521d Add files via upload 2026-06-01 17:37:51 +08:00
87owo 78c3f2c9b8 Add files via upload 2026-06-01 16:59:04 +08:00
87owo ad256abb7c Add files via upload 2026-06-01 13:59:39 +08:00
87owo 86d160ae5f Add files via upload 2026-06-01 12:55:09 +08:00
87owo fde6afb39e Add files via upload 2026-05-31 23:59:07 +08:00
87owo 7c3e78296a Add files via upload 2026-05-31 23:35:58 +08:00
87owo 9cc06ff767 Add files via upload 2026-05-31 23:09:51 +08:00
87owo 766b6e81d0 Add files via upload 2026-05-28 16:20:20 +08:00
87owo 0349e01629 Add files via upload 2026-05-28 15:53:52 +08:00
87owo 0bd9da2bbc Add files via upload 2026-05-28 14:31:24 +08:00
87owo f14c7c3f55 Add files via upload 2026-05-28 14:05:15 +08:00
87owo ef5793fb15 Add files via upload 2026-05-26 09:30:27 +08:00
87owo 9f22fe6d97 Add files via upload 2026-05-26 09:29:51 +08:00
87owo 3147d576ee Add files via upload 2026-05-25 22:53:18 +08:00
87owo 2ceec2588c Add files via upload 2026-05-25 14:19:26 +08:00
87owo 5b4d88b9c7 Add files via upload 2026-05-25 14:18:52 +08:00
87owo 55043cca36 Add files via upload 2026-05-24 02:44:05 +08:00
87owo 097ebe025f Add files via upload 2026-05-23 14:10:33 +08:00
87owo 9f92307f64 Add files via upload 2026-05-22 13:49:52 +08:00
11 changed files with 1848 additions and 742 deletions

No files matched your search

+100 -79
View File
@@ -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()
+88 -70
View File
@@ -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))
+69 -22
View File
@@ -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):
+69 -22
View File
@@ -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
View File
@@ -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
View File
@@ -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
View File
@@ -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>
+499 -86
View File
@@ -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
View File
@@ -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
View File
@@ -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
View File
@@ -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",
]
}