Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
5798a35456 | ||
|
|
a8a2060978 | ||
|
|
7cf2763511 | ||
|
|
fc59619518 | ||
|
|
d01cfd2dd6 | ||
|
|
9563a4806a | ||
|
|
fa1be109e3 | ||
|
|
89a5a5928c | ||
|
|
7830378f23 | ||
|
|
043f616469 | ||
|
|
3bf631313a | ||
|
|
a416e2d4a8 | ||
|
|
53218cf1ce | ||
|
|
c5f0c8ed47 | ||
|
|
05e1c45adb | ||
|
|
e3787523c7 | ||
|
|
1c4e908b1b | ||
|
|
98b60b9833 | ||
|
|
71f8232ef0 | ||
|
|
0e4de407e3 | ||
|
|
802d732b3f | ||
|
|
373897f579 | ||
|
|
3ef72917c8 | ||
|
|
0d27125453 | ||
|
|
82c519b7d9 | ||
|
|
3c57a8a53c | ||
|
|
727ba6ccbf | ||
|
|
b32f1df9ec | ||
|
|
76b9afc6ec | ||
|
|
9fbf7baf76 |
No files matched your search
+174
-42
@@ -1,4 +1,4 @@
|
||||
import re, os, sys, math, time, zlib, datetime, hashlib
|
||||
import re, os, sys, math, time, zlib, mmap, datetime, hashlib
|
||||
import ctypes, ctypes.wintypes, pefile, multiprocessing
|
||||
import numpy as np
|
||||
|
||||
@@ -113,6 +113,9 @@ def verify_signature(file_path):
|
||||
|
||||
class FeatureExtractor:
|
||||
_STRING_PATTERN = re.compile(b'[\x20-\x7E]{5,}')
|
||||
_STRING_FAST_PATH_BYTES = 4194304
|
||||
_STRING_BATCH_BYTES = 4194304
|
||||
_HISTOGRAM_BATCH_WINDOWS = 1024
|
||||
|
||||
_BYTE_HIST_KEYS = [f'ByteHist_{i:02X}' for i in range(256)]
|
||||
_BYTE_ENT_KEYS = [f'ByteEnt_{i:02X}' for i in range(256)]
|
||||
@@ -125,6 +128,49 @@ class FeatureExtractor:
|
||||
_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))
|
||||
|
||||
_OPTIONAL_HEADER_FIELDS = [
|
||||
'Magic', 'MajorLinkerVersion', 'MinorLinkerVersion', 'SizeOfCode',
|
||||
'SizeOfInitializedData', 'SizeOfUninitializedData', 'AddressOfEntryPoint',
|
||||
'BaseOfCode', 'ImageBase', 'SectionAlignment', 'FileAlignment',
|
||||
'MajorOperatingSystemVersion', 'MinorOperatingSystemVersion',
|
||||
'MajorImageVersion', 'MinorImageVersion', 'MajorSubsystemVersion',
|
||||
'MinorSubsystemVersion', 'SizeOfImage', 'SizeOfHeaders', 'CheckSum',
|
||||
'Subsystem', 'DllCharacteristics', 'SizeOfStackReserve', 'SizeOfStackCommit',
|
||||
'SizeOfHeapReserve', 'SizeOfHeapCommit', 'LoaderFlags', 'NumberOfRvaAndSizes'
|
||||
]
|
||||
|
||||
_SECTION_DEFAULTS = {
|
||||
'SectionCount': 0.0,
|
||||
'SectionMaxEntropy': 0.0,
|
||||
'SectionMinEntropy': 0.0,
|
||||
'SectionMeanEntropy': 0.0,
|
||||
'SectionMaxRawSize': 0.0,
|
||||
'SectionMinRawSize': 0.0,
|
||||
'SectionMeanRawSize': 0.0,
|
||||
'SectionMaxVSize': 0.0,
|
||||
'SectionMinVSize': 0.0,
|
||||
'SectionMeanVSize': 0.0,
|
||||
'ExecutableSections': 0.0,
|
||||
'WritableSections': 0.0,
|
||||
'ReadableSections': 0.0,
|
||||
'SectionException': 0.0
|
||||
}
|
||||
|
||||
_CONDITIONAL_DEFAULTS = {
|
||||
'IsDriver': 0.0,
|
||||
'HasTlsCallbacks': 0.0,
|
||||
'IsDebug': 0.0,
|
||||
'IsPreRelease': 0.0,
|
||||
'IsPatched': 0.0,
|
||||
'IsPrivateBuild': 0.0,
|
||||
'IsSpecialBuild': 0.0,
|
||||
'ImportCount': 0.0,
|
||||
'ImportFunctionCount': 0.0,
|
||||
'ExportCount': 0.0,
|
||||
'IconCount': 0.0,
|
||||
'DebugCount': 0.0
|
||||
}
|
||||
|
||||
@staticmethod
|
||||
def _safe_float(val):
|
||||
try:
|
||||
@@ -134,6 +180,18 @@ class FeatureExtractor:
|
||||
except Exception:
|
||||
return 0.0
|
||||
|
||||
@classmethod
|
||||
def _base_defaults(cls):
|
||||
base = dict.fromkeys(cls._OPTIONAL_HEADER_FIELDS, 0.0)
|
||||
base.update(cls._SECTION_DEFAULTS)
|
||||
base.update(cls._CONDITIONAL_DEFAULTS)
|
||||
|
||||
for i in range(16):
|
||||
base[f'DataDirectory_{i}_Size'] = 0.0
|
||||
base[f'DataDirectory_{i}_VA'] = 0.0
|
||||
|
||||
return base
|
||||
|
||||
@staticmethod
|
||||
def _calc_entropy(data):
|
||||
if not data:
|
||||
@@ -151,25 +209,84 @@ class FeatureExtractor:
|
||||
res = {"StringCount": 0.0, "StringMeanLength": 0.0, "StringEntropy": 0.0}
|
||||
res.update(dict.fromkeys(cls._STRING_KEYS, 0.0))
|
||||
|
||||
matches = cls._STRING_PATTERN.findall(file_bytes)
|
||||
if not matches:
|
||||
return res
|
||||
if len(file_bytes) <= cls._STRING_FAST_PATH_BYTES:
|
||||
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)
|
||||
combined_data = b''.join(matches)
|
||||
count = len(matches)
|
||||
total_len = len(combined_data)
|
||||
global_counts = np.bincount(np.frombuffer(combined_data, dtype=np.uint8), minlength=256)
|
||||
else:
|
||||
count = 0
|
||||
total_len = 0
|
||||
global_counts = np.zeros(256, dtype=np.int64)
|
||||
mv = memoryview(file_bytes)
|
||||
pos = 0
|
||||
size = len(file_bytes)
|
||||
|
||||
while pos < size:
|
||||
chunk_end = min(pos + cls._STRING_BATCH_BYTES, size)
|
||||
chunk_arr = np.frombuffer(mv[pos:chunk_end], dtype=np.uint8)
|
||||
separators = (chunk_arr < 0x20) | (chunk_arr > 0x7E)
|
||||
|
||||
if separators.any():
|
||||
last_separator = len(separators) - 1 - int(np.argmax(separators[::-1]))
|
||||
segment_end = pos + last_separator + 1
|
||||
matches = cls._STRING_PATTERN.findall(mv[pos:segment_end])
|
||||
|
||||
if matches:
|
||||
combined_data = b''.join(matches)
|
||||
count += len(matches)
|
||||
total_len += len(combined_data)
|
||||
global_counts += np.bincount(np.frombuffer(combined_data, dtype=np.uint8), minlength=256)
|
||||
|
||||
pos = segment_end
|
||||
continue
|
||||
|
||||
pending_len = 0
|
||||
pending_counts = np.zeros(256, dtype=np.int64)
|
||||
|
||||
while pos < size:
|
||||
chunk_end = min(pos + cls._STRING_BATCH_BYTES, size)
|
||||
chunk_arr = np.frombuffer(mv[pos:chunk_end], dtype=np.uint8)
|
||||
separators = (chunk_arr < 0x20) | (chunk_arr > 0x7E)
|
||||
|
||||
if separators.any():
|
||||
first_separator = int(np.argmax(separators))
|
||||
if first_separator:
|
||||
pending_counts += np.bincount(chunk_arr[:first_separator], minlength=256)
|
||||
pending_len += first_separator
|
||||
pos += first_separator + 1
|
||||
break
|
||||
|
||||
pending_counts += np.bincount(chunk_arr, minlength=256)
|
||||
pending_len += len(chunk_arr)
|
||||
pos = chunk_end
|
||||
|
||||
if pending_len >= 5:
|
||||
count += 1
|
||||
total_len += pending_len
|
||||
global_counts += pending_counts
|
||||
|
||||
if count == 0 or total_len == 0:
|
||||
return res
|
||||
|
||||
res["StringCount"] = float(count)
|
||||
res["StringMeanLength"] = float(total_len) / count
|
||||
res["StringEntropy"] = cls._calc_entropy(combined_data)
|
||||
|
||||
arr = np.frombuffer(combined_data, dtype=np.uint8)
|
||||
counts = np.bincount(arr, minlength=127)
|
||||
char_counts = counts[0x20:0x7F]
|
||||
counts_nonzero = global_counts[global_counts > 0]
|
||||
if len(counts_nonzero) > 0:
|
||||
res["StringEntropy"] = float(np.log2(total_len) - np.sum(counts_nonzero * np.log2(counts_nonzero)) / total_len)
|
||||
|
||||
valid_chars = global_counts[0x20:0x7F]
|
||||
res.update(dict(zip(cls._STRING_KEYS, valid_chars.astype(np.float64) / total_len)))
|
||||
|
||||
res.update(dict(zip(cls._STRING_KEYS, char_counts.astype(np.float64) / total_len)))
|
||||
return res
|
||||
|
||||
|
||||
|
||||
@classmethod
|
||||
def _extract_histograms(cls, file_bytes):
|
||||
arr = np.frombuffer(file_bytes, dtype=np.uint8)
|
||||
@@ -177,56 +294,64 @@ class FeatureExtractor:
|
||||
res = {}
|
||||
|
||||
if sz == 0:
|
||||
res['FileEntropy'] = 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.astype(np.float64) / sz
|
||||
res.update(dict(zip(cls._BYTE_HIST_KEYS, byte_hist)))
|
||||
|
||||
byte_counts = np.zeros(256, dtype=np.int64)
|
||||
ent_hist = np.zeros(256, dtype=np.float64)
|
||||
window = 2048
|
||||
step = 1024
|
||||
|
||||
if sz < window:
|
||||
w_counts = byte_counts
|
||||
p = w_counts[w_counts > 0] / float(sz)
|
||||
byte_counts += np.bincount(arr, minlength=256)
|
||||
p = byte_counts[byte_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)
|
||||
byte_bins = byte_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)
|
||||
|
||||
for w_start in range(0, num_windows, cls._HISTOGRAM_BATCH_WINDOWS):
|
||||
w_end = min(w_start + cls._HISTOGRAM_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)
|
||||
encoded = arr[start_byte:end_byte].astype(np.int32).reshape(b_num + 1, step)
|
||||
encoded += np.arange(b_num + 1, dtype=np.int32)[:, None] * 256
|
||||
|
||||
chunk_counts = np.bincount(encoded.ravel(), minlength=(b_num + 1) * 256).reshape(b_num + 1, 256)
|
||||
del encoded
|
||||
|
||||
byte_counts += np.sum(chunk_counts[:-1], axis=0)
|
||||
if w_end == num_windows:
|
||||
byte_counts += chunk_counts[-1]
|
||||
|
||||
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())
|
||||
|
||||
covered_size = (num_windows + 1) * step
|
||||
if covered_size < sz:
|
||||
byte_counts += np.bincount(arr[covered_size:], minlength=256)
|
||||
|
||||
counts_nonzero = byte_counts[byte_counts > 0]
|
||||
res['FileEntropy'] = float(np.log2(sz) - np.sum(counts_nonzero * np.log2(counts_nonzero)) / sz)
|
||||
|
||||
byte_hist = byte_counts.astype(np.float64) / sz
|
||||
res.update(dict(zip(cls._BYTE_HIST_KEYS, byte_hist)))
|
||||
|
||||
ent_sum = np.sum(ent_hist)
|
||||
if ent_sum > 0:
|
||||
ent_hist /= ent_sum
|
||||
@@ -234,6 +359,7 @@ class FeatureExtractor:
|
||||
res.update(dict(zip(cls._BYTE_ENT_KEYS, ent_hist)))
|
||||
return res
|
||||
|
||||
|
||||
@classmethod
|
||||
def _extract_overlay_features(cls, pe, fsize):
|
||||
overlay_offset = pe.get_overlay_data_start_offset()
|
||||
@@ -390,7 +516,7 @@ class FeatureExtractor:
|
||||
if fsize == 0:
|
||||
return None
|
||||
|
||||
base, dlls, apis, pe = {}, set(), set(), None
|
||||
base, dlls, apis, pe = cls._base_defaults(), set(), set(), None
|
||||
try:
|
||||
pe = pefile.PE(data=file_bytes, fast_load=True)
|
||||
|
||||
@@ -408,7 +534,6 @@ class FeatureExtractor:
|
||||
pass
|
||||
|
||||
base['TrustSigned'] = float(verify_signature(file_path))
|
||||
base['FileEntropy'] = cls._calc_entropy(file_bytes)
|
||||
base['FileSize'] = float(fsize)
|
||||
|
||||
fh = pe.FILE_HEADER
|
||||
@@ -613,20 +738,22 @@ def _extract_worker_wrapper(args):
|
||||
return _extract_worker(*args)
|
||||
|
||||
def _extract_worker(file_path, label):
|
||||
mm = None
|
||||
f = None
|
||||
|
||||
try:
|
||||
fsize = os.path.getsize(file_path)
|
||||
if fsize == 0 or fsize > MAX_FILE_SIZE:
|
||||
return 'error', None
|
||||
|
||||
with open(file_path, "rb") as f:
|
||||
file_bytes = f.read()
|
||||
|
||||
sha256 = hashlib.sha256(file_bytes).hexdigest()
|
||||
f = open(file_path, "rb")
|
||||
mm = mmap.mmap(f.fileno(), 0, access=mmap.ACCESS_READ)
|
||||
sha256 = hashlib.sha256(mm).hexdigest()
|
||||
|
||||
if sha256 in GLOBAL_HASHES:
|
||||
return 'skipped', None
|
||||
|
||||
res = FeatureExtractor.extract(file_bytes, file_path, fsize)
|
||||
res = FeatureExtractor.extract(mm, file_path, fsize)
|
||||
if res:
|
||||
final_res = {"Label": label, "FileHash": sha256, **res}
|
||||
return 'success', (sha256, JSON_DUMPS(final_res) + "\n")
|
||||
@@ -634,6 +761,11 @@ def _extract_worker(file_path, label):
|
||||
return 'error', None
|
||||
except Exception:
|
||||
return 'error', None
|
||||
finally:
|
||||
if mm:
|
||||
mm.close()
|
||||
if f:
|
||||
f.close()
|
||||
|
||||
####################################################################################################
|
||||
|
||||
|
||||
+186
-42
@@ -1,4 +1,4 @@
|
||||
import os, re, sys, math, json, zlib, pefile, datetime
|
||||
import os, re, sys, math, json, zlib, mmap, pefile, datetime
|
||||
import ctypes, ctypes.wintypes
|
||||
import numpy as np
|
||||
import onnxruntime as ort
|
||||
@@ -99,6 +99,9 @@ class WinTrust:
|
||||
|
||||
class FeatureExtractor:
|
||||
_STRING_PATTERN = re.compile(b'[\x20-\x7E]{5,}')
|
||||
_STRING_FAST_PATH_BYTES = 4194304
|
||||
_STRING_BATCH_BYTES = 4194304
|
||||
_HISTOGRAM_BATCH_WINDOWS = 1024
|
||||
|
||||
_BYTE_HIST_KEYS = [f'ByteHist_{i:02X}' for i in range(256)]
|
||||
_BYTE_ENT_KEYS = [f'ByteEnt_{i:02X}' for i in range(256)]
|
||||
@@ -111,6 +114,49 @@ class FeatureExtractor:
|
||||
_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))
|
||||
|
||||
_OPTIONAL_HEADER_FIELDS = [
|
||||
'Magic', 'MajorLinkerVersion', 'MinorLinkerVersion', 'SizeOfCode',
|
||||
'SizeOfInitializedData', 'SizeOfUninitializedData', 'AddressOfEntryPoint',
|
||||
'BaseOfCode', 'ImageBase', 'SectionAlignment', 'FileAlignment',
|
||||
'MajorOperatingSystemVersion', 'MinorOperatingSystemVersion',
|
||||
'MajorImageVersion', 'MinorImageVersion', 'MajorSubsystemVersion',
|
||||
'MinorSubsystemVersion', 'SizeOfImage', 'SizeOfHeaders', 'CheckSum',
|
||||
'Subsystem', 'DllCharacteristics', 'SizeOfStackReserve', 'SizeOfStackCommit',
|
||||
'SizeOfHeapReserve', 'SizeOfHeapCommit', 'LoaderFlags', 'NumberOfRvaAndSizes'
|
||||
]
|
||||
|
||||
_SECTION_DEFAULTS = {
|
||||
'SectionCount': 0.0,
|
||||
'SectionMaxEntropy': 0.0,
|
||||
'SectionMinEntropy': 0.0,
|
||||
'SectionMeanEntropy': 0.0,
|
||||
'SectionMaxRawSize': 0.0,
|
||||
'SectionMinRawSize': 0.0,
|
||||
'SectionMeanRawSize': 0.0,
|
||||
'SectionMaxVSize': 0.0,
|
||||
'SectionMinVSize': 0.0,
|
||||
'SectionMeanVSize': 0.0,
|
||||
'ExecutableSections': 0.0,
|
||||
'WritableSections': 0.0,
|
||||
'ReadableSections': 0.0,
|
||||
'SectionException': 0.0
|
||||
}
|
||||
|
||||
_CONDITIONAL_DEFAULTS = {
|
||||
'IsDriver': 0.0,
|
||||
'HasTlsCallbacks': 0.0,
|
||||
'IsDebug': 0.0,
|
||||
'IsPreRelease': 0.0,
|
||||
'IsPatched': 0.0,
|
||||
'IsPrivateBuild': 0.0,
|
||||
'IsSpecialBuild': 0.0,
|
||||
'ImportCount': 0.0,
|
||||
'ImportFunctionCount': 0.0,
|
||||
'ExportCount': 0.0,
|
||||
'IconCount': 0.0,
|
||||
'DebugCount': 0.0
|
||||
}
|
||||
|
||||
@staticmethod
|
||||
def _safe_float(val):
|
||||
try:
|
||||
@@ -120,6 +166,18 @@ class FeatureExtractor:
|
||||
except Exception:
|
||||
return 0.0
|
||||
|
||||
@classmethod
|
||||
def _base_defaults(cls):
|
||||
base = dict.fromkeys(cls._OPTIONAL_HEADER_FIELDS, 0.0)
|
||||
base.update(cls._SECTION_DEFAULTS)
|
||||
base.update(cls._CONDITIONAL_DEFAULTS)
|
||||
|
||||
for i in range(16):
|
||||
base[f'DataDirectory_{i}_Size'] = 0.0
|
||||
base[f'DataDirectory_{i}_VA'] = 0.0
|
||||
|
||||
return base
|
||||
|
||||
@staticmethod
|
||||
def _calc_entropy(data):
|
||||
if not data:
|
||||
@@ -137,25 +195,84 @@ class FeatureExtractor:
|
||||
res = {"StringCount": 0.0, "StringMeanLength": 0.0, "StringEntropy": 0.0}
|
||||
res.update(dict.fromkeys(cls._STRING_KEYS, 0.0))
|
||||
|
||||
matches = cls._STRING_PATTERN.findall(file_bytes)
|
||||
if not matches:
|
||||
return res
|
||||
if len(file_bytes) <= cls._STRING_FAST_PATH_BYTES:
|
||||
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)
|
||||
combined_data = b''.join(matches)
|
||||
count = len(matches)
|
||||
total_len = len(combined_data)
|
||||
global_counts = np.bincount(np.frombuffer(combined_data, dtype=np.uint8), minlength=256)
|
||||
else:
|
||||
count = 0
|
||||
total_len = 0
|
||||
global_counts = np.zeros(256, dtype=np.int64)
|
||||
mv = memoryview(file_bytes)
|
||||
pos = 0
|
||||
size = len(file_bytes)
|
||||
|
||||
while pos < size:
|
||||
chunk_end = min(pos + cls._STRING_BATCH_BYTES, size)
|
||||
chunk_arr = np.frombuffer(mv[pos:chunk_end], dtype=np.uint8)
|
||||
separators = (chunk_arr < 0x20) | (chunk_arr > 0x7E)
|
||||
|
||||
if separators.any():
|
||||
last_separator = len(separators) - 1 - int(np.argmax(separators[::-1]))
|
||||
segment_end = pos + last_separator + 1
|
||||
matches = cls._STRING_PATTERN.findall(mv[pos:segment_end])
|
||||
|
||||
if matches:
|
||||
combined_data = b''.join(matches)
|
||||
count += len(matches)
|
||||
total_len += len(combined_data)
|
||||
global_counts += np.bincount(np.frombuffer(combined_data, dtype=np.uint8), minlength=256)
|
||||
|
||||
pos = segment_end
|
||||
continue
|
||||
|
||||
pending_len = 0
|
||||
pending_counts = np.zeros(256, dtype=np.int64)
|
||||
|
||||
while pos < size:
|
||||
chunk_end = min(pos + cls._STRING_BATCH_BYTES, size)
|
||||
chunk_arr = np.frombuffer(mv[pos:chunk_end], dtype=np.uint8)
|
||||
separators = (chunk_arr < 0x20) | (chunk_arr > 0x7E)
|
||||
|
||||
if separators.any():
|
||||
first_separator = int(np.argmax(separators))
|
||||
if first_separator:
|
||||
pending_counts += np.bincount(chunk_arr[:first_separator], minlength=256)
|
||||
pending_len += first_separator
|
||||
pos += first_separator + 1
|
||||
break
|
||||
|
||||
pending_counts += np.bincount(chunk_arr, minlength=256)
|
||||
pending_len += len(chunk_arr)
|
||||
pos = chunk_end
|
||||
|
||||
if pending_len >= 5:
|
||||
count += 1
|
||||
total_len += pending_len
|
||||
global_counts += pending_counts
|
||||
|
||||
if count == 0 or total_len == 0:
|
||||
return res
|
||||
|
||||
res["StringCount"] = float(count)
|
||||
res["StringMeanLength"] = float(total_len) / count
|
||||
res["StringEntropy"] = cls._calc_entropy(combined_data)
|
||||
|
||||
arr = np.frombuffer(combined_data, dtype=np.uint8)
|
||||
counts = np.bincount(arr, minlength=127)
|
||||
char_counts = counts[0x20:0x7F]
|
||||
counts_nonzero = global_counts[global_counts > 0]
|
||||
if len(counts_nonzero) > 0:
|
||||
res["StringEntropy"] = float(np.log2(total_len) - np.sum(counts_nonzero * np.log2(counts_nonzero)) / total_len)
|
||||
|
||||
valid_chars = global_counts[0x20:0x7F]
|
||||
res.update(dict(zip(cls._STRING_KEYS, valid_chars.astype(np.float64) / total_len)))
|
||||
|
||||
res.update(dict(zip(cls._STRING_KEYS, char_counts.astype(np.float64) / total_len)))
|
||||
return res
|
||||
|
||||
|
||||
|
||||
@classmethod
|
||||
def _extract_histograms(cls, file_bytes):
|
||||
arr = np.frombuffer(file_bytes, dtype=np.uint8)
|
||||
@@ -163,55 +280,64 @@ class FeatureExtractor:
|
||||
res = {}
|
||||
|
||||
if sz == 0:
|
||||
res['FileEntropy'] = 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.astype(np.float64) / sz
|
||||
res.update(dict(zip(cls._BYTE_HIST_KEYS, byte_hist)))
|
||||
|
||||
byte_counts = np.zeros(256, dtype=np.int64)
|
||||
ent_hist = np.zeros(256, dtype=np.float64)
|
||||
window = 2048
|
||||
step = 1024
|
||||
|
||||
if sz < window:
|
||||
w_counts = byte_counts
|
||||
p = w_counts[w_counts > 0] / float(sz)
|
||||
byte_counts += np.bincount(arr, minlength=256)
|
||||
p = byte_counts[byte_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)
|
||||
byte_bins = byte_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)
|
||||
|
||||
for w_start in range(0, num_windows, cls._HISTOGRAM_BATCH_WINDOWS):
|
||||
w_end = min(w_start + cls._HISTOGRAM_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)
|
||||
encoded = arr[start_byte:end_byte].astype(np.int32).reshape(b_num + 1, step)
|
||||
encoded += np.arange(b_num + 1, dtype=np.int32)[:, None] * 256
|
||||
|
||||
chunk_counts = np.bincount(encoded.ravel(), minlength=(b_num + 1) * 256).reshape(b_num + 1, 256)
|
||||
del encoded
|
||||
|
||||
byte_counts += np.sum(chunk_counts[:-1], axis=0)
|
||||
if w_end == num_windows:
|
||||
byte_counts += chunk_counts[-1]
|
||||
|
||||
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())
|
||||
|
||||
covered_size = (num_windows + 1) * step
|
||||
if covered_size < sz:
|
||||
byte_counts += np.bincount(arr[covered_size:], minlength=256)
|
||||
|
||||
counts_nonzero = byte_counts[byte_counts > 0]
|
||||
res['FileEntropy'] = float(np.log2(sz) - np.sum(counts_nonzero * np.log2(counts_nonzero)) / sz)
|
||||
|
||||
byte_hist = byte_counts.astype(np.float64) / sz
|
||||
res.update(dict(zip(cls._BYTE_HIST_KEYS, byte_hist)))
|
||||
|
||||
ent_sum = np.sum(ent_hist)
|
||||
if ent_sum > 0:
|
||||
ent_hist /= ent_sum
|
||||
@@ -219,6 +345,7 @@ class FeatureExtractor:
|
||||
res.update(dict(zip(cls._BYTE_ENT_KEYS, ent_hist)))
|
||||
return res
|
||||
|
||||
|
||||
@classmethod
|
||||
def _extract_overlay_features(cls, pe, fsize):
|
||||
overlay_offset = pe.get_overlay_data_start_offset()
|
||||
@@ -372,15 +499,19 @@ class FeatureExtractor:
|
||||
@classmethod
|
||||
def extract(cls, file_path):
|
||||
pe = None
|
||||
mm = None
|
||||
source_file = None
|
||||
|
||||
try:
|
||||
fsize = os.path.getsize(file_path)
|
||||
if fsize == 0 or fsize > MAX_FILE_SIZE:
|
||||
return None
|
||||
|
||||
with open(file_path, "rb") as f:
|
||||
file_bytes = f.read()
|
||||
source_file = open(file_path, "rb")
|
||||
mm = mmap.mmap(source_file.fileno(), 0, access=mmap.ACCESS_READ)
|
||||
file_bytes = mm
|
||||
|
||||
base, dlls, apis = {}, set(), set()
|
||||
base, dlls, apis = cls._base_defaults(), set(), set()
|
||||
pe = pefile.PE(data=file_bytes, fast_load=True)
|
||||
|
||||
try:
|
||||
@@ -397,7 +528,6 @@ class FeatureExtractor:
|
||||
pass
|
||||
|
||||
base['TrustSigned'] = float(WinTrust.verify(file_path))
|
||||
base['FileEntropy'] = cls._calc_entropy(file_bytes)
|
||||
base['FileSize'] = float(fsize)
|
||||
|
||||
fh = pe.FILE_HEADER
|
||||
@@ -420,7 +550,8 @@ class FeatureExtractor:
|
||||
if hasattr(pe, 'OPTIONAL_HEADER'):
|
||||
op = pe.OPTIONAL_HEADER
|
||||
fields = ['Magic', 'MajorLinkerVersion', 'MinorLinkerVersion', 'SizeOfCode', 'SizeOfInitializedData', 'SizeOfUninitializedData', 'AddressOfEntryPoint', 'BaseOfCode', 'ImageBase', 'SectionAlignment', 'FileAlignment', 'MajorOperatingSystemVersion', 'MinorOperatingSystemVersion', 'MajorImageVersion', 'MinorImageVersion', 'MajorSubsystemVersion', 'MinorSubsystemVersion', 'SizeOfImage', 'SizeOfHeaders', 'CheckSum', 'Subsystem', 'DllCharacteristics', 'SizeOfStackReserve', 'SizeOfStackCommit', 'SizeOfHeapReserve', 'SizeOfHeapCommit', 'LoaderFlags', 'NumberOfRvaAndSizes']
|
||||
for f in fields: base[f] = getattr(op, f, 0)
|
||||
for field_name in fields:
|
||||
base[field_name] = getattr(op, field_name, 0)
|
||||
base['IsDriver'] = 1.0 if base.get('Subsystem') == 1 else 0.0
|
||||
|
||||
if hasattr(op, 'DATA_DIRECTORY'):
|
||||
@@ -571,11 +702,24 @@ class FeatureExtractor:
|
||||
|
||||
return {"Base": base, "DLLs": list(dlls), "APIs": list(apis)}
|
||||
|
||||
except Exception:
|
||||
except pefile.PEFormatError:
|
||||
return None
|
||||
finally:
|
||||
if pe:
|
||||
pe.close()
|
||||
if pe is not None:
|
||||
try:
|
||||
pe.close()
|
||||
except Exception:
|
||||
pass
|
||||
if mm is not None:
|
||||
try:
|
||||
mm.close()
|
||||
except Exception:
|
||||
pass
|
||||
if source_file is not None:
|
||||
try:
|
||||
source_file.close()
|
||||
except Exception:
|
||||
pass
|
||||
|
||||
####################################################################################################
|
||||
|
||||
|
||||
@@ -58,15 +58,14 @@ def analyze_schema(jsonl_path):
|
||||
for line in f:
|
||||
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())
|
||||
try:
|
||||
data = JSON_LOADS(line)
|
||||
base_data = data.get('Base')
|
||||
if base_data:
|
||||
base_keys.update(base_data.keys())
|
||||
|
||||
except Exception:
|
||||
pass
|
||||
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)]
|
||||
|
||||
@@ -58,15 +58,14 @@ def analyze_schema(jsonl_path):
|
||||
for line in f:
|
||||
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())
|
||||
try:
|
||||
data = JSON_LOADS(line)
|
||||
base_data = data.get('Base')
|
||||
if base_data:
|
||||
base_keys.update(base_data.keys())
|
||||
|
||||
except Exception:
|
||||
pass
|
||||
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)]
|
||||
|
||||
@@ -648,9 +648,38 @@ select.hidden + .custom-select-wrapper {
|
||||
transition: opacity 0.4s ease, visibility 0.4s ease;
|
||||
}
|
||||
|
||||
#loading_overlay.fade-out {
|
||||
opacity: 0;
|
||||
visibility: hidden;
|
||||
#loading_overlay.fade-out {
|
||||
opacity: 0;
|
||||
visibility: hidden;
|
||||
}
|
||||
|
||||
#loading_overlay.startup-error {
|
||||
padding: 28px;
|
||||
background-color: #f5f5f7;
|
||||
color: #1d1d1f;
|
||||
}
|
||||
|
||||
.startup-error-box {
|
||||
max-width: 520px;
|
||||
padding: 24px;
|
||||
border: 1px solid rgba(0, 0, 0, 0.12);
|
||||
border-radius: 8px;
|
||||
background: #ffffff;
|
||||
box-shadow: 0 16px 40px rgba(0, 0, 0, 0.12);
|
||||
}
|
||||
|
||||
.startup-error-box h1 {
|
||||
margin: 0 0 10px;
|
||||
font-size: 18px;
|
||||
font-weight: 700;
|
||||
}
|
||||
|
||||
.startup-error-box p {
|
||||
margin: 0;
|
||||
font-size: 13px;
|
||||
line-height: 1.6;
|
||||
color: #515154;
|
||||
word-break: break-word;
|
||||
}
|
||||
|
||||
.spinner {
|
||||
|
||||
+1177
-1129
File diff suppressed because it is too large.
Load diff
@@ -6,7 +6,17 @@
|
||||
<title>PYAS Security</title>
|
||||
<link rel="stylesheet" href="/static/css/style.css">
|
||||
<script defer src="/static/js/i18n.js"></script>
|
||||
<script defer src="/static/js/main.js"></script>
|
||||
<script defer src="/static/js/main.js"></script>
|
||||
<script>
|
||||
window.addEventListener('DOMContentLoaded', function () {
|
||||
window.setTimeout(function () {
|
||||
var app = document.querySelector('.app-container');
|
||||
var overlay = document.getElementById('loading_overlay');
|
||||
if (app) app.classList.add('fade-in');
|
||||
if (overlay && !overlay.classList.contains('fade-out')) overlay.classList.add('fade-out');
|
||||
}, 15000);
|
||||
});
|
||||
</script>
|
||||
</head>
|
||||
<body>
|
||||
<div id="loading_overlay">
|
||||
@@ -309,7 +319,7 @@
|
||||
<div class="license-widget">
|
||||
<textarea class="license-text" readonly>87owo Publicly Available Source Code License Agreement
|
||||
|
||||
Last Updated: 2026-06-09
|
||||
Last Updated: 2026-07-18
|
||||
IMPORTANT NOTICE: THIS LICENSE IS "SOURCE AVAILABLE" AND IS NOT AN OPEN SOURCE LICENSE APPROVED BY THE OPEN SOURCE INITIATIVE (OSI). BY DOWNLOADING, ACCESSING, OR USING THE SOFTWARE, YOU AGREE TO BE BOUND BY THESE TERMS.
|
||||
|
||||
1. Definitions and Interpretation
|
||||
@@ -361,6 +371,7 @@ IMPORTANT NOTICE: THIS LICENSE IS "SOURCE AVAILABLE" AND IS NOT AN OPEN SOURCE L
|
||||
- 7.3. Scope of Collected Extensions: Sample collection targets potential executable and system binary formats, which explicitly include, but are not limited to, files with the following extensions: `.exe`, `.dll`, `.sys`, `.ocx`, `.scr`, `.efi`, `.acm`, `.ax`, `.cpl`, `.drv`, `.com`, `.mui`, `.pyd`, `.wfx`, `.api`, `.awx`, `.rll`, and `.winmd`.
|
||||
- 7.4. Public Disclosure of Threat Intelligence: You explicitly acknowledge and agree that the metadata, behavioral reports, and threat intelligence data generated from the cloud analysis of submitted samples may be publicly disclosed, published, or shared with the cybersecurity community for collaborative defense purposes.
|
||||
- 7.5. Opt-Out Mechanism: Cloud-based telemetry and sample submission are fully optional. Users who do not wish to participate in sample collection or cloud-assisted analysis may disable the corresponding cloud services at any time via the toggle switches provided within the Software’s settings interface.
|
||||
- 7.6. Driver Signature: Certain kernel-mode drivers distributed with this software may be digitally signed using code signing methods that are not certified by Microsoft Windows Hardware Quality Labs (WHQL). By installing or using this software, you acknowledge this and decide for yourself whether to install and trust such drivers on your system.
|
||||
|
||||
8. Warranty Disclaimer and Limitation of Liability
|
||||
|
||||
|
||||
+2
-1
@@ -1,6 +1,6 @@
|
||||
87owo Publicly Available Source Code License Agreement
|
||||
|
||||
Last Updated: 2026-06-09
|
||||
Last Updated: 2026-07-18
|
||||
IMPORTANT NOTICE: THIS LICENSE IS "SOURCE AVAILABLE" AND IS NOT AN OPEN SOURCE LICENSE APPROVED BY THE OPEN SOURCE INITIATIVE (OSI). BY DOWNLOADING, ACCESSING, OR USING THE SOFTWARE, YOU AGREE TO BE BOUND BY THESE TERMS.
|
||||
|
||||
1. Definitions and Interpretation
|
||||
@@ -52,6 +52,7 @@ IMPORTANT NOTICE: THIS LICENSE IS "SOURCE AVAILABLE" AND IS NOT AN OPEN SOURCE L
|
||||
- 7.3. Scope of Collected Extensions: Sample collection targets potential executable and system binary formats, which explicitly include, but are not limited to, files with the following extensions: `.exe`, `.dll`, `.sys`, `.ocx`, `.scr`, `.efi`, `.acm`, `.ax`, `.cpl`, `.drv`, `.com`, `.mui`, `.pyd`, `.wfx`, `.api`, `.awx`, `.rll`, and `.winmd`.
|
||||
- 7.4. Public Disclosure of Threat Intelligence: You explicitly acknowledge and agree that the metadata, behavioral reports, and threat intelligence data generated from the cloud analysis of submitted samples may be publicly disclosed, published, or shared with the cybersecurity community for collaborative defense purposes.
|
||||
- 7.5. Opt-Out Mechanism: Cloud-based telemetry and sample submission are fully optional. Users who do not wish to participate in sample collection or cloud-assisted analysis may disable the corresponding cloud services at any time via the toggle switches provided within the Software’s settings interface.
|
||||
- 7.6. Driver Signature: Certain kernel-mode drivers distributed with this software may be digitally signed using code signing methods that are not certified by Microsoft Windows Hardware Quality Labs (WHQL). By installing or using this software, you acknowledge this and decide for yourself whether to install and trust such drivers on your system.
|
||||
|
||||
8. Warranty Disclaimer and Limitation of Liability
|
||||
|
||||
|
||||
@@ -13,6 +13,12 @@ from PYAS_Protect import ProtectMixin
|
||||
from PYAS_Scanner import ScannerMixin
|
||||
from PYAS_Tools import COPYDATASTRUCT, FILE_NOTIFY_INFORMATION, FILTER_MESSAGE_HEADER, IO_COUNTERS, LUID, LUID_AND_ATTRIBUTES, MEMORY_BASIC_INFORMATION, MIB_TCPROW_OWNER_PID, POINT, PROCESSENTRY32W, PROCESS_BASIC_INFORMATION, PYAS_FULL_MESSAGE, PYAS_MESSAGE, PYAS_USER_MESSAGE, RECT, SERVICE_STATUS_PROCESS, SHQUERYRBINFO, TOKEN_PRIVILEGES, ToolsMixin, UNICODE_STRING
|
||||
|
||||
PYAS_WINDOW_TITLE = "PYAS Security"
|
||||
WM_COPYDATA = 0x004A
|
||||
SMTO_ABORTIFHUNG = 0x0002
|
||||
SMTO_ERRORONEXIT = 0x0020
|
||||
PYAS_SEND_MESSAGE_FLAGS = SMTO_ABORTIFHUNG | SMTO_ERRORONEXIT
|
||||
|
||||
####################################################################################################
|
||||
|
||||
class _MainMixin:
|
||||
@@ -31,48 +37,12 @@ class _MainMixin:
|
||||
self.init_windll()
|
||||
|
||||
if not self.check_singleton("PYAS_Security_Mutex"):
|
||||
hwnd = self.user32.FindWindowW(None, "PYAS Security")
|
||||
if hwnd:
|
||||
if "-quit" in self.args_pyas:
|
||||
try:
|
||||
cds = COPYDATASTRUCT()
|
||||
cds.dwData = 3
|
||||
cds.cbData = 0
|
||||
cds.lpData = None
|
||||
self.user32.SendMessageTimeoutW(hwnd, 0x004A, 0, ctypes.byref(cds), 0x0002, 3000, None)
|
||||
if self.forward_to_existing_instance():
|
||||
os._exit(0)
|
||||
|
||||
time.sleep(0.5)
|
||||
except Exception:
|
||||
pass
|
||||
|
||||
elif "-scan" in self.args_pyas:
|
||||
try:
|
||||
idx = self.args_pyas.index("-scan")
|
||||
target = self.args_pyas[idx+1]
|
||||
cds = COPYDATASTRUCT()
|
||||
cds.dwData = 1
|
||||
encoded = target.encode('utf-8')
|
||||
cds.cbData = len(encoded) + 1
|
||||
|
||||
buffer = ctypes.create_string_buffer(encoded)
|
||||
cds.lpData = ctypes.cast(buffer, ctypes.c_void_p)
|
||||
self.user32.SendMessageTimeoutW(hwnd, 0x004A, 0, ctypes.byref(cds), 0x0002, 3000, None)
|
||||
self.user32.SetForegroundWindow(hwnd)
|
||||
|
||||
except Exception:
|
||||
pass
|
||||
else:
|
||||
try:
|
||||
cds = COPYDATASTRUCT()
|
||||
cds.dwData = 2
|
||||
cds.cbData = 0
|
||||
cds.lpData = None
|
||||
self.user32.SendMessageTimeoutW(hwnd, 0x004A, 0, ctypes.byref(cds), 0x0002, 3000, None)
|
||||
self.user32.SetForegroundWindow(hwnd)
|
||||
|
||||
except Exception:
|
||||
pass
|
||||
os._exit(0)
|
||||
self.h_recovery_mutex = self.kernel32.CreateMutexW(None, False, "PYAS_Security_Recovery_Mutex")
|
||||
if ctypes.get_last_error() == 183:
|
||||
os._exit(0)
|
||||
|
||||
if "-quit" in self.args_pyas:
|
||||
os._exit(0)
|
||||
@@ -81,6 +51,68 @@ class _MainMixin:
|
||||
self.load_config()
|
||||
self.load_logs()
|
||||
|
||||
def find_existing_window(self, timeout=2.0):
|
||||
deadline = time.monotonic() + timeout
|
||||
while time.monotonic() < deadline:
|
||||
hwnd = self.user32.FindWindowW(None, PYAS_WINDOW_TITLE)
|
||||
if hwnd:
|
||||
return hwnd
|
||||
time.sleep(0.05)
|
||||
return None
|
||||
|
||||
def send_existing_window_message(self, hwnd, message_id, payload=None, timeout=1000):
|
||||
if not hwnd:
|
||||
return False
|
||||
|
||||
cds = COPYDATASTRUCT()
|
||||
cds.dwData = message_id
|
||||
buffer = None
|
||||
|
||||
if payload is not None:
|
||||
encoded = str(payload).encode('utf-8')
|
||||
buffer = ctypes.create_string_buffer(encoded + b'\x00')
|
||||
cds.cbData = len(encoded) + 1
|
||||
cds.lpData = ctypes.cast(buffer, ctypes.c_void_p)
|
||||
else:
|
||||
cds.cbData = 0
|
||||
cds.lpData = None
|
||||
|
||||
try:
|
||||
return bool(self.user32.SendMessageTimeoutW(hwnd, WM_COPYDATA, 0, ctypes.byref(cds), PYAS_SEND_MESSAGE_FLAGS, timeout, None))
|
||||
except Exception:
|
||||
return False
|
||||
|
||||
def forward_to_existing_instance(self):
|
||||
hwnd = self.find_existing_window()
|
||||
if not hwnd:
|
||||
return False
|
||||
|
||||
if "-quit" in self.args_pyas or "-driver-unload" in self.args_pyas or "-driver-uninstall" in self.args_pyas:
|
||||
return self.send_existing_window_message(hwnd, 3, timeout=1200)
|
||||
|
||||
if "-scan" in self.args_pyas:
|
||||
try:
|
||||
idx = self.args_pyas.index("-scan")
|
||||
target = self.args_pyas[idx + 1]
|
||||
except Exception:
|
||||
return False
|
||||
|
||||
if self.send_existing_window_message(hwnd, 1, target, timeout=1200):
|
||||
try:
|
||||
self.user32.SetForegroundWindow(hwnd)
|
||||
except Exception:
|
||||
pass
|
||||
return True
|
||||
return False
|
||||
|
||||
if self.send_existing_window_message(hwnd, 2, timeout=1200):
|
||||
try:
|
||||
self.user32.SetForegroundWindow(hwnd)
|
||||
except Exception:
|
||||
pass
|
||||
return True
|
||||
return False
|
||||
|
||||
def check_singleton(self, name):
|
||||
try:
|
||||
self.h_mutex = self.kernel32.CreateMutexW(None, False, name)
|
||||
@@ -225,7 +257,9 @@ class _MainMixin:
|
||||
self.kernel32.ReadProcessMemory.restype = ctypes.wintypes.BOOL
|
||||
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.QueryFullProcessImageNameW.argtypes = [ctypes.wintypes.HANDLE, ctypes.wintypes.DWORD, ctypes.wintypes.LPWSTR, ctypes.POINTER(ctypes.wintypes.DWORD)]
|
||||
self.kernel32.QueryDosDeviceW.restype = ctypes.wintypes.DWORD
|
||||
self.kernel32.QueryDosDeviceW.argtypes = [ctypes.wintypes.LPCWSTR, ctypes.wintypes.LPWSTR, ctypes.wintypes.DWORD]
|
||||
self.kernel32.GetProcessIoCounters.argtypes = [ctypes.wintypes.HANDLE, ctypes.POINTER(IO_COUNTERS)]
|
||||
self.kernel32.GetProcessIoCounters.restype = ctypes.wintypes.BOOL
|
||||
self.kernel32.VirtualQueryEx.argtypes = [ctypes.wintypes.HANDLE, ctypes.c_void_p, ctypes.POINTER(MEMORY_BASIC_INFORMATION), ctypes.c_size_t]
|
||||
@@ -254,12 +288,16 @@ class _MainMixin:
|
||||
self.driver_listener_thread = None
|
||||
self.engine_initialized = False
|
||||
self.scan_running = False
|
||||
self.scan_preparing = False
|
||||
self.scan_stop_requested = False
|
||||
self.scan_finished = False
|
||||
self.virus_lock = {}
|
||||
self.virus_results = []
|
||||
self.scan_count = 0
|
||||
self.scan_events = {}
|
||||
self.hash_cache = {}
|
||||
self.file_task_timers = {}
|
||||
self.file_task_generations = {}
|
||||
self.mbr_backup = {}
|
||||
self.cloud_pending = set()
|
||||
self.last_io_counters = {}
|
||||
@@ -270,13 +308,14 @@ class _MainMixin:
|
||||
self.lock_driver_unload = threading.Lock()
|
||||
self.driver_unload_worker = None
|
||||
self.driver_unload_result = None
|
||||
self.closing = False
|
||||
self.lock_update = threading.RLock()
|
||||
self.lock_proc = threading.RLock()
|
||||
self.lock_net = threading.RLock()
|
||||
self.lock_io = threading.RLock()
|
||||
|
||||
self.pyas_default = {
|
||||
"version": "3.6.2",
|
||||
"version": "3.6.4",
|
||||
"api_host": "https://pyas-security.com/",
|
||||
"api_key": "fBRZxYS1UxykM-qzNOlKOEl63WILzlvgNMn6QfsG6FXCAAIktCrOPTAfY5_hEyuZ",
|
||||
"suffix": [".exe", ".dll", ".sys", ".ocx", ".scr", ".efi", ".acm", ".ax", ".cpl", ".drv", ".com", ".mui", ".pyd", ".wfx", ".api", ".awx", ".rll", ".winmd"],
|
||||
@@ -438,6 +477,7 @@ class _MainMixin:
|
||||
self.manage_autostart(False)
|
||||
|
||||
elif key == "document_switch":
|
||||
self._cancel_pending_file_scans()
|
||||
with self.lock_file_ops:
|
||||
if getattr(self, 'h_dir_file', None):
|
||||
try:
|
||||
@@ -709,7 +749,7 @@ class _MainMixin:
|
||||
self._window.restore()
|
||||
self._window.show()
|
||||
|
||||
hwnd = self.user32.FindWindowW(None, "PYAS Security")
|
||||
hwnd = self.user32.FindWindowW(None, PYAS_WINDOW_TITLE)
|
||||
if hwnd:
|
||||
self.user32.SetForegroundWindow(hwnd)
|
||||
|
||||
@@ -722,7 +762,7 @@ class _MainMixin:
|
||||
if self._window:
|
||||
self._window.minimize()
|
||||
else:
|
||||
hwnd = self.user32.FindWindowW(None, "PYAS Security")
|
||||
hwnd = self.user32.FindWindowW(None, PYAS_WINDOW_TITLE)
|
||||
if hwnd:
|
||||
self.user32.ShowWindow(hwnd, 6)
|
||||
|
||||
@@ -730,11 +770,48 @@ class _MainMixin:
|
||||
if self._window:
|
||||
self._window.hide()
|
||||
|
||||
def destroy_window_with_timeout(self, window, timeout=1.0):
|
||||
if not window:
|
||||
return
|
||||
|
||||
done = threading.Event()
|
||||
|
||||
def destroy_window():
|
||||
try:
|
||||
window.destroy()
|
||||
except Exception:
|
||||
pass
|
||||
finally:
|
||||
done.set()
|
||||
|
||||
threading.Thread(target=destroy_window, daemon=True).start()
|
||||
done.wait(timeout)
|
||||
|
||||
def flush_logs_now(self):
|
||||
with self.lock_logs:
|
||||
if not getattr(self, 'logs_dirty', False):
|
||||
return
|
||||
|
||||
try:
|
||||
os.makedirs(os.path.dirname(self.file_log), exist_ok=True)
|
||||
with open(self.file_log, "w", encoding="utf-8") as f:
|
||||
json.dump(self.logs_data, f, indent=4, ensure_ascii=False)
|
||||
self.logs_dirty = False
|
||||
except Exception:
|
||||
pass
|
||||
|
||||
def close(self, *args, **kwargs):
|
||||
if getattr(self, 'closing', False):
|
||||
return True
|
||||
|
||||
self.closing = True
|
||||
|
||||
with self.lock_config:
|
||||
driver_enabled = self.pyas_config.get("driver_switch", False)
|
||||
|
||||
if driver_enabled and not self.stop_system_driver():
|
||||
driver_loaded = driver_enabled or self.check_system_driver()
|
||||
if driver_loaded and not self.stop_system_driver():
|
||||
self.closing = False
|
||||
self.write_log("WARN", "Driver Protection", detail="Controlled unload failed", success=False)
|
||||
if self._window:
|
||||
try:
|
||||
@@ -750,9 +827,11 @@ class _MainMixin:
|
||||
pass
|
||||
self.tray_icon = None
|
||||
|
||||
if self._window:
|
||||
window = self._window
|
||||
self._window = None
|
||||
if window:
|
||||
try:
|
||||
self._window.hide()
|
||||
window.hide()
|
||||
except Exception:
|
||||
pass
|
||||
|
||||
@@ -763,6 +842,8 @@ class _MainMixin:
|
||||
self.pyas_config["driver_switch"] = False
|
||||
self.pyas_config["network_switch"] = False
|
||||
|
||||
self._cancel_pending_file_tasks()
|
||||
|
||||
with self.lock_file_ops:
|
||||
if getattr(self, 'h_dir_file', None):
|
||||
try:
|
||||
@@ -793,24 +874,17 @@ class _MainMixin:
|
||||
pass
|
||||
self.suspended_procs.clear()
|
||||
|
||||
if hasattr(self, 'h_mutex') and self.h_mutex:
|
||||
try:
|
||||
self.kernel32.CloseHandle(self.h_mutex)
|
||||
except Exception:
|
||||
pass
|
||||
|
||||
if self._window:
|
||||
self._window.destroy()
|
||||
|
||||
with self.lock_logs:
|
||||
if getattr(self, 'logs_dirty', False):
|
||||
for mutex_name in ('h_mutex', 'h_recovery_mutex'):
|
||||
handle = getattr(self, mutex_name, None)
|
||||
if handle:
|
||||
try:
|
||||
os.makedirs(os.path.dirname(self.file_log), exist_ok=True)
|
||||
with open(self.file_log, "w", encoding="utf-8") as f:
|
||||
json.dump(self.logs_data, f, indent=4, ensure_ascii=False)
|
||||
|
||||
self.kernel32.CloseHandle(handle)
|
||||
except Exception:
|
||||
pass
|
||||
setattr(self, mutex_name, None)
|
||||
|
||||
self.flush_logs_now()
|
||||
self.destroy_window_with_timeout(window)
|
||||
os._exit(0)
|
||||
|
||||
def init_ui_ready(self):
|
||||
@@ -1018,6 +1092,33 @@ def get_base_path():
|
||||
|
||||
return os.path.dirname(os.path.abspath(__file__))
|
||||
|
||||
|
||||
def get_frontend_asset_errors():
|
||||
base_path = get_base_path()
|
||||
required_files = [
|
||||
os.path.join("Interface", "templates", "index.html"),
|
||||
os.path.join("Interface", "static", "css", "style.css"),
|
||||
os.path.join("Interface", "static", "js", "i18n.js"),
|
||||
os.path.join("Interface", "static", "js", "main.js"),
|
||||
]
|
||||
errors = []
|
||||
|
||||
for rel_path in required_files:
|
||||
abs_path = os.path.join(base_path, rel_path)
|
||||
if not os.path.isfile(abs_path):
|
||||
errors.append(f"Missing UI asset: {rel_path}")
|
||||
elif os.path.getsize(abs_path) <= 0:
|
||||
errors.append(f"Empty UI asset: {rel_path}")
|
||||
|
||||
return errors
|
||||
|
||||
|
||||
def show_startup_error(message):
|
||||
try:
|
||||
ctypes.windll.user32.MessageBoxW(None, message, PYAS_WINDOW_TITLE, 0x00000010)
|
||||
except Exception:
|
||||
pass
|
||||
|
||||
####################################################################################################
|
||||
|
||||
class NoCacheRequestHandler(SimpleHTTPRequestHandler):
|
||||
@@ -1041,7 +1142,14 @@ class WindowHook:
|
||||
def __init__(self, title, api_ref=None):
|
||||
self.title, self.api_ref = title, api_ref
|
||||
self.old_wndproc = None
|
||||
self.WM_DPICHANGED, self.WM_NCHITTEST, self.WM_COPYDATA, self.HTCAPTION, self.GWLP_WNDPROC = 0x02E0, 0x0084, 0x004A, 2, -4
|
||||
self.WM_DPICHANGED = 0x02E0
|
||||
self.WM_NCHITTEST = 0x0084
|
||||
self.WM_CLOSE = 0x0010
|
||||
self.WM_COPYDATA = WM_COPYDATA
|
||||
self.WM_SYSCOMMAND = 0x0112
|
||||
self.SC_CLOSE = 0xF060
|
||||
self.HTCAPTION = 2
|
||||
self.GWLP_WNDPROC = -4
|
||||
|
||||
class RECT(ctypes.Structure):
|
||||
_fields_ = [("left", ctypes.c_long), ("top", ctypes.c_long), ("right", ctypes.c_long), ("bottom", ctypes.c_long)]
|
||||
@@ -1052,6 +1160,7 @@ class WindowHook:
|
||||
self.user32 = ctypes.windll.user32
|
||||
self.user32.FindWindowW.argtypes, self.user32.FindWindowW.restype = [ctypes.c_wchar_p, ctypes.c_wchar_p], ctypes.wintypes.HWND
|
||||
self.user32.CallWindowProcW.argtypes, self.user32.CallWindowProcW.restype = [ctypes.c_void_p, ctypes.c_void_p, ctypes.c_uint, ctypes.c_void_p, ctypes.c_void_p], ctypes.c_void_p
|
||||
self.user32.DefWindowProcW.argtypes, self.user32.DefWindowProcW.restype = [ctypes.c_void_p, ctypes.c_uint, ctypes.c_void_p, ctypes.c_void_p], ctypes.c_void_p
|
||||
|
||||
if ctypes.sizeof(ctypes.c_void_p) == 8:
|
||||
self.SetWindowLong, self.GetWindowLong = self.user32.SetWindowLongPtrW, self.user32.GetWindowLongPtrW
|
||||
@@ -1063,6 +1172,9 @@ class WindowHook:
|
||||
self.new_wndproc_cb = self.WNDPROC(self.wndproc)
|
||||
|
||||
def hook(self):
|
||||
if self.old_wndproc:
|
||||
return
|
||||
|
||||
hwnd = self.user32.FindWindowW(None, self.title)
|
||||
if hwnd:
|
||||
self.old_wndproc = self.GetWindowLong(hwnd, self.GWLP_WNDPROC)
|
||||
@@ -1071,38 +1183,40 @@ class WindowHook:
|
||||
try:
|
||||
self.user32.ChangeWindowMessageFilterEx.argtypes = [ctypes.c_void_p, ctypes.c_uint, ctypes.c_uint, ctypes.c_void_p]
|
||||
self.user32.ChangeWindowMessageFilterEx(hwnd, self.WM_COPYDATA, 1, None)
|
||||
|
||||
except Exception:
|
||||
pass
|
||||
|
||||
def wndproc(self, hwnd, msg, wparam, lparam):
|
||||
if msg in [0x0010, 0x0002, 0x0012, 0x0212] or (msg == 0x0112 and (wparam & 0xFFF0) == 0xF060):
|
||||
return 0
|
||||
def call_default(self, hwnd, msg, wparam, lparam):
|
||||
if self.old_wndproc:
|
||||
return self.user32.CallWindowProcW(self.old_wndproc, hwnd, msg, wparam, lparam)
|
||||
return self.user32.DefWindowProcW(hwnd, msg, wparam, lparam)
|
||||
|
||||
def wndproc(self, hwnd, msg, wparam, lparam):
|
||||
if msg == self.WM_COPYDATA:
|
||||
try:
|
||||
cds = COPYDATASTRUCT.from_address(lparam)
|
||||
if cds.dwData == 1:
|
||||
path = ctypes.string_at(cds.lpData, cds.cbData).decode('utf-8').strip('\x00')
|
||||
|
||||
if self.api_ref:
|
||||
if self.api_ref._window:
|
||||
self.api_ref._window.restore();
|
||||
self.api_ref._window.show()
|
||||
|
||||
threading.Thread(target=self.api_ref.restore_from_tray, daemon=True).start()
|
||||
threading.Thread(target=self.api_ref.trigger_context_scan, args=(path,), daemon=True).start()
|
||||
|
||||
elif cds.dwData == 2 and self.api_ref and self.api_ref._window:
|
||||
self.api_ref._window.restore();
|
||||
self.api_ref._window.show()
|
||||
elif cds.dwData == 2 and self.api_ref:
|
||||
threading.Thread(target=self.api_ref.restore_from_tray, daemon=True).start()
|
||||
|
||||
elif cds.dwData == 3 and self.api_ref:
|
||||
self.api_ref.close()
|
||||
threading.Thread(target=self.api_ref.close, daemon=True).start()
|
||||
|
||||
except Exception:
|
||||
pass
|
||||
return 1
|
||||
|
||||
if msg == self.WM_CLOSE or (msg == self.WM_SYSCOMMAND and (wparam & 0xFFF0) == self.SC_CLOSE):
|
||||
if self.api_ref and not getattr(self.api_ref, 'closing', False):
|
||||
threading.Thread(target=self.api_ref.hide_window, daemon=True).start()
|
||||
return 0
|
||||
return self.call_default(hwnd, msg, wparam, lparam)
|
||||
|
||||
if msg == self.WM_NCHITTEST:
|
||||
x, y = lparam & 0xFFFF, (lparam >> 16) & 0xFFFF
|
||||
if x >= 32768:
|
||||
@@ -1119,11 +1233,10 @@ class WindowHook:
|
||||
try:
|
||||
rect = self.RECT.from_address(lparam)
|
||||
self.user32.SetWindowPos(hwnd, None, rect.left, rect.top, rect.right - rect.left, rect.bottom - rect.top, 0x0004 | 0x0010 | 0x0020)
|
||||
|
||||
except Exception:
|
||||
pass
|
||||
|
||||
return self.user32.CallWindowProcW(self.old_wndproc, hwnd, msg, wparam, lparam) if self.old_wndproc else self.user32.DefWindowProcW(hwnd, msg, wparam, lparam)
|
||||
return self.call_default(hwnd, msg, wparam, lparam)
|
||||
|
||||
####################################################################################################
|
||||
|
||||
@@ -1139,11 +1252,27 @@ def start_api(port_container, ready_event):
|
||||
ready_event.set()
|
||||
|
||||
if __name__ == "__main__":
|
||||
if "-driver-unload" in sys.argv or "-driver-uninstall" in sys.argv:
|
||||
controller = WindowAPI()
|
||||
if "-driver-uninstall" in sys.argv:
|
||||
success, delete_error = controller.uninstall_system_driver()
|
||||
if not success:
|
||||
controller.write_log("WARN", "Driver Service", detail=f"DeleteService failed: 0x{delete_error & 0xFFFFFFFF:08X}", success=False)
|
||||
else:
|
||||
success = controller.stop_system_driver()
|
||||
controller.flush_logs_now()
|
||||
os._exit(0 if success else 2)
|
||||
|
||||
hide_on_start = "-h" in sys.argv or "-hide" in sys.argv
|
||||
init_width, init_height = 980, 670
|
||||
|
||||
os.environ["WEBVIEW2_ADDITIONAL_BROWSER_ARGUMENTS"] = "--proxy-bypass-list=127.0.0.1,localhost"
|
||||
|
||||
frontend_errors = get_frontend_asset_errors()
|
||||
if frontend_errors:
|
||||
show_startup_error("PYAS UI files are incomplete.\n\n" + "\n".join(frontend_errors))
|
||||
os._exit(4)
|
||||
|
||||
port_container = []
|
||||
server_ready = threading.Event()
|
||||
api_thread = threading.Thread(target=start_api, args=(port_container, server_ready), daemon=True)
|
||||
@@ -1157,22 +1286,43 @@ if __name__ == "__main__":
|
||||
user32 = ctypes.windll.user32
|
||||
pos_x, pos_y = (user32.GetSystemMetrics(0) - init_width) // 2, (user32.GetSystemMetrics(1) - init_height) // 2
|
||||
|
||||
startup_url = f"http://127.0.0.1:{port_container[0]}/"
|
||||
window = webview.create_window(
|
||||
title="PYAS Security", url=f"http://127.0.0.1:{port_container[0]}/", width=init_width, height=init_height, x=pos_x, y=pos_y,
|
||||
title=PYAS_WINDOW_TITLE, url=startup_url, width=init_width, height=init_height, x=pos_x, y=pos_y,
|
||||
frameless=True, easy_drag=False, js_api=js_api, background_color='#e0e0e0', hidden=hide_on_start)
|
||||
|
||||
if platform.system() == "Windows":
|
||||
window_hook = WindowHook("PYAS Security", js_api)
|
||||
window_hook = WindowHook(PYAS_WINDOW_TITLE, js_api)
|
||||
window.events.shown += window_hook.hook
|
||||
|
||||
js_api.set_window(window)
|
||||
js_api.show_tray()
|
||||
|
||||
webview_ready_event = threading.Event()
|
||||
|
||||
def mark_webview_ready():
|
||||
webview_ready_event.set()
|
||||
|
||||
def bind_dnd():
|
||||
mark_webview_ready()
|
||||
try:
|
||||
window.dom.document.events.drop += DOMEventHandler(js_api.on_drop, True, True)
|
||||
except Exception:
|
||||
pass
|
||||
|
||||
def webview_startup_watchdog():
|
||||
if not hide_on_start and not webview_ready_event.wait(60.0):
|
||||
try:
|
||||
webbrowser.open(startup_url)
|
||||
except Exception:
|
||||
pass
|
||||
show_startup_error("PYAS WebView did not finish loading. The app was opened in your default browser as a fallback.")
|
||||
try:
|
||||
js_api.flush_logs_now()
|
||||
except Exception:
|
||||
pass
|
||||
os._exit(3)
|
||||
|
||||
window.events.loaded += bind_dnd
|
||||
threading.Thread(target=webview_startup_watchdog, daemon=True).start()
|
||||
webview.start()
|
||||
+153
-37
@@ -189,6 +189,9 @@ class rule_scanner:
|
||||
|
||||
class pe_scanner:
|
||||
_STRING_PATTERN = re.compile(b'[\x20-\x7E]{5,}')
|
||||
_STRING_FAST_PATH_BYTES = 4194304
|
||||
_STRING_BATCH_BYTES = 4194304
|
||||
_HISTOGRAM_BATCH_WINDOWS = 1024
|
||||
|
||||
_BYTE_HIST_KEYS = [f'ByteHist_{i:02X}' for i in range(256)]
|
||||
_BYTE_ENT_KEYS = [f'ByteEnt_{i:02X}' for i in range(256)]
|
||||
@@ -202,6 +205,49 @@ class pe_scanner:
|
||||
_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))
|
||||
|
||||
_OPTIONAL_HEADER_FIELDS = [
|
||||
'Magic', 'MajorLinkerVersion', 'MinorLinkerVersion', 'SizeOfCode',
|
||||
'SizeOfInitializedData', 'SizeOfUninitializedData', 'AddressOfEntryPoint',
|
||||
'BaseOfCode', 'ImageBase', 'SectionAlignment', 'FileAlignment',
|
||||
'MajorOperatingSystemVersion', 'MinorOperatingSystemVersion',
|
||||
'MajorImageVersion', 'MinorImageVersion', 'MajorSubsystemVersion',
|
||||
'MinorSubsystemVersion', 'SizeOfImage', 'SizeOfHeaders', 'CheckSum',
|
||||
'Subsystem', 'DllCharacteristics', 'SizeOfStackReserve', 'SizeOfStackCommit',
|
||||
'SizeOfHeapReserve', 'SizeOfHeapCommit', 'LoaderFlags', 'NumberOfRvaAndSizes'
|
||||
]
|
||||
|
||||
_SECTION_DEFAULTS = {
|
||||
'SectionCount': 0.0,
|
||||
'SectionMaxEntropy': 0.0,
|
||||
'SectionMinEntropy': 0.0,
|
||||
'SectionMeanEntropy': 0.0,
|
||||
'SectionMaxRawSize': 0.0,
|
||||
'SectionMinRawSize': 0.0,
|
||||
'SectionMeanRawSize': 0.0,
|
||||
'SectionMaxVSize': 0.0,
|
||||
'SectionMinVSize': 0.0,
|
||||
'SectionMeanVSize': 0.0,
|
||||
'ExecutableSections': 0.0,
|
||||
'WritableSections': 0.0,
|
||||
'ReadableSections': 0.0,
|
||||
'SectionException': 0.0
|
||||
}
|
||||
|
||||
_CONDITIONAL_DEFAULTS = {
|
||||
'IsDriver': 0.0,
|
||||
'HasTlsCallbacks': 0.0,
|
||||
'IsDebug': 0.0,
|
||||
'IsPreRelease': 0.0,
|
||||
'IsPatched': 0.0,
|
||||
'IsPrivateBuild': 0.0,
|
||||
'IsSpecialBuild': 0.0,
|
||||
'ImportCount': 0.0,
|
||||
'ImportFunctionCount': 0.0,
|
||||
'ExportCount': 0.0,
|
||||
'IconCount': 0.0,
|
||||
'DebugCount': 0.0
|
||||
}
|
||||
|
||||
def __init__(self):
|
||||
self.model = None
|
||||
self.input_name = None
|
||||
@@ -276,6 +322,17 @@ class pe_scanner:
|
||||
except Exception:
|
||||
return 0.0
|
||||
|
||||
def _base_defaults(self):
|
||||
base = dict.fromkeys(self._OPTIONAL_HEADER_FIELDS, 0.0)
|
||||
base.update(self._SECTION_DEFAULTS)
|
||||
base.update(self._CONDITIONAL_DEFAULTS)
|
||||
|
||||
for i in range(16):
|
||||
base[f'DataDirectory_{i}_Size'] = 0.0
|
||||
base[f'DataDirectory_{i}_VA'] = 0.0
|
||||
|
||||
return base
|
||||
|
||||
def _calc_entropy(self, data):
|
||||
if not data:
|
||||
return 0.0
|
||||
@@ -291,20 +348,69 @@ class pe_scanner:
|
||||
res = {"StringCount": 0.0, "StringMeanLength": 0.0, "StringEntropy": 0.0}
|
||||
res.update(dict.fromkeys(self._STRING_KEYS, 0.0))
|
||||
|
||||
total_len = 0
|
||||
count = 0
|
||||
global_counts = numpy.zeros(256, dtype=numpy.int64)
|
||||
mv = memoryview(file_bytes)
|
||||
if len(file_bytes) <= self._STRING_FAST_PATH_BYTES:
|
||||
matches = self._STRING_PATTERN.findall(file_bytes)
|
||||
if not matches:
|
||||
return res
|
||||
|
||||
for m in self._STRING_PATTERN.finditer(file_bytes):
|
||||
count += 1
|
||||
start, end = m.span()
|
||||
total_len += (end - start)
|
||||
arr = numpy.frombuffer(mv[start:end], dtype=numpy.uint8)
|
||||
global_counts += numpy.bincount(arr, minlength=256)
|
||||
combined_data = b''.join(matches)
|
||||
count = len(matches)
|
||||
total_len = len(combined_data)
|
||||
global_counts = numpy.bincount(numpy.frombuffer(combined_data, dtype=numpy.uint8), minlength=256)
|
||||
else:
|
||||
count = 0
|
||||
total_len = 0
|
||||
global_counts = numpy.zeros(256, dtype=numpy.int64)
|
||||
mv = memoryview(file_bytes)
|
||||
pos = 0
|
||||
size = len(file_bytes)
|
||||
|
||||
if count == 0 or total_len == 0:
|
||||
return res
|
||||
while pos < size:
|
||||
chunk_end = min(pos + self._STRING_BATCH_BYTES, size)
|
||||
chunk_arr = numpy.frombuffer(mv[pos:chunk_end], dtype=numpy.uint8)
|
||||
separators = (chunk_arr < 0x20) | (chunk_arr > 0x7E)
|
||||
|
||||
if separators.any():
|
||||
last_separator = len(separators) - 1 - int(numpy.argmax(separators[::-1]))
|
||||
segment_end = pos + last_separator + 1
|
||||
matches = self._STRING_PATTERN.findall(mv[pos:segment_end])
|
||||
|
||||
if matches:
|
||||
combined_data = b''.join(matches)
|
||||
count += len(matches)
|
||||
total_len += len(combined_data)
|
||||
global_counts += numpy.bincount(numpy.frombuffer(combined_data, dtype=numpy.uint8), minlength=256)
|
||||
|
||||
pos = segment_end
|
||||
continue
|
||||
|
||||
pending_len = 0
|
||||
pending_counts = numpy.zeros(256, dtype=numpy.int64)
|
||||
|
||||
while pos < size:
|
||||
chunk_end = min(pos + self._STRING_BATCH_BYTES, size)
|
||||
chunk_arr = numpy.frombuffer(mv[pos:chunk_end], dtype=numpy.uint8)
|
||||
separators = (chunk_arr < 0x20) | (chunk_arr > 0x7E)
|
||||
|
||||
if separators.any():
|
||||
first_separator = int(numpy.argmax(separators))
|
||||
if first_separator:
|
||||
pending_counts += numpy.bincount(chunk_arr[:first_separator], minlength=256)
|
||||
pending_len += first_separator
|
||||
pos += first_separator + 1
|
||||
break
|
||||
|
||||
pending_counts += numpy.bincount(chunk_arr, minlength=256)
|
||||
pending_len += len(chunk_arr)
|
||||
pos = chunk_end
|
||||
|
||||
if pending_len >= 5:
|
||||
count += 1
|
||||
total_len += pending_len
|
||||
global_counts += pending_counts
|
||||
|
||||
if count == 0 or total_len == 0:
|
||||
return res
|
||||
|
||||
res["StringCount"] = float(count)
|
||||
res["StringMeanLength"] = float(total_len) / count
|
||||
@@ -318,62 +424,72 @@ class pe_scanner:
|
||||
|
||||
return res
|
||||
|
||||
|
||||
|
||||
def _extract_histograms(self, file_bytes):
|
||||
arr = numpy.frombuffer(file_bytes, dtype=numpy.uint8)
|
||||
sz = len(arr)
|
||||
res = {}
|
||||
|
||||
if sz == 0:
|
||||
res['FileEntropy'] = 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.astype(numpy.float64) / sz
|
||||
res.update(dict(zip(self._BYTE_HIST_KEYS, byte_hist)))
|
||||
|
||||
byte_counts = numpy.zeros(256, dtype=numpy.int64)
|
||||
ent_hist = numpy.zeros(256, dtype=numpy.float64)
|
||||
window = 2048
|
||||
step = 1024
|
||||
|
||||
if sz < window:
|
||||
w_counts = byte_counts
|
||||
p = w_counts[w_counts > 0] / float(sz)
|
||||
byte_counts += numpy.bincount(arr, minlength=256)
|
||||
p = byte_counts[byte_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)
|
||||
byte_bins = byte_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)
|
||||
|
||||
for w_start in range(0, num_windows, self._HISTOGRAM_BATCH_WINDOWS):
|
||||
w_end = min(w_start + self._HISTOGRAM_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)
|
||||
encoded = arr[start_byte:end_byte].astype(numpy.int32).reshape(b_num + 1, step)
|
||||
encoded += numpy.arange(b_num + 1, dtype=numpy.int32)[:, None] * 256
|
||||
|
||||
chunk_counts = numpy.bincount(encoded.ravel(), minlength=(b_num + 1) * 256).reshape(b_num + 1, 256)
|
||||
del encoded
|
||||
|
||||
byte_counts += numpy.sum(chunk_counts[:-1], axis=0)
|
||||
if w_end == num_windows:
|
||||
byte_counts += chunk_counts[-1]
|
||||
|
||||
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())
|
||||
|
||||
covered_size = (num_windows + 1) * step
|
||||
if covered_size < sz:
|
||||
byte_counts += numpy.bincount(arr[covered_size:], minlength=256)
|
||||
|
||||
counts_nonzero = byte_counts[byte_counts > 0]
|
||||
res['FileEntropy'] = float(numpy.log2(sz) - numpy.sum(counts_nonzero * numpy.log2(counts_nonzero)) / sz)
|
||||
|
||||
byte_hist = byte_counts.astype(numpy.float64) / sz
|
||||
res.update(dict(zip(self._BYTE_HIST_KEYS, byte_hist)))
|
||||
|
||||
ent_sum = numpy.sum(ent_hist)
|
||||
if ent_sum > 0:
|
||||
ent_hist /= ent_sum
|
||||
@@ -381,6 +497,7 @@ class pe_scanner:
|
||||
res.update(dict(zip(self._BYTE_ENT_KEYS, ent_hist)))
|
||||
return res
|
||||
|
||||
|
||||
def _extract_overlay_features(self, pe, fsize):
|
||||
overlay_offset = pe.get_overlay_data_start_offset()
|
||||
if not overlay_offset or overlay_offset >= fsize:
|
||||
@@ -539,7 +656,7 @@ class pe_scanner:
|
||||
f = open(file_path, "rb")
|
||||
mm = mmap.mmap(f.fileno(), 0, access=mmap.ACCESS_READ)
|
||||
|
||||
base = {}
|
||||
base = self._base_defaults()
|
||||
dlls = set()
|
||||
apis = set()
|
||||
pe = pefile.PE(data=mm, fast_load=True)
|
||||
@@ -558,7 +675,6 @@ class pe_scanner:
|
||||
pass
|
||||
|
||||
base['TrustSigned'] = 1.0 if self.signer.sign_verify(file_path) else 0.0
|
||||
base['FileEntropy'] = self._calc_entropy(mm)
|
||||
base['FileSize'] = float(fsize)
|
||||
|
||||
fh = pe.FILE_HEADER
|
||||
|
||||
+6
-2
@@ -1,6 +1,6 @@
|
||||
#define AppId "{{a7d7bac3-93b8-4630-8308-c7a56bf7fdf4}"
|
||||
#define AppName "PYAS"
|
||||
#define AppVersion "3.6.2.0"
|
||||
#define AppVersion "3.6.4.0"
|
||||
#define AppPublisher "PYAS Security"
|
||||
#define AppURL "https://github.com/87owo/PYAS"
|
||||
#define AppExeName "PYAS.exe"
|
||||
@@ -59,7 +59,11 @@ Name: "install_webview2"; Description: "{cm:InstallWebView2}"; GroupDescription:
|
||||
Name: "install_vcredist"; Description: "{cm:InstallVCRedist}"; GroupDescription: "{cm:Dependencies}"
|
||||
|
||||
[Files]
|
||||
Source: "Payload\*"; DestDir: "{app}"; Flags: ignoreversion recursesubdirs createallsubdirs
|
||||
Source: "Payload\Engine\*"; DestDir: "{app}\Engine"; Flags: ignoreversion recursesubdirs createallsubdirs
|
||||
Source: "Payload\Interface\*"; DestDir: "{app}\Interface"; Flags: ignoreversion recursesubdirs createallsubdirs
|
||||
Source: "Payload\License\*"; DestDir: "{app}\License"; Flags: ignoreversion recursesubdirs createallsubdirs
|
||||
Source: "Payload\PYAS.exe"; DestDir: "{app}"; Flags: ignoreversion
|
||||
Source: "Payload\Plugins\*"; DestDir: "{app}\Plugins"; Flags: ignoreversion recursesubdirs createallsubdirs
|
||||
Source: "Redist\VC_redist.x64.exe"; DestDir: "{tmp}\PYAS_Redist"; Flags: deleteafterinstall ignoreversion
|
||||
Source: "Redist\MicrosoftEdgeWebview2Setup.exe"; DestDir: "{tmp}\PYAS_Redist"; Flags: deleteafterinstall ignoreversion
|
||||
|
||||
|
||||
+267
-84
@@ -7,8 +7,35 @@ FLT_PORT_FLAG_SYNC_HANDLE = 0x00000001
|
||||
HRESULT_IO_PENDING = 0x800703E5
|
||||
ERROR_OPERATION_ABORTED = 995
|
||||
ERROR_NOT_FOUND = 1168
|
||||
ERROR_SHARING_VIOLATION = 32
|
||||
ERROR_LOCK_VIOLATION = 33
|
||||
WAIT_OBJECT_0 = 0
|
||||
WAIT_TIMEOUT = 258
|
||||
INVALID_HANDLE_VALUE = ctypes.c_void_p(-1).value
|
||||
GENERIC_READ = 0x80000000
|
||||
FILE_SHARE_READ = 0x00000001
|
||||
OPEN_EXISTING = 3
|
||||
FILE_ATTRIBUTE_NORMAL = 0x00000080
|
||||
FILE_SCAN_DEBOUNCE_SECONDS = 0.35
|
||||
FILE_SCAN_RETRY_SECONDS = 0.25
|
||||
FILE_LOCK_RETRY_SECONDS = 0.5
|
||||
PYAS_CONNECTION_MAGIC = 0x53415950
|
||||
PYAS_CONNECTION_VERSION = 1
|
||||
PROCESS_TERMINATE = 0x0001
|
||||
PROCESS_VM_READ = 0x0010
|
||||
PROCESS_QUERY_INFORMATION = 0x0400
|
||||
PROCESS_SUSPEND_RESUME = 0x0800
|
||||
PROCESS_QUERY_LIMITED_INFORMATION = 0x1000
|
||||
PYAS_PROCESS_SCAN_ACCESS = PROCESS_TERMINATE | PROCESS_VM_READ | PROCESS_QUERY_INFORMATION | PROCESS_SUSPEND_RESUME | PROCESS_QUERY_LIMITED_INFORMATION
|
||||
PYAS_PROCESS_NETWORK_ACCESS = PROCESS_TERMINATE | PROCESS_QUERY_INFORMATION | PROCESS_QUERY_LIMITED_INFORMATION
|
||||
|
||||
class PYAS_CONNECTION_CONTEXT(ctypes.Structure):
|
||||
_fields_ = [
|
||||
("Size", ctypes.wintypes.ULONG),
|
||||
("Version", ctypes.wintypes.ULONG),
|
||||
("Magic", ctypes.wintypes.ULONG),
|
||||
("ProcessId", ctypes.wintypes.ULONG)
|
||||
]
|
||||
|
||||
class OVERLAPPED(ctypes.Structure):
|
||||
_fields_ = [
|
||||
@@ -77,75 +104,98 @@ class ProtectMixin:
|
||||
|
||||
####################################################################################################
|
||||
|
||||
def lock_file(self, target_path, lock, quiet=False):
|
||||
def _try_lock_file(self, file_path):
|
||||
fd = None
|
||||
try:
|
||||
fd = os.open(file_path, os.O_RDWR | os.O_BINARY)
|
||||
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_path] = (fd, lock_size)
|
||||
return True, None
|
||||
except Exception as e:
|
||||
if fd is not None:
|
||||
try:
|
||||
os.close(fd)
|
||||
except Exception:
|
||||
pass
|
||||
return False, e
|
||||
|
||||
def _queue_file_lock(self, file_path):
|
||||
self._schedule_file_task("lock", file_path, self._retry_file_lock, FILE_LOCK_RETRY_SECONDS)
|
||||
|
||||
def _retry_file_lock(self, file_path):
|
||||
if not os.path.isfile(file_path):
|
||||
return
|
||||
|
||||
with self.lock_file_ops:
|
||||
if lock:
|
||||
if not os.path.exists(target_path):
|
||||
return
|
||||
if file_path in self.virus_lock:
|
||||
return
|
||||
success, _ = self._try_lock_file(file_path)
|
||||
|
||||
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)
|
||||
if not success:
|
||||
self._queue_file_lock(file_path)
|
||||
|
||||
def lock_file(self, target_path, lock, quiet=False):
|
||||
if lock:
|
||||
if not os.path.exists(target_path):
|
||||
return False
|
||||
|
||||
paths_to_lock = []
|
||||
if os.path.isdir(target_path):
|
||||
for root, _, files in os.walk(target_path):
|
||||
for file_name in files:
|
||||
paths_to_lock.append(os.path.join(root, file_name))
|
||||
else:
|
||||
paths_to_lock.append(target_path)
|
||||
|
||||
failed_locks = []
|
||||
with self.lock_file_ops:
|
||||
for file_path in paths_to_lock:
|
||||
if file_path in self.virus_lock:
|
||||
continue
|
||||
|
||||
success = False
|
||||
last_error = None
|
||||
for _ in range(5):
|
||||
try:
|
||||
fd = os.open(file_path, os.O_RDWR | os.O_BINARY)
|
||||
try:
|
||||
try:
|
||||
size = os.path.getsize(file_path)
|
||||
except Exception:
|
||||
size = 1
|
||||
success, error = self._try_lock_file(file_path)
|
||||
if not success:
|
||||
failed_locks.append((file_path, error))
|
||||
|
||||
lock_size = size if size > 0 else 1
|
||||
msvcrt.locking(fd, msvcrt.LK_NBRLCK, lock_size)
|
||||
self.virus_lock[file_path] = (fd, lock_size)
|
||||
success = True
|
||||
break
|
||||
for file_path, error in failed_locks:
|
||||
self._queue_file_lock(file_path)
|
||||
if not quiet:
|
||||
self.write_log("INFO", "File Lock Deferred", source=file_path, detail=str(error))
|
||||
|
||||
except Exception as e:
|
||||
os.close(fd)
|
||||
raise e
|
||||
return not failed_locks
|
||||
|
||||
except Exception as e:
|
||||
last_error = e
|
||||
time.sleep(0.1)
|
||||
self._cancel_pending_file_tasks("lock", target_path)
|
||||
with self.lock_file_ops:
|
||||
target_norm = os.path.normcase(target_path)
|
||||
target_dir = target_norm + os.sep
|
||||
keys_to_unlock = []
|
||||
|
||||
if not success and not quiet:
|
||||
self.write_log("WARN", "lock_file", source=file_path, detail=str(last_error), 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 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]
|
||||
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:
|
||||
if not quiet:
|
||||
self.write_log("WARN", "unlock_file", source=file_path, detail=str(e), success=False)
|
||||
finally:
|
||||
try:
|
||||
msvcrt.locking(fd, msvcrt.LK_UNLCK, lock_size)
|
||||
except Exception as e:
|
||||
if not quiet:
|
||||
self.write_log("WARN", "unlock_file", source=file_path, detail=str(e), success=False)
|
||||
os.close(fd)
|
||||
except Exception:
|
||||
pass
|
||||
del self.virus_lock[file_path]
|
||||
|
||||
finally:
|
||||
try:
|
||||
os.close(fd)
|
||||
except Exception:
|
||||
pass
|
||||
del self.virus_lock[file_path]
|
||||
return True
|
||||
|
||||
def relock_file(self):
|
||||
while True:
|
||||
@@ -448,7 +498,7 @@ class ProtectMixin:
|
||||
self.exist_process = cur
|
||||
|
||||
for pid in new_pids:
|
||||
h = self.kernel32.OpenProcess(0x1F0FFF, False, pid)
|
||||
h = self.kernel32.OpenProcess(PYAS_PROCESS_SCAN_ACCESS, False, pid)
|
||||
if h:
|
||||
with self.lock_config:
|
||||
should_suspend = self.pyas_config.get("suspend_switch", True)
|
||||
@@ -476,7 +526,7 @@ class ProtectMixin:
|
||||
suspended = self.pyas_config.get("suspend_switch", True)
|
||||
|
||||
if not h:
|
||||
h = self.kernel32.OpenProcess(0x1F0FFF, False, pid)
|
||||
h = self.kernel32.OpenProcess(PYAS_PROCESS_SCAN_ACCESS, False, pid)
|
||||
if not h:
|
||||
return
|
||||
|
||||
@@ -578,7 +628,7 @@ class ProtectMixin:
|
||||
raw_path = self.device_path_to_drive(raw_path)
|
||||
|
||||
file_path = self.norm_path(raw_path)
|
||||
if file_path and file_path not in scanned_paths:
|
||||
if file_path and file_path not in scanned_paths and self.path_system not in file_path:
|
||||
|
||||
scanned_paths.add(file_path)
|
||||
file_path_lower = file_path.lower()
|
||||
@@ -648,7 +698,7 @@ class ProtectMixin:
|
||||
suffix = self.pyas_config.get("suffix", [])
|
||||
|
||||
if not ext_filter or os.path.splitext(file_path)[-1].lower() in suffix:
|
||||
self.protect_pool.submit(self.handle_new_file, file_path)
|
||||
self._queue_file_scan(file_path)
|
||||
|
||||
if notify.NextEntryOffset == 0:
|
||||
break
|
||||
@@ -666,33 +716,106 @@ class ProtectMixin:
|
||||
pass
|
||||
self.h_dir_file = None
|
||||
|
||||
def _cancel_pending_file_tasks(self, task_name=None, target_path=None):
|
||||
target_norm = os.path.normcase(target_path) if target_path else None
|
||||
target_dir = target_norm + os.sep if target_norm else None
|
||||
|
||||
with self.lock_file_ops:
|
||||
task_keys = []
|
||||
for task_key in self.file_task_timers:
|
||||
name, path_key = task_key
|
||||
if task_name and name != task_name:
|
||||
continue
|
||||
if target_norm and path_key != target_norm and not path_key.startswith(target_dir):
|
||||
continue
|
||||
task_keys.append(task_key)
|
||||
|
||||
timers = [self.file_task_timers.pop(task_key) for task_key in task_keys]
|
||||
for task_key in task_keys:
|
||||
self.file_task_generations.pop(task_key, None)
|
||||
|
||||
for timer in timers:
|
||||
timer.cancel()
|
||||
|
||||
def _cancel_pending_file_scans(self):
|
||||
self._cancel_pending_file_tasks("scan")
|
||||
|
||||
def _schedule_file_task(self, task_name, file_path, callback, delay):
|
||||
task_key = (task_name, os.path.normcase(file_path))
|
||||
|
||||
with self.lock_file_ops:
|
||||
previous_timer = self.file_task_timers.get(task_key)
|
||||
if previous_timer:
|
||||
previous_timer.cancel()
|
||||
|
||||
generation = self.file_task_generations.get(task_key, 0) + 1
|
||||
timer = threading.Timer(delay, self._run_file_task, args=(task_key, file_path, generation, callback))
|
||||
timer.daemon = True
|
||||
self.file_task_timers[task_key] = timer
|
||||
self.file_task_generations[task_key] = generation
|
||||
|
||||
timer.start()
|
||||
|
||||
def _run_file_task(self, task_key, file_path, generation, callback):
|
||||
with self.lock_file_ops:
|
||||
if self.file_task_generations.get(task_key) != generation:
|
||||
return
|
||||
if self.file_task_timers.get(task_key) is threading.current_thread():
|
||||
self.file_task_timers.pop(task_key, None)
|
||||
|
||||
try:
|
||||
callback(file_path)
|
||||
finally:
|
||||
with self.lock_file_ops:
|
||||
if self.file_task_generations.get(task_key) == generation:
|
||||
self.file_task_timers.pop(task_key, None)
|
||||
self.file_task_generations.pop(task_key, None)
|
||||
|
||||
def _queue_file_scan(self, file_path, delay=FILE_SCAN_DEBOUNCE_SECONDS):
|
||||
self._schedule_file_task("scan", file_path, self.handle_new_file, delay)
|
||||
|
||||
def _acquire_file_scan_gate(self, file_path):
|
||||
ctypes.set_last_error(0)
|
||||
handle = self.kernel32.CreateFileW(
|
||||
file_path,
|
||||
GENERIC_READ,
|
||||
FILE_SHARE_READ,
|
||||
None,
|
||||
OPEN_EXISTING,
|
||||
FILE_ATTRIBUTE_NORMAL,
|
||||
None
|
||||
)
|
||||
|
||||
if handle == INVALID_HANDLE_VALUE:
|
||||
return None, ctypes.get_last_error()
|
||||
|
||||
return handle, 0
|
||||
|
||||
def handle_new_file(self, file_path):
|
||||
try:
|
||||
for _ in range(5):
|
||||
try:
|
||||
fd = os.open(file_path, os.O_RDONLY | os.O_BINARY)
|
||||
os.close(fd)
|
||||
with self.lock_config:
|
||||
if not self.pyas_config.get("document_switch", False):
|
||||
return
|
||||
|
||||
size = os.path.getsize(file_path)
|
||||
if size > 0:
|
||||
break
|
||||
|
||||
except Exception:
|
||||
pass
|
||||
time.sleep(0.1)
|
||||
else:
|
||||
gate_handle, error = self._acquire_file_scan_gate(file_path)
|
||||
if not gate_handle:
|
||||
if error in (ERROR_SHARING_VIOLATION, ERROR_LOCK_VIOLATION):
|
||||
self._queue_file_scan(file_path, FILE_SCAN_RETRY_SECONDS)
|
||||
return
|
||||
|
||||
with self.lock_file_ops:
|
||||
if file_path in self.virus_lock: return
|
||||
try:
|
||||
with self.lock_file_ops:
|
||||
if file_path in self.virus_lock:
|
||||
return
|
||||
|
||||
result = self.safe_scan_engine(file_path)
|
||||
self.cloud_check(file_path)
|
||||
|
||||
if result:
|
||||
if self.manage_named_list("quarantine", [file_path], action="add", lock_func=self.lock_file) > 0:
|
||||
self.write_log("BLOCK", "File Block", source=file_path, file_hash=self.calc_file_hash(file_path))
|
||||
result = self.safe_scan_engine(file_path)
|
||||
self.cloud_check(file_path)
|
||||
|
||||
if result:
|
||||
if self.manage_named_list("quarantine", [file_path], action="add", lock_func=self.lock_file) > 0:
|
||||
self.write_log("BLOCK", "File Block", source=file_path, file_hash=self.calc_file_hash(file_path))
|
||||
finally:
|
||||
self.kernel32.CloseHandle(gate_handle)
|
||||
except Exception:
|
||||
pass
|
||||
|
||||
@@ -726,7 +849,7 @@ class ProtectMixin:
|
||||
def handle_new_connection(self, key):
|
||||
pid, remote_addr, remote_port = key
|
||||
try:
|
||||
h = self.kernel32.OpenProcess(0x1F0FFF, False, pid)
|
||||
h = self.kernel32.OpenProcess(PYAS_PROCESS_NETWORK_ACCESS, False, pid)
|
||||
if not h:
|
||||
return
|
||||
|
||||
@@ -940,6 +1063,46 @@ class ProtectMixin:
|
||||
self.advapi32.CloseServiceHandle(service)
|
||||
self.advapi32.CloseServiceHandle(scm)
|
||||
|
||||
def _get_driver_client_image_path(self):
|
||||
buffer = ctypes.create_unicode_buffer(32768)
|
||||
size = ctypes.wintypes.DWORD(len(buffer))
|
||||
if not self.kernel32.QueryFullProcessImageNameW(
|
||||
self.kernel32.GetCurrentProcess(),
|
||||
0,
|
||||
buffer,
|
||||
ctypes.byref(size)
|
||||
):
|
||||
return None
|
||||
|
||||
image_path = os.path.abspath(buffer.value)
|
||||
drive, tail = os.path.splitdrive(image_path)
|
||||
if not drive or not tail:
|
||||
return None
|
||||
|
||||
device_buffer = ctypes.create_unicode_buffer(32768)
|
||||
if not self.kernel32.QueryDosDeviceW(drive, device_buffer, len(device_buffer)):
|
||||
return None
|
||||
|
||||
return device_buffer.value.rstrip("\\") + tail
|
||||
|
||||
def _configure_driver_identity(self):
|
||||
image_path = self._get_driver_client_image_path()
|
||||
if not image_path:
|
||||
return False, ctypes.get_last_error() or 1
|
||||
|
||||
try:
|
||||
access = winreg.KEY_SET_VALUE | getattr(winreg, "KEY_WOW64_64KEY", 0)
|
||||
with winreg.CreateKeyEx(
|
||||
winreg.HKEY_LOCAL_MACHINE,
|
||||
r"SYSTEM\CurrentControlSet\Services\PYAS_Driver",
|
||||
0,
|
||||
access
|
||||
) as key:
|
||||
winreg.SetValueEx(key, "ClientImagePath", 0, winreg.REG_SZ, image_path)
|
||||
return True, 0
|
||||
except OSError as e:
|
||||
return False, int(getattr(e, "winerror", 0) or 1)
|
||||
|
||||
def _ensure_driver_service(self):
|
||||
desired_service_access = 0x0002 | 0x0004 | 0x0010 | 0x00010000
|
||||
dependencies = ctypes.create_unicode_buffer("FltMgr\0\0")
|
||||
@@ -1024,6 +1187,11 @@ class ProtectMixin:
|
||||
self.advapi32.CloseServiceHandle(service)
|
||||
self.advapi32.CloseServiceHandle(scm)
|
||||
|
||||
def uninstall_system_driver(self):
|
||||
if not self.stop_system_driver():
|
||||
return False, 1051
|
||||
return self._delete_driver_service()
|
||||
|
||||
def _delete_driver_service(self):
|
||||
scm = self.advapi32.OpenSCManagerW(None, None, 0x0001)
|
||||
if not scm:
|
||||
@@ -1062,6 +1230,11 @@ class ProtectMixin:
|
||||
self.write_log("WARN", "Driver Service", detail=f"CreateServiceW/ChangeServiceConfigW failed: 0x{service_error & 0xFFFFFFFF:08X}", success=False)
|
||||
return False
|
||||
|
||||
configured, identity_error = self._configure_driver_identity()
|
||||
if not configured:
|
||||
self.write_log("WARN", "Driver Identity", detail=f"ClientImagePath configuration failed: 0x{identity_error & 0xFFFFFFFF:08X}", success=False)
|
||||
return False
|
||||
|
||||
self._reg_write(winreg.HKEY_LOCAL_MACHINE, r"SYSTEM\CurrentControlSet\Services\PYAS_Driver\Instances", "DefaultInstance", winreg.REG_SZ, "PYAS Instance")
|
||||
self._reg_write(winreg.HKEY_LOCAL_MACHINE, r"SYSTEM\CurrentControlSet\Services\PYAS_Driver\Instances\PYAS Instance", "Altitude", winreg.REG_SZ, "320000")
|
||||
self._reg_write(winreg.HKEY_LOCAL_MACHINE, r"SYSTEM\CurrentControlSet\Services\PYAS_Driver\Instances\PYAS Instance", "Flags", winreg.REG_DWORD, 0)
|
||||
@@ -1120,12 +1293,18 @@ class ProtectMixin:
|
||||
return None
|
||||
|
||||
def _connect_driver_port(self, asynchronous=False):
|
||||
context = PYAS_CONNECTION_CONTEXT()
|
||||
context.Size = ctypes.sizeof(context)
|
||||
context.Version = PYAS_CONNECTION_VERSION
|
||||
context.Magic = PYAS_CONNECTION_MAGIC
|
||||
context.ProcessId = os.getpid()
|
||||
|
||||
temp_port = ctypes.wintypes.HANDLE()
|
||||
status = self.fltlib.FilterConnectCommunicationPort(
|
||||
"\\PYAS_Output_Pipe",
|
||||
0 if asynchronous else FLT_PORT_FLAG_SYNC_HANDLE,
|
||||
None,
|
||||
0,
|
||||
ctypes.byref(context),
|
||||
ctypes.sizeof(context),
|
||||
None,
|
||||
ctypes.byref(temp_port)
|
||||
)
|
||||
@@ -1526,8 +1705,12 @@ class ProtectMixin:
|
||||
self._resume_driver_listener()
|
||||
|
||||
def check_system_driver(self):
|
||||
state = self._query_driver_service_state()
|
||||
return state in (2, 3, 4)
|
||||
runtime_state = self._query_driver_state()
|
||||
if runtime_state in (1, 2, 3, 4):
|
||||
return True
|
||||
|
||||
service_state = self._query_driver_service_state()
|
||||
return service_state in (2, 3, 4)
|
||||
|
||||
def clear_driver_rules(self):
|
||||
with self.lock_driver:
|
||||
|
||||
+227
-96
@@ -159,25 +159,92 @@ class ScannerMixin:
|
||||
|
||||
####################################################################################################
|
||||
|
||||
def start_scan(self, targets):
|
||||
with self.lock_virus:
|
||||
if getattr(self, 'scan_running', False):
|
||||
return
|
||||
def _scan_result_messages(self, count=None, scanned=None, elapsed=None):
|
||||
if count is None or scanned is None or elapsed is None:
|
||||
with self.lock_virus:
|
||||
count = len(getattr(self, 'virus_results', [])) if count is None else count
|
||||
scanned = getattr(self, 'scan_count', 0) if scanned is None else scanned
|
||||
scan_start = getattr(self, 'scan_start', time.time())
|
||||
elapsed = int(time.time() - scan_start) if elapsed is None else elapsed
|
||||
|
||||
return {
|
||||
"traditional_switch": f"發現 {count} 個病毒,掃描 {scanned} 個檔案,耗時 {elapsed} 秒",
|
||||
"simplified_switch": f"发现 {count} 个病毒,扫描 {scanned} 个文件,耗时 {elapsed} 秒",
|
||||
"english_switch": f"Found {count} viruses, scanned {scanned} files, time {elapsed}s",
|
||||
"japanese_switch": f"{count} 個のウイルスを発見し、{scanned} 個のファイルをスキャンしました(所要時間 {elapsed} 秒)",
|
||||
"korean_switch": f"바이러스 {count}개 발견, 파일 {scanned}개 검사, 소요 시간 {elapsed}초",
|
||||
"french_switch": f"{count} virus trouvés, {scanned} fichiers analysés, temps {elapsed}s",
|
||||
"spanish_switch": f"Se encontraron {count} virus, {scanned} archivos escaneados, tiempo {elapsed}s",
|
||||
"hindi_switch": f"{count} वायरस मिले, {scanned} फ़ाइलें स्कैन की गईं, समय {elapsed}s",
|
||||
"arabic_switch": f"تم العثور على {count} فيروسات، تم فحص {scanned} ملفات، الوقت {elapsed} ثانية",
|
||||
"russian_switch": f"Найдено {count} вирусов, проверено {scanned} файлов, время {elapsed}с",
|
||||
"slovenian_switch": f"Najdenih {count} virusov, skeniranih {scanned} datotek, čas {elapsed}s"
|
||||
}
|
||||
|
||||
def _is_scan_cancel_requested(self):
|
||||
with self.lock_virus:
|
||||
return getattr(self, 'scan_stop_requested', False)
|
||||
|
||||
def _finish_scan_cancelled(self, messages=None):
|
||||
with self.lock_virus:
|
||||
count = len(getattr(self, 'virus_results', []))
|
||||
scanned = getattr(self, 'scan_count', 0)
|
||||
scan_start = getattr(self, 'scan_start', time.time())
|
||||
elapsed = int(time.time() - scan_start)
|
||||
self.scan_running = False
|
||||
self.scan_preparing = False
|
||||
self.scan_finished = True
|
||||
self.scan_stop_requested = False
|
||||
|
||||
messages = messages or self._scan_result_messages(count, scanned, elapsed)
|
||||
|
||||
if self._window:
|
||||
js_cmd = f"if(window.finishScan) window.finishScan({json.dumps(messages)}, {count});"
|
||||
self.ui_queue.put(js_cmd)
|
||||
|
||||
return False
|
||||
|
||||
def _begin_scan_preparation(self):
|
||||
with self.lock_virus:
|
||||
if getattr(self, 'scan_running', False) or getattr(self, 'scan_preparing', False):
|
||||
return False
|
||||
|
||||
self.scan_preparing = True
|
||||
self.scan_stop_requested = False
|
||||
self.scan_finished = False
|
||||
self.virus_results = []
|
||||
self.scan_count = 0
|
||||
self.scan_start = time.time()
|
||||
return True
|
||||
|
||||
def start_scan(self, targets, from_preparing=False):
|
||||
with self.lock_virus:
|
||||
if getattr(self, 'scan_running', False) or (getattr(self, 'scan_preparing', False) and not from_preparing):
|
||||
return False
|
||||
|
||||
if getattr(self, 'scan_stop_requested', False):
|
||||
self.scan_running = False
|
||||
self.scan_preparing = False
|
||||
self.scan_finished = True
|
||||
return False
|
||||
|
||||
self.scan_preparing = False
|
||||
self.scan_running = True
|
||||
self.scan_finished = False
|
||||
self.scan_stop_requested = False
|
||||
self.virus_results = []
|
||||
self.scan_count = 0
|
||||
self.scan_start = time.time()
|
||||
|
||||
self.scan_pool.submit(self.scan_worker, targets)
|
||||
return True
|
||||
|
||||
def scan_worker(self, targets):
|
||||
last_update = 0.0
|
||||
try:
|
||||
for file_path, is_explicit in self.yield_files(targets):
|
||||
with self.lock_virus:
|
||||
if not self.scan_running:
|
||||
if not self.scan_running or getattr(self, 'scan_stop_requested', False):
|
||||
break
|
||||
|
||||
norm_path = self.norm_path(file_path)
|
||||
@@ -203,6 +270,8 @@ class ScannerMixin:
|
||||
continue
|
||||
|
||||
with self.lock_virus:
|
||||
if not self.scan_running or getattr(self, 'scan_stop_requested', False):
|
||||
break
|
||||
self.scan_count += 1
|
||||
|
||||
with self.lock_config:
|
||||
@@ -213,6 +282,10 @@ class ScannerMixin:
|
||||
continue
|
||||
|
||||
result = self.safe_scan_engine(norm_path)
|
||||
with self.lock_virus:
|
||||
if not self.scan_running or getattr(self, 'scan_stop_requested', False):
|
||||
break
|
||||
|
||||
if result:
|
||||
with self.lock_virus:
|
||||
self.virus_results.append(norm_path)
|
||||
@@ -233,116 +306,178 @@ class ScannerMixin:
|
||||
|
||||
finally:
|
||||
with self.lock_virus:
|
||||
cancelled = getattr(self, 'scan_stop_requested', False)
|
||||
self.scan_running = False
|
||||
self.scan_preparing = False
|
||||
self.scan_finished = True
|
||||
self.scan_stop_requested = False
|
||||
count, scanned, elapsed = len(self.virus_results), self.scan_count, int(time.time() - self.scan_start)
|
||||
|
||||
messages = {
|
||||
"traditional_switch": f"發現 {count} 個病毒,掃描 {scanned} 個檔案,耗時 {elapsed} 秒",
|
||||
"simplified_switch": f"发现 {count} 个病毒,扫描 {scanned} 个文件,耗时 {elapsed} 秒",
|
||||
"english_switch": f"Found {count} viruses, scanned {scanned} files, time {elapsed}s",
|
||||
"japanese_switch": f"{count} 個のウイルスを発見し、{scanned} 個のファイルをスキャンしました(所要時間 {elapsed} 秒)",
|
||||
"korean_switch": f"바이러스 {count}개 발견, 파일 {scanned}개 검사, 소요 시간 {elapsed}초",
|
||||
"french_switch": f"{count} virus trouvés, {scanned} fichiers analysés, temps {elapsed}s",
|
||||
"spanish_switch": f"Se encontraron {count} virus, {scanned} archivos escaneados, tiempo {elapsed}s",
|
||||
"hindi_switch": f"{count} वायरस मिले, {scanned} फ़ाइलें स्कैन की गईं, समय {elapsed}s",
|
||||
"arabic_switch": f"تم العثور على {count} فيروسات، تم فحص {scanned} ملفات، الوقت {elapsed} ثانية",
|
||||
"russian_switch": f"Найдено {count} вирусов, проверено {scanned} файлов, время {elapsed}с",
|
||||
"slovenian_switch": f"Najdenih {count} virusov, skeniranih {scanned} datotek, čas {elapsed}s"
|
||||
}
|
||||
messages = self._scan_result_messages(count, scanned, elapsed)
|
||||
log_detail = f"Found {count} viruses, scanned {scanned} files, time {elapsed}s"
|
||||
|
||||
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")
|
||||
self.write_log("INFO", "Scan Completed", detail=log_detail)
|
||||
|
||||
####################################################################################################
|
||||
|
||||
def stop_scan(self):
|
||||
with self.lock_virus: self.scan_running = False
|
||||
with self.lock_virus:
|
||||
active = getattr(self, 'scan_running', False) or getattr(self, 'scan_preparing', False)
|
||||
if active:
|
||||
self.scan_stop_requested = True
|
||||
self.scan_running = False
|
||||
return active
|
||||
|
||||
def trigger_scan(self, method):
|
||||
messages = {
|
||||
"traditional_switch": "已取消掃描",
|
||||
"simplified_switch": "已取消扫描", "english_switch":
|
||||
"Scan cancelled", "japanese_switch": "スキャンがキャンセルされました",
|
||||
"korean_switch": "스캔이 취소되었습니다",
|
||||
"french_switch": "Analyse annulée",
|
||||
"spanish_switch": "Escaneo cancelado",
|
||||
"hindi_switch": "स्कैन रद्द कर दिया गया",
|
||||
"arabic_switch": "تم إلغاء الفحص",
|
||||
"russian_switch": "Сканирование отменено",
|
||||
"slovenian_switch": "Skeniranje preklicano"
|
||||
}
|
||||
targets = []
|
||||
|
||||
if method == "smart":
|
||||
for folder in ["Desktop", "Downloads", "AppData"]:
|
||||
fp = os.path.join(self.path_user, folder)
|
||||
if os.path.exists(fp):
|
||||
targets.append(fp)
|
||||
if not self._begin_scan_preparation():
|
||||
return False
|
||||
|
||||
if os.path.exists(self.path_config):
|
||||
targets.append(self.path_config)
|
||||
try:
|
||||
if method == "smart":
|
||||
for folder in ["Desktop", "Downloads", "AppData"]:
|
||||
if self._is_scan_cancel_requested():
|
||||
return self._finish_scan_cancelled()
|
||||
|
||||
buf = ctypes.create_unicode_buffer(1024)
|
||||
max_address = 0x7FFFFFFFFFFF if ctypes.sizeof(ctypes.c_void_p) == 8 else 0x7FFFFFFF
|
||||
system_dir = self.path_system.lower()
|
||||
fp = os.path.join(self.path_user, folder)
|
||||
if os.path.exists(fp):
|
||||
targets.append(fp)
|
||||
|
||||
for proc in self.get_process_list():
|
||||
if proc["path"] and proc["path"] != "None":
|
||||
targets.append(proc["path"])
|
||||
if self._is_scan_cancel_requested():
|
||||
return self._finish_scan_cancelled()
|
||||
|
||||
pid = proc["pid"]
|
||||
if pid <= 4:
|
||||
continue
|
||||
if os.path.exists(self.path_config):
|
||||
targets.append(self.path_config)
|
||||
|
||||
h_process = self.kernel32.OpenProcess(0x1000, False, pid)
|
||||
if h_process:
|
||||
try:
|
||||
address = 0
|
||||
mbi = MEMORY_BASIC_INFORMATION()
|
||||
while address < max_address and self.kernel32.VirtualQueryEx(h_process, ctypes.c_void_p(address), ctypes.byref(mbi), ctypes.sizeof(mbi)):
|
||||
if mbi.State == 0x1000 and mbi.Type == 0x1000000:
|
||||
if self.psapi.GetMappedFileNameW(h_process, ctypes.c_void_p(address), buf, 1024):
|
||||
raw_path = buf.value
|
||||
if raw_path.startswith("\\"):
|
||||
raw_path = self.device_path_to_drive(raw_path)
|
||||
buf = ctypes.create_unicode_buffer(1024)
|
||||
max_address = 0x7FFFFFFFFFFF if ctypes.sizeof(ctypes.c_void_p) == 8 else 0x7FFFFFFF
|
||||
system_dir = self.path_system.lower()
|
||||
|
||||
file_path = self.norm_path(raw_path)
|
||||
if file_path and not file_path.lower().startswith(system_dir):
|
||||
targets.append(file_path)
|
||||
for proc in self.get_process_list():
|
||||
if self._is_scan_cancel_requested():
|
||||
return self._finish_scan_cancelled()
|
||||
|
||||
if mbi.RegionSize == 0:
|
||||
break
|
||||
address += mbi.RegionSize
|
||||
finally:
|
||||
self.kernel32.CloseHandle(h_process)
|
||||
if proc["path"] and proc["path"] != "None":
|
||||
targets.append(proc["path"])
|
||||
|
||||
self.start_scan(list(set(targets)))
|
||||
pid = proc["pid"]
|
||||
if pid <= 4:
|
||||
continue
|
||||
|
||||
elif method == "file":
|
||||
targets = self.select_files()
|
||||
if targets:
|
||||
self.start_scan(targets)
|
||||
h_process = self.kernel32.OpenProcess(0x1000, False, pid)
|
||||
if h_process:
|
||||
try:
|
||||
address = 0
|
||||
mbi = MEMORY_BASIC_INFORMATION()
|
||||
while address < max_address and not self._is_scan_cancel_requested() and self.kernel32.VirtualQueryEx(h_process, ctypes.c_void_p(address), ctypes.byref(mbi), ctypes.sizeof(mbi)):
|
||||
if mbi.State == 0x1000 and mbi.Type == 0x1000000:
|
||||
if self.psapi.GetMappedFileNameW(h_process, ctypes.c_void_p(address), buf, 1024):
|
||||
raw_path = buf.value
|
||||
if raw_path.startswith("\\"):
|
||||
raw_path = self.device_path_to_drive(raw_path)
|
||||
|
||||
elif self._window:
|
||||
self._window.evaluate_js(f"if(window.finishScan) window.finishScan({json.dumps(messages)}, 0);")
|
||||
file_path = self.norm_path(raw_path)
|
||||
if file_path and not file_path.lower().startswith(system_dir):
|
||||
targets.append(file_path)
|
||||
|
||||
elif method == "path":
|
||||
targets = self.select_folder()
|
||||
if targets:
|
||||
self.start_scan(targets)
|
||||
if mbi.RegionSize == 0:
|
||||
break
|
||||
address += mbi.RegionSize
|
||||
finally:
|
||||
self.kernel32.CloseHandle(h_process)
|
||||
|
||||
elif self._window:
|
||||
self._window.evaluate_js(f"if(window.finishScan) window.finishScan({json.dumps(messages)}, 0);")
|
||||
if self._is_scan_cancel_requested():
|
||||
return self._finish_scan_cancelled()
|
||||
|
||||
elif method == "full":
|
||||
targets = [f"{chr(d)}:/" for d in range(65, 91) if os.path.exists(f"{chr(d)}:/")]
|
||||
self.start_scan(targets)
|
||||
if not self.start_scan(list(set(targets)), from_preparing=True):
|
||||
return self._finish_scan_cancelled()
|
||||
|
||||
return True
|
||||
|
||||
elif method == "file":
|
||||
targets = self.select_files()
|
||||
if self._is_scan_cancel_requested():
|
||||
return self._finish_scan_cancelled()
|
||||
|
||||
if targets:
|
||||
if not self.start_scan(targets, from_preparing=True):
|
||||
return self._finish_scan_cancelled()
|
||||
return True
|
||||
|
||||
return self._finish_scan_cancelled()
|
||||
|
||||
elif method == "path":
|
||||
targets = self.select_folder()
|
||||
if self._is_scan_cancel_requested():
|
||||
return self._finish_scan_cancelled()
|
||||
|
||||
if targets:
|
||||
if not self.start_scan(targets, from_preparing=True):
|
||||
return self._finish_scan_cancelled()
|
||||
return True
|
||||
|
||||
return self._finish_scan_cancelled()
|
||||
|
||||
elif method == "full":
|
||||
targets = [f"{chr(d)}:/" for d in range(65, 91) if os.path.exists(f"{chr(d)}:/")]
|
||||
if self._is_scan_cancel_requested() or not self.start_scan(targets, from_preparing=True):
|
||||
return self._finish_scan_cancelled()
|
||||
return True
|
||||
|
||||
return self._finish_scan_cancelled()
|
||||
|
||||
except Exception as e:
|
||||
self.write_log("WARN", "trigger_scan", detail=str(e), success=False)
|
||||
return self._finish_scan_cancelled()
|
||||
|
||||
####################################################################################################
|
||||
|
||||
def _queue_virus_delete(self, file_path):
|
||||
self._schedule_file_task("delete", file_path, self._retry_virus_delete, 0.5)
|
||||
|
||||
def _retry_virus_delete(self, file_path):
|
||||
file_hash = self.calc_file_hash(file_path)
|
||||
try:
|
||||
with self.lock_file_ops:
|
||||
if file_path in self.virus_lock:
|
||||
self.lock_file(file_path, False)
|
||||
|
||||
target_key = os.path.normcase(self.norm_path(file_path, must_exist=False) or file_path)
|
||||
for proc in self.get_process_list():
|
||||
proc_path = proc.get("path")
|
||||
if not proc_path or proc_path == "None":
|
||||
continue
|
||||
proc_key = os.path.normcase(self.norm_path(proc_path, must_exist=False) or proc_path)
|
||||
if proc_key == target_key:
|
||||
self.kill_process(proc["pid"])
|
||||
|
||||
try:
|
||||
os.chmod(file_path, stat.S_IWRITE)
|
||||
except Exception:
|
||||
pass
|
||||
os.remove(file_path)
|
||||
except FileNotFoundError:
|
||||
pass
|
||||
except Exception as e:
|
||||
self.lock_file(file_path, True, quiet=True)
|
||||
self._queue_virus_delete(file_path)
|
||||
self.write_log("INFO", "Virus Delete Deferred", source=file_path, detail=str(e))
|
||||
return
|
||||
|
||||
self.remove_list_items("quarantine", [file_path])
|
||||
with self.lock_virus:
|
||||
target_key = os.path.normcase(self.norm_path(file_path, must_exist=False) or file_path)
|
||||
self.virus_results = [
|
||||
result for result in self.virus_results
|
||||
if os.path.normcase(self.norm_path(result, must_exist=False) or result) != target_key
|
||||
]
|
||||
self.write_log("INFO", "Virus Delete", source=file_path, file_hash=file_hash, operate=True, success=True)
|
||||
|
||||
def solve_scan(self, file_paths):
|
||||
deleted_paths = []
|
||||
proc_map = {}
|
||||
@@ -383,23 +518,19 @@ class ScannerMixin:
|
||||
except Exception:
|
||||
pass
|
||||
|
||||
delete_success = False
|
||||
for _ in range(5):
|
||||
try:
|
||||
os.remove(path)
|
||||
delete_success = True
|
||||
break
|
||||
|
||||
except Exception:
|
||||
time.sleep(0.1)
|
||||
|
||||
if not delete_success:
|
||||
file_hash = self.calc_file_hash(path)
|
||||
try:
|
||||
os.remove(path)
|
||||
except Exception as e:
|
||||
self.lock_file(path, True, quiet=True)
|
||||
self._queue_virus_delete(path)
|
||||
self.write_log("INFO", "Virus Delete Deferred", source=path, detail=str(e))
|
||||
continue
|
||||
|
||||
self.remove_list_items("quarantine", [path])
|
||||
deleted_paths.append(raw_path)
|
||||
deleted_paths.append(raw_path)
|
||||
deleted_set.add(path)
|
||||
self.write_log("INFO", "Virus Delete", source=path, file_hash=self.calc_file_hash(path), operate=True, success=True)
|
||||
self.write_log("INFO", "Virus Delete", source=path, file_hash=file_hash, operate=True, success=True)
|
||||
|
||||
except Exception as e:
|
||||
self.lock_file(path, True)
|
||||
|
||||
+6
-1
@@ -4,6 +4,11 @@ import ctypes, ctypes.wintypes
|
||||
|
||||
from concurrent.futures import ThreadPoolExecutor
|
||||
|
||||
PROCESS_TERMINATE = 0x0001
|
||||
PROCESS_QUERY_INFORMATION = 0x0400
|
||||
PROCESS_QUERY_LIMITED_INFORMATION = 0x1000
|
||||
PYAS_PROCESS_TERMINATE_ACCESS = PROCESS_TERMINATE | PROCESS_QUERY_INFORMATION | PROCESS_QUERY_LIMITED_INFORMATION
|
||||
|
||||
####################################################################################################
|
||||
|
||||
class PROCESSENTRY32W(ctypes.Structure):
|
||||
@@ -657,7 +662,7 @@ class ToolsMixin:
|
||||
|
||||
def kill_process(self, pid, expected_path=None):
|
||||
try:
|
||||
h = self.kernel32.OpenProcess(0x1F0FFF, False, pid)
|
||||
h = self.kernel32.OpenProcess(PYAS_PROCESS_TERMINATE_ACCESS, False, pid)
|
||||
if h:
|
||||
try:
|
||||
file_path = self.norm_path(self.get_process_file(h))
|
||||
|
||||
+1
-1
@@ -2,7 +2,7 @@ import pyinstaller_versionfile
|
||||
|
||||
pyinstaller_versionfile.create_versionfile(
|
||||
output_file="versionfile.txt",
|
||||
version='3.6.2.0',
|
||||
version='3.6.4.0',
|
||||
company_name="PYAS Security",
|
||||
file_description="PYAS Security Antivirus",
|
||||
internal_name="PYAS",
|
||||
|
||||
@@ -8,7 +8,9 @@
|
||||
constexpr auto PYAS_PORT_NAME = L"\\PYAS_Output_Pipe";
|
||||
constexpr auto MAX_PATH_LEN = 1024;
|
||||
constexpr auto PYAS_POOL_TAG = 'SAYP';
|
||||
constexpr auto PYAS_OB_ALTITUDE = L"320000.4101";
|
||||
constexpr auto PYAS_OB_ALTITUDE = L"320000.4101";
|
||||
constexpr auto PYAS_CONNECTION_MAGIC = 0x53415950;
|
||||
constexpr auto PYAS_CONNECTION_VERSION = 1;
|
||||
|
||||
#ifndef PROCESS_TERMINATE
|
||||
#define PROCESS_TERMINATE (0x0001)
|
||||
@@ -130,7 +132,14 @@ typedef enum _PYAS_COMMAND {
|
||||
typedef struct _PYAS_USER_MESSAGE {
|
||||
ULONG Command;
|
||||
WCHAR Path[MAX_PATH_LEN];
|
||||
} PYAS_USER_MESSAGE, * PPYAS_USER_MESSAGE;
|
||||
} PYAS_USER_MESSAGE, * PPYAS_USER_MESSAGE;
|
||||
|
||||
typedef struct _PYAS_CONNECTION_CONTEXT {
|
||||
ULONG Size;
|
||||
ULONG Version;
|
||||
ULONG Magic;
|
||||
ULONG ProcessId;
|
||||
} PYAS_CONNECTION_CONTEXT, * PPYAS_CONNECTION_CONTEXT;
|
||||
|
||||
typedef struct _DRIVER_DATA {
|
||||
PDRIVER_OBJECT DriverObject;
|
||||
@@ -150,7 +159,9 @@ typedef struct _DRIVER_DATA {
|
||||
volatile LONG ClientCloseActive;
|
||||
EX_RUNDOWN_REF PortRundown;
|
||||
KEVENT CleanupCompleteEvent;
|
||||
PVOID ObRegistrationHandle;
|
||||
PVOID ObRegistrationHandle;
|
||||
UNICODE_STRING ClientImagePath;
|
||||
WCHAR ClientImagePathBuffer[MAX_PATH_LEN];
|
||||
} DRIVER_DATA, * PDRIVER_DATA;
|
||||
|
||||
typedef struct _RULE_NODE {
|
||||
|
||||
@@ -49,6 +49,99 @@ static NTSTATUS SelectFailure(NTSTATUS CurrentStatus, NTSTATUS CandidateStatus)
|
||||
return CandidateStatus;
|
||||
}
|
||||
|
||||
static NTSTATUS LoadClientIdentityFromRegistry(PUNICODE_STRING RegistryPath) {
|
||||
if (!RegistryPath || !RegistryPath->Buffer || !GlobalData.ClientImagePath.Buffer) return STATUS_INVALID_PARAMETER;
|
||||
|
||||
UNICODE_STRING ValueName = RTL_CONSTANT_STRING(L"ClientImagePath");
|
||||
OBJECT_ATTRIBUTES ObjectAttributes;
|
||||
InitializeObjectAttributes(&ObjectAttributes, RegistryPath, OBJ_CASE_INSENSITIVE | OBJ_KERNEL_HANDLE, NULL, NULL);
|
||||
|
||||
HANDLE KeyHandle = NULL;
|
||||
NTSTATUS Status = ZwOpenKey(&KeyHandle, KEY_READ, &ObjectAttributes);
|
||||
if (!NT_SUCCESS(Status)) return Status;
|
||||
|
||||
ULONG ResultLength = 0;
|
||||
Status = ZwQueryValueKey(KeyHandle, &ValueName, KeyValuePartialInformation, NULL, 0, &ResultLength);
|
||||
if (Status != STATUS_BUFFER_TOO_SMALL && Status != STATUS_BUFFER_OVERFLOW) {
|
||||
ZwClose(KeyHandle);
|
||||
return Status;
|
||||
}
|
||||
|
||||
if (ResultLength == 0 || ResultLength > FIELD_OFFSET(KEY_VALUE_PARTIAL_INFORMATION, Data) + GlobalData.ClientImagePath.MaximumLength) {
|
||||
ZwClose(KeyHandle);
|
||||
return STATUS_NAME_TOO_LONG;
|
||||
}
|
||||
|
||||
PKEY_VALUE_PARTIAL_INFORMATION Information = (PKEY_VALUE_PARTIAL_INFORMATION)PyasAllocate(ResultLength);
|
||||
if (!Information) {
|
||||
ZwClose(KeyHandle);
|
||||
return STATUS_INSUFFICIENT_RESOURCES;
|
||||
}
|
||||
|
||||
Status = ZwQueryValueKey(KeyHandle, &ValueName, KeyValuePartialInformation, Information, ResultLength, &ResultLength);
|
||||
ZwClose(KeyHandle);
|
||||
|
||||
if (NT_SUCCESS(Status)) {
|
||||
if ((Information->Type != REG_SZ && Information->Type != REG_EXPAND_SZ) ||
|
||||
Information->DataLength < sizeof(WCHAR) ||
|
||||
(Information->DataLength % sizeof(WCHAR)) != 0 ||
|
||||
Information->DataLength > GlobalData.ClientImagePath.MaximumLength - sizeof(WCHAR)) {
|
||||
Status = STATUS_INVALID_PARAMETER;
|
||||
}
|
||||
else {
|
||||
RtlZeroMemory(GlobalData.ClientImagePath.Buffer, GlobalData.ClientImagePath.MaximumLength);
|
||||
RtlCopyMemory(GlobalData.ClientImagePath.Buffer, Information->Data, Information->DataLength);
|
||||
|
||||
USHORT CharacterCount = (USHORT)(Information->DataLength / sizeof(WCHAR));
|
||||
while (CharacterCount > 0 && GlobalData.ClientImagePath.Buffer[CharacterCount - 1] == L'\0') {
|
||||
CharacterCount--;
|
||||
}
|
||||
|
||||
if (CharacterCount == 0) {
|
||||
Status = STATUS_INVALID_PARAMETER;
|
||||
}
|
||||
else {
|
||||
GlobalData.ClientImagePath.Buffer[CharacterCount] = L'\0';
|
||||
GlobalData.ClientImagePath.Length = CharacterCount * sizeof(WCHAR);
|
||||
Status = STATUS_SUCCESS;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
PyasFree(Information);
|
||||
return Status;
|
||||
}
|
||||
|
||||
static BOOLEAN IsAuthorizedClientConnection(PVOID ConnectionContext, ULONG SizeOfContext) {
|
||||
if (!ConnectionContext || SizeOfContext < sizeof(PYAS_CONNECTION_CONTEXT)) return FALSE;
|
||||
if (!GlobalData.ClientImagePath.Buffer || GlobalData.ClientImagePath.Length == 0) return FALSE;
|
||||
if (KeGetCurrentIrql() != PASSIVE_LEVEL) return FALSE;
|
||||
|
||||
PYAS_CONNECTION_CONTEXT Context = { 0 };
|
||||
__try {
|
||||
RtlCopyMemory(&Context, ConnectionContext, sizeof(Context));
|
||||
}
|
||||
__except (EXCEPTION_EXECUTE_HANDLER) {
|
||||
return FALSE;
|
||||
}
|
||||
|
||||
if (Context.Size < sizeof(PYAS_CONNECTION_CONTEXT)) return FALSE;
|
||||
if (Context.Version != PYAS_CONNECTION_VERSION) return FALSE;
|
||||
if (Context.Magic != PYAS_CONNECTION_MAGIC) return FALSE;
|
||||
if (Context.ProcessId != (ULONG)(ULONG_PTR)PsGetCurrentProcessId()) return FALSE;
|
||||
|
||||
PUNICODE_STRING ImagePath = NULL;
|
||||
NTSTATUS Status = SeLocateProcessImageName(PsGetCurrentProcess(), &ImagePath);
|
||||
if (!NT_SUCCESS(Status) || !ImagePath || !ImagePath->Buffer) {
|
||||
if (ImagePath) ExFreePool(ImagePath);
|
||||
return FALSE;
|
||||
}
|
||||
|
||||
BOOLEAN Authorized = RtlEqualUnicodeString(ImagePath, &GlobalData.ClientImagePath, TRUE);
|
||||
ExFreePool(ImagePath);
|
||||
return Authorized;
|
||||
}
|
||||
|
||||
static NTSTATUS UnregisterRuntimeCallbacksPass(BOOLEAN WaitWithoutTimeout) {
|
||||
NTSTATUS Status = STATUS_SUCCESS;
|
||||
|
||||
@@ -434,10 +527,9 @@ static NTSTATUS PortConnect(
|
||||
PVOID* ConnectionPortCookie
|
||||
) {
|
||||
UNREFERENCED_PARAMETER(ServerPortCookie);
|
||||
UNREFERENCED_PARAMETER(ConnectionContext);
|
||||
UNREFERENCED_PARAMETER(SizeOfContext);
|
||||
|
||||
if (!ConnectionPortCookie) return STATUS_INVALID_PARAMETER;
|
||||
if (!IsAuthorizedClientConnection(ConnectionContext, SizeOfContext)) return STATUS_ACCESS_DENIED;
|
||||
*ConnectionPortCookie = NULL;
|
||||
|
||||
if (InterlockedCompareExchange(&GlobalData.PortStopping, 0, 0) != 0) {
|
||||
@@ -544,6 +636,8 @@ extern "C" NTSTATUS DriverEntry(
|
||||
) {
|
||||
RtlZeroMemory(&GlobalData, sizeof(GlobalData));
|
||||
GlobalData.DriverObject = DriverObject;
|
||||
GlobalData.ClientImagePath.Buffer = GlobalData.ClientImagePathBuffer;
|
||||
GlobalData.ClientImagePath.MaximumLength = sizeof(GlobalData.ClientImagePathBuffer);
|
||||
KeInitializeSpinLock(&GlobalData.PortMutex);
|
||||
KeInitializeSpinLock(&GlobalData.TrackerMutex);
|
||||
KeInitializeSpinLock(&GlobalData.PidLock);
|
||||
@@ -567,6 +661,9 @@ extern "C" NTSTATUS DriverEntry(
|
||||
Status = LoadRulesFromDisk(RegistryPath);
|
||||
if (!NT_SUCCESS(Status)) goto Failure;
|
||||
|
||||
Status = LoadClientIdentityFromRegistry(RegistryPath);
|
||||
if (!NT_SUCCESS(Status)) goto Failure;
|
||||
|
||||
Status = FltRegisterFilter(DriverObject, &FilterRegistration, &GlobalData.FilterHandle);
|
||||
if (!NT_SUCCESS(Status)) goto Failure;
|
||||
|
||||
|
||||
@@ -1,16 +1,29 @@
|
||||
{
|
||||
"DynamicRules": [
|
||||
{
|
||||
"Name": "Process_Protect_Antivirus_Command",
|
||||
"Name": "Self_Antivirus_Protect_Command",
|
||||
"Code": 1001,
|
||||
"Kill": false,
|
||||
"Category": "Process",
|
||||
"Initiator": [
|
||||
"\\Device\\HarddiskVolume*\\Windows\\System32\\cmd.exe",
|
||||
"\\Device\\HarddiskVolume*\\Windows\\SysWOW64\\cmd.exe",
|
||||
"\\Device\\HarddiskVolume*\\Windows\\System32\\WindowsPowerShell\\*\\powershell.exe",
|
||||
"\\Device\\HarddiskVolume*\\Windows\\SysWOW64\\WindowsPowerShell\\*\\powershell.exe",
|
||||
"\\Device\\HarddiskVolume*\\Program Files\\PowerShell\\*\\pwsh.exe",
|
||||
"\\Device\\HarddiskVolume*\\Windows\\System32\\taskkill.exe",
|
||||
"\\Device\\HarddiskVolume*\\Windows\\System32\\taskmgr.exe",
|
||||
"\\Device\\HarddiskVolume*\\Program Files (x86)\\PowerShell\\*\\pwsh.exe",
|
||||
"\\Device\\HarddiskVolume*\\Windows\\System32\\wscript.exe",
|
||||
"\\Device\\HarddiskVolume*\\Windows\\SysWOW64\\wscript.exe",
|
||||
"\\Device\\HarddiskVolume*\\Windows\\System32\\cscript.exe",
|
||||
"\\Device\\HarddiskVolume*\\Windows\\SysWOW64\\cscript.exe",
|
||||
"\\Device\\HarddiskVolume*\\Windows\\System32\\mshta.exe",
|
||||
"\\Device\\HarddiskVolume*\\Windows\\SysWOW64\\mshta.exe",
|
||||
"\\Device\\HarddiskVolume*\\Windows\\System32\\wsl.exe",
|
||||
"\\Device\\HarddiskVolume*\\Windows\\System32\\bash.exe",
|
||||
"\\Device\\HarddiskVolume*\\Windows\\System32\\rundll32.exe",
|
||||
"\\Device\\HarddiskVolume*\\Windows\\SysWOW64\\rundll32.exe",
|
||||
"\\Device\\HarddiskVolume*\\Windows\\System32\\regsvr32.exe",
|
||||
"\\Device\\HarddiskVolume*\\Windows\\SysWOW64\\regsvr32.exe",
|
||||
],
|
||||
"InitiatorExclude": [
|
||||
"*\\PYAS.exe",
|
||||
@@ -18,6 +31,14 @@
|
||||
"CommandLine": [
|
||||
"*taskkill*PYAS.exe*",
|
||||
"*taskkill*msedgewebview2.exe*",
|
||||
"*Stop-Process*PYAS*",
|
||||
"*Stop-Process*msedgewebview2*",
|
||||
"*Get-Process*PYAS*Stop-Process*",
|
||||
"*Get-Process*msedgewebview2*Stop-Process*",
|
||||
"*wmic*PYAS.exe*call terminate*",
|
||||
"*wmic*PYAS.exe*delete*",
|
||||
"*wmic*msedgewebview2.exe*call terminate*",
|
||||
"*wmic*msedgewebview2.exe*delete*",
|
||||
],
|
||||
"Operations": [
|
||||
"Execute",
|
||||
@@ -28,27 +49,33 @@
|
||||
"*\\powershell.exe",
|
||||
"*\\pwsh.exe",
|
||||
"*\\wmic.exe",
|
||||
"*\\net.exe",
|
||||
"*\\net1.exe",
|
||||
],
|
||||
"Priority": 110,
|
||||
"FileOpenNameAvailable": true,
|
||||
"SubsystemProcess": false,
|
||||
},
|
||||
{
|
||||
"Name": "Process_Protect_PYAS_Handle",
|
||||
"Name": "Self_Antivirus_Protect_Handle",
|
||||
"Code": 1002,
|
||||
"Kill": false,
|
||||
"Category": "Process",
|
||||
"InitiatorExclude": [
|
||||
"*\\PYAS.exe",
|
||||
"*\\PYAS_Setup.exe",
|
||||
"*\\unins000.exe",
|
||||
"\\Device\\HarddiskVolume*\\Users\\*\\AppData\\Local\\Temp\\*\\PYAS_Setup.tmp",
|
||||
"\\Device\\HarddiskVolume*\\Users\\*\\AppData\\Local\\Temp\\*\\_unins.tmp",
|
||||
"\\Device\\HarddiskVolume*\\Windows\\explorer.exe",
|
||||
"\\Device\\HarddiskVolume*\\Windows\\System32\\csrss.exe",
|
||||
"\\Device\\HarddiskVolume*\\Windows\\System32\\lsass.exe",
|
||||
"\\Device\\HarddiskVolume*\\Windows\\System32\\services.exe",
|
||||
"\\Device\\HarddiskVolume*\\Windows\\System32\\svchost.exe",
|
||||
"\\Device\\HarddiskVolume*\\Windows\\System32\\msiexec.exe",
|
||||
"\\Device\\HarddiskVolume*\\Windows\\SystemApps\\*\\SearchHost.exe",
|
||||
"\\Device\\HarddiskVolume*\\Program Files\\*",
|
||||
"\\Device\\HarddiskVolume*\\Program Files (x86)\\*",
|
||||
"*\\games\\Arknights Game\\Arknights.exe",
|
||||
"*\\games\\EndField Game\\EndField.exe",
|
||||
],
|
||||
"Target": [
|
||||
"*\\PYAS.exe",
|
||||
@@ -81,7 +108,245 @@
|
||||
]
|
||||
},
|
||||
{
|
||||
"Name": "File_Protect_Antivirus",
|
||||
"Name": "Attack_Command_Block_EFI_Mount",
|
||||
"Code": 1003,
|
||||
"Kill": false,
|
||||
"Category": "Process",
|
||||
"Target": [
|
||||
"*\\mountvol.exe",
|
||||
],
|
||||
"CommandLine": [
|
||||
"*mountvol* /S*",
|
||||
],
|
||||
"Operations": [
|
||||
"Execute",
|
||||
],
|
||||
"Priority": 170,
|
||||
"FileOpenNameAvailable": true,
|
||||
},
|
||||
{
|
||||
"Name": "Attack_Command_Block_Destructive_Cmd_Delete",
|
||||
"Code": 1004,
|
||||
"Kill": true,
|
||||
"Category": "Process",
|
||||
"Target": [
|
||||
"*\\cmd.exe",
|
||||
],
|
||||
"CommandLine": [
|
||||
"*rd /s /q C:\\*",
|
||||
"*rmdir /s /q C:\\*",
|
||||
"*del X:\\* /s /f /q*",
|
||||
"*rd /s /q X:\\EFI*",
|
||||
],
|
||||
"Operations": [
|
||||
"Execute",
|
||||
],
|
||||
"Priority": 190,
|
||||
"FileOpenNameAvailable": true,
|
||||
},
|
||||
{
|
||||
"Name": "Attack_Command_Block_Forced_OOBE_Sysprep",
|
||||
"Code": 1005,
|
||||
"Kill": false,
|
||||
"Category": "Process",
|
||||
"Target": [
|
||||
"*\\sysprep.exe",
|
||||
],
|
||||
"CommandLine": [
|
||||
"*sysprep* /oobe*",
|
||||
"*sysprep*/oobe*",
|
||||
"* /oobe* /reboot*",
|
||||
"* /generalize* /oobe*",
|
||||
],
|
||||
"Operations": [
|
||||
"Execute",
|
||||
],
|
||||
"Priority": 185,
|
||||
"FileOpenNameAvailable": true,
|
||||
},
|
||||
{
|
||||
"Name": "Process_Protect_UserWritable_SystemName_Dll_Load",
|
||||
"Code": 1006,
|
||||
"Kill": false,
|
||||
"Category": "Process",
|
||||
"Initiator": [
|
||||
"\\Device\\HarddiskVolume*\\Users\\*\\AppData\\*\\svchost.exe",
|
||||
"\\Device\\HarddiskVolume*\\Users\\*\\AppData\\*\\rundll32.exe",
|
||||
"\\Device\\HarddiskVolume*\\Users\\*\\AppData\\*\\regsvr32.exe",
|
||||
"\\Device\\HarddiskVolume*\\Users\\*\\AppData\\*\\dllhost.exe",
|
||||
"\\Device\\HarddiskVolume*\\Users\\*\\AppData\\*\\msiexec.exe",
|
||||
"\\Device\\HarddiskVolume*\\Users\\*\\AppData\\*\\taskhostw.exe",
|
||||
"\\Device\\HarddiskVolume*\\Users\\*\\AppData\\*\\spoolsv.exe",
|
||||
"\\Device\\HarddiskVolume*\\Users\\*\\Downloads\\*\\svchost.exe",
|
||||
"\\Device\\HarddiskVolume*\\Users\\*\\Downloads\\*\\rundll32.exe",
|
||||
"\\Device\\HarddiskVolume*\\Users\\*\\Downloads\\*\\regsvr32.exe",
|
||||
"\\Device\\HarddiskVolume*\\Users\\*\\Desktop\\*\\svchost.exe",
|
||||
"\\Device\\HarddiskVolume*\\Users\\*\\Desktop\\*\\rundll32.exe",
|
||||
"\\Device\\HarddiskVolume*\\Users\\*\\Desktop\\*\\regsvr32.exe",
|
||||
"\\Device\\HarddiskVolume*\\ProgramData\\*\\svchost.exe",
|
||||
"\\Device\\HarddiskVolume*\\ProgramData\\*\\rundll32.exe",
|
||||
"\\Device\\HarddiskVolume*\\ProgramData\\*\\regsvr32.exe",
|
||||
],
|
||||
"Target": [
|
||||
"\\Device\\HarddiskVolume*\\Users\\*\\AppData\\*\\*.dll",
|
||||
"\\Device\\HarddiskVolume*\\Users\\*\\Downloads\\*\\*.dll",
|
||||
"\\Device\\HarddiskVolume*\\Users\\*\\Desktop\\*\\*.dll",
|
||||
"\\Device\\HarddiskVolume*\\ProgramData\\*\\*.dll",
|
||||
],
|
||||
"Operations": [
|
||||
"ImageLoad",
|
||||
],
|
||||
"Priority": 160,
|
||||
},
|
||||
{
|
||||
"Name": "Memory_Protect_Critical_Process_CreateRemoteThread",
|
||||
"Code": 1007,
|
||||
"Kill": false,
|
||||
"Category": "Memory",
|
||||
"InitiatorExclude": [
|
||||
"\\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\\*",
|
||||
"*\\Huorong\\Sysdiag\\bin\\HipsDaemon.exe",
|
||||
"*\\360Safe.exe",
|
||||
"*\\PYAS.exe",
|
||||
],
|
||||
"Target": [
|
||||
"\\Device\\HarddiskVolume*\\Windows\\System32\\lsass.exe",
|
||||
"\\Device\\HarddiskVolume*\\Windows\\System32\\winlogon.exe",
|
||||
"\\Device\\HarddiskVolume*\\Windows\\System32\\services.exe",
|
||||
"\\Device\\HarddiskVolume*\\Windows\\System32\\csrss.exe",
|
||||
],
|
||||
"Operations": [
|
||||
"WriteMemory",
|
||||
"VmOperation",
|
||||
"CreateThread",
|
||||
],
|
||||
"OperationMatch": "All",
|
||||
"MinimumRiskScore": 90,
|
||||
"HandleTypes": [
|
||||
"Create",
|
||||
"Duplicate",
|
||||
],
|
||||
"ObjectTypes": [
|
||||
"Process",
|
||||
],
|
||||
"Priority": 175,
|
||||
},
|
||||
{
|
||||
"Name": "Memory_Protect_Critical_Process_ThreadHijack",
|
||||
"Code": 1008,
|
||||
"Kill": false,
|
||||
"Category": "Memory",
|
||||
"InitiatorExclude": [
|
||||
"\\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\\*",
|
||||
"*\\Huorong\\Sysdiag\\bin\\HipsDaemon.exe",
|
||||
"*\\360Safe.exe",
|
||||
"*\\PYAS.exe",
|
||||
],
|
||||
"Target": [
|
||||
"\\Device\\HarddiskVolume*\\Windows\\System32\\lsass.exe",
|
||||
"\\Device\\HarddiskVolume*\\Windows\\System32\\winlogon.exe",
|
||||
"\\Device\\HarddiskVolume*\\Windows\\System32\\services.exe",
|
||||
"\\Device\\HarddiskVolume*\\Windows\\System32\\csrss.exe",
|
||||
],
|
||||
"Operations": [
|
||||
"WriteMemory",
|
||||
"VmOperation",
|
||||
"SetThreadContext",
|
||||
],
|
||||
"OperationMatch": "All",
|
||||
"MinimumRiskScore": 90,
|
||||
"HandleTypes": [
|
||||
"Create",
|
||||
"Duplicate",
|
||||
],
|
||||
"ObjectTypes": [
|
||||
"Process",
|
||||
],
|
||||
"Priority": 175,
|
||||
},
|
||||
{
|
||||
"Name": "Memory_Protect_Service_Process_CreateRemoteThread",
|
||||
"Code": 1009,
|
||||
"Kill": false,
|
||||
"Category": "Memory",
|
||||
"InitiatorExclude": [
|
||||
"\\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\\*",
|
||||
"\\Device\\HarddiskVolume*\\Users\\*\\AppData\\Local\\*",
|
||||
"*\\Huorong\\Sysdiag\\bin\\HipsDaemon.exe",
|
||||
"*\\360Safe.exe",
|
||||
"*\\PYAS.exe",
|
||||
],
|
||||
"Target": [
|
||||
"\\Device\\HarddiskVolume*\\Windows\\System32\\svchost.exe",
|
||||
"\\Device\\HarddiskVolume*\\Windows\\SysWOW64\\svchost.exe",
|
||||
"\\Device\\HarddiskVolume*\\Windows\\System32\\spoolsv.exe",
|
||||
],
|
||||
"Operations": [
|
||||
"WriteMemory",
|
||||
"VmOperation",
|
||||
"CreateThread",
|
||||
],
|
||||
"OperationMatch": "All",
|
||||
"MinimumRiskScore": 90,
|
||||
"HandleTypes": [
|
||||
"Create",
|
||||
"Duplicate",
|
||||
],
|
||||
"ObjectTypes": [
|
||||
"Process",
|
||||
],
|
||||
"Priority": 170,
|
||||
},
|
||||
{
|
||||
"Name": "Memory_Protect_Service_Process_ThreadHijack",
|
||||
"Code": 1010,
|
||||
"Kill": false,
|
||||
"Category": "Memory",
|
||||
"InitiatorExclude": [
|
||||
"\\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\\*",
|
||||
"*\\Huorong\\Sysdiag\\bin\\HipsDaemon.exe",
|
||||
"*\\360Safe.exe",
|
||||
"*\\PYAS.exe",
|
||||
],
|
||||
"Target": [
|
||||
"\\Device\\HarddiskVolume*\\Windows\\System32\\svchost.exe",
|
||||
"\\Device\\HarddiskVolume*\\Windows\\SysWOW64\\svchost.exe",
|
||||
"\\Device\\HarddiskVolume*\\Windows\\System32\\spoolsv.exe",
|
||||
],
|
||||
"Operations": [
|
||||
"WriteMemory",
|
||||
"VmOperation",
|
||||
"SetThreadContext",
|
||||
],
|
||||
"OperationMatch": "All",
|
||||
"MinimumRiskScore": 90,
|
||||
"HandleTypes": [
|
||||
"Create",
|
||||
"Duplicate",
|
||||
],
|
||||
"ObjectTypes": [
|
||||
"Process",
|
||||
],
|
||||
"Priority": 170,
|
||||
},
|
||||
{
|
||||
"Name": "Self_Antivirus_Protect_File",
|
||||
"Code": 2001,
|
||||
"Kill": false,
|
||||
"Category": "File",
|
||||
@@ -90,8 +355,10 @@
|
||||
"*\\PYAS_Setup.exe",
|
||||
"*\\unins000.exe",
|
||||
"*\\notepad*.exe",
|
||||
"\\Device\\HarddiskVolume*\\Windows\\System32\\cleanmgr.exe",
|
||||
"\\Device\\HarddiskVolume*\\Users\\*\\AppData\\Local\\Temp\\*\\PYAS_Setup.tmp",
|
||||
"\\Device\\HarddiskVolume*\\Users\\*\\AppData\\Local\\Temp\\*\\_unins.tmp",
|
||||
"\\Device\\HarddiskVolume*\\Windows\\explorer.exe",
|
||||
"\\Device\\HarddiskVolume*\\Windows\\System32\\cleanmgr.exe",
|
||||
],
|
||||
"Target": [
|
||||
"*\\PYAS.exe",
|
||||
@@ -109,7 +376,7 @@
|
||||
"*\\Interface\\static\\img\\*.ico",
|
||||
"*\\Interface\\static\\js\\*.js",
|
||||
"*\\Interface\\templates\\*.html",
|
||||
"*\\Plugins\\Filter\\PYAS_Driver.sys",
|
||||
"*\\Plugins\\Filter\\*.sys",
|
||||
"*\\Plugins\\Rules\\*.json",
|
||||
],
|
||||
"Operations": [
|
||||
@@ -118,7 +385,7 @@
|
||||
]
|
||||
},
|
||||
{
|
||||
"Name": "File_Protect_System",
|
||||
"Name": "System_Boot_Protect_File",
|
||||
"Code": 2002,
|
||||
"Kill": true,
|
||||
"Category": "File",
|
||||
@@ -132,6 +399,7 @@
|
||||
"\\Device\\HarddiskVolume*\\Windows\\System32\\taskhostw.exe",
|
||||
"\\Device\\HarddiskVolume*\\Windows\\System32\\TrustedInstaller.exe",
|
||||
"\\Device\\HarddiskVolume*\\Windows\\WinSxS\\*\\TiWorker.exe",
|
||||
"\\Device\\HarddiskVolume*\\$WinREAgent\\Scratch\\*\\DismHost.exe",
|
||||
],
|
||||
"Target": [
|
||||
"\\Device\\HarddiskVolume*\\bootmgr",
|
||||
@@ -147,9 +415,9 @@
|
||||
]
|
||||
},
|
||||
{
|
||||
"Name": "File_Protect_System_Exploit",
|
||||
"Name": "Illegal_Characters_Protect_File",
|
||||
"Code": 2003,
|
||||
"Kill": true,
|
||||
"Kill": false,
|
||||
"Category": "File",
|
||||
"InitiatorExclude": [
|
||||
"*\\PYAS.exe",
|
||||
@@ -179,17 +447,23 @@
|
||||
]
|
||||
},
|
||||
{
|
||||
"Name": "File_Protect_System_Ransom",
|
||||
"Name": "System_Ransom_Protect_File",
|
||||
"Code": 2004,
|
||||
"Kill": true,
|
||||
"Category": "File",
|
||||
"Initiator": [
|
||||
"\\Device\\HarddiskVolume*\\Windows\\System32\\cmd.exe",
|
||||
"\\Device\\HarddiskVolume*\\Windows\\SysWOW64\\cmd.exe",
|
||||
"\\Device\\HarddiskVolume*\\Windows\\System32\\WindowsPowerShell\\*\\powershell.exe",
|
||||
"\\Device\\HarddiskVolume*\\Windows\\SysWOW64\\WindowsPowerShell\\*\\powershell.exe",
|
||||
"\\Device\\HarddiskVolume*\\Program Files\\PowerShell\\*\\pwsh.exe",
|
||||
"\\Device\\HarddiskVolume*\\Program Files (x86)\\PowerShell\\*\\pwsh.exe",
|
||||
"\\Device\\HarddiskVolume*\\Windows\\System32\\wscript.exe",
|
||||
"\\Device\\HarddiskVolume*\\Windows\\SysWOW64\\wscript.exe",
|
||||
"\\Device\\HarddiskVolume*\\Windows\\System32\\cscript.exe",
|
||||
"\\Device\\HarddiskVolume*\\Windows\\SysWOW64\\cscript.exe",
|
||||
"\\Device\\HarddiskVolume*\\Windows\\System32\\mshta.exe",
|
||||
"\\Device\\HarddiskVolume*\\Windows\\SysWOW64\\mshta.exe",
|
||||
],
|
||||
"Target": [
|
||||
"\\Device\\HarddiskVolume*\\Windows\\*",
|
||||
@@ -210,7 +484,7 @@
|
||||
"TimeWindow": 3000,
|
||||
},
|
||||
{
|
||||
"Name": "File_Protect_Ransom",
|
||||
"Name": "User_Ransom_Protect_File",
|
||||
"Code": 2005,
|
||||
"Kill": true,
|
||||
"Category": "File",
|
||||
@@ -249,6 +523,7 @@
|
||||
"*\\resources\\bin\\SDK\\DownloadSDKServer.exe",
|
||||
"*\\bin\\*\\codex.exe",
|
||||
"*\\bin\\codex.exe",
|
||||
"*\\Weixin\\Weixin.exe",
|
||||
],
|
||||
"Target": [
|
||||
"\\Device\\HarddiskVolume*\\Users\\*",
|
||||
@@ -265,9 +540,18 @@
|
||||
"\\Device\\HarddiskVolume*\\Users\\*\\Temp\\*",
|
||||
"\\Device\\HarddiskVolume*\\Users\\*\\Local\\Temp\\*",
|
||||
"\\Device\\HarddiskVolume*\\Users\\*\\cache\\*",
|
||||
"\\Device\\HarddiskVolume*\\Users\\*\\.cache\\*",
|
||||
"\\Device\\HarddiskVolume*\\Users\\*\\.aws\\*",
|
||||
"\\Device\\HarddiskVolume*\\Users\\*\\.codex\\*",
|
||||
"\\Device\\HarddiskVolume*\\Users\\*\\.designer\\*",
|
||||
"\\Device\\HarddiskVolume*\\Users\\*\\.idlerc\\*",
|
||||
"\\Device\\HarddiskVolume*\\Users\\*\\.keras\\*",
|
||||
"\\Device\\HarddiskVolume*\\Users\\*\\.matplotlib\\*",
|
||||
"\\Device\\HarddiskVolume*\\Users\\*\\.ssh\\*",
|
||||
"*\\ventoy\\Boot\\*",
|
||||
"*\\AdminRegionConfig.*",
|
||||
"*\\nt_qq\\nt_*\\*",
|
||||
"\\Device\\HarddiskVolume*\\Users\\*\\Documents\\Tencent Files\\*\\nt_qq\\nt_*\\*",
|
||||
],
|
||||
"Extensions": [
|
||||
".zip",
|
||||
@@ -300,11 +584,6 @@
|
||||
".mov",
|
||||
".wmv",
|
||||
".mkv",
|
||||
".apk",
|
||||
".jar",
|
||||
".class",
|
||||
".db",
|
||||
".sqlite",
|
||||
],
|
||||
"Operations": [
|
||||
"Write",
|
||||
@@ -315,7 +594,7 @@
|
||||
"TimeWindow": 3000,
|
||||
},
|
||||
{
|
||||
"Name": "Device_Protect_Disk_Wiper",
|
||||
"Name": "System_Boot_Protect_Disk_Wiper",
|
||||
"Code": 2006,
|
||||
"Kill": false,
|
||||
"Category": "Device",
|
||||
@@ -334,9 +613,9 @@
|
||||
]
|
||||
},
|
||||
{
|
||||
"Name": "Registry_Protect_System",
|
||||
"Name": "Registry_Protect_System_Suspicious",
|
||||
"Code": 3001,
|
||||
"Kill": true,
|
||||
"Kill": false,
|
||||
"Category": "Registry",
|
||||
"InitiatorExclude": [
|
||||
"\\Device\\HarddiskVolume*\\Windows\\*",
|
||||
@@ -344,55 +623,112 @@
|
||||
"\\Device\\HarddiskVolume*\\Program Files (x86)\\*",
|
||||
"\\Device\\HarddiskVolume*\\ProgramData\\Microsoft\\Windows Defender\\*",
|
||||
"\\Device\\HarddiskVolume*\\ProgramData\\Microsoft\\Windows Defender Advanced Threat Protection\\*",
|
||||
"*\\Google\\Chrome\\Application\\chrome.exe",
|
||||
"*\\Microsoft\\Edge\\Application\\msedge.exe",
|
||||
"*\\Mozilla Firefox\\firefox.exe",
|
||||
"*\\Thorium\\Application\\thorium.exe",
|
||||
"*\\Sources\\SetupHost.exe",
|
||||
"*\\Work\\*\\DismHost.exe",
|
||||
"*\\Steam\\steam.exe",
|
||||
"*\\steam\\*\\steam.exe",
|
||||
"*\\Steam\\steamapps\\common\\*",
|
||||
"*\\Battle.net\\*\\Battle.net.exe",
|
||||
"*\\Epic Games\\Launcher\\Portal\\Binaries\\*\\EpicGamesLauncher.exe",
|
||||
"*\\PC4399_WPFLauncher.exe",
|
||||
"*\\Neverness To Everness\\NTELauncher.exe",
|
||||
"*\\ThunderEv.exe",
|
||||
"*\\ThunderStart.exe",
|
||||
"*\\bin\\APlayer.exe",
|
||||
"*\\bilibili\\bilibili.exe",
|
||||
"*\\KuGou.exe",
|
||||
"*\\XLGame.exe",
|
||||
"*\\XLLiveUD.exe",
|
||||
"*\\hrkill-*.exe",
|
||||
"*\\Huorong\\Sysdiag\\bin\\HipsDaemon.exe",
|
||||
"*\\360Safe.exe",
|
||||
"*\\PYAS.exe",
|
||||
"*\\WPS Office\\ksolaunch.exe",
|
||||
"*\\WPS Office\\*\\wps.exe",
|
||||
"*\\WPS Office\\*\\*.exe",
|
||||
"*\\Application\\AndrowsLauncher.exe",
|
||||
"*\\resources\\bin\\SDK\\DownloadSDKServer.exe",
|
||||
"*\\bin\\*\\codex.exe",
|
||||
"*\\bin\\codex.exe",
|
||||
],
|
||||
"Target": [
|
||||
"\\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",
|
||||
"*\\NoWindowsUpdate",
|
||||
"*\\AutoAdminLogon",
|
||||
"*\\DefaultUserName",
|
||||
"*\\DefaultPassword",
|
||||
"*\\AltDefaultUserName",
|
||||
"*\\AltDefaultPassword",
|
||||
"*\\LegalNoticeCaption",
|
||||
"*\\LegalNoticeText",
|
||||
"*\\AutoReboot",
|
||||
"*\\DisableAutomaticRestartOnFailure",
|
||||
"*\\SystemBiosVersion",
|
||||
"*\\ClearRecentDocsOnExit",
|
||||
"*\\HideClock",
|
||||
"*\\HideFastUserSwitching",
|
||||
"*\\ShutdownWithoutLogon",
|
||||
"*\\DisableCAD",
|
||||
],
|
||||
"Operations": [
|
||||
"Write",
|
||||
"Delete",
|
||||
"Create",
|
||||
],
|
||||
"Priority": 130,
|
||||
},
|
||||
{
|
||||
"Name": "Registry_Protect_Open_With_Suspicious",
|
||||
"Code": 3002,
|
||||
"Kill": false,
|
||||
"Category": "Registry",
|
||||
"InitiatorExclude": [
|
||||
"\\Device\\HarddiskVolume*\\Windows\\*",
|
||||
"\\Device\\HarddiskVolume*\\Program Files\\*",
|
||||
"\\Device\\HarddiskVolume*\\Program Files (x86)\\*",
|
||||
"*\\PYAS.exe",
|
||||
],
|
||||
"Target": [
|
||||
"*\\Classes\\.exe",
|
||||
"*\\Classes\\.com",
|
||||
"*\\Classes\\.bat",
|
||||
"*\\Classes\\.cmd",
|
||||
"*\\Classes\\exefile\\*",
|
||||
"*\\Classes\\comfile\\*",
|
||||
"*\\Classes\\batfile\\*",
|
||||
"*\\Classes\\cmdfile\\*",
|
||||
],
|
||||
"Operations": [
|
||||
"Write",
|
||||
"Delete",
|
||||
"Create",
|
||||
],
|
||||
"Priority": 150,
|
||||
},
|
||||
{
|
||||
"Name": "Registry_Protect_BCD_Suspicious",
|
||||
"Code": 3003,
|
||||
"Kill": false,
|
||||
"Category": "Registry",
|
||||
"InitiatorExclude": [
|
||||
"\\Device\\HarddiskVolume*\\Windows\\*",
|
||||
"\\Device\\HarddiskVolume*\\ProgramData\\Microsoft\\Windows Defender\\*",
|
||||
"\\Device\\HarddiskVolume*\\ProgramData\\Microsoft\\Windows Defender Advanced Threat Protection\\*",
|
||||
"*\\PYAS.exe",
|
||||
],
|
||||
"Target": [
|
||||
"\\REGISTRY\\MACHINE\\BCD00000000",
|
||||
"\\REGISTRY\\MACHINE\\BCD00000000\\*",
|
||||
],
|
||||
"Operations": [
|
||||
"Write",
|
||||
"Delete",
|
||||
"Create",
|
||||
],
|
||||
"Priority": 140,
|
||||
},
|
||||
{
|
||||
"Name": "Registry_Protect_System_Critical",
|
||||
"Code": 3004,
|
||||
"Kill": true,
|
||||
"Category": "Registry",
|
||||
"InitiatorExclude": [
|
||||
"\\Device\\HarddiskVolume*\\Windows\\*",
|
||||
"*\\PYAS.exe",
|
||||
],
|
||||
"Target": [
|
||||
"*\\EnableLUA",
|
||||
"*\\ConsentPromptBehaviorAdmin",
|
||||
"*\\ConsentPromptBehaviorUser",
|
||||
"*\\EnableSecureUIAPaths",
|
||||
"*\\BootExecute",
|
||||
"*\\SubSystems",
|
||||
"*\\FirmwareBootDevice",
|
||||
"*\\BootDriverFlags",
|
||||
"*\\SystemStartOptions",
|
||||
"*\\Userinit",
|
||||
"*\\UIHost",
|
||||
"*\\Debugger",
|
||||
"*\\NoControlPanel",
|
||||
"*\\NoDrives",
|
||||
"*\\NoFileMenu",
|
||||
@@ -407,7 +743,6 @@
|
||||
"*\\NoFolderOptions",
|
||||
"*\\RestrictRun",
|
||||
"*\\NoViewContextMenu",
|
||||
"*\\HideClock",
|
||||
"*\\NoStartMenuMyGames",
|
||||
"*\\NoStartMenuMyMusic",
|
||||
"*\\DisableCMD",
|
||||
@@ -426,7 +761,6 @@
|
||||
"*\\NoActiveDesktop",
|
||||
"*\\NoSetActiveDesktop",
|
||||
"*\\NoRecentDocsMenu",
|
||||
"*\\NoWindowsUpdate",
|
||||
"*\\NoChangeStartMenu",
|
||||
"*\\NoFavoritesMenu",
|
||||
"*\\NoRecentDocsHistory",
|
||||
@@ -436,44 +770,89 @@
|
||||
"*\\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",
|
||||
],
|
||||
"Operations": [
|
||||
"Write",
|
||||
"Delete",
|
||||
"Create",
|
||||
]
|
||||
}
|
||||
],
|
||||
"Priority": 180,
|
||||
},
|
||||
{
|
||||
"Name": "Registry_Protect_SafeMode_Autostart",
|
||||
"Code": 3005,
|
||||
"Kill": true,
|
||||
"Category": "Registry",
|
||||
"InitiatorExclude": [
|
||||
"\\Device\\HarddiskVolume*\\Windows\\*",
|
||||
"\\Device\\HarddiskVolume*\\ProgramData\\Microsoft\\Windows Defender\\*",
|
||||
"\\Device\\HarddiskVolume*\\ProgramData\\Microsoft\\Windows Defender Advanced Threat Protection\\*",
|
||||
"*\\Sources\\SetupHost.exe",
|
||||
"*\\Work\\*\\DismHost.exe",
|
||||
"*\\Huorong\\Sysdiag\\bin\\HipsDaemon.exe",
|
||||
"*\\360Safe.exe",
|
||||
"*\\PYAS.exe",
|
||||
],
|
||||
"Target": [
|
||||
"\\REGISTRY\\MACHINE\\SYSTEM\\*ControlSet*\\Control\\SafeBoot\\Minimal\\*",
|
||||
"\\REGISTRY\\MACHINE\\SYSTEM\\*ControlSet*\\Control\\SafeBoot\\Network\\*",
|
||||
"\\REGISTRY\\MACHINE\\SYSTEM\\*ControlSet*\\Control\\SafeBoot\\AlternateShell",
|
||||
"\\REGISTRY\\MACHINE\\SYSTEM\\*ControlSet*\\Control\\SafeBoot\\AlternateShell\\*",
|
||||
"\\REGISTRY\\MACHINE\\SYSTEM\\*ControlSet*\\Control\\SafeBoot\\Option",
|
||||
"\\REGISTRY\\MACHINE\\SYSTEM\\*ControlSet*\\Control\\SafeBoot\\Option\\*",
|
||||
],
|
||||
"Operations": [
|
||||
"Write",
|
||||
"Delete",
|
||||
"Create",
|
||||
],
|
||||
"Priority": 170,
|
||||
},
|
||||
{
|
||||
"Name": "Registry_Protect_OOBE_Reset",
|
||||
"Code": 3006,
|
||||
"Kill": false,
|
||||
"Category": "Registry",
|
||||
"InitiatorExclude": [
|
||||
"*\\Sources\\SetupHost.exe",
|
||||
"*\\Work\\*\\DismHost.exe",
|
||||
"*\\Windows\\UUS\\amd64\\MoUsoCoreWorker.exe",
|
||||
"*\\Windows\\System32\\MoUsoCoreWorker.exe",
|
||||
"*\\Windows\\System32\\Sysprep\\sysprep.exe",
|
||||
"*\\Windows\\System32\\oobe\\*",
|
||||
"*\\Windows\\System32\\svchost.exe",
|
||||
"*\\Windows\\explorer.exe",
|
||||
"*\\PYAS.exe",
|
||||
],
|
||||
"Target": [
|
||||
"\\REGISTRY\\MACHINE\\SYSTEM\\Setup\\OOBEInProgress",
|
||||
"\\REGISTRY\\MACHINE\\SYSTEM\\Setup\\SystemSetupInProgress",
|
||||
"\\REGISTRY\\MACHINE\\SYSTEM\\Setup\\SetupType",
|
||||
"\\REGISTRY\\MACHINE\\SYSTEM\\Setup\\CmdLine",
|
||||
"\\REGISTRY\\MACHINE\\SYSTEM\\Setup\\AuditInProgress",
|
||||
"\\REGISTRY\\MACHINE\\SYSTEM\\Setup\\MiniSetupInProgress",
|
||||
"\\REGISTRY\\MACHINE\\SYSTEM\\Setup\\SetupPhase",
|
||||
"\\REGISTRY\\MACHINE\\SYSTEM\\Setup\\Status\\SysprepStatus\\*",
|
||||
"\\REGISTRY\\MACHINE\\SYSTEM\\Setup\\Status\\ChildCompletion\\*",
|
||||
"\\REGISTRY\\MACHINE\\SOFTWARE\\Microsoft\\Windows\\CurrentVersion\\Setup\\State\\ImageState",
|
||||
"\\REGISTRY\\MACHINE\\SOFTWARE\\Microsoft\\Windows\\CurrentVersion\\Setup\\OOBE\\*",
|
||||
"\\REGISTRY\\MACHINE\\SOFTWARE\\Microsoft\\Windows\\CurrentVersion\\OOBE\\*",
|
||||
],
|
||||
"TargetExclude": [
|
||||
"\\REGISTRY\\MACHINE\\SOFTWARE\\Microsoft\\Windows\\CurrentVersion\\OOBE\\TelemetryCorrelation",
|
||||
],
|
||||
"Operations": [
|
||||
"Write",
|
||||
"Delete",
|
||||
"Create",
|
||||
],
|
||||
"Priority": 175,
|
||||
},
|
||||
]
|
||||
}
|
||||
Reference in new issue
Block a user