Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
766b6e81d0 | ||
|
|
0349e01629 | ||
|
|
0bd9da2bbc | ||
|
|
f14c7c3f55 | ||
|
|
ef5793fb15 | ||
|
|
9f22fe6d97 | ||
|
|
3147d576ee | ||
|
|
2ceec2588c | ||
|
|
5b4d88b9c7 | ||
|
|
55043cca36 | ||
|
|
097ebe025f | ||
|
|
9f92307f64 |
No files matched your search
+100
-79
@@ -1,6 +1,6 @@
|
||||
import os, sys, math, time, datetime, pefile, hashlib
|
||||
import ctypes, ctypes.wintypes, multiprocessing
|
||||
import re, zlib
|
||||
import re, os, sys, math, time, zlib, datetime, hashlib
|
||||
import ctypes, ctypes.wintypes, pefile, multiprocessing
|
||||
import numpy as np
|
||||
|
||||
####################################################################################################
|
||||
|
||||
@@ -109,6 +109,17 @@ def verify_signature(file_path):
|
||||
|
||||
class FeatureExtractor:
|
||||
_STRING_PATTERN = re.compile(b'[\x20-\x7E]{5,}')
|
||||
|
||||
_BYTE_HIST_KEYS = [f'ByteHist_{i:02X}' for i in range(256)]
|
||||
_BYTE_ENT_KEYS = [f'ByteEnt_{i:02X}' for i in range(256)]
|
||||
_STRING_KEYS = [f'StringHist_{i:02d}' for i in range(95)]
|
||||
_SECTION_HASH_KEYS = [f'SectionHash_{i:02d}' for i in range(50)]
|
||||
_CHAR_FLAGS = [0x00000020, 0x00000040, 0x00000080, 0x02000000, 0x20000000, 0x40000000, 0x80000000]
|
||||
_CHAR_COUNT_KEYS = {flag: f'Char_{flag:08X}_Count' for flag in _CHAR_FLAGS}
|
||||
_CHAR_ENT_KEYS = {flag: f'Char_{flag:08X}_MeanEntropy' for flag in _CHAR_FLAGS}
|
||||
|
||||
_C_LOG_C_TABLE = np.zeros(2049, dtype=np.float64)
|
||||
_C_LOG_C_TABLE[1:] = np.arange(1, 2049) * np.log2(np.arange(1, 2049))
|
||||
|
||||
@staticmethod
|
||||
def _safe_float(val):
|
||||
@@ -121,90 +132,97 @@ class FeatureExtractor:
|
||||
@staticmethod
|
||||
def _calc_entropy(data):
|
||||
if not data: return 0.0
|
||||
import numpy as np
|
||||
arr = np.frombuffer(data, dtype=np.uint8)
|
||||
sz = len(arr)
|
||||
counts = np.bincount(arr, minlength=256)
|
||||
probs = counts[counts > 0] / len(arr)
|
||||
return float(-np.sum(probs * np.log2(probs)))
|
||||
counts = counts[counts > 0]
|
||||
return float(np.log2(sz) - np.sum(counts * np.log2(counts)) / sz)
|
||||
|
||||
@classmethod
|
||||
def _extract_strings(cls, file_bytes):
|
||||
res = {"StringCount": 0.0, "StringMeanLength": 0.0, "StringEntropy": 0.0}
|
||||
for i in range(95):
|
||||
res[f'StringHist_{i:02d}'] = 0.0
|
||||
res.update(dict.fromkeys(cls._STRING_KEYS, 0.0))
|
||||
|
||||
count = 0
|
||||
total_len = 0
|
||||
char_counts = [0] * 95
|
||||
combined_data = bytearray()
|
||||
|
||||
for match in cls._STRING_PATTERN.finditer(file_bytes):
|
||||
m = match.group()
|
||||
count += 1
|
||||
length = len(m)
|
||||
total_len += length
|
||||
combined_data.extend(m)
|
||||
for b in m:
|
||||
char_counts[b - 0x20] += 1
|
||||
|
||||
if count == 0:
|
||||
matches = cls._STRING_PATTERN.findall(file_bytes)
|
||||
if not matches:
|
||||
return res
|
||||
|
||||
combined_data = b''.join(matches)
|
||||
count = len(matches)
|
||||
total_len = len(combined_data)
|
||||
|
||||
res["StringCount"] = float(count)
|
||||
res["StringMeanLength"] = float(total_len) / count
|
||||
res["StringEntropy"] = cls._calc_entropy(combined_data)
|
||||
|
||||
for i in range(95):
|
||||
res[f'StringHist_{i:02d}'] = float(char_counts[i]) / total_len
|
||||
arr = np.frombuffer(combined_data, dtype=np.uint8)
|
||||
counts = np.bincount(arr, minlength=127)
|
||||
char_counts = counts[0x20:0x7F]
|
||||
|
||||
res.update(dict(zip(cls._STRING_KEYS, char_counts.astype(np.float64) / total_len)))
|
||||
return res
|
||||
|
||||
@classmethod
|
||||
def _extract_histograms(cls, file_bytes):
|
||||
import numpy as np
|
||||
arr = np.frombuffer(file_bytes, dtype=np.uint8)
|
||||
sz = len(arr)
|
||||
res = {}
|
||||
|
||||
if sz == 0:
|
||||
for i in range(256):
|
||||
res[f'ByteHist_{i:02X}'] = 0.0
|
||||
res[f'ByteEnt_{i:02X}'] = 0.0
|
||||
res.update(dict.fromkeys(cls._BYTE_HIST_KEYS, 0.0))
|
||||
res.update(dict.fromkeys(cls._BYTE_ENT_KEYS, 0.0))
|
||||
return res
|
||||
|
||||
byte_counts = np.bincount(arr, minlength=256)
|
||||
byte_hist = byte_counts / sz
|
||||
for i, v in enumerate(byte_hist):
|
||||
res[f'ByteHist_{i:02X}'] = float(v)
|
||||
byte_hist = byte_counts.astype(np.float64) / sz
|
||||
res.update(dict(zip(cls._BYTE_HIST_KEYS, byte_hist)))
|
||||
|
||||
ent_hist = np.zeros(256, dtype=np.float64)
|
||||
window = 2048
|
||||
step = 1024
|
||||
num_windows = (sz - window) // step + 1 if sz >= window else 1
|
||||
|
||||
for i in range(num_windows):
|
||||
w = arr if sz < window else arr[i*step : i*step+window]
|
||||
w_counts = np.bincount(w, minlength=256)
|
||||
|
||||
p = w_counts[w_counts > 0] / len(w)
|
||||
entropy = -np.sum(p * np.log2(p))
|
||||
|
||||
ent_bin = int(entropy * 2.0)
|
||||
if ent_bin > 15:
|
||||
ent_bin = 15
|
||||
|
||||
if sz < window:
|
||||
w_counts = byte_counts
|
||||
p = w_counts[w_counts > 0] / float(sz)
|
||||
entropy = float(-np.sum(p * np.log2(p)))
|
||||
ent_bin = min(int(entropy * 2.0), 15)
|
||||
byte_bins = w_counts.reshape(16, 16).sum(axis=1)
|
||||
|
||||
idx_start = ent_bin * 16
|
||||
ent_hist[idx_start : idx_start+16] += byte_bins
|
||||
else:
|
||||
num_windows = (sz - window) // step + 1
|
||||
batch_windows = 10000
|
||||
|
||||
for w_start in range(0, num_windows, batch_windows):
|
||||
w_end = min(w_start + batch_windows, num_windows)
|
||||
b_num = w_end - w_start
|
||||
|
||||
start_byte = w_start * step
|
||||
end_byte = start_byte + b_num * step + step
|
||||
b_arr = arr[start_byte:end_byte]
|
||||
|
||||
arr_2d = b_arr.reshape(b_num + 1, step)
|
||||
offsets = (np.arange(b_num + 1, dtype=np.int32)[:, None] * 256)
|
||||
flat_idx = (arr_2d.astype(np.int32) + offsets).ravel()
|
||||
|
||||
chunk_counts = np.bincount(flat_idx, minlength=(b_num + 1) * 256).reshape(b_num + 1, 256)
|
||||
window_counts = chunk_counts[:-1] + chunk_counts[1:]
|
||||
|
||||
sum_c_log_c = np.sum(cls._C_LOG_C_TABLE[window_counts], axis=1)
|
||||
entropies = 11.0 - sum_c_log_c / 2048.0
|
||||
|
||||
ent_bins = (entropies * 2.0).astype(np.int32)
|
||||
np.clip(ent_bins, 0, 15, out=ent_bins)
|
||||
|
||||
byte_bins = window_counts.reshape(b_num, 16, 16).sum(axis=2)
|
||||
flat_ent_bins = (ent_bins[:, None] * 16 + np.arange(16)).ravel()
|
||||
np.add.at(ent_hist, flat_ent_bins, byte_bins.ravel())
|
||||
|
||||
ent_sum = np.sum(ent_hist)
|
||||
if ent_sum > 0:
|
||||
ent_hist /= ent_sum
|
||||
|
||||
for i, v in enumerate(ent_hist):
|
||||
res[f'ByteEnt_{i:02X}'] = float(v)
|
||||
|
||||
res.update(dict(zip(cls._BYTE_ENT_KEYS, ent_hist)))
|
||||
return res
|
||||
|
||||
@classmethod
|
||||
@@ -373,9 +391,9 @@ class FeatureExtractor:
|
||||
except Exception:
|
||||
pass
|
||||
|
||||
base['TrustSigned'] = verify_signature(file_path)
|
||||
base['TrustSigned'] = float(verify_signature(file_path))
|
||||
base['FileEntropy'] = cls._calc_entropy(file_bytes)
|
||||
base['FileSize'] = fsize
|
||||
base['FileSize'] = float(fsize)
|
||||
|
||||
fh = pe.FILE_HEADER
|
||||
base['Machine'] = getattr(fh, 'Machine', 0)
|
||||
@@ -410,13 +428,9 @@ class FeatureExtractor:
|
||||
if len(op.DATA_DIRECTORY) > exc_idx and op.DATA_DIRECTORY[exc_idx].Size > 0:
|
||||
base['ExceptionCount'] = op.DATA_DIRECTORY[exc_idx].Size // 12 if base['Machine'] in (0x8664, 0xAA64) else 0.0
|
||||
|
||||
char_flags = [0x00000020, 0x00000040, 0x00000080, 0x02000000, 0x20000000, 0x40000000, 0x80000000]
|
||||
for flag in char_flags:
|
||||
base[f'Char_{flag:08X}_Count'] = 0.0
|
||||
base[f'Char_{flag:08X}_MeanEntropy'] = 0.0
|
||||
|
||||
for i in range(50):
|
||||
base[f'SectionHash_{i:02d}'] = 0.0
|
||||
res_flags_count = dict.fromkeys(cls._CHAR_COUNT_KEYS.values(), 0.0)
|
||||
res_flags_ent = dict.fromkeys(cls._CHAR_ENT_KEYS.values(), 0.0)
|
||||
res_sec_hash = dict.fromkeys(cls._SECTION_HASH_KEYS, 0.0)
|
||||
|
||||
if hasattr(pe, 'sections'):
|
||||
base['SectionCount'] = len(pe.sections)
|
||||
@@ -426,7 +440,7 @@ class FeatureExtractor:
|
||||
for section in pe.sections:
|
||||
sec_name = section.Name.rstrip(b'\x00')
|
||||
hash_val = zlib.crc32(sec_name) % 50
|
||||
base[f'SectionHash_{hash_val:02d}'] += 1.0
|
||||
res_sec_hash[cls._SECTION_HASH_KEYS[hash_val]] += 1.0
|
||||
|
||||
try:
|
||||
s_data = section.get_data()
|
||||
@@ -443,10 +457,10 @@ class FeatureExtractor:
|
||||
if section.Characteristics & 0x40000000: read_sec += 1
|
||||
if section.SizeOfRawData + section.PointerToRawData > fsize: sec_exc = 1
|
||||
|
||||
for flag in char_flags:
|
||||
for flag in cls._CHAR_FLAGS:
|
||||
if section.Characteristics & flag:
|
||||
base[f'Char_{flag:08X}_Count'] += 1.0
|
||||
base[f'Char_{flag:08X}_MeanEntropy'] += s_entropy
|
||||
res_flags_count[cls._CHAR_COUNT_KEYS[flag]] += 1.0
|
||||
res_flags_ent[cls._CHAR_ENT_KEYS[flag]] += s_entropy
|
||||
|
||||
base['SectionMaxEntropy'] = max(entropies) if entropies else 0.0
|
||||
base['SectionMinEntropy'] = min(entropies) if entropies else 0.0
|
||||
@@ -457,14 +471,19 @@ class FeatureExtractor:
|
||||
base['SectionMaxVSize'] = max(v_sizes) if v_sizes else 0.0
|
||||
base['SectionMinVSize'] = min(v_sizes) if v_sizes else 0.0
|
||||
base['SectionMeanVSize'] = sum(v_sizes) / len(v_sizes) if v_sizes else 0.0
|
||||
base['ExecutableSections'] = exec_sec
|
||||
base['WritableSections'] = write_sec
|
||||
base['ReadableSections'] = read_sec
|
||||
base['ExecutableSections'] = float(exec_sec)
|
||||
base['WritableSections'] = float(write_sec)
|
||||
base['ReadableSections'] = float(read_sec)
|
||||
base['SectionException'] = float(sec_exc)
|
||||
|
||||
for flag in char_flags:
|
||||
if base[f'Char_{flag:08X}_Count'] > 0:
|
||||
base[f'Char_{flag:08X}_MeanEntropy'] = base[f'Char_{flag:08X}_MeanEntropy'] / base[f'Char_{flag:08X}_Count']
|
||||
for flag in cls._CHAR_FLAGS:
|
||||
cnt_key = cls._CHAR_COUNT_KEYS[flag]
|
||||
if res_flags_count[cnt_key] > 0:
|
||||
res_flags_ent[cls._CHAR_ENT_KEYS[flag]] /= res_flags_count[cnt_key]
|
||||
|
||||
base.update(res_flags_count)
|
||||
base.update(res_flags_ent)
|
||||
base.update(res_sec_hash)
|
||||
|
||||
string_keys = ['FileDescription', 'FileVersion', 'ProductName', 'ProductVersion', 'CompanyName', 'LegalCopyright', 'Comments', 'InternalName', 'LegalTrademarks', 'SpecialBuild', 'PrivateBuild']
|
||||
for key in string_keys:
|
||||
@@ -496,7 +515,7 @@ class FeatureExtractor:
|
||||
base['IsSpecialBuild'] = 1.0 if flags & 0x20 else 0.0
|
||||
|
||||
if hasattr(pe, 'DIRECTORY_ENTRY_IMPORT'):
|
||||
base['ImportCount'] = len(pe.DIRECTORY_ENTRY_IMPORT)
|
||||
base['ImportCount'] = float(len(pe.DIRECTORY_ENTRY_IMPORT))
|
||||
func_count = 0
|
||||
for entry in pe.DIRECTORY_ENTRY_IMPORT:
|
||||
if getattr(entry, 'dll', None):
|
||||
@@ -507,20 +526,20 @@ class FeatureExtractor:
|
||||
if getattr(imp, 'name', None):
|
||||
try: apis.add(imp.name.decode('ascii', 'ignore'))
|
||||
except Exception: pass
|
||||
base['ImportFunctionCount'] = func_count
|
||||
base['ImportFunctionCount'] = float(func_count)
|
||||
|
||||
base['ExportCount'] = len(pe.DIRECTORY_ENTRY_EXPORT.symbols) if (hasattr(pe, 'DIRECTORY_ENTRY_EXPORT') and hasattr(pe.DIRECTORY_ENTRY_EXPORT, 'symbols')) else 0.0
|
||||
base['ExportCount'] = float(len(pe.DIRECTORY_ENTRY_EXPORT.symbols)) if (hasattr(pe, 'DIRECTORY_ENTRY_EXPORT') and hasattr(pe.DIRECTORY_ENTRY_EXPORT, 'symbols')) else 0.0
|
||||
|
||||
if hasattr(pe, 'DIRECTORY_ENTRY_RESOURCE'):
|
||||
icon_count = 0
|
||||
for entry in pe.DIRECTORY_ENTRY_RESOURCE.entries:
|
||||
if getattr(entry, 'id', None) == 3 and hasattr(entry, 'directory'):
|
||||
icon_count += len(getattr(entry.directory, 'entries', []))
|
||||
base['IconCount'] = icon_count
|
||||
base['IconCount'] = float(icon_count)
|
||||
else:
|
||||
base['IconCount'] = 0.0
|
||||
|
||||
base['DebugCount'] = len(pe.DIRECTORY_ENTRY_DEBUG) if hasattr(pe, 'DIRECTORY_ENTRY_DEBUG') else 0.0
|
||||
base['DebugCount'] = float(len(pe.DIRECTORY_ENTRY_DEBUG)) if hasattr(pe, 'DIRECTORY_ENTRY_DEBUG') else 0.0
|
||||
|
||||
base.update(cls._extract_strings(file_bytes))
|
||||
base.update(cls._extract_histograms(file_bytes))
|
||||
@@ -542,13 +561,15 @@ class FeatureExtractor:
|
||||
|
||||
def load_existing_hashes():
|
||||
hashes = set()
|
||||
if os.path.exists(JSONL_PATH):
|
||||
with open(JSONL_PATH, 'r', encoding='utf-8') as f:
|
||||
for line in f:
|
||||
try:
|
||||
data = JSON_LOADS(line)
|
||||
if 'FileHash' in data: hashes.add(data['FileHash'])
|
||||
except Exception: pass
|
||||
if not os.path.exists(JSONL_PATH):
|
||||
return hashes
|
||||
|
||||
pattern = re.compile(rb'"FileHash"\s*:\s*"([a-fA-F0-9]{64})"')
|
||||
with open(JSONL_PATH, 'rb') as f:
|
||||
for line in f:
|
||||
match = pattern.search(line)
|
||||
if match:
|
||||
hashes.add(match.group(1).decode('ascii'))
|
||||
return hashes
|
||||
|
||||
GLOBAL_HASHES = set()
|
||||
|
||||
@@ -1,8 +1,7 @@
|
||||
import os, sys, math, json, pefile, datetime
|
||||
import os, re, sys, math, json, zlib, pefile, datetime
|
||||
import ctypes, ctypes.wintypes
|
||||
import numpy as np
|
||||
import onnxruntime as ort
|
||||
import ctypes, ctypes.wintypes
|
||||
import re, zlib
|
||||
|
||||
####################################################################################################
|
||||
|
||||
@@ -100,6 +99,17 @@ class WinTrust:
|
||||
|
||||
class FeatureExtractor:
|
||||
_STRING_PATTERN = re.compile(b'[\x20-\x7E]{5,}')
|
||||
|
||||
_BYTE_HIST_KEYS = [f'ByteHist_{i:02X}' for i in range(256)]
|
||||
_BYTE_ENT_KEYS = [f'ByteEnt_{i:02X}' for i in range(256)]
|
||||
_STRING_KEYS = [f'StringHist_{i:02d}' for i in range(95)]
|
||||
_SECTION_HASH_KEYS = [f'SectionHash_{i:02d}' for i in range(50)]
|
||||
_CHAR_FLAGS = [0x00000020, 0x00000040, 0x00000080, 0x02000000, 0x20000000, 0x40000000, 0x80000000]
|
||||
_CHAR_COUNT_KEYS = {flag: f'Char_{flag:08X}_Count' for flag in _CHAR_FLAGS}
|
||||
_CHAR_ENT_KEYS = {flag: f'Char_{flag:08X}_MeanEntropy' for flag in _CHAR_FLAGS}
|
||||
|
||||
_C_LOG_C_TABLE = np.zeros(2049, dtype=np.float64)
|
||||
_C_LOG_C_TABLE[1:] = np.arange(1, 2049) * np.log2(np.arange(1, 2049))
|
||||
|
||||
@staticmethod
|
||||
def _safe_float(val):
|
||||
@@ -112,90 +122,97 @@ class FeatureExtractor:
|
||||
@staticmethod
|
||||
def _calc_entropy(data):
|
||||
if not data: return 0.0
|
||||
import numpy as np
|
||||
arr = np.frombuffer(data, dtype=np.uint8)
|
||||
sz = len(arr)
|
||||
counts = np.bincount(arr, minlength=256)
|
||||
probs = counts[counts > 0] / len(arr)
|
||||
return float(-np.sum(probs * np.log2(probs)))
|
||||
counts = counts[counts > 0]
|
||||
return float(np.log2(sz) - np.sum(counts * np.log2(counts)) / sz)
|
||||
|
||||
@classmethod
|
||||
def _extract_strings(cls, file_bytes):
|
||||
res = {"StringCount": 0.0, "StringMeanLength": 0.0, "StringEntropy": 0.0}
|
||||
for i in range(95):
|
||||
res[f'StringHist_{i:02d}'] = 0.0
|
||||
res.update(dict.fromkeys(cls._STRING_KEYS, 0.0))
|
||||
|
||||
count = 0
|
||||
total_len = 0
|
||||
char_counts = [0] * 95
|
||||
combined_data = bytearray()
|
||||
|
||||
for match in cls._STRING_PATTERN.finditer(file_bytes):
|
||||
m = match.group()
|
||||
count += 1
|
||||
length = len(m)
|
||||
total_len += length
|
||||
combined_data.extend(m)
|
||||
for b in m:
|
||||
char_counts[b - 0x20] += 1
|
||||
|
||||
if count == 0:
|
||||
matches = cls._STRING_PATTERN.findall(file_bytes)
|
||||
if not matches:
|
||||
return res
|
||||
|
||||
combined_data = b''.join(matches)
|
||||
count = len(matches)
|
||||
total_len = len(combined_data)
|
||||
|
||||
res["StringCount"] = float(count)
|
||||
res["StringMeanLength"] = float(total_len) / count
|
||||
res["StringEntropy"] = cls._calc_entropy(combined_data)
|
||||
|
||||
for i in range(95):
|
||||
res[f'StringHist_{i:02d}'] = float(char_counts[i]) / total_len
|
||||
arr = np.frombuffer(combined_data, dtype=np.uint8)
|
||||
counts = np.bincount(arr, minlength=127)
|
||||
char_counts = counts[0x20:0x7F]
|
||||
|
||||
res.update(dict(zip(cls._STRING_KEYS, char_counts.astype(np.float64) / total_len)))
|
||||
return res
|
||||
|
||||
@classmethod
|
||||
def _extract_histograms(cls, file_bytes):
|
||||
import numpy as np
|
||||
arr = np.frombuffer(file_bytes, dtype=np.uint8)
|
||||
sz = len(arr)
|
||||
res = {}
|
||||
|
||||
if sz == 0:
|
||||
for i in range(256):
|
||||
res[f'ByteHist_{i:02X}'] = 0.0
|
||||
res[f'ByteEnt_{i:02X}'] = 0.0
|
||||
res.update(dict.fromkeys(cls._BYTE_HIST_KEYS, 0.0))
|
||||
res.update(dict.fromkeys(cls._BYTE_ENT_KEYS, 0.0))
|
||||
return res
|
||||
|
||||
byte_counts = np.bincount(arr, minlength=256)
|
||||
byte_hist = byte_counts / sz
|
||||
for i, v in enumerate(byte_hist):
|
||||
res[f'ByteHist_{i:02X}'] = float(v)
|
||||
byte_hist = byte_counts.astype(np.float64) / sz
|
||||
res.update(dict(zip(cls._BYTE_HIST_KEYS, byte_hist)))
|
||||
|
||||
ent_hist = np.zeros(256, dtype=np.float64)
|
||||
window = 2048
|
||||
step = 1024
|
||||
num_windows = (sz - window) // step + 1 if sz >= window else 1
|
||||
|
||||
for i in range(num_windows):
|
||||
w = arr if sz < window else arr[i*step : i*step+window]
|
||||
w_counts = np.bincount(w, minlength=256)
|
||||
|
||||
p = w_counts[w_counts > 0] / len(w)
|
||||
entropy = -np.sum(p * np.log2(p))
|
||||
|
||||
ent_bin = int(entropy * 2.0)
|
||||
if ent_bin > 15:
|
||||
ent_bin = 15
|
||||
|
||||
if sz < window:
|
||||
w_counts = byte_counts
|
||||
p = w_counts[w_counts > 0] / float(sz)
|
||||
entropy = float(-np.sum(p * np.log2(p)))
|
||||
ent_bin = min(int(entropy * 2.0), 15)
|
||||
byte_bins = w_counts.reshape(16, 16).sum(axis=1)
|
||||
|
||||
idx_start = ent_bin * 16
|
||||
ent_hist[idx_start : idx_start+16] += byte_bins
|
||||
else:
|
||||
num_windows = (sz - window) // step + 1
|
||||
batch_windows = 10000
|
||||
|
||||
for w_start in range(0, num_windows, batch_windows):
|
||||
w_end = min(w_start + batch_windows, num_windows)
|
||||
b_num = w_end - w_start
|
||||
|
||||
start_byte = w_start * step
|
||||
end_byte = start_byte + b_num * step + step
|
||||
b_arr = arr[start_byte:end_byte]
|
||||
|
||||
arr_2d = b_arr.reshape(b_num + 1, step)
|
||||
offsets = (np.arange(b_num + 1, dtype=np.int32)[:, None] * 256)
|
||||
flat_idx = (arr_2d.astype(np.int32) + offsets).ravel()
|
||||
|
||||
chunk_counts = np.bincount(flat_idx, minlength=(b_num + 1) * 256).reshape(b_num + 1, 256)
|
||||
window_counts = chunk_counts[:-1] + chunk_counts[1:]
|
||||
|
||||
sum_c_log_c = np.sum(cls._C_LOG_C_TABLE[window_counts], axis=1)
|
||||
entropies = 11.0 - sum_c_log_c / 2048.0
|
||||
|
||||
ent_bins = (entropies * 2.0).astype(np.int32)
|
||||
np.clip(ent_bins, 0, 15, out=ent_bins)
|
||||
|
||||
byte_bins = window_counts.reshape(b_num, 16, 16).sum(axis=2)
|
||||
flat_ent_bins = (ent_bins[:, None] * 16 + np.arange(16)).ravel()
|
||||
np.add.at(ent_hist, flat_ent_bins, byte_bins.ravel())
|
||||
|
||||
ent_sum = np.sum(ent_hist)
|
||||
if ent_sum > 0:
|
||||
ent_hist /= ent_sum
|
||||
|
||||
for i, v in enumerate(ent_hist):
|
||||
res[f'ByteEnt_{i:02X}'] = float(v)
|
||||
|
||||
res.update(dict(zip(cls._BYTE_ENT_KEYS, ent_hist)))
|
||||
return res
|
||||
|
||||
@classmethod
|
||||
@@ -407,13 +424,9 @@ class FeatureExtractor:
|
||||
if len(op.DATA_DIRECTORY) > exc_idx and op.DATA_DIRECTORY[exc_idx].Size > 0:
|
||||
base['ExceptionCount'] = op.DATA_DIRECTORY[exc_idx].Size // 12 if base['Machine'] in (0x8664, 0xAA64) else 0.0
|
||||
|
||||
char_flags = [0x00000020, 0x00000040, 0x00000080, 0x02000000, 0x20000000, 0x40000000, 0x80000000]
|
||||
for flag in char_flags:
|
||||
base[f'Char_{flag:08X}_Count'] = 0.0
|
||||
base[f'Char_{flag:08X}_MeanEntropy'] = 0.0
|
||||
|
||||
for i in range(50):
|
||||
base[f'SectionHash_{i:02d}'] = 0.0
|
||||
res_flags_count = dict.fromkeys(cls._CHAR_COUNT_KEYS.values(), 0.0)
|
||||
res_flags_ent = dict.fromkeys(cls._CHAR_ENT_KEYS.values(), 0.0)
|
||||
res_sec_hash = dict.fromkeys(cls._SECTION_HASH_KEYS, 0.0)
|
||||
|
||||
if hasattr(pe, 'sections'):
|
||||
base['SectionCount'] = len(pe.sections)
|
||||
@@ -423,7 +436,7 @@ class FeatureExtractor:
|
||||
for section in pe.sections:
|
||||
sec_name = section.Name.rstrip(b'\x00')
|
||||
hash_val = zlib.crc32(sec_name) % 50
|
||||
base[f'SectionHash_{hash_val:02d}'] += 1.0
|
||||
res_sec_hash[cls._SECTION_HASH_KEYS[hash_val]] += 1.0
|
||||
|
||||
try:
|
||||
s_data = section.get_data()
|
||||
@@ -440,10 +453,10 @@ class FeatureExtractor:
|
||||
if section.Characteristics & 0x40000000: read_sec += 1
|
||||
if section.SizeOfRawData + section.PointerToRawData > fsize: sec_exc = 1
|
||||
|
||||
for flag in char_flags:
|
||||
for flag in cls._CHAR_FLAGS:
|
||||
if section.Characteristics & flag:
|
||||
base[f'Char_{flag:08X}_Count'] += 1.0
|
||||
base[f'Char_{flag:08X}_MeanEntropy'] += s_entropy
|
||||
res_flags_count[cls._CHAR_COUNT_KEYS[flag]] += 1.0
|
||||
res_flags_ent[cls._CHAR_ENT_KEYS[flag]] += s_entropy
|
||||
|
||||
base['SectionMaxEntropy'] = max(entropies) if entropies else 0.0
|
||||
base['SectionMinEntropy'] = min(entropies) if entropies else 0.0
|
||||
@@ -454,14 +467,19 @@ class FeatureExtractor:
|
||||
base['SectionMaxVSize'] = max(v_sizes) if v_sizes else 0.0
|
||||
base['SectionMinVSize'] = min(v_sizes) if v_sizes else 0.0
|
||||
base['SectionMeanVSize'] = sum(v_sizes) / len(v_sizes) if v_sizes else 0.0
|
||||
base['ExecutableSections'] = exec_sec
|
||||
base['WritableSections'] = write_sec
|
||||
base['ReadableSections'] = read_sec
|
||||
base['ExecutableSections'] = float(exec_sec)
|
||||
base['WritableSections'] = float(write_sec)
|
||||
base['ReadableSections'] = float(read_sec)
|
||||
base['SectionException'] = float(sec_exc)
|
||||
|
||||
for flag in char_flags:
|
||||
if base[f'Char_{flag:08X}_Count'] > 0:
|
||||
base[f'Char_{flag:08X}_MeanEntropy'] = base[f'Char_{flag:08X}_MeanEntropy'] / base[f'Char_{flag:08X}_Count']
|
||||
for flag in cls._CHAR_FLAGS:
|
||||
cnt_key = cls._CHAR_COUNT_KEYS[flag]
|
||||
if res_flags_count[cnt_key] > 0:
|
||||
res_flags_ent[cls._CHAR_ENT_KEYS[flag]] /= res_flags_count[cnt_key]
|
||||
|
||||
base.update(res_flags_count)
|
||||
base.update(res_flags_ent)
|
||||
base.update(res_sec_hash)
|
||||
|
||||
string_keys = ['FileDescription', 'FileVersion', 'ProductName', 'ProductVersion', 'CompanyName', 'LegalCopyright', 'Comments', 'InternalName', 'LegalTrademarks', 'SpecialBuild', 'PrivateBuild']
|
||||
for key in string_keys:
|
||||
@@ -493,7 +511,7 @@ class FeatureExtractor:
|
||||
base['IsSpecialBuild'] = 1.0 if flags & 0x20 else 0.0
|
||||
|
||||
if hasattr(pe, 'DIRECTORY_ENTRY_IMPORT'):
|
||||
base['ImportCount'] = len(pe.DIRECTORY_ENTRY_IMPORT)
|
||||
base['ImportCount'] = float(len(pe.DIRECTORY_ENTRY_IMPORT))
|
||||
func_count = 0
|
||||
for entry in pe.DIRECTORY_ENTRY_IMPORT:
|
||||
if getattr(entry, 'dll', None):
|
||||
@@ -504,20 +522,20 @@ class FeatureExtractor:
|
||||
if getattr(imp, 'name', None):
|
||||
try: apis.add(imp.name.decode('ascii', 'ignore'))
|
||||
except Exception: pass
|
||||
base['ImportFunctionCount'] = func_count
|
||||
base['ImportFunctionCount'] = float(func_count)
|
||||
|
||||
base['ExportCount'] = len(pe.DIRECTORY_ENTRY_EXPORT.symbols) if (hasattr(pe, 'DIRECTORY_ENTRY_EXPORT') and hasattr(pe.DIRECTORY_ENTRY_EXPORT, 'symbols')) else 0.0
|
||||
base['ExportCount'] = float(len(pe.DIRECTORY_ENTRY_EXPORT.symbols)) if (hasattr(pe, 'DIRECTORY_ENTRY_EXPORT') and hasattr(pe.DIRECTORY_ENTRY_EXPORT, 'symbols')) else 0.0
|
||||
|
||||
if hasattr(pe, 'DIRECTORY_ENTRY_RESOURCE'):
|
||||
icon_count = 0
|
||||
for entry in pe.DIRECTORY_ENTRY_RESOURCE.entries:
|
||||
if getattr(entry, 'id', None) == 3 and hasattr(entry, 'directory'):
|
||||
icon_count += len(getattr(entry.directory, 'entries', []))
|
||||
base['IconCount'] = icon_count
|
||||
base['IconCount'] = float(icon_count)
|
||||
else:
|
||||
base['IconCount'] = 0.0
|
||||
|
||||
base['DebugCount'] = len(pe.DIRECTORY_ENTRY_DEBUG) if hasattr(pe, 'DIRECTORY_ENTRY_DEBUG') else 0.0
|
||||
base['DebugCount'] = float(len(pe.DIRECTORY_ENTRY_DEBUG)) if hasattr(pe, 'DIRECTORY_ENTRY_DEBUG') else 0.0
|
||||
|
||||
base.update(cls._extract_strings(file_bytes))
|
||||
base.update(cls._extract_histograms(file_bytes))
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import os, sys, time, json, onnxmltools, zlib
|
||||
import os, sys, time, json, zlib, array, onnxmltools, multiprocessing
|
||||
import numpy as np
|
||||
import pandas as pd
|
||||
import lightgbm as lgb
|
||||
@@ -55,14 +55,15 @@ def analyze_schema(jsonl_path):
|
||||
|
||||
with open(jsonl_path, 'rb') as f:
|
||||
for line in f:
|
||||
try:
|
||||
data = JSON_LOADS(line)
|
||||
base_data = data.get('Base')
|
||||
if base_data:
|
||||
base_keys.update(base_data.keys())
|
||||
num_lines += 1
|
||||
except Exception:
|
||||
continue
|
||||
num_lines += 1
|
||||
if not base_keys:
|
||||
try:
|
||||
data = JSON_LOADS(line)
|
||||
base_data = data.get('Base')
|
||||
if base_data:
|
||||
base_keys.update(base_data.keys())
|
||||
except Exception:
|
||||
pass
|
||||
|
||||
base_schema = sorted([k for k in base_keys if k.lower().strip() not in EXCLUDE_FEATURES])
|
||||
dll_schema = [f"DllHash_{i:03d}" for i in range(DLL_HASH_DIM)]
|
||||
@@ -73,28 +74,69 @@ def analyze_schema(jsonl_path):
|
||||
####################################################################################################
|
||||
|
||||
def load_data_efficiently(jsonl_path, base_schema, dll_schema, api_schema, num_lines):
|
||||
print(f"[*] Stage 2: Building sparse feature matrix dynamically (Expected samples: {num_lines})...")
|
||||
print(f"[*] Stage 2: Building sparse feature matrix dynamically via Multiprocessing...")
|
||||
final_features = base_schema + dll_schema + api_schema
|
||||
num_features = len(final_features)
|
||||
|
||||
base_idx = {feat: i for i, feat in enumerate(base_schema)}
|
||||
offset_dll = len(base_schema)
|
||||
offset_api = offset_dll + DLL_HASH_DIM
|
||||
|
||||
rows, cols, vals = [], [], []
|
||||
y = np.zeros(num_lines, dtype=np.int8)
|
||||
file_size = os.path.getsize(jsonl_path)
|
||||
workers = min(os.cpu_count() or 4, 16)
|
||||
chunk_size = file_size // workers
|
||||
|
||||
tasks = []
|
||||
for i in range(workers):
|
||||
start = i * chunk_size
|
||||
end = file_size if i == workers - 1 else (i + 1) * chunk_size
|
||||
tasks.append((jsonl_path, start, end, base_schema, num_features, offset_dll, offset_api))
|
||||
|
||||
with multiprocessing.Pool(workers) as pool:
|
||||
results = pool.map(_chunk_worker, tasks)
|
||||
|
||||
Xs = [r[0] for r in results if r[0].shape[0] > 0]
|
||||
ys = [r[1] for r in results if r[1].shape[0] > 0]
|
||||
|
||||
X_final = sp.vstack(Xs, format='csr') if Xs else sp.csr_matrix((0, num_features), dtype=np.float32)
|
||||
y_final = np.concatenate(ys) if ys else np.array([], dtype=np.int8)
|
||||
|
||||
return X_final, pd.Series(y_final), final_features
|
||||
|
||||
with open(jsonl_path, 'rb') as f:
|
||||
def _chunk_worker(args):
|
||||
filepath, start_byte, end_byte, base_schema, num_features, offset_dll, offset_api = args
|
||||
|
||||
base_idx = {feat: i for i, feat in enumerate(base_schema)}
|
||||
base_idx_get = base_idx.get
|
||||
|
||||
rows = array.array('i')
|
||||
cols = array.array('i')
|
||||
vals = array.array('f')
|
||||
y_list = array.array('b')
|
||||
|
||||
with open(filepath, 'rb') as f:
|
||||
if start_byte > 0:
|
||||
f.seek(start_byte - 1)
|
||||
if f.read(1) != b'\n':
|
||||
f.seek(start_byte)
|
||||
f.readline()
|
||||
else:
|
||||
f.seek(start_byte)
|
||||
else:
|
||||
f.seek(0)
|
||||
|
||||
row_idx = 0
|
||||
for line in f:
|
||||
while f.tell() < end_byte:
|
||||
line = f.readline()
|
||||
if not line:
|
||||
break
|
||||
|
||||
try:
|
||||
data = JSON_LOADS(line)
|
||||
y[row_idx] = data.get('Label', 0)
|
||||
y_list.append(data.get('Label', 0))
|
||||
|
||||
base_data = data.get('Base')
|
||||
if base_data:
|
||||
for k, v in base_data.items():
|
||||
idx = base_idx.get(k)
|
||||
idx = base_idx_get(k)
|
||||
if idx is not None:
|
||||
val = float(v)
|
||||
if val != 0.0:
|
||||
@@ -121,11 +163,15 @@ def load_data_efficiently(jsonl_path, base_schema, dll_schema, api_schema, num_l
|
||||
row_idx += 1
|
||||
except Exception:
|
||||
continue
|
||||
|
||||
y = y[:row_idx]
|
||||
X = sp.csr_matrix((vals, (rows, cols)), shape=(row_idx, num_features), dtype=np.float32)
|
||||
|
||||
y_arr = np.frombuffer(y_list, dtype=np.int8)
|
||||
np_rows = np.frombuffer(rows, dtype=np.int32)
|
||||
np_cols = np.frombuffer(cols, dtype=np.int32)
|
||||
np_vals = np.frombuffer(vals, dtype=np.float32)
|
||||
|
||||
return X, pd.Series(y), final_features
|
||||
X_local = sp.csr_matrix((np_vals, (np_rows, np_cols)), shape=(row_idx, num_features), dtype=np.float32)
|
||||
|
||||
return X_local, y_arr
|
||||
|
||||
####################################################################################################
|
||||
|
||||
@@ -233,6 +279,7 @@ def evaluate_and_save(model, X_test, y_test):
|
||||
####################################################################################################
|
||||
|
||||
if __name__ == "__main__":
|
||||
multiprocessing.freeze_support()
|
||||
print("\n---------------- PE Malware Trainer v4.0 ----------------\n")
|
||||
|
||||
if not os.path.exists(JSONL_PATH):
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import os, sys, time, json, onnxmltools, zlib
|
||||
import os, sys, time, json, zlib, array, onnxmltools, multiprocessing
|
||||
import numpy as np
|
||||
import pandas as pd
|
||||
import lightgbm as lgb
|
||||
@@ -55,14 +55,15 @@ def analyze_schema(jsonl_path):
|
||||
|
||||
with open(jsonl_path, 'rb') as f:
|
||||
for line in f:
|
||||
try:
|
||||
data = JSON_LOADS(line)
|
||||
base_data = data.get('Base')
|
||||
if base_data:
|
||||
base_keys.update(base_data.keys())
|
||||
num_lines += 1
|
||||
except Exception:
|
||||
continue
|
||||
num_lines += 1
|
||||
if not base_keys:
|
||||
try:
|
||||
data = JSON_LOADS(line)
|
||||
base_data = data.get('Base')
|
||||
if base_data:
|
||||
base_keys.update(base_data.keys())
|
||||
except Exception:
|
||||
pass
|
||||
|
||||
base_schema = sorted([k for k in base_keys if k.lower().strip() not in EXCLUDE_FEATURES])
|
||||
dll_schema = [f"DllHash_{i:03d}" for i in range(DLL_HASH_DIM)]
|
||||
@@ -73,28 +74,69 @@ def analyze_schema(jsonl_path):
|
||||
####################################################################################################
|
||||
|
||||
def load_data_efficiently(jsonl_path, base_schema, dll_schema, api_schema, num_lines):
|
||||
print(f"[*] Stage 2: Building sparse feature matrix dynamically (Expected samples: {num_lines})...")
|
||||
print(f"[*] Stage 2: Building sparse feature matrix dynamically via Multiprocessing...")
|
||||
final_features = base_schema + dll_schema + api_schema
|
||||
num_features = len(final_features)
|
||||
|
||||
base_idx = {feat: i for i, feat in enumerate(base_schema)}
|
||||
offset_dll = len(base_schema)
|
||||
offset_api = offset_dll + DLL_HASH_DIM
|
||||
|
||||
rows, cols, vals = [], [], []
|
||||
y = np.zeros(num_lines, dtype=np.int8)
|
||||
file_size = os.path.getsize(jsonl_path)
|
||||
workers = min(os.cpu_count() or 4, 16)
|
||||
chunk_size = file_size // workers
|
||||
|
||||
tasks = []
|
||||
for i in range(workers):
|
||||
start = i * chunk_size
|
||||
end = file_size if i == workers - 1 else (i + 1) * chunk_size
|
||||
tasks.append((jsonl_path, start, end, base_schema, num_features, offset_dll, offset_api))
|
||||
|
||||
with multiprocessing.Pool(workers) as pool:
|
||||
results = pool.map(_chunk_worker, tasks)
|
||||
|
||||
Xs = [r[0] for r in results if r[0].shape[0] > 0]
|
||||
ys = [r[1] for r in results if r[1].shape[0] > 0]
|
||||
|
||||
X_final = sp.vstack(Xs, format='csr') if Xs else sp.csr_matrix((0, num_features), dtype=np.float32)
|
||||
y_final = np.concatenate(ys) if ys else np.array([], dtype=np.int8)
|
||||
|
||||
return X_final, pd.Series(y_final), final_features
|
||||
|
||||
with open(jsonl_path, 'rb') as f:
|
||||
def _chunk_worker(args):
|
||||
filepath, start_byte, end_byte, base_schema, num_features, offset_dll, offset_api = args
|
||||
|
||||
base_idx = {feat: i for i, feat in enumerate(base_schema)}
|
||||
base_idx_get = base_idx.get
|
||||
|
||||
rows = array.array('i')
|
||||
cols = array.array('i')
|
||||
vals = array.array('f')
|
||||
y_list = array.array('b')
|
||||
|
||||
with open(filepath, 'rb') as f:
|
||||
if start_byte > 0:
|
||||
f.seek(start_byte - 1)
|
||||
if f.read(1) != b'\n':
|
||||
f.seek(start_byte)
|
||||
f.readline()
|
||||
else:
|
||||
f.seek(start_byte)
|
||||
else:
|
||||
f.seek(0)
|
||||
|
||||
row_idx = 0
|
||||
for line in f:
|
||||
while f.tell() < end_byte:
|
||||
line = f.readline()
|
||||
if not line:
|
||||
break
|
||||
|
||||
try:
|
||||
data = JSON_LOADS(line)
|
||||
y[row_idx] = data.get('Label', 0)
|
||||
y_list.append(data.get('Label', 0))
|
||||
|
||||
base_data = data.get('Base')
|
||||
if base_data:
|
||||
for k, v in base_data.items():
|
||||
idx = base_idx.get(k)
|
||||
idx = base_idx_get(k)
|
||||
if idx is not None:
|
||||
val = float(v)
|
||||
if val != 0.0:
|
||||
@@ -121,11 +163,15 @@ def load_data_efficiently(jsonl_path, base_schema, dll_schema, api_schema, num_l
|
||||
row_idx += 1
|
||||
except Exception:
|
||||
continue
|
||||
|
||||
y = y[:row_idx]
|
||||
X = sp.csr_matrix((vals, (rows, cols)), shape=(row_idx, num_features), dtype=np.float32)
|
||||
|
||||
y_arr = np.frombuffer(y_list, dtype=np.int8)
|
||||
np_rows = np.frombuffer(rows, dtype=np.int32)
|
||||
np_cols = np.frombuffer(cols, dtype=np.int32)
|
||||
np_vals = np.frombuffer(vals, dtype=np.float32)
|
||||
|
||||
return X, pd.Series(y), final_features
|
||||
X_local = sp.csr_matrix((np_vals, (np_rows, np_cols)), shape=(row_idx, num_features), dtype=np.float32)
|
||||
|
||||
return X_local, y_arr
|
||||
|
||||
####################################################################################################
|
||||
|
||||
@@ -233,6 +279,7 @@ def evaluate_and_save(model, X_test, y_test):
|
||||
####################################################################################################
|
||||
|
||||
if __name__ == "__main__":
|
||||
multiprocessing.freeze_support()
|
||||
print("\n---------------- PE Malware Trainer v4.0 ----------------\n")
|
||||
|
||||
if not os.path.exists(JSONL_PATH):
|
||||
|
||||
+143
-11
@@ -49,6 +49,7 @@ window.AppI18n = {
|
||||
"scan_context_desc": "此選項可以啟用右鍵掃描功能",
|
||||
"theme_color": "顯色主題",
|
||||
"theme_color_desc": "此選項可以選擇主題顏色",
|
||||
"theme_system": "跟隨系統",
|
||||
"theme_white": "白色主題",
|
||||
"theme_red": "紅色主題",
|
||||
"theme_yellow": "黃色主題",
|
||||
@@ -134,7 +135,18 @@ 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": "已禁用",
|
||||
"btn_enable": "啟用",
|
||||
"btn_disable": "禁用",
|
||||
"search_item": "搜尋項目...",
|
||||
"type_reg": "登錄檔",
|
||||
"type_srv": "服務",
|
||||
"type_tsk": "排程任務",
|
||||
},
|
||||
"simplified_switch": {
|
||||
"app_name": "PYAS Security",
|
||||
@@ -185,6 +197,7 @@ window.AppI18n = {
|
||||
"scan_context_desc": "此选项可以启用右键扫描功能",
|
||||
"theme_color": "显色主题",
|
||||
"theme_color_desc": "此选项可以选择主题颜色",
|
||||
"theme_system": "跟随系统",
|
||||
"theme_white": "白色主题",
|
||||
"theme_red": "红色主题",
|
||||
"theme_yellow": "黄色主题",
|
||||
@@ -270,7 +283,18 @@ 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": "已禁用",
|
||||
"btn_enable": "启用",
|
||||
"btn_disable": "禁用",
|
||||
"search_item": "搜索项目...",
|
||||
"type_reg": "注册表",
|
||||
"type_srv": "服务",
|
||||
"type_tsk": "计划任务",
|
||||
},
|
||||
"english_switch": {
|
||||
"app_name": "PYAS Security",
|
||||
@@ -321,6 +345,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",
|
||||
@@ -406,7 +431,18 @@ 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",
|
||||
"btn_enable": "Enable",
|
||||
"btn_disable": "Disable",
|
||||
"search_item": "Search items...",
|
||||
"type_reg": "Registry",
|
||||
"type_srv": "Service",
|
||||
"type_tsk": "Task",
|
||||
},
|
||||
"japanese_switch": {
|
||||
"app_name": "PYAS Security",
|
||||
@@ -457,6 +493,7 @@ window.AppI18n = {
|
||||
"scan_context_desc": "右クリックでスキャン機能を有効にします",
|
||||
"theme_color": "カラーテーマ",
|
||||
"theme_color_desc": "アプリケーションのテーマカラーを選択します",
|
||||
"theme_system": "システムに従う",
|
||||
"theme_white": "ライトモード",
|
||||
"theme_red": "レッドテーマ",
|
||||
"theme_yellow": "イエローテーマ",
|
||||
@@ -542,7 +579,18 @@ 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": "無効",
|
||||
"btn_enable": "有効化",
|
||||
"btn_disable": "無効化",
|
||||
"search_item": "項目を検索...",
|
||||
"type_reg": "レジストリ",
|
||||
"type_srv": "サービス",
|
||||
"type_tsk": "タスク",
|
||||
},
|
||||
"korean_switch": {
|
||||
"app_name": "PYAS Security",
|
||||
@@ -593,6 +641,7 @@ window.AppI18n = {
|
||||
"scan_context_desc": "우클릭 스캔 기능을 활성화합니다",
|
||||
"theme_color": "색상 테마",
|
||||
"theme_color_desc": "응용 프로그램 테마 색상을 선택합니다",
|
||||
"theme_system": "시스템 테마",
|
||||
"theme_white": "라이트 모드",
|
||||
"theme_red": "레드 테마",
|
||||
"theme_yellow": "옐로우 테마",
|
||||
@@ -678,7 +727,18 @@ 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": "사용 안 함",
|
||||
"btn_enable": "사용",
|
||||
"btn_disable": "사용 안 함",
|
||||
"search_item": "항목 검색...",
|
||||
"type_reg": "레지스트리",
|
||||
"type_srv": "서비스",
|
||||
"type_tsk": "작업",
|
||||
},
|
||||
"french_switch": {
|
||||
"app_name": "PYAS Security",
|
||||
@@ -729,6 +789,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",
|
||||
@@ -814,7 +875,18 @@ 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é",
|
||||
"btn_enable": "Activer",
|
||||
"btn_disable": "Désactiver",
|
||||
"search_item": "Rechercher des éléments...",
|
||||
"type_reg": "Registre",
|
||||
"type_srv": "Service",
|
||||
"type_tsk": "Tâche",
|
||||
},
|
||||
"spanish_switch": {
|
||||
"app_name": "PYAS Security",
|
||||
@@ -865,6 +937,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",
|
||||
@@ -950,7 +1023,18 @@ 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",
|
||||
"btn_enable": "Habilitar",
|
||||
"btn_disable": "Deshabilitar",
|
||||
"search_item": "Buscar elementos...",
|
||||
"type_reg": "Registro",
|
||||
"type_srv": "Servicio",
|
||||
"type_tsk": "Tarea",
|
||||
},
|
||||
"hindi_switch": {
|
||||
"app_name": "PYAS Security",
|
||||
@@ -1001,6 +1085,7 @@ window.AppI18n = {
|
||||
"scan_context_desc": "राइट-क्लिक स्कैन कार्यक्षमता सक्षम करें",
|
||||
"theme_color": "रंग थीम",
|
||||
"theme_color_desc": "एप्लिकेशन का रंग थीम चुनें",
|
||||
"theme_system": "सिस्टम थीम",
|
||||
"theme_white": "लाइट मोड",
|
||||
"theme_red": "लाल थीम",
|
||||
"theme_yellow": "पीला थीम",
|
||||
@@ -1086,7 +1171,18 @@ 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": "अक्षम",
|
||||
"btn_enable": "सक्षम करें",
|
||||
"btn_disable": "अक्षम करें",
|
||||
"search_item": "आइटम खोजें...",
|
||||
"type_reg": "रजिस्ट्री",
|
||||
"type_srv": "सेवा",
|
||||
"type_tsk": "कार्य",
|
||||
},
|
||||
"arabic_switch": {
|
||||
"app_name": "PYAS Security",
|
||||
@@ -1137,6 +1233,7 @@ window.AppI18n = {
|
||||
"scan_context_desc": "تمكين ميزة الفحص بزر الماوس الأيمن",
|
||||
"theme_color": "سمة اللون",
|
||||
"theme_color_desc": "تحديد سمة ألوان التطبيق",
|
||||
"theme_system": "سمة النظام",
|
||||
"theme_white": "الوضع الفاتح",
|
||||
"theme_red": "سمة حمراء",
|
||||
"theme_yellow": "سمة صفراء",
|
||||
@@ -1222,7 +1319,18 @@ 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": "مُعطّل",
|
||||
"btn_enable": "تمكين",
|
||||
"btn_disable": "تعطيل",
|
||||
"search_item": "البحث في العناصر...",
|
||||
"type_reg": "التسجيل",
|
||||
"type_srv": "خدمة",
|
||||
"type_tsk": "مهمة",
|
||||
},
|
||||
"russian_switch": {
|
||||
"app_name": "PYAS Security",
|
||||
@@ -1273,6 +1381,7 @@ window.AppI18n = {
|
||||
"scan_context_desc": "Включить функцию сканирования по правому клику",
|
||||
"theme_color": "Цветовая тема",
|
||||
"theme_color_desc": "Выберите цветовую тему приложения",
|
||||
"theme_system": "Системная тема",
|
||||
"theme_white": "Светлый режим",
|
||||
"theme_red": "Красная тема",
|
||||
"theme_yellow": "Желтая тема",
|
||||
@@ -1358,7 +1467,18 @@ 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": "Отключено",
|
||||
"btn_enable": "Включить",
|
||||
"btn_disable": "Отключить",
|
||||
"search_item": "Поиск элементов...",
|
||||
"type_reg": "Реестр",
|
||||
"type_srv": "Служба",
|
||||
"type_tsk": "Задача",
|
||||
},
|
||||
"slovenian_switch": {
|
||||
"app_name": "PYAS Security",
|
||||
@@ -1409,6 +1529,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",
|
||||
@@ -1494,7 +1615,18 @@ 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",
|
||||
"btn_enable": "Omogoči",
|
||||
"btn_disable": "Onemogoči",
|
||||
"search_item": "Išči elemente...",
|
||||
"type_reg": "Register",
|
||||
"type_srv": "Storitev",
|
||||
"type_tsk": "Opravilo",
|
||||
}
|
||||
},
|
||||
langMap: {
|
||||
|
||||
@@ -9,6 +9,7 @@ document.addEventListener('DOMContentLoaded', () => {
|
||||
|
||||
const appState = {
|
||||
lang: "english_switch",
|
||||
theme: "system_switch",
|
||||
scanning: false,
|
||||
firstLaunch: false,
|
||||
taskmgrTimer: null,
|
||||
@@ -79,11 +80,12 @@ 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}]
|
||||
};
|
||||
|
||||
const buildCustomSelectElement = (select) => {
|
||||
@@ -198,7 +200,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 +213,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`);
|
||||
@@ -503,6 +514,46 @@ document.addEventListener('DOMContentLoaded', () => {
|
||||
}
|
||||
};
|
||||
|
||||
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);
|
||||
});
|
||||
};
|
||||
|
||||
document.querySelector('#startup_window .primary-btn')?.addEventListener('click', () => {
|
||||
const checked = getCheckedValues('#startup_window');
|
||||
if (checked.length > 0 && window.pywebview) {
|
||||
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 = 'status_enabled';
|
||||
});
|
||||
widget.renderData();
|
||||
}
|
||||
window.pywebview.api.manage_startup(checked, 'enable').then(fetchStartup);
|
||||
}
|
||||
});
|
||||
|
||||
document.querySelector('#startup_window .danger-btn')?.addEventListener('click', () => {
|
||||
const checked = getCheckedValues('#startup_window');
|
||||
if (checked.length > 0 && window.pywebview) {
|
||||
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 = 'status_disabled';
|
||||
});
|
||||
widget.renderData();
|
||||
}
|
||||
window.pywebview.api.manage_startup(checked, 'disable').then(fetchStartup);
|
||||
}
|
||||
});
|
||||
|
||||
const refreshConfigLists = () => {
|
||||
if (!window.pywebview) return;
|
||||
window.pywebview.api.get_config().then(cfg => {
|
||||
@@ -523,7 +574,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 +586,8 @@ document.addEventListener('DOMContentLoaded', () => {
|
||||
if (appState.taskmgrActive) fetchProcs();
|
||||
else if (appState.taskmgrTimer) { clearTimeout(appState.taskmgrTimer); appState.taskmgrTimer = null; }
|
||||
|
||||
if (targetId === 'startup_window') fetchStartup();
|
||||
|
||||
if (['whitelist_window', 'quarantine_window', 'popup_window', 'custom_protect_window'].includes(targetId)) refreshConfigLists();
|
||||
|
||||
if (targetId === 'log_export_window' && window.pywebview) {
|
||||
@@ -708,7 +761,7 @@ document.addEventListener('DOMContentLoaded', () => {
|
||||
|
||||
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));
|
||||
window.pywebview.api.scan_system_repair().then(list => renderDataGrid('#repair_window', list, cols.repair, 'id', true));
|
||||
});
|
||||
|
||||
document.querySelector('#repair_window .danger-btn')?.addEventListener('click', () => {
|
||||
@@ -716,7 +769,7 @@ document.addEventListener('DOMContentLoaded', () => {
|
||||
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');
|
||||
renderDataGrid('#repair_window', [], cols.repair, 'id', true);
|
||||
});
|
||||
});
|
||||
|
||||
@@ -861,8 +914,9 @@ 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);
|
||||
|
||||
@@ -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>
|
||||
@@ -214,6 +220,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>
|
||||
@@ -320,6 +327,21 @@ Last updated: 2025-12-19
|
||||
<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>
|
||||
<button class="action-btn modern-btn primary-btn" data-i18n>啟用</button>
|
||||
<button class="action-btn modern-btn danger-btn" data-i18n>禁用</button>
|
||||
</div>
|
||||
<div class="manage-widget flex-grow"><ul class="manage-list"></ul></div>
|
||||
</section>
|
||||
|
||||
<section id="whitelist_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>
|
||||
|
||||
@@ -318,14 +318,14 @@ class WindowAPI:
|
||||
self.cloud_pending = set()
|
||||
|
||||
self.pyas_default = {
|
||||
"version": "3.5.6",
|
||||
"version": "3.5.7",
|
||||
"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,
|
||||
@@ -833,13 +833,15 @@ class WindowAPI:
|
||||
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 +973,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()
|
||||
@@ -1147,6 +1157,117 @@ 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]
|
||||
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]
|
||||
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]
|
||||
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):
|
||||
@@ -1592,12 +1713,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 +1818,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:
|
||||
|
||||
+79
-60
@@ -179,6 +179,17 @@ class rule_scanner:
|
||||
class pe_scanner:
|
||||
_STRING_PATTERN = re.compile(b'[\x20-\x7E]{5,}')
|
||||
|
||||
_BYTE_HIST_KEYS = [f'ByteHist_{i:02X}' for i in range(256)]
|
||||
_BYTE_ENT_KEYS = [f'ByteEnt_{i:02X}' for i in range(256)]
|
||||
_STRING_KEYS = [f'StringHist_{i:02d}' for i in range(95)]
|
||||
_SECTION_HASH_KEYS = [f'SectionHash_{i:02d}' for i in range(50)]
|
||||
_CHAR_FLAGS = [0x00000020, 0x00000040, 0x00000080, 0x02000000, 0x20000000, 0x40000000, 0x80000000]
|
||||
_CHAR_COUNT_KEYS = {flag: f'Char_{flag:08X}_Count' for flag in _CHAR_FLAGS}
|
||||
_CHAR_ENT_KEYS = {flag: f'Char_{flag:08X}_MeanEntropy' for flag in _CHAR_FLAGS}
|
||||
|
||||
_C_LOG_C_TABLE = numpy.zeros(2049, dtype=numpy.float64)
|
||||
_C_LOG_C_TABLE[1:] = numpy.arange(1, 2049) * numpy.log2(numpy.arange(1, 2049))
|
||||
|
||||
def __init__(self):
|
||||
self.model = None
|
||||
self.input_name = None
|
||||
@@ -221,39 +232,32 @@ class pe_scanner:
|
||||
if not data:
|
||||
return 0.0
|
||||
arr = numpy.frombuffer(data, dtype=numpy.uint8)
|
||||
sz = len(arr)
|
||||
counts = numpy.bincount(arr, minlength=256)
|
||||
probs = counts[counts > 0] / len(arr)
|
||||
return float(-numpy.sum(probs * numpy.log2(probs)))
|
||||
counts = counts[counts > 0]
|
||||
return float(numpy.log2(sz) - numpy.sum(counts * numpy.log2(counts)) / sz)
|
||||
|
||||
def _extract_strings(self, file_bytes):
|
||||
res = {"StringCount": 0.0, "StringMeanLength": 0.0, "StringEntropy": 0.0}
|
||||
for i in range(95):
|
||||
res[f'StringHist_{i:02d}'] = 0.0
|
||||
res.update(dict.fromkeys(self._STRING_KEYS, 0.0))
|
||||
|
||||
count = 0
|
||||
total_len = 0
|
||||
char_counts = [0] * 95
|
||||
combined_data = bytearray()
|
||||
|
||||
for match in self._STRING_PATTERN.finditer(file_bytes):
|
||||
m = match.group()
|
||||
count += 1
|
||||
length = len(m)
|
||||
total_len += length
|
||||
combined_data.extend(m)
|
||||
for b in m:
|
||||
char_counts[b - 0x20] += 1
|
||||
|
||||
if count == 0:
|
||||
matches = self._STRING_PATTERN.findall(file_bytes)
|
||||
if not matches:
|
||||
return res
|
||||
|
||||
combined_data = b''.join(matches)
|
||||
count = len(matches)
|
||||
total_len = len(combined_data)
|
||||
|
||||
res["StringCount"] = float(count)
|
||||
res["StringMeanLength"] = float(total_len) / count
|
||||
res["StringEntropy"] = self._calc_entropy(combined_data)
|
||||
|
||||
for i in range(95):
|
||||
res[f'StringHist_{i:02d}'] = float(char_counts[i]) / total_len
|
||||
arr = numpy.frombuffer(combined_data, dtype=numpy.uint8)
|
||||
counts = numpy.bincount(arr, minlength=127)
|
||||
char_counts = counts[0x20:0x7F]
|
||||
|
||||
res.update(dict(zip(self._STRING_KEYS, char_counts.astype(numpy.float64) / total_len)))
|
||||
return res
|
||||
|
||||
def _extract_histograms(self, file_bytes):
|
||||
@@ -262,43 +266,60 @@ class pe_scanner:
|
||||
res = {}
|
||||
|
||||
if sz == 0:
|
||||
for i in range(256):
|
||||
res[f'ByteHist_{i:02X}'] = 0.0
|
||||
res[f'ByteEnt_{i:02X}'] = 0.0
|
||||
res.update(dict.fromkeys(self._BYTE_HIST_KEYS, 0.0))
|
||||
res.update(dict.fromkeys(self._BYTE_ENT_KEYS, 0.0))
|
||||
return res
|
||||
|
||||
byte_counts = numpy.bincount(arr, minlength=256)
|
||||
byte_hist = byte_counts / sz
|
||||
for i, v in enumerate(byte_hist):
|
||||
res[f'ByteHist_{i:02X}'] = float(v)
|
||||
byte_hist = byte_counts.astype(numpy.float64) / sz
|
||||
res.update(dict(zip(self._BYTE_HIST_KEYS, byte_hist)))
|
||||
|
||||
ent_hist = numpy.zeros(256, dtype=numpy.float64)
|
||||
window = 2048
|
||||
step = 1024
|
||||
num_windows = (sz - window) // step + 1 if sz >= window else 1
|
||||
|
||||
for i in range(num_windows):
|
||||
w = arr if sz < window else arr[i*step : i*step+window]
|
||||
w_counts = numpy.bincount(w, minlength=256)
|
||||
|
||||
p = w_counts[w_counts > 0] / len(w)
|
||||
entropy = -numpy.sum(p * numpy.log2(p))
|
||||
|
||||
ent_bin = int(entropy * 2.0)
|
||||
if ent_bin > 15:
|
||||
ent_bin = 15
|
||||
|
||||
if sz < window:
|
||||
w_counts = byte_counts
|
||||
p = w_counts[w_counts > 0] / float(sz)
|
||||
entropy = float(-numpy.sum(p * numpy.log2(p)))
|
||||
ent_bin = min(int(entropy * 2.0), 15)
|
||||
byte_bins = w_counts.reshape(16, 16).sum(axis=1)
|
||||
idx_start = ent_bin * 16
|
||||
ent_hist[idx_start : idx_start+16] += byte_bins
|
||||
else:
|
||||
num_windows = (sz - window) // step + 1
|
||||
batch_windows = 10000
|
||||
|
||||
for w_start in range(0, num_windows, batch_windows):
|
||||
w_end = min(w_start + batch_windows, num_windows)
|
||||
b_num = w_end - w_start
|
||||
|
||||
start_byte = w_start * step
|
||||
end_byte = start_byte + b_num * step + step
|
||||
b_arr = arr[start_byte:end_byte]
|
||||
|
||||
arr_2d = b_arr.reshape(b_num + 1, step)
|
||||
offsets = (numpy.arange(b_num + 1, dtype=numpy.int32)[:, None] * 256)
|
||||
flat_idx = (arr_2d.astype(numpy.int32) + offsets).ravel()
|
||||
|
||||
chunk_counts = numpy.bincount(flat_idx, minlength=(b_num + 1) * 256).reshape(b_num + 1, 256)
|
||||
window_counts = chunk_counts[:-1] + chunk_counts[1:]
|
||||
|
||||
sum_c_log_c = numpy.sum(self._C_LOG_C_TABLE[window_counts], axis=1)
|
||||
entropies = 11.0 - sum_c_log_c / 2048.0
|
||||
|
||||
ent_bins = (entropies * 2.0).astype(numpy.int32)
|
||||
numpy.clip(ent_bins, 0, 15, out=ent_bins)
|
||||
|
||||
byte_bins = window_counts.reshape(b_num, 16, 16).sum(axis=2)
|
||||
flat_ent_bins = (ent_bins[:, None] * 16 + numpy.arange(16)).ravel()
|
||||
numpy.add.at(ent_hist, flat_ent_bins, byte_bins.ravel())
|
||||
|
||||
ent_sum = numpy.sum(ent_hist)
|
||||
if ent_sum > 0:
|
||||
ent_hist /= ent_sum
|
||||
|
||||
for i, v in enumerate(ent_hist):
|
||||
res[f'ByteEnt_{i:02X}'] = float(v)
|
||||
|
||||
res.update(dict(zip(self._BYTE_ENT_KEYS, ent_hist)))
|
||||
return res
|
||||
|
||||
def _extract_overlay_features(self, pe, fsize):
|
||||
@@ -516,28 +537,21 @@ class pe_scanner:
|
||||
if len(op.DATA_DIRECTORY) > exc_idx and op.DATA_DIRECTORY[exc_idx].Size > 0:
|
||||
base['ExceptionCount'] = op.DATA_DIRECTORY[exc_idx].Size // 12 if base['Machine'] in (0x8664, 0xAA64) else 0.0
|
||||
|
||||
char_flags = [0x00000020, 0x00000040, 0x00000080, 0x02000000, 0x20000000, 0x40000000, 0x80000000]
|
||||
for flag in char_flags:
|
||||
base[f'Char_{flag:08X}_Count'] = 0.0
|
||||
base[f'Char_{flag:08X}_MeanEntropy'] = 0.0
|
||||
|
||||
for i in range(50):
|
||||
base[f'SectionHash_{i:02d}'] = 0.0
|
||||
res_flags_count = dict.fromkeys(self._CHAR_COUNT_KEYS.values(), 0.0)
|
||||
res_flags_ent = dict.fromkeys(self._CHAR_ENT_KEYS.values(), 0.0)
|
||||
res_sec_hash = dict.fromkeys(self._SECTION_HASH_KEYS, 0.0)
|
||||
|
||||
if hasattr(pe, 'sections'):
|
||||
base['SectionCount'] = len(pe.sections)
|
||||
entropies = []
|
||||
raw_sizes = []
|
||||
v_sizes = []
|
||||
exec_sec = 0
|
||||
write_sec = 0
|
||||
read_sec = 0
|
||||
sec_exc = 0
|
||||
exec_sec = write_sec = read_sec = sec_exc = 0
|
||||
|
||||
for section in pe.sections:
|
||||
sec_name = section.Name.rstrip(b'\x00')
|
||||
hash_val = zlib.crc32(sec_name) % 50
|
||||
base[f'SectionHash_{hash_val:02d}'] += 1.0
|
||||
res_sec_hash[self._SECTION_HASH_KEYS[hash_val]] += 1.0
|
||||
|
||||
try:
|
||||
s_data = section.get_data()
|
||||
@@ -554,10 +568,10 @@ class pe_scanner:
|
||||
if section.Characteristics & 0x40000000: read_sec += 1
|
||||
if section.SizeOfRawData + section.PointerToRawData > fsize: sec_exc = 1
|
||||
|
||||
for flag in char_flags:
|
||||
for flag in self._CHAR_FLAGS:
|
||||
if section.Characteristics & flag:
|
||||
base[f'Char_{flag:08X}_Count'] += 1.0
|
||||
base[f'Char_{flag:08X}_MeanEntropy'] += s_entropy
|
||||
res_flags_count[self._CHAR_COUNT_KEYS[flag]] += 1.0
|
||||
res_flags_ent[self._CHAR_ENT_KEYS[flag]] += s_entropy
|
||||
|
||||
base['SectionMaxEntropy'] = max(entropies) if entropies else 0.0
|
||||
base['SectionMinEntropy'] = min(entropies) if entropies else 0.0
|
||||
@@ -573,9 +587,14 @@ class pe_scanner:
|
||||
base['ReadableSections'] = float(read_sec)
|
||||
base['SectionException'] = float(sec_exc)
|
||||
|
||||
for flag in char_flags:
|
||||
if base[f'Char_{flag:08X}_Count'] > 0:
|
||||
base[f'Char_{flag:08X}_MeanEntropy'] = base[f'Char_{flag:08X}_MeanEntropy'] / base[f'Char_{flag:08X}_Count']
|
||||
for flag in self._CHAR_FLAGS:
|
||||
cnt_key = self._CHAR_COUNT_KEYS[flag]
|
||||
if res_flags_count[cnt_key] > 0:
|
||||
res_flags_ent[self._CHAR_ENT_KEYS[flag]] /= res_flags_count[cnt_key]
|
||||
|
||||
base.update(res_flags_count)
|
||||
base.update(res_flags_ent)
|
||||
base.update(res_sec_hash)
|
||||
|
||||
string_keys = ['FileDescription', 'FileVersion', 'ProductName', 'ProductVersion', 'CompanyName', 'LegalCopyright', 'Comments', 'InternalName', 'LegalTrademarks', 'SpecialBuild', 'PrivateBuild']
|
||||
for key in string_keys:
|
||||
|
||||
+1
-1
@@ -2,7 +2,7 @@ import pyinstaller_versionfile
|
||||
|
||||
pyinstaller_versionfile.create_versionfile(
|
||||
output_file="versionfile.txt",
|
||||
version='3.5.6.0',
|
||||
version='3.5.7.0',
|
||||
company_name="PYAS Security",
|
||||
file_description="PYAS Security Antivirus",
|
||||
internal_name="PYAS",
|
||||
|
||||
@@ -135,6 +135,8 @@
|
||||
"*\\bin\\APlayer.exe",
|
||||
"*\\Neverness To Everness\\NTELauncher.exe",
|
||||
"*\\Application\\AndrowsLauncher.exe",
|
||||
"*\\bin\\*\\codex.exe",
|
||||
"*\\bin\\codex.exe",
|
||||
],
|
||||
"Rule_Process_ExploitableBlacklist": [
|
||||
"*\\cmd.exe",
|
||||
|
||||
Reference in new issue
Block a user