Compare commits

...
30 Commits
Author SHA1 Message Date
87owo 5798a35456 Add files via upload 2026-07-18 18:08:03 +08:00
87owo a8a2060978 Add files via upload 2026-07-18 17:38:02 +08:00
87owo 7cf2763511 Add files via upload 2026-07-18 17:34:32 +08:00
87owo fc59619518 Revise Driver Signature notice in LICENSE
Updated the language in the Driver Signature section for clarity.
2026-07-18 17:17:58 +08:00
87owo d01cfd2dd6 Update LICENSE.txt with new date and driver notice
Updated the last modified date in the license agreement and added a new section regarding driver signature notice.
2026-07-18 17:03:00 +08:00
87owo 9563a4806a Add files via upload 2026-07-17 17:55:26 +08:00
87owo fa1be109e3 Add files via upload 2026-07-17 17:41:26 +08:00
87owo 89a5a5928c Add files via upload 2026-07-17 11:36:43 +08:00
87owo 7830378f23 Add files via upload 2026-07-17 09:42:55 +08:00
87owo 043f616469 Add files via upload 2026-07-17 09:40:34 +08:00
87owo 3bf631313a Add files via upload 2026-07-17 09:38:11 +08:00
87owo a416e2d4a8 Add files via upload 2026-07-16 16:21:57 +08:00
87owo 53218cf1ce Add files via upload 2026-07-15 22:06:33 +08:00
87owo c5f0c8ed47 Add files via upload 2026-07-15 21:52:37 +08:00
87owo 05e1c45adb Add files via upload 2026-07-15 21:20:11 +08:00
87owo e3787523c7 Add files via upload 2026-07-15 21:19:15 +08:00
87owo 1c4e908b1b Add files via upload 2026-07-14 04:54:50 +08:00
87owo 98b60b9833 Add files via upload 2026-07-14 04:54:15 +08:00
87owo 71f8232ef0 Delete Plugins/Rules directory 2026-07-14 04:54:03 +08:00
87owo 0e4de407e3 Add files via upload 2026-07-14 04:53:34 +08:00
87owo 802d732b3f Add files via upload 2026-07-13 19:36:21 +08:00
87owo 373897f579 Add files via upload 2026-07-12 12:01:26 +08:00
87owo 3ef72917c8 Add files via upload 2026-07-12 12:00:57 +08:00
87owo 0d27125453 Add files via upload 2026-07-12 11:50:19 +08:00
87owo 82c519b7d9 Add files via upload 2026-07-12 11:49:34 +08:00
87owo 3c57a8a53c Add files via upload 2026-07-11 21:21:12 +08:00
87owo 727ba6ccbf Add files via upload 2026-07-11 15:16:35 +08:00
87owo b32f1df9ec Add files via upload 2026-07-10 00:23:20 +08:00
87owo 76b9afc6ec Add files via upload 2026-07-10 00:22:49 +08:00
87owo 9fbf7baf76 Add files via upload 2026-07-10 00:12:16 +08:00
18 changed files with 3070 additions and 1631 deletions

No files matched your search

+174 -42
View File
@@ -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
View File
@@ -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
####################################################################################################
+7 -8
View File
@@ -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)]
+7 -8
View File
@@ -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)]
+32 -3
View File
@@ -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
View File
File diff suppressed because it is too large. Load diff
+13 -2
View File
@@ -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
View File
@@ -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
+231 -81
View File
@@ -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
View File
@@ -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
View File
@@ -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
View File
@@ -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
View File
@@ -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
View File
@@ -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
View File
@@ -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",
+14 -3
View File
@@ -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 {
+99 -2
View File
@@ -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;
+468 -89
View File
@@ -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,
},
]
}