Compare commits

..
117 Commits
Author SHA1 Message Date
87owo 364f48a6f0 Update README.md 2026-04-25 16:55:34 +08:00
87owo cfebf6df0e Update README.md 2026-04-25 16:40:38 +08:00
87owo 688855b72d Update README.md 2026-04-25 16:38:08 +08:00
87owo 77b275a04b Update README.md 2026-04-25 16:30:11 +08:00
87owo 98b142df12 Update README.md 2026-04-25 16:01:11 +08:00
87owo c5fe845f18 Delete PYAS_Resource.py 2026-04-25 15:59:56 +08:00
87owo e5e9629d31 Delete PYAS_Interface.py 2026-04-25 15:59:48 +08:00
87owo b65b9e6bfb Delete PYAS_Config.py 2026-04-25 15:59:26 +08:00
87owo 93ae8b02e3 Add files via upload 2026-04-25 15:55:56 +08:00
87owo 5c183ea200 Add files via upload 2026-04-25 15:54:58 +08:00
87owo 661765f08c Add files via upload 2026-04-25 15:54:33 +08:00
87owo 60fa19c681 Add files via upload 2026-04-25 15:53:27 +08:00
87owo 606eddba53 Add files via upload 2026-04-21 14:56:52 +08:00
87owo 71605ade31 Add files via upload 2026-04-19 02:21:41 +08:00
87owo ee409e4872 Add files via upload 2026-04-18 01:41:13 +08:00
87owo 91024abc4a Add files via upload 2026-04-15 19:59:41 +08:00
87owo 99de36a6cf Add files via upload 2026-04-15 19:57:35 +08:00
87owo ac1e48dc30 Add files via upload 2026-04-12 13:49:40 +08:00
87owo 494bab3380 Add files via upload 2026-04-12 13:42:38 +08:00
87owo 6696ca93ce Add files via upload 2026-04-11 16:50:33 +08:00
87owo b7e80fb8ed Add files via upload 2026-04-10 17:47:07 +08:00
87owo ce5b642d3e Add files via upload 2026-04-10 15:50:15 +08:00
87owo 76d414164a Update PYAS.py 2026-04-10 15:16:07 +08:00
87owo b6d13047ba Merge pull request #40 from MichaelMVS/fix-bare-except-clauses
Fix bare except clauses to catch only Exception types
2026-04-10 15:05:22 +08:00
87owo f1ba295796 Update README.md 2026-04-10 14:45:49 +08:00
zocomputer 415abef4fc Fix bare except clauses to catch only Exception types
Replace bare 'except:' with 'except Exception:' in three locations:
- check_process_survival (line ~1632): when accessing pe.szExeFile
- get_process_name (line ~1782): when getting process name from snapshot
- pipe_server_thread (line ~2340): when terminating malicious processes

Bare except clauses also catch SystemExit and KeyboardInterrupt,
which can prevent proper shutdown and mask serious issues.
Using 'except Exception:' allows the code to properly handle
expected errors while letting system-exiting events propagate.
2026-04-10 06:40:26 +00:00
87owo 95af2014d8 Update README.md 2026-04-10 14:39:38 +08:00
87owo f75b039daf Add files via upload 2026-04-10 14:35:02 +08:00
87owo 65b1ac78f3 Rename train_vt.py to train_server_lfp.py 2026-04-10 14:27:44 +08:00
87owo b067591882 Add files via upload 2026-04-10 14:24:58 +08:00
87owo 7941d76fd7 Delete Engine/Properties/train.py 2026-04-10 14:13:24 +08:00
87owo 7bff601beb Add files via upload 2026-04-10 14:12:27 +08:00
87owo 7b4da32bee Update README.md 2026-04-10 14:00:42 +08:00
87owo bbf2974703 Update README.md 2026-04-10 13:50:28 +08:00
87owo f5abaf06de Add files via upload 2026-04-08 17:57:14 +08:00
87owo 88023dd09e Add files via upload 2026-04-08 01:20:52 +08:00
87owo eec2996fab Add files via upload 2026-04-07 23:53:35 +08:00
87owo 97ca200c29 Add files via upload 2026-04-07 23:52:55 +08:00
87owo f0600ea3a3 Add files via upload 2026-04-07 23:52:20 +08:00
87owo 19b3eb51d7 Add files via upload 2026-04-04 16:02:36 +08:00
87owo 871dabf142 Add files via upload 2026-04-02 01:30:02 +08:00
87owo 58795b9349 Add files via upload 2026-04-01 23:42:12 +08:00
87owo 1ef769842b Add files via upload 2026-04-01 22:45:57 +08:00
87owo 6051570414 Add files via upload 2026-04-01 22:45:18 +08:00
87owo da5d5d0dc7 Add files via upload 2026-03-31 19:39:18 +08:00
87owo 782aa22d4d Add files via upload 2026-03-31 19:14:08 +08:00
87owo 0084867f38 Add files via upload 2026-03-29 21:14:20 +08:00
87owo 5a27858f86 Add files via upload 2026-03-27 02:17:54 +08:00
87owo 7905d8375f Add files via upload 2026-03-25 15:59:26 +08:00
87owo ba8aa63d75 Add files via upload 2026-03-16 15:10:16 +08:00
87owo ac82778e26 Add files via upload 2026-03-11 17:55:19 +08:00
87owo 55d2661e38 Add files via upload 2026-03-11 17:37:54 +08:00
87owo 4803b89294 Update train.py 2026-03-11 16:05:26 +08:00
87owo e48adbd87d Add files via upload 2026-03-11 16:00:07 +08:00
87owo 227314d83c Add files via upload 2026-03-08 03:53:52 +08:00
87owo 3c0e21eb02 Add files via upload 2026-03-08 03:42:55 +08:00
87owo 86d6153ef1 Add files via upload 2026-03-08 02:56:01 +08:00
87owo 78cf8bded3 Add files via upload 2026-03-07 12:50:56 +08:00
87owo 26d12181cd Add files via upload 2026-03-07 12:49:07 +08:00
87owo 8d184b011c Add files via upload 2026-03-01 18:40:56 +08:00
87owo ed3a1444ec Add files via upload 2026-03-01 18:19:34 +08:00
87owo d3e70a47cd Add files via upload 2026-03-01 18:19:08 +08:00
87owo 3171151219 Add files via upload 2026-03-01 18:16:04 +08:00
87owo e407867635 Add files via upload 2026-03-01 17:46:39 +08:00
87owo 6d9cfab55f Add files via upload 2026-02-26 02:50:09 +08:00
87owo f0bce029f0 Add files via upload 2026-02-26 01:21:41 +08:00
87owo d47960a0cf Add files via upload 2026-02-26 01:20:03 +08:00
87owo 8416018afe Add files via upload 2026-02-23 17:48:40 +08:00
87owo e93fc70169 Add files via upload 2026-02-23 17:47:50 +08:00
87owo 5b3474c781 Add files via upload 2026-02-23 01:04:21 +08:00
87owo 8d2a9f3b2b Add files via upload 2026-02-20 14:14:19 +08:00
87owo 73d09f9b09 Add files via upload 2026-02-20 13:49:18 +08:00
87owo 4e7c6efa2a Add files via upload 2026-02-20 13:46:13 +08:00
87owo 81a884bc61 Update PYAS_Engine.py 2026-02-18 23:56:27 +08:00
87owo ac4f960352 Add files via upload 2026-02-10 17:45:23 +08:00
87owo e4b3e4e438 Add files via upload 2026-02-10 17:44:48 +08:00
87owo d6ee476296 Add files via upload 2026-02-10 17:44:15 +08:00
87owo c23741ba9a Add files via upload 2026-02-10 17:43:47 +08:00
87owo 7bac5b4740 Add files via upload 2026-02-10 17:43:04 +08:00
87owo 1fe440168e Add files via upload 2026-02-04 23:11:56 +08:00
87owo 26ad57138b Add files via upload 2026-02-04 23:11:14 +08:00
87owo 92e1fdaf0f Add files via upload 2026-02-04 23:09:59 +08:00
87owo 357540d0ad Update README.md 2026-01-24 01:19:33 +08:00
87owo 405622a91e Update README.md 2026-01-24 01:14:02 +08:00
87owo 5686c73c56 Update README.md 2026-01-24 01:11:38 +08:00
87owo 76e2f787ac Update README.md 2026-01-24 01:10:28 +08:00
87owo 02b67f4e6a Update README.md 2026-01-24 01:06:59 +08:00
87owo a2ab527761 Update README.md 2026-01-24 01:05:17 +08:00
87owo f1142ae742 Add files via upload 2026-01-23 14:48:01 +08:00
87owo decb222dac Add files via upload 2026-01-23 14:25:34 +08:00
87owo 00c598e6f7 Add files via upload 2026-01-23 14:25:11 +08:00
87owo e9843dd3a5 Add files via upload 2026-01-23 14:24:27 +08:00
87owo 37f390742b Add files via upload 2026-01-23 14:23:14 +08:00
87owo 533e7dfd15 Add files via upload 2026-01-23 13:49:06 +08:00
87owo ddcbe1cd51 Delete Engine directory 2026-01-23 13:46:15 +08:00
87owo 4eabd04bc6 Add files via upload 2026-01-23 13:44:24 +08:00
87owo c8f2da6ac9 Add files via upload 2026-01-23 13:43:17 +08:00
87owo 8820ac4bf3 Add files via upload 2026-01-23 13:42:37 +08:00
87owo 236b200f39 Add files via upload 2026-01-12 14:28:24 +08:00
87owo 6789e32674 Add files via upload 2026-01-12 14:27:37 +08:00
87owo 9eb55deedf Add files via upload 2026-01-12 13:29:30 +08:00
87owo 6e76b5b8eb Add files via upload 2026-01-12 13:28:04 +08:00
87owo 491b0da83d Add files via upload 2026-01-10 22:48:17 +08:00
87owo 5d2c17e940 Add files via upload 2026-01-10 22:47:35 +08:00
87owo 0497c4a9e7 Add files via upload 2026-01-10 20:05:44 +08:00
87owo 6d0765848b Add files via upload 2026-01-10 20:03:39 +08:00
87owo 40c4ae71bd Merge pull request #35 from secretlay3r/main
refactor: replace ifs with dicts/sets + simplify exception handling
2026-01-10 19:37:25 +08:00
secretlay3r 8edba10dff Update PYAS.py
backup_mbr():
- Shorten except blocks logic into one
- Add len(mbr) == 512 check  
- Add MBR_SIGNATURE instead of just b"\x55\xAA"

pipe_server_thread():
- Replace whole ifs chain with RULES dict
- Replace [4001, 5001] with KILL_CODES set (for readability)

get_window_info()  and  get_process_name():
- Extract process name logic into private _get_process_name()
2026-01-10 14:17:27 +03:00
87owo 85aa7bc652 Add files via upload 2026-01-09 14:05:47 +08:00
87owo 854ad1db07 Add files via upload 2026-01-09 00:11:30 +08:00
87owo 955600a1e1 Add files via upload 2026-01-07 22:19:13 +08:00
87owo b739134d85 Add files via upload 2026-01-07 22:18:36 +08:00
87owo 723152566d Add files via upload 2026-01-07 21:10:34 +08:00
87owo 678b045e0e Update README.md 2026-01-07 17:47:13 +08:00
87owo 681f4df49a Add files via upload 2026-01-07 17:44:23 +08:00
87owo b19f7d7098 Add files via upload 2026-01-06 15:11:42 +08:00
87owo daaae3ec10 Add files via upload 2026-01-06 14:43:06 +08:00
36 changed files with 9277 additions and 287831 deletions

No files matched your search

+16
View File
@@ -0,0 +1,16 @@
FROM python:3.10-slim
RUN apt-get update && apt-get install -y \
build-essential \
libfuzzy-dev \
git \
&& rm -rf /var/lib/apt/lists/*
WORKDIR /app
COPY requirements.txt .
RUN pip install --no-cache-dir -r requirements.txt
COPY . .
CMD ["python", "-u", "predict_onnx_linux.py"]
+211
View File
@@ -0,0 +1,211 @@
import os, time, uuid, logging, requests, hashlib
from pathlib import Path
from typing import Optional, List, Dict, Any
####################################################################################################
class PYAS_Client:
def __init__(self, api_key: str, host: str, timeout: int = 30):
self.base_url = host.rstrip('/')
self.timeout = timeout
self.logger = logging.getLogger("PYAS_API")
self.session = requests.Session()
self.session.headers.update({"X-API-Key": api_key, "User-Agent": "PYAS-API/1.1"})
self.logger.info(f"Initialized PYAS API Client targeting {self.base_url}")
def _request(self, method: str, endpoint: str, **kwargs) -> Optional[requests.Response]:
self.logger.debug(f"Request: {method} {endpoint}")
try:
r = self.session.request(method, f"{self.base_url}{endpoint}", timeout=self.timeout, **kwargs)
if r.status_code == 200:
return r
error_map = {401: "API Key invalid", 402: "Insufficient points", 404: "Not found", 500: "Server Error"}
self.logger.error(f"[{endpoint}] Failed: {error_map.get(r.status_code, f'HTTP {r.status_code}')}")
except Exception as e:
self.logger.error(f"Connection error: {e}")
return None
####################################################################################################
def _calculate_sha256(self, path: Path) -> str:
self.logger.debug(f"Calculating SHA256 for {path}")
sha256_hash = hashlib.sha256()
with open(path, "rb") as f:
for byte_block in iter(lambda: f.read(1048576), b""):
sha256_hash.update(byte_block)
return sha256_hash.hexdigest()
def upload_file(self, file_path: str, chunk_size: int = 5242880) -> Optional[str]:
path = Path(file_path)
if not path.exists():
self.logger.error(f"File not found: {file_path}")
return None
file_size = path.stat().st_size
self.logger.info(f"Initiating upload for {path.name} ({file_size} bytes)")
if file_size > 536870912:
self.logger.error(f"File size exceeds 512MB limit: {file_size} bytes")
return None
local_sha256 = self._calculate_sha256(path)
status_req = self._request("GET", f"/api/processing_status/{local_sha256}")
if status_req and status_req.json().get('status') in ['done', 'processing', 'queued']:
self.logger.info(f"File already exists or processing on server: {local_sha256}")
return local_sha256
if file_size > chunk_size:
return self._upload_chunked(path, chunk_size, file_size)
return self._upload_standard(path)
####################################################################################################
def _upload_standard(self, path: Path, max_retries: int = 3) -> Optional[str]:
self.logger.info(f"Starting standard upload for {path.name}")
with open(path, 'rb') as f:
file_data = f.read()
for attempt in range(max_retries):
r = self._request("POST", "/api/upload", files={'file': (path.name, file_data)})
if r and 'url' in r.json():
sha256 = r.json().get('url', '').split('/')[-1]
self.logger.info(f"Standard upload successful: {sha256}")
return sha256
self.logger.warning(f"Standard upload retry {attempt + 1}/{max_retries}")
time.sleep(1)
self.logger.error("Standard upload failed after retries")
return None
def _upload_chunked(self, path: Path, chunk_size: int, file_size: int, max_retries: int = 3) -> Optional[str]:
total_chunks = (file_size + chunk_size - 1) // chunk_size
upload_id = uuid.uuid4().hex
self.logger.info(f"Starting chunked upload for {path.name} (Chunks: {total_chunks}, ID: {upload_id})")
with open(path, 'rb') as f:
for i in range(total_chunks):
chunk_data = f.read(chunk_size)
headers = {"X-Chunk-Index": str(i), "X-Total-Chunks": str(total_chunks), "X-Upload-ID": upload_id}
chunk_success = False
for attempt in range(max_retries):
r = self._request("POST", "/api/upload", files={'file': (path.name, chunk_data)}, headers=headers)
if r:
chunk_success = True
self.logger.debug(f"Uploaded chunk {i + 1}/{total_chunks}")
if i == total_chunks - 1 and 'url' in r.json():
sha256 = r.json().get('url', '').split('/')[-1]
self.logger.info(f"Chunked upload successful: {sha256}")
return sha256
break
self.logger.warning(f"Chunk upload retry {attempt + 1}/{max_retries} for chunk {i}")
time.sleep(1)
if not chunk_success:
self.logger.error(f"Failed to upload chunk {i + 1}/{total_chunks}")
return None
return None
####################################################################################################
def wait_for_analysis(self, sha256: str, interval: int = 5, max_retries: int = 10) -> bool:
self.logger.info(f"Waiting for analysis completion: {sha256}")
for attempt in range(max_retries):
r = self._request("GET", f"/api/processing_status/{sha256}")
if r:
status = r.json().get('status')
self.logger.debug(f"Analysis status: {status}")
if status == 'done':
self.logger.info(f"Analysis completed: {sha256}")
return True
if status in ['missing', 'error', 'failed']:
self.logger.error(f"Analysis failed with status: {status}")
return False
time.sleep(interval)
self.logger.error(f"Analysis polling timed out for {sha256}")
return False
def get_report(self, sha256: str) -> Optional[Dict[str, Any]]:
self.logger.info(f"Fetching report for {sha256}")
r = self._request("GET", f"/api/report/{sha256}")
if r:
return r.json()
self.logger.error(f"Failed to fetch report for {sha256}")
return None
####################################################################################################
def rescan(self, sha256: str) -> bool:
self.logger.info(f"Triggering rescan for {sha256}")
r = self._request("POST", f"/api/rescan/{sha256}")
success = bool(r and r.json().get('status') == 'success')
if success:
self.logger.info(f"Rescan triggered successfully for {sha256}")
else:
self.logger.error(f"Rescan failed for {sha256}")
return success
def search_files(self, query: str, limit: int = 50, page: int = 0, snapshot: Optional[str] = None) -> List[Dict[str, Any]]:
self.logger.info(f"Searching files (Query: '{query}', Limit: {limit}, Page: {page})")
payload = {'query': query, 'page': page}
if snapshot:
payload['snapshot'] = snapshot
r = self._request("POST", "/api/search_more", data=payload)
results = r.json().get('results', [])[:limit] if r else []
self.logger.info(f"Search returned {len(results)} results")
return results
def download_sample(self, sha256: str, save_dir: str = "downloads") -> Optional[str]:
self.logger.info(f"Downloading sample {sha256}")
os.makedirs(save_dir, exist_ok=True)
path = os.path.join(save_dir, f"{sha256}.zip")
r = self._request("GET", f"/api/download/{sha256}", stream=True)
if r:
with open(path, 'wb') as f:
for chunk in r.iter_content(8192):
if chunk:
f.write(chunk)
self.logger.info(f"Sample downloaded successfully to {path}")
return path
self.logger.error(f"Failed to download sample {sha256}")
return None
####################################################################################################
if __name__ == "__main__":
logging.basicConfig(level=logging.DEBUG, format='[%(asctime)s] %(levelname)s [%(name)s] - %(message)s', datefmt='%Y-%m-%d %H:%M:%S')
API_KEY = ""
HOST = "https://pyas-security.com/"
client = PYAS_Client(API_KEY, HOST)
target_file = r"test.exe"
sha = client.upload_file(target_file)
if sha and client.wait_for_analysis(sha):
report = client.get_report(sha)
if report:
meta = report.get('data', {}).get('metadata', {})
logging.info(f"Result: {meta.get('label')} ({meta.get('score')}%)")
for item in client.search_files('date>=2020; ext=exe | dll', limit=3):
client.download_sample(item['sha256'])
+1
View File
@@ -0,0 +1 @@
["FileEntropy", "SectionMeanEntropy", "IsExe", "IsDll", "IsDriver", "Is64Bit", "Machine", "Magic", "CheckSum", "ImageBase", "SizeOfImage", "SizeOfHeaders", "Characteristics", "DllCharacteristics", "Subsystem", "LoaderFlags", "MajorLinkerVersion", "MinorLinkerVersion", "SizeOfCode", "SizeOfInitializedData", "SizeOfUninitializedData", "AddressOfEntryPoint", "BaseOfCode", "SectionAlignment", "FileAlignment", "MajorOperatingSystemVersion", "MinorOperatingSystemVersion", "MajorImageVersion", "MinorImageVersion", "MajorSubsystemVersion", "MinorSubsystemVersion", "SizeOfStackReserve", "SizeOfStackCommit", "SizeOfHeapReserve", "SizeOfHeapCommit", "NumberOfSections", "NumberOfRvaAndSizes", "PointerToSymbolTable", "NumberOfSymbols", "SizeOfOptionalHeader", "TextSectionMaxEntropy", "TextSectionMeanEntropy", "TextSizeRatio", "DataSectionMaxEntropy", "DataSectionMeanEntropy", "DataSizeRatio", "RsrcSectionMaxEntropy", "RsrcSectionMeanEntropy", "RsrcSizeRatio", "SectionCount", "ExecutableSections", "WritableSections", "ReadableSections", "SectionException", "Char_00000020_Count", "Char_00000020_MeanEntropy", "Char_00000040_Count", "Char_00000040_MeanEntropy", "Char_00000080_Count", "Char_00000080_MeanEntropy", "Char_02000000_Count", "Char_02000000_MeanEntropy", "Char_20000000_Count", "Char_20000000_MeanEntropy", "Char_40000000_Count", "Char_40000000_MeanEntropy", "Char_80000000_Count", "Char_80000000_MeanEntropy", "IconCount", "ApiCount", "ExportCount", "DebugCount", "ExceptionCount", "ImportCount", "ImportFunctionCount", "FileDescriptionLength", "FileVersionLength", "ProductNameLength", "ProductVersionLength", "CompanyNameLength", "LegalCopyrightLength", "CommentsLength", "InternalNameLength", "LegalTrademarksLength", "SpecialBuildLength", "PrivateBuildLength", "TrustSigned", "IsDebug", "IsPatched", "IsPrivateBuild", "IsPreRelease", "IsSpecialBuild", "IsAdmin", "IsInstall", "HasTlsCallbacks", "HasInvalidTimestamp", "HasRelocationDirectory", "HasPacked", "FileTimeException", "Cat_AntiDebug", "Cat_Audio", "Cat_COM", "Cat_Camera", "Cat_Clipboard", "Cat_DataObfuscation", "Cat_DotNet", "Cat_Encryption", "Cat_FileIO", "Cat_Graphics", "Cat_Injection", "Cat_Input", "Cat_Keylogging", "Cat_Memory", "Cat_MultiThreading", "Cat_Native", "Cat_Network", "Cat_Privileges", "Cat_ProcessControl", "Cat_Registry", "Cat_Resource", "Cat_ScreenCapture", "Cat_Services", "Cat_Synchronization", "Cat_WindowControl", "Dll_advapi32_dll", "Dll_bcrypt_dll", "Dll_comctl32_dll", "Dll_crypt32_dll", "Dll_dbghelp_dll", "Dll_gdi32_dll", "Dll_hal_dll", "Dll_imagehlp_dll", "Dll_iphlpapi_dll", "Dll_kernel32_dll", "Dll_mscoree_dll", "Dll_netapi32_dll", "Dll_ntdll_dll", "Dll_ole32_dll", "Dll_oleaut32_dll", "Dll_psapi_dll", "Dll_shell32_dll", "Dll_shlwapi_dll", "Dll_urlmon_dll", "Dll_user32_dll", "Dll_vbscript_dll", "Dll_version_dll", "Dll_winhttp_dll", "Dll_wininet_dll", "Dll_winmm_dll", "Dll_ws2_32_dll", "Dll_wsock32_dll", "Dll_wtsapi32_dll", "Api_AdjustTokenPrivileges", "Api_Base64Decode", "Api_BitBlt", "Api_CallNextHookEx", "Api_CallWindowProcA", "Api_CapCreateCaptureWindowA", "Api_CapCreateCaptureWindowW", "Api_CharLowerA", "Api_CharUpperA", "Api_CheckRemoteDebuggerPresent", "Api_CloseClipboard", "Api_CloseHandle", "Api_CoCreateInstance", "Api_CoInitialize", "Api_CoUninitialize", "Api_CompareStringA", "Api_ConnectNamedPipe", "Api_ControlService", "Api_CopyFileA", "Api_CopyFileW", "Api_CreateCompatibleBitmap", "Api_CreateCompatibleDC", "Api_CreateDCA", "Api_CreateDCW", "Api_CreateEventA", "Api_CreateEventW", "Api_CreateFileA", "Api_CreateFileMappingA", "Api_CreateFileMappingW", "Api_CreateFileW", "Api_CreateMutexA", "Api_CreateMutexW", "Api_CreateProcessA", "Api_CreateProcessW", "Api_CreateRemoteThread", "Api_CreateServiceA", "Api_CreateServiceW", "Api_CreateThread", "Api_CreateThreadpoolWork", "Api_CreateWindowExA", "Api_CryptAcquireContextA", "Api_CryptAcquireContextW", "Api_CryptCreateHash", "Api_CryptDecodeObject", "Api_CryptDecrypt", "Api_CryptDeriveKey", "Api_CryptDestroyHash", "Api_CryptDestroyKey", "Api_CryptEncrypt", "Api_CryptExportKey", "Api_CryptGenKey", "Api_CryptHashData", "Api_CryptImportKey", "Api_CryptReleaseContext", "Api_D3D11CreateDevice", "Api_DecodePointer", "Api_DefWindowProcA", "Api_DeleteCriticalSection", "Api_DeleteDC", "Api_DeleteFileA", "Api_DeleteFileW", "Api_DeleteObject", "Api_DeleteService", "Api_DestroyWindow", "Api_DeviceIoControl", "Api_Direct3DCreate9", "Api_DispatchMessageA", "Api_DnsQuery_A", "Api_DnsQuery_W", "Api_EncodePointer", "Api_EnterCriticalSection", "Api_EnumChildWindows", "Api_EnumWindows", "Api_ExitProcess", "Api_ExitThread", "Api_FindClose", "Api_FindFirstFileA", "Api_FindNextFileA", "Api_FindResourceA", "Api_FindWindowA", "Api_FindWindowW", "Api_FlushFileBuffers", "Api_FreeEnvironmentStringsW", "Api_FreeLibrary", "Api_FreeResource", "Api_GdipSaveImageToFile", "Api_GetACP", "Api_GetActiveWindow", "Api_GetAsyncKeyState", "Api_GetCapture", "Api_GetClientRect", "Api_GetClipboardData", "Api_GetCommandLineA", "Api_Line truncated
BIN
View File
Binary file not shown.
+611
View File
@@ -0,0 +1,611 @@
import os, sys, time, re, math, pefile, json, datetime, mmap
import numpy as np
import onnxruntime as ort
from signify.authenticode.signed_file.pe import SignedPEFile
from signify.authenticode.verification_result import AuthenticodeVerificationResult
####################################################################################################
MODEL_FILE = "model.onnx"
FEATURE_FILE = "features.json"
MAX_FILE_SIZE = 4 * 1024 * 1024 * 1024
####################################################################################################
class WinTrust:
@staticmethod
def verify(file_path):
if not file_path or not os.path.exists(file_path) or os.path.getsize(file_path) == 0:
return 0
try:
with open(file_path, 'rb') as f:
with mmap.mmap(f.fileno(), 0, access=mmap.ACCESS_READ) as mm:
pe = SignedPEFile(mm)
if not pe.signatures:
return 0
status, _ = pe.explain_verify()
if status == AuthenticodeVerificationResult.OK:
return 1
except Exception:
pass
return 0
####################################################################################################
class FeatureExtractor:
API_CAT_MAPPING = {
'ProcessControl': {
'CreateProcessA', 'CreateProcessW', 'WinExec', 'ShellExecuteA', 'ShellExecuteW',
'ShellExecuteExW', 'ExitProcess', 'TerminateProcess', 'OpenProcess', 'GetExitCodeProcess',
'SetThreadPriority', 'GetThreadPriority', 'GetCurrentProcess', 'GetCurrentProcessId',
'GetModuleFileNameA', 'GetCommandLineA', 'GetModuleFileNameW', 'GetStartupInfoW'
},
'Injection': {
'VirtualAlloc', 'VirtualAllocEx', 'VirtualProtect', 'VirtualProtectEx', 'WriteProcessMemory',
'ReadProcessMemory', 'CreateRemoteThread', 'QueueUserAPC', 'SetThreadContext', 'GetThreadContext',
'Wow64SetThreadContext', 'NtUnmapViewOfSection', 'ZwUnmapViewOfSection', 'RtlCreateUserThread',
'SetWindowsHookExA', 'SetWindowsHookExW', 'UnhookWindowsHookEx', 'LoadLibraryA', 'LoadLibraryW',
'LoadLibraryExA', 'LoadLibraryExW', 'GetProcAddress', 'GetModuleHandleA', 'GetModuleHandleW',
'FreeLibrary', 'CreateFileMappingA', 'CreateFileMappingW', 'MapViewOfFile', 'UnmapViewOfSection',
'VirtualFree', 'VirtualQuery'
},
'Synchronization': {
'WaitForSingleObject', 'WaitForSingleObjectEx', 'WaitForMultipleObjects', 'WaitForMultipleObjectsEx',
'CreateMutexA', 'CreateMutexW', 'OpenMutexW', 'ReleaseMutex', 'CreateEventA', 'CreateEventW',
'OpenEventW', 'SetEvent', 'ResetEvent', 'EnterCriticalSection', 'LeaveCriticalSection',
'InitializeCriticalSection', 'DeleteCriticalSection', 'Sleep', 'SleepEx',
'InitializeCriticalSectionAndSpinCount', 'InterlockedDecrement', 'InterlockedIncrement',
'InterlockedExchange', 'InterlockedCompareExchange', 'SetTimer', 'KillTimer'
},
'MultiThreading': {
'CreateThread', 'ResumeThread', 'SuspendThread', 'ExitThread', 'TerminateThread',
'GetCurrentThread', 'GetCurrentThreadId', 'TlsAlloc', 'TlsSetValue', 'TlsGetValue',
'CreateThreadpoolWork', 'SubmitThreadpoolWork', 'TlsFree'
},
'Network': {
'socket', 'connect', 'send', 'recv', 'bind', 'listen', 'accept', 'gethostbyname', 'getaddrinfo',
'WSAStartup', 'WSACleanup', 'WSAIoctl', 'WSASocketA', 'WSASocketW', 'closesocket', 'htons',
'InternetOpenA', 'InternetOpenW', 'InternetConnectA', 'InternetConnectW', 'InternetOpenUrlA',
'InternetOpenUrlW', 'HttpOpenRequestA', 'HttpOpenRequestW', 'HttpSendRequestA', 'HttpSendRequestW',
'InternetReadFile', 'URLDownloadToFileA', 'URLDownloadToFileW', 'DnsQuery_A', 'DnsQuery_W'
},
'Encryption': {
'CryptAcquireContextA', 'CryptAcquireContextW', 'CryptCreateHash', 'CryptHashData',
'CryptDeriveKey', 'CryptEncrypt', 'CryptDecrypt', 'CryptDestroyKey', 'CryptDestroyHash',
'CryptReleaseContext', 'CryptGenKey', 'CryptImportKey', 'CryptExportKey'
},
'DataObfuscation': {
'RtlDecompressBuffer', 'MultiByteToWideChar', 'WideCharToMultiByte', 'Base64Decode',
'CryptDecodeObject', 'IsDBCSLeadByte', 'CharUpperA', 'CharLowerA',
'GetStringTypeW', 'LCMapStringW', 'IsValidCodePage', 'DecodePointer', 'EncodePointer'
},
'FileIO': {
'CreateFileA', 'CreateFileW', 'WriteFile', 'ReadFile', 'DeleteFileA', 'DeleteFileW',
'CopyFileA', 'CopyFileW', 'MoveFileA', 'MoveFileW', 'FindFirstFileA', 'FindNextFileA',
'GetTempPathA', 'GetTempPathW', 'GetTempFileNameA', 'GetTempFileNameW', 'SetFileAttributesA',
'SetFileAttributesW', 'DeviceIoControl', 'SetFileTime', 'GetFileSize', 'GetFileSizeEx',
'SetFilePointer', 'FlushFileBuffers', 'ConnectNamedPipe', 'PeekNamedPipe',
'CloseHandle', 'GetFileType', 'SetStdHandle', 'SetFilePointerEx', 'FindClose',
'SetHandleCount', 'SetEndOfFile', 'GetFullPathNameA', 'GetFileAttributesA'
},
'Registry': {
'RegOpenKeyExA', 'RegOpenKeyExW', 'RegSetValueExA', 'RegSetValueExW',
'RegCreateKeyExA', 'RegCreateKeyExW', 'RegDeleteKeyA', 'RegDeleteKeyW',
'RegEnumValueA', 'RegEnumValueW', 'RegQueryValueExA', 'RegQueryValueExW',
'RegDeleteValueA', 'RegDeleteValueW', 'RegCloseKey'
},
'Services': {
'OpenSCManagerA', 'OpenSCManagerW', 'CreateServiceA', 'CreateServiceW',
'StartServiceA', 'StartServiceW', 'ControlService', 'DeleteService'
},
'Privileges': {
'AdjustTokenPrivileges', 'LookupPrivilegeValueA', 'LookupPrivilegeValueW',
'OpenProcessToken', 'NetUserAdd', 'NetLocalGroupAddMembers', 'IsAdmin',
'GetUserNameA', 'GetUserNameW'
},
'Native': {
'RtlUnwind', 'RtlVirtualUnwind', 'RtlCaptureContext', 'RtlLookupFunctionEntry',
'NtClose', 'NtQueryInformationProcess', 'RtlAllocateHeap', 'RtlFreeHeap',
'GetSystemTimeAsFileTime', 'GetLocalTime', 'GlobalMemoryStatus',
'GetVersionExA', 'GetVersionExW', 'GetComputerNameA', 'GetComputerNameW',
'GetLastError', 'GetACP', 'RaiseException', 'SetLastError', 'GetEnvironmentStringsW',
'FreeEnvironmentStringsW', 'GetLocaleInfoA', 'lstrlenA', 'GetVersion', 'GetSystemInfo',
'CompareStringA'
},
'DotNet': {
'_CorExeMain', '_CorDllMain'
},
'AntiDebug': {
'IsDebuggerPresent', 'CheckRemoteDebuggerPresent', 'OutputDebugStringA', 'OutputDebugStringW',
'GetTickCount', 'GetTickCount64', 'QueryPerformanceCounter', 'FindWindowA', 'FindWindowW',
'EnumWindows', 'EnumChildWindows', 'GetWindowRect', 'GetClientRect', 'SetWindowDisplayAffinity',
'UnhandledExceptionFilter', 'SetUnhandledExceptionFilter', 'IsProcessorFeaturePresent',
'SetErrorMode', 'GetTimeZoneInformation', 'GetDriveTypeW', 'GetDiskFreeSpaceA'
},
'Keylogging': {
'GetAsyncKeyState', 'GetKeyState', 'GetKeyboardState', 'MapVirtualKeyA', 'MapVirtualKeyW',
'ToAscii', 'ToAsciiEx', 'ToUnicode', 'ToUnicodeEx', 'GetKeyNameTextA', 'GetForegroundWindow',
'KeybdEvent', 'SendInput', 'MapVirtualKeyExA', 'MapVirtualKeyExW', 'CallNextHookEx'
},
'Input': {
'GetCursorPos', 'SetCursorPos', 'MouseEvent', 'GetDoubleClickTime',
'GetCapture', 'SetCapture'
},
'ScreenCapture': {
'BitBlt', 'StretchBlt', 'GetDC', 'GetWindowDC', 'CreateCompatibleDC', 'CreateCompatibleBitmap',
'GdipSaveImageToFile', 'PrintWindow', 'GetDesktopWindow', 'CreateDCW', 'CreateDCA',
'SelectObject', 'DeleteDC', 'DeleteObject', 'GetDeviceCaps', 'GetSystemMetrics'
},
'Graphics': {
'Direct3DCreate9', 'D3D11CreateDevice'
},
'Audio': {
'WaveInOpen', 'WaveInClose', 'WaveInStart', 'WaveInStop'
},
'Clipboard': {
'OpenClipboard', 'CloseClipboard', 'GetClipboardData', 'SetClipboardData'
},
'Camera': {
'CapCreateCaptureWindowA', 'CapCreateCaptureWindowW'
},
'Memory': {
'HeapAlloc', 'HeapFree', 'HeapSize', 'HeapCreate', 'GetProcessHeap',
'LocalAlloc', 'LocalFree', 'GlobalAlloc', 'GlobalFree', 'GlobalUnlock',
'HeapDestroy', 'GlobalLock', 'SysFreeString', 'SysAllocStringLen'
},
'Resource': {
'LoadResource', 'SizeofResource', 'LockResource', 'FreeResource', 'FindResourceA'
},
'WindowControl': {
'ShowWindow', 'DestroyWindow', 'TranslateMessage', 'DispatchMessageA', 'GetWindow',
'PeekMessageA', 'GetWindowLongA', 'CallWindowProcA', 'SetWindowLongA', 'GetWindowTextA',
'ScreenToClient', 'GetActiveWindow', 'CreateWindowExA', 'DefWindowProcA', 'GetMessageA',
'RegisterClassA', 'UnregisterClassA', 'MessageBoxA'
},
'COM': {
'CoCreateInstance', 'CoUninitialize', 'CoInitialize'
}
}
TARGET_DLLS = {
'kernel32.dll', 'user32.dll', 'gdi32.dll', 'advapi32.dll', 'shell32.dll', 'ntdll.dll', 'hal.dll',
'ws2_32.dll', 'wsock32.dll', 'wininet.dll', 'winhttp.dll', 'urlmon.dll', 'crypt32.dll', 'bcrypt.dll',
'psapi.dll', 'dbghelp.dll', 'imagehlp.dll', 'shlwapi.dll', 'ole32.dll', 'oleaut32.dll', 'comctl32.dll',
'mscoree.dll', 'vbscript.dll', 'netapi32.dll', 'iphlpapi.dll', 'wtsapi32.dll', 'version.dll', 'winmm.dll'
}
BASE_SCHEMA = [
"FileEntropy", "SectionMeanEntropy", "IsExe", "IsDll", "IsDriver", "Is64Bit", "Machine", "Magic",
"CheckSum", "ImageBase", "SizeOfImage", "SizeOfHeaders",
"Characteristics", "DllCharacteristics", "Subsystem", "LoaderFlags",
"MajorLinkerVersion", "MinorLinkerVersion", "SizeOfCode",
"SizeOfInitializedData", "SizeOfUninitializedData", "AddressOfEntryPoint",
"BaseOfCode", "SectionAlignment", "FileAlignment",
"MajorOperatingSystemVersion", "MinorOperatingSystemVersion",
"MajorImageVersion", "MinorImageVersion",
"MajorSubsystemVersion", "MinorSubsystemVersion",
"SizeOfStackReserve", "SizeOfStackCommit", "SizeOfHeapReserve", "SizeOfHeapCommit",
"NumberOfSections", "NumberOfRvaAndSizes", "PointerToSymbolTable", "NumberOfSymbols",
"SizeOfOptionalHeader",
"TextSectionMaxEntropy", "TextSectionMeanEntropy", "TextSizeRatio",
"DataSectionMaxEntropy", "DataSectionMeanEntropy", "DataSizeRatio",
"RsrcSectionMaxEntropy", "RsrcSectionMeanEntropy", "RsrcSizeRatio",
"SectionCount", "ExecutableSections",
"WritableSections", "ReadableSections", "SectionException",
"Char_00000020_Count", "Char_00000020_MeanEntropy",
"Char_00000040_Count", "Char_00000040_MeanEntropy",
"Char_00000080_Count", "Char_00000080_MeanEntropy",
"Char_02000000_Count", "Char_02000000_MeanEntropy",
"Char_20000000_Count", "Char_20000000_MeanEntropy",
"Char_40000000_Count", "Char_40000000_MeanEntropy",
"Char_80000000_Count", "Char_80000000_MeanEntropy",
"IconCount", "ApiCount", "ExportCount", "DebugCount", "ExceptionCount",
"ImportCount", "ImportFunctionCount",
"FileDescriptionLength", "FileVersionLength", "ProductNameLength",
"ProductVersionLength", "CompanyNameLength", "LegalCopyrightLength",
"CommentsLength", "InternalNameLength", "LegalTrademarksLength",
"SpecialBuildLength", "PrivateBuildLength",
"TrustSigned", "IsDebug", "IsPatched", "IsPrivateBuild", "IsPreRelease",
"IsSpecialBuild", "IsAdmin", "IsInstall", "HasTlsCallbacks",
"HasInvalidTimestamp", "HasRelocationDirectory", "HasPacked", "FileTimeException"
]
CAT_SCHEMA = [f"Cat_{k}" for k in sorted(API_CAT_MAPPING.keys())]
DLL_SCHEMA = [f"Dll_{d.replace('.', '_')}" for d in sorted(TARGET_DLLS)]
_ALL_APIS = {api for apis in API_CAT_MAPPING.values() for api in apis}
API_SCHEMA = [f"Api_{api}" for api in sorted(_ALL_APIS)]
SCHEMA = BASE_SCHEMA + CAT_SCHEMA + DLL_SCHEMA + API_SCHEMA
_API_TO_CATS = {}
for _cat, _apis in API_CAT_MAPPING.items():
for _api in _apis:
if _api not in _API_TO_CATS:
_API_TO_CATS[_api] = []
_API_TO_CATS[_api].append(_cat)
PACKERS = {
'upx', 'aspack', 'asprotect', 'pecompact', 'upack', 'fsg', 'mew',
'mpress', 'ezip', 'pklt', 'shrink', 'petite', 'telock',
'themida', 'winlicense', 'tmd', 'vmp', 'enigma', 'obsidium',
'pelock', 'exestealth', 'yoda', 'armadillo', 'zprotect',
'sforce', 'starforce', 'qihoo', 'wisevec', 'megastop',
}
INSTALLER_SIGS = [b"Nullsoft.NSIS", b"Inno Setup", b"7-Zip.7zip", b"InstallShield"]
####################################################################################################
@staticmethod
def _safe_float(val):
try:
f = float(val)
if math.isinf(f) or math.isnan(f): return 0.0
return f
except Exception:
return 0.0
@staticmethod
def _calc_entropy(data):
if not data: return 0.0
arr = np.frombuffer(data, dtype=np.uint8)
counts = np.bincount(arr, minlength=256)
probs = counts[counts > 0] / len(arr)
return float(-np.sum(probs * np.log2(probs)))
@staticmethod
def _safe_div(n, d):
return float(n) / d if d > 0 else 0.0
####################################################################################################
@classmethod
def extract(cls, file_path):
fts = {k: 0.0 for k in cls.SCHEMA}
pe = 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()
pe = pefile.PE(data=file_bytes, fast_load=True)
fts['TrustSigned'] = WinTrust.verify(file_path)
fh = pe.FILE_HEADER
fts['Machine'] = fh.Machine
fts['NumberOfSections'] = fh.NumberOfSections
fts['TimeDateStamp'] = fh.TimeDateStamp
fts['PointerToSymbolTable'] = fh.PointerToSymbolTable
fts['NumberOfSymbols'] = fh.NumberOfSymbols
fts['SizeOfOptionalHeader'] = fh.SizeOfOptionalHeader
fts['Characteristics'] = fh.Characteristics
curr_ts = datetime.datetime.utcnow().timestamp()
fts['HasInvalidTimestamp'] = 1 if (fh.TimeDateStamp < 631152000 or fh.TimeDateStamp > curr_ts + 2592000) else 0
fts['FileTimeException'] = 1 if fh.TimeDateStamp == 0 else 0
if hasattr(pe, 'OPTIONAL_HEADER'):
op = pe.OPTIONAL_HEADER
fts['Magic'] = getattr(op, 'Magic', 0)
fts['MajorLinkerVersion'] = getattr(op, 'MajorLinkerVersion', 0)
fts['MinorLinkerVersion'] = getattr(op, 'MinorLinkerVersion', 0)
fts['SizeOfCode'] = getattr(op, 'SizeOfCode', 0)
fts['SizeOfInitializedData'] = getattr(op, 'SizeOfInitializedData', 0)
fts['SizeOfUninitializedData'] = getattr(op, 'SizeOfUninitializedData', 0)
fts['AddressOfEntryPoint'] = getattr(op, 'AddressOfEntryPoint', 0)
fts['BaseOfCode'] = getattr(op, 'BaseOfCode', 0)
fts['ImageBase'] = getattr(op, 'ImageBase', 0)
fts['SectionAlignment'] = getattr(op, 'SectionAlignment', 0)
fts['FileAlignment'] = getattr(op, 'FileAlignment', 0)
fts['MajorOperatingSystemVersion'] = getattr(op, 'MajorOperatingSystemVersion', 0)
fts['MinorOperatingSystemVersion'] = getattr(op, 'MinorOperatingSystemVersion', 0)
fts['MajorImageVersion'] = getattr(op, 'MajorImageVersion', 0)
fts['MinorImageVersion'] = getattr(op, 'MinorImageVersion', 0)
fts['MajorSubsystemVersion'] = getattr(op, 'MajorSubsystemVersion', 0)
fts['MinorSubsystemVersion'] = getattr(op, 'MinorSubsystemVersion', 0)
fts['SizeOfImage'] = getattr(op, 'SizeOfImage', 0)
fts['SizeOfHeaders'] = getattr(op, 'SizeOfHeaders', 0)
fts['CheckSum'] = getattr(op, 'CheckSum', 0)
fts['Subsystem'] = getattr(op, 'Subsystem', 0)
fts['DllCharacteristics'] = getattr(op, 'DllCharacteristics', 0)
fts['SizeOfStackReserve'] = getattr(op, 'SizeOfStackReserve', 0)
fts['SizeOfStackCommit'] = getattr(op, 'SizeOfStackCommit', 0)
fts['SizeOfHeapReserve'] = getattr(op, 'SizeOfHeapReserve', 0)
fts['SizeOfHeapCommit'] = getattr(op, 'SizeOfHeapCommit', 0)
fts['LoaderFlags'] = getattr(op, 'LoaderFlags', 0)
fts['NumberOfRvaAndSizes'] = getattr(op, 'NumberOfRvaAndSizes', 0)
fts['Is64Bit'] = 1 if fh.Machine in (0x8664, 0xAA64, 0x0200) else 0
fts['IsExe'] = 1 if pe.is_exe() else 0
fts['IsDll'] = 1 if pe.is_dll() else 0
fts['IsDriver'] = 1 if fts['Subsystem'] == 1 else 0
if hasattr(op, 'DATA_DIRECTORY'):
reloc_idx = pefile.DIRECTORY_ENTRY.get('IMAGE_DIRECTORY_ENTRY_BASERELOC', 5)
if len(op.DATA_DIRECTORY) > reloc_idx and op.DATA_DIRECTORY[reloc_idx].Size > 0:
fts['HasRelocationDirectory'] = 1
try:
fts['FileEntropy'] = cls._calc_entropy(file_bytes)
except Exception:
pass
sec_entropies = []
text_entropies = []
data_entropies = []
rsrc_entropies = []
char_flags = [0x00000020, 0x00000040, 0x00000080, 0x02000000, 0x20000000, 0x40000000, 0x80000000]
if hasattr(pe, 'sections'):
fts['SectionCount'] = len(pe.sections)
for section in pe.sections:
try:
name = section.Name.decode('ascii', 'ignore').strip('\x00')
except Exception:
name = ""
try:
s_data = section.get_data()
s_entropy = cls._calc_entropy(s_data)
except Exception:
s_data = b""
s_entropy = 0.0
sec_entropies.append(s_entropy)
if any(p in name.lower() for p in cls.PACKERS): fts['HasPacked'] = 1
if section.Characteristics & 0x20000000: fts['ExecutableSections'] += 1
if section.Characteristics & 0x80000000: fts['WritableSections'] += 1
if section.Characteristics & 0x40000000: fts['ReadableSections'] += 1
if section.SizeOfRawData + section.PointerToRawData > fsize: fts['SectionException'] = 1
for flag in char_flags:
if section.Characteristics & flag:
fts[f'Char_{flag:08X}_Count'] += 1
fts[f'Char_{flag:08X}_MeanEntropy'] += s_entropy
ratio = cls._safe_div(section.SizeOfRawData, fsize)
if name.startswith('.text'):
text_entropies.append(s_entropy)
fts['TextSizeRatio'] += ratio
elif name.startswith('.data'):
data_entropies.append(s_entropy)
fts['DataSizeRatio'] += ratio
elif name.startswith('.rsrc'):
rsrc_entropies.append(s_entropy)
fts['RsrcSizeRatio'] += ratio
if b"requireAdministrator" in s_data: fts['IsAdmin'] = 1
for sig in cls.INSTALLER_SIGS:
if sig in s_data:
fts['IsInstall'] = 1
break
fts['SectionMeanEntropy'] = cls._safe_div(sum(sec_entropies), len(sec_entropies))
fts['TextSectionMaxEntropy'] = max(text_entropies) if text_entropies else 0.0
fts['TextSectionMeanEntropy'] = cls._safe_div(sum(text_entropies), len(text_entropies))
fts['DataSectionMaxEntropy'] = max(data_entropies) if data_entropies else 0.0
fts['DataSectionMeanEntropy'] = cls._safe_div(sum(data_entropies), len(data_entropies))
fts['RsrcSectionMaxEntropy'] = max(rsrc_entropies) if rsrc_entropies else 0.0
fts['RsrcSectionMeanEntropy'] = cls._safe_div(sum(rsrc_entropies), len(rsrc_entropies))
for flag in char_flags:
fts[f'Char_{flag:08X}_MeanEntropy'] = cls._safe_div(fts[f'Char_{flag:08X}_MeanEntropy'], fts[f'Char_{flag:08X}_Count'])
try:
pe.parse_data_directories(directories=[
pefile.DIRECTORY_ENTRY['IMAGE_DIRECTORY_ENTRY_IMPORT'],
pefile.DIRECTORY_ENTRY['IMAGE_DIRECTORY_ENTRY_EXPORT'],
pefile.DIRECTORY_ENTRY['IMAGE_DIRECTORY_ENTRY_RESOURCE'],
pefile.DIRECTORY_ENTRY['IMAGE_DIRECTORY_ENTRY_DEBUG'],
pefile.DIRECTORY_ENTRY['IMAGE_DIRECTORY_ENTRY_TLS']
])
except Exception:
pass
if hasattr(pe, 'DIRECTORY_ENTRY_TLS') and hasattr(pe.DIRECTORY_ENTRY_TLS, 'struct'):
if getattr(pe.DIRECTORY_ENTRY_TLS.struct, 'AddressOfCallBacks', 0) != 0:
fts['HasTlsCallbacks'] = 1
if hasattr(pe, 'VS_FIXEDFILEINFO') and len(pe.VS_FIXEDFILEINFO) > 0:
flags = getattr(pe.VS_FIXEDFILEINFO[0], 'FileFlags', 0)
fts['IsDebug'] = 1 if flags & 0x1 else 0
fts['IsPreRelease'] = 1 if flags & 0x2 else 0
fts['IsPatched'] = 1 if flags & 0x4 else 0
fts['IsPrivateBuild'] = 1 if flags & 0x8 else 0
fts['IsSpecialBuild'] = 1 if flags & 0x20 else 0
if hasattr(pe, 'FileInfo'):
for fileinfo_list in pe.FileInfo:
for fileinfo in fileinfo_list:
if getattr(fileinfo, 'name', '') in ('StringFileInfo', b'StringFileInfo'):
for st in getattr(fileinfo, 'StringTable', []):
for key, val in st.entries.items():
try:
k = key.decode('utf-8', 'ignore') if isinstance(key, bytes) else str(key)
v = val.decode('utf-8', 'ignore') if isinstance(val, bytes) else str(val)
length = len(v)
if k == 'FileDescription': fts['FileDescriptionLength'] = length
elif k == 'FileVersion': fts['FileVersionLength'] = length
elif k == 'ProductName': fts['ProductNameLength'] = length
elif k == 'ProductVersion': fts['ProductVersionLength'] = length
elif k == 'CompanyName': fts['CompanyNameLength'] = length
elif k == 'LegalCopyright': fts['LegalCopyrightLength'] = length
elif k == 'Comments': fts['CommentsLength'] = length
elif k == 'InternalName': fts['InternalNameLength'] = length
elif k == 'LegalTrademarks': fts['LegalTrademarksLength'] = length
elif k == 'SpecialBuild': fts['SpecialBuildLength'] = length
elif k == 'PrivateBuild': fts['PrivateBuildLength'] = length
except Exception:
continue
if hasattr(pe, 'DIRECTORY_ENTRY_IMPORT'):
fts['ImportCount'] = len(pe.DIRECTORY_ENTRY_IMPORT)
for entry in pe.DIRECTORY_ENTRY_IMPORT:
if getattr(entry, 'dll', None):
try:
dll_name = entry.dll.decode('ascii', 'ignore').lower()
if dll_name in cls.TARGET_DLLS:
fts[f"Dll_{dll_name.replace('.', '_')}"] = 1.0
except Exception:
pass
imports = getattr(entry, 'imports', [])
for imp in imports:
fts['ImportFunctionCount'] += 1
if not getattr(imp, 'name', None): continue
try:
func_name = imp.name.decode('ascii', 'ignore')
if func_name in cls._ALL_APIS:
fts[f'Api_{func_name}'] += 1
fts['ApiCount'] += 1
for cat in cls._API_TO_CATS.get(func_name, []):
fts[f'Cat_{cat}'] += 1
except Exception:
continue
if hasattr(pe, 'DIRECTORY_ENTRY_EXPORT') and hasattr(pe.DIRECTORY_ENTRY_EXPORT, 'symbols'):
fts['ExportCount'] = len(pe.DIRECTORY_ENTRY_EXPORT.symbols)
if hasattr(pe, 'DIRECTORY_ENTRY_RESOURCE'):
for entry in pe.DIRECTORY_ENTRY_RESOURCE.entries:
if getattr(entry, 'id', None) == 3 and hasattr(entry, 'directory'):
fts['IconCount'] += len(getattr(entry.directory, 'entries', []))
if hasattr(pe, 'OPTIONAL_HEADER') and hasattr(pe.OPTIONAL_HEADER, 'DATA_DIRECTORY') and len(pe.OPTIONAL_HEADER.DATA_DIRECTORY) > 3:
exc_dir = pe.OPTIONAL_HEADER.DATA_DIRECTORY[3]
if exc_dir.Size > 0:
fts['ExceptionCount'] = exc_dir.Size // 12 if fts['Machine'] in (0x8664, 0xAA64) else 0
if hasattr(pe, 'DIRECTORY_ENTRY_DEBUG'):
fts['DebugCount'] = len(pe.DIRECTORY_ENTRY_DEBUG)
for k in fts:
fts[k] = cls._safe_float(fts[k])
return fts
except Exception:
return None
finally:
if pe:
pe.close()
####################################################################################################
class ModelPredictor:
def __init__(self, model_path, feature_path):
self.sess = None
self.input_name = None
self.feature_order = []
self.model_features = self._load_features(feature_path)
self._load_model(model_path)
self._build_input_map()
def _load_features(self, path):
if os.path.exists(path):
try:
with open(path, 'r') as f:
return json.load(f)
except Exception:
pass
return [re.sub(r'[^A-Za-z0-9_]+', '', f) for f in FeatureExtractor.SCHEMA]
def _load_model(self, model_path):
if not os.path.exists(model_path):
raise FileNotFoundError(f"Model missing: {model_path}")
self.sess = ort.InferenceSession(model_path, providers=['CPUExecutionProvider'])
model_inputs = self.sess.get_inputs()
self.input_name = model_inputs[0].name
expected_dim = model_inputs[0].shape[1]
current_dim = len(self.model_features)
if current_dim != expected_dim:
raise ValueError(f"Dimension Mismatch: Expected {expected_dim}, got {current_dim}.")
def _build_input_map(self):
local_map = {re.sub(r'[^A-Za-z0-9_]+', '', raw_name): raw_name for raw_name in FeatureExtractor.SCHEMA}
self.feature_order = [local_map.get(req_feat) for req_feat in self.model_features]
def predict(self, raw_data):
vec_data = [raw_data.get(key, 0.0) if key else 0.0 for key in self.feature_order]
vec = np.array(vec_data, dtype=np.float32).reshape(1, -1)
outputs = self.sess.run(None, {self.input_name: vec})
if len(outputs) > 1:
result = outputs[1]
if isinstance(result, list) and len(result) > 0:
prob_dict = result[0]
if hasattr(prob_dict, 'get'):
return float(prob_dict.get(1, prob_dict.get('1', 0.0)))
elif isinstance(result, np.ndarray):
if result.ndim == 2 and result.shape[1] > 1:
return float(result[0][1])
return 0.0
####################################################################################################
def get_threat_name(prob_float):
prob_pct = min(int(prob_float * 100), 99)
if prob_pct >= 80:
return f"General:WinPE/Malware.{prob_pct}!ml"
elif prob_pct >= 50:
return f"General:WinPE/Suspicious.{prob_pct}!ml"
else:
return "Undetected"
def scan_target(target, predictor):
if not os.path.exists(target):
print(json.dumps({"result": "error", "prob": 0.0, "file": target, "details": "Path not found"}))
sys.stdout.flush()
return
try:
data = FeatureExtractor.extract(target)
if not data:
print(json.dumps({"result": "error", "prob": 0.0, "file": target, "details": "Extraction failed"}))
sys.stdout.flush()
return
prob = predictor.predict(data)
label = get_threat_name(prob)
print(json.dumps({"result": label, "prob": round(prob, 4), "file": target}))
sys.stdout.flush()
except Exception as e:
print(json.dumps({"result": "error", "prob": 0.0, "file": target, "details": str(e)}))
sys.stdout.flush()
####################################################################################################
if __name__ == "__main__":
base_dir = os.path.dirname(sys.executable) if getattr(sys, 'frozen', False) else os.path.dirname(os.path.abspath(__file__))
model_path = os.path.join(base_dir, MODEL_FILE)
feature_path = os.path.join(base_dir, FEATURE_FILE)
try:
predictor = ModelPredictor(model_path, feature_path)
except Exception as e:
print(json.dumps({"result": "fatal", "details": str(e)}))
sys.stdout.flush()
sys.exit(1)
if len(sys.argv) > 1:
for path in sys.argv[1:]:
scan_target(path.strip(), predictor)
else:
for line in sys.stdin:
target = line.strip()
if not target or target.lower() in ['q', 'exit']:
break
scan_target(target, predictor)
+4
View File
@@ -0,0 +1,4 @@
onnxruntime
numpy
pefile
signify[full]
File renamed without changes.
@@ -1,5 +1,5 @@
from PIL import Image
import numpy, pefile, os
import numpy, os
####################################################################################################
File renamed without changes.
@@ -11,7 +11,7 @@ tf.keras.mixed_precision.set_global_policy('mixed_float16')
####################################################################################################
def load_dataset(data_dir, image_size=(224, 224), val_split=0.00001, batch_size=256, color_mode="grayscale"):
def load_dataset(data_dir, image_size=(224, 224), val_split=0.0001, batch_size=256, color_mode="grayscale"):
channels = 1 if color_mode == "grayscale" else 3
file_paths, labels, class_indices = get_file_list(data_dir)
@@ -24,14 +24,13 @@ def load_dataset(data_dir, image_size=(224, 224), val_split=0.00001, batch_size=
train_labels = labels[num_val:]
val_files = file_paths[:num_val]
val_labels = labels[:num_val]
num_classes = len(class_indices)
train_ds = tf.data.Dataset.from_tensor_slices((train_files, train_labels))
train_ds = train_ds.map(lambda f, l: parse_fn(f, l, image_size, channels, num_classes), num_parallel_calls=tf.data.AUTOTUNE)
train_ds = train_ds.map(lambda f, l: parse_fn(f, l, image_size, channels), num_parallel_calls=tf.data.AUTOTUNE)
train_ds = train_ds.batch(batch_size).prefetch(tf.data.AUTOTUNE)
val_ds = tf.data.Dataset.from_tensor_slices((val_files, val_labels))
val_ds = val_ds.map(lambda f, l: parse_fn(f, l, image_size, channels, num_classes), num_parallel_calls=tf.data.AUTOTUNE)
val_ds = val_ds.map(lambda f, l: parse_fn(f, l, image_size, channels), num_parallel_calls=tf.data.AUTOTUNE)
val_ds = val_ds.batch(batch_size).prefetch(tf.data.AUTOTUNE)
train_ds.class_indices = class_indices
@@ -39,12 +38,12 @@ def load_dataset(data_dir, image_size=(224, 224), val_split=0.00001, batch_size=
####################################################################################################
def parse_fn(filename, label, image_size, channels, num_classes):
def parse_fn(filename, label, image_size, channels):
img = tf.io.read_file(filename)
img = tf.image.decode_image(img, channels=channels, expand_animations=False)
img = tf.cond(tf.reduce_all(tf.equal(tf.shape(img)[:2], image_size)), lambda: tf.cast(img, tf.float32), lambda: tf.image.resize(img, image_size))
img = tf.cast(img, tf.float32) / 255.0
label = tf.one_hot(label, depth=num_classes)
label = tf.cast(label, tf.float32)
return img, label
def get_file_list(directory):
@@ -99,7 +98,7 @@ def coordinate_attention(x, reduction):
return layers.Multiply()([x, x_h_sigmoid, x_w_sigmoid])
def build_model(input_shape, num_classes):
def build_model(input_shape):
i = layers.Input(shape=input_shape)
x = multi_conv(i, strides=1)
x = gated_conv(x, strides=2)
@@ -112,7 +111,7 @@ def build_model(input_shape, num_classes):
x = layers.GlobalMaxPooling2D()(x)
x = layers.Dense(x.shape[-1] // 2, activation='gelu', dtype='float32')(x)
o = layers.Dense(num_classes, activation='softmax', dtype='float32')(x)
o = layers.Dense(1, activation='sigmoid', dtype='float32')(x)
return models.Model(i, o)
####################################################################################################
@@ -124,18 +123,20 @@ def calculate_lr_cosine_sq(epoch, total_epochs=30, lr_start=5e-4, lr_end=5e-7):
def lr_scheduler(epoch):
return calculate_lr_cosine_sq(epoch)
def categorical_focal_loss(gamma=2.0, alpha=0.25):
def binary_focal_loss(gamma=2.0, alpha=0.25):
def focal_loss(y_true, y_pred):
y_true = tf.cast(y_true, tf.float32)
y_pred = tf.clip_by_value(y_pred, tf.keras.backend.epsilon(), 1.0 - tf.keras.backend.epsilon())
cross_entropy = -y_true * tf.math.log(y_pred)
loss = alpha * tf.math.pow(1.0 - y_pred, gamma) * cross_entropy
return tf.reduce_sum(loss, axis=-1)
p_t = tf.where(tf.equal(y_true, 1), y_pred, 1 - y_pred)
alpha_t = tf.where(tf.equal(y_true, 1), alpha, 1 - alpha)
loss = -alpha_t * tf.math.pow(1.0 - p_t, gamma) * tf.math.log(p_t)
return tf.reduce_mean(loss)
return focal_loss
class CustomModelCheckpoint(callbacks.Callback):
def on_epoch_end(self, epoch, logs=False):
models.save_model(self.model, f"PYAS_Model_Epoch_{epoch + 1}.h5")
spec = (tf.TensorSpec((None,) + train_ds.image_shape, tf.float32),)
spec = (tf.TensorSpec((None,) + self.model.input.shape[1:], tf.float32),)
model_proto, _ = tf2onnx.convert.from_keras(self.model, input_signature=spec, opset=17)
with open(f"PYAS_Model_Epoch_{epoch + 1}.onnx", "wb") as f:
f.write(model_proto.SerializeToString())
@@ -148,16 +149,17 @@ for imgs, _ in train_ds.take(1):
break
try:
model = models.load_model('PYAS_Model_Epoch_0.h5')
model = models.load_model('PYAS_Model_Epoch_0.h5', compile=False)
print("Load model from disk")
except:
print("Creating a new model")
model = build_model(train_ds.image_shape, len(train_ds.class_indices))
model = build_model(train_ds.image_shape)
print(f"Total parameters: {model.count_params()}")
model.compile(optimizer='adam', loss=categorical_focal_loss(gamma=2.0, alpha=0.25),
metrics=['accuracy', metrics.Precision(name='precision'), metrics.Recall(name='recall'),
model.compile(optimizer='adam', loss=binary_focal_loss(gamma=2.0, alpha=0.25),
metrics=[metrics.BinaryAccuracy(name='accuracy'), metrics.AUC(name='auc'),
metrics.Precision(name='precision'), metrics.Recall(name='recall'),
metrics.FalsePositives(name='fp'), metrics.TrueNegatives(name='tn')])
model.fit(train_ds, epochs=30, callbacks=[CustomModelCheckpoint(), callbacks.LearningRateScheduler(lr_scheduler)], validation_data=val_ds)
model.fit(train_ds, epochs=30, callbacks=[CustomModelCheckpoint(), callbacks.LearningRateScheduler(lr_scheduler)], validation_data=val_ds, class_weight={0: 10.0, 1: 1.0})
input('Training Complete')
+721
View File
@@ -0,0 +1,721 @@
import os, sys, time, ctypes, sqlite3, datetime, pefile, math, hashlib
import numpy as np
import ctypes.wintypes
####################################################################################################
DB_PATH = "pe_features.db"
BATCH_SIZE = 1000
MAX_FILE_SIZE = 4 * 1024 * 1024 * 1024
TARGET_EXTENSIONS = {
'.exe', '.dll', '.sys', '.ocx', '.scr', '.efi', '.acm', '.ax', '.cpl', '.drv', '.com', '.mui', '.pyd'
}
####################################################################################################
class WINTRUST_FILE_INFO(ctypes.Structure):
_fields_ = [
("cbStruct", ctypes.wintypes.DWORD),
("pcwszFilePath", ctypes.wintypes.LPCWSTR),
("hFile", ctypes.wintypes.HANDLE),
("pgKnownSubject", ctypes.wintypes.LPVOID)
]
class WINTRUST_DATA_UNION(ctypes.Union):
_fields_ = [
("pFile", ctypes.POINTER(WINTRUST_FILE_INFO)),
("pCatalog", ctypes.wintypes.LPVOID),
("pBlob", ctypes.wintypes.LPVOID),
("pSgnr", ctypes.wintypes.LPVOID),
("pCert", ctypes.wintypes.LPVOID)
]
class WINTRUST_DATA(ctypes.Structure):
_fields_ = [
("cbStruct", ctypes.wintypes.DWORD),
("pPolicyCallbackData", ctypes.wintypes.LPVOID),
("pSIPClientData", ctypes.wintypes.LPVOID),
("dwUIChoice", ctypes.wintypes.DWORD),
("fdwRevocationChecks", ctypes.wintypes.DWORD),
("dwUnionChoice", ctypes.wintypes.DWORD),
("u", WINTRUST_DATA_UNION),
("dwStateAction", ctypes.wintypes.DWORD),
("hWVTStateData", ctypes.wintypes.HANDLE),
("pwszURLReference", ctypes.wintypes.LPCWSTR),
("dwProvFlags", ctypes.wintypes.DWORD),
("dwUIContext", ctypes.wintypes.DWORD),
("pSignatureSettings", ctypes.wintypes.LPVOID)
]
class GUID(ctypes.Structure):
_fields_ = [
("Data1", ctypes.wintypes.DWORD),
("Data2", ctypes.wintypes.WORD),
("Data3", ctypes.wintypes.WORD),
("Data4", ctypes.c_ubyte * 8)
]
V2_GUID = GUID(0x00AAC56B, 0xCD44, 0x11D0, (ctypes.c_ubyte * 8)(0x8C, 0xC2, 0x00, 0xC0, 0x4F, 0xC2, 0x95, 0xEE))
####################################################################################################
def verify_signature(file_path):
if os.name != 'nt':
return 0
try:
wintrust = ctypes.windll.wintrust
wintrust.WinVerifyTrust.argtypes = [ctypes.wintypes.HWND, ctypes.POINTER(GUID), ctypes.wintypes.LPVOID]
wintrust.WinVerifyTrust.restype = ctypes.wintypes.LONG
abs_path = os.path.abspath(file_path)
fi = WINTRUST_FILE_INFO()
fi.cbStruct = ctypes.sizeof(WINTRUST_FILE_INFO)
fi.pcwszFilePath = abs_path
fi.hFile = None
fi.pgKnownSubject = None
wt_union = WINTRUST_DATA_UNION()
wt_union.pFile = ctypes.pointer(fi)
td = WINTRUST_DATA()
td.cbStruct = ctypes.sizeof(WINTRUST_DATA)
td.pPolicyCallbackData = None
td.pSIPClientData = None
td.dwUIChoice = 2
td.fdwRevocationChecks = 0
td.dwUnionChoice = 1
td.u = wt_union
td.dwStateAction = 0
td.hWVTStateData = None
td.pwszURLReference = None
td.dwProvFlags = 0
td.dwUIContext = 0
td.pSignatureSettings = None
return 1 if wintrust.WinVerifyTrust(None, ctypes.byref(V2_GUID), ctypes.byref(td)) == 0 else 0
except Exception as e:
print(e)
return 0
####################################################################################################
class Features:
API_CAT_MAPPING = {
'ProcessControl': {
'CreateProcessA', 'CreateProcessW', 'WinExec', 'ShellExecuteA', 'ShellExecuteW',
'ShellExecuteExW', 'ExitProcess', 'TerminateProcess', 'OpenProcess', 'GetExitCodeProcess',
'SetThreadPriority', 'GetThreadPriority', 'GetCurrentProcess', 'GetCurrentProcessId',
'GetModuleFileNameA', 'GetCommandLineA', 'GetModuleFileNameW', 'GetStartupInfoW'
},
'Injection': {
'VirtualAlloc', 'VirtualAllocEx', 'VirtualProtect', 'VirtualProtectEx', 'WriteProcessMemory',
'ReadProcessMemory', 'CreateRemoteThread', 'QueueUserAPC', 'SetThreadContext', 'GetThreadContext',
'Wow64SetThreadContext', 'NtUnmapViewOfSection', 'ZwUnmapViewOfSection', 'RtlCreateUserThread',
'SetWindowsHookExA', 'SetWindowsHookExW', 'UnhookWindowsHookEx', 'LoadLibraryA', 'LoadLibraryW',
'LoadLibraryExA', 'LoadLibraryExW', 'GetProcAddress', 'GetModuleHandleA', 'GetModuleHandleW',
'FreeLibrary', 'CreateFileMappingA', 'CreateFileMappingW', 'MapViewOfFile', 'UnmapViewOfSection',
'VirtualFree', 'VirtualQuery'
},
'Synchronization': {
'WaitForSingleObject', 'WaitForSingleObjectEx', 'WaitForMultipleObjects', 'WaitForMultipleObjectsEx',
'CreateMutexA', 'CreateMutexW', 'OpenMutexW', 'ReleaseMutex', 'CreateEventA', 'CreateEventW',
'OpenEventW', 'SetEvent', 'ResetEvent', 'EnterCriticalSection', 'LeaveCriticalSection',
'InitializeCriticalSection', 'DeleteCriticalSection', 'Sleep', 'SleepEx',
'InitializeCriticalSectionAndSpinCount', 'InterlockedDecrement', 'InterlockedIncrement',
'InterlockedExchange', 'InterlockedCompareExchange', 'SetTimer', 'KillTimer'
},
'MultiThreading': {
'CreateThread', 'ResumeThread', 'SuspendThread', 'ExitThread', 'TerminateThread',
'GetCurrentThread', 'GetCurrentThreadId', 'TlsAlloc', 'TlsSetValue', 'TlsGetValue',
'CreateThreadpoolWork', 'SubmitThreadpoolWork', 'TlsFree'
},
'Network': {
'socket', 'connect', 'send', 'recv', 'bind', 'listen', 'accept', 'gethostbyname', 'getaddrinfo',
'WSAStartup', 'WSACleanup', 'WSAIoctl', 'WSASocketA', 'WSASocketW', 'closesocket', 'htons',
'InternetOpenA', 'InternetOpenW', 'InternetConnectA', 'InternetConnectW', 'InternetOpenUrlA',
'InternetOpenUrlW', 'HttpOpenRequestA', 'HttpOpenRequestW', 'HttpSendRequestA', 'HttpSendRequestW',
'InternetReadFile', 'URLDownloadToFileA', 'URLDownloadToFileW', 'DnsQuery_A', 'DnsQuery_W'
},
'Encryption': {
'CryptAcquireContextA', 'CryptAcquireContextW', 'CryptCreateHash', 'CryptHashData',
'CryptDeriveKey', 'CryptEncrypt', 'CryptDecrypt', 'CryptDestroyKey', 'CryptDestroyHash',
'CryptReleaseContext', 'CryptGenKey', 'CryptImportKey', 'CryptExportKey'
},
'DataObfuscation': {
'RtlDecompressBuffer', 'MultiByteToWideChar', 'WideCharToMultiByte', 'Base64Decode',
'CryptDecodeObject', 'IsDBCSLeadByte', 'CharUpperA', 'CharLowerA',
'GetStringTypeW', 'LCMapStringW', 'IsValidCodePage', 'DecodePointer', 'EncodePointer'
},
'FileIO': {
'CreateFileA', 'CreateFileW', 'WriteFile', 'ReadFile', 'DeleteFileA', 'DeleteFileW',
'CopyFileA', 'CopyFileW', 'MoveFileA', 'MoveFileW', 'FindFirstFileA', 'FindNextFileA',
'GetTempPathA', 'GetTempPathW', 'GetTempFileNameA', 'GetTempFileNameW', 'SetFileAttributesA',
'SetFileAttributesW', 'DeviceIoControl', 'SetFileTime', 'GetFileSize', 'GetFileSizeEx',
'SetFilePointer', 'FlushFileBuffers', 'ConnectNamedPipe', 'PeekNamedPipe',
'CloseHandle', 'GetFileType', 'SetStdHandle', 'SetFilePointerEx', 'FindClose',
'SetHandleCount', 'SetEndOfFile', 'GetFullPathNameA', 'GetFileAttributesA'
},
'Registry': {
'RegOpenKeyExA', 'RegOpenKeyExW', 'RegSetValueExA', 'RegSetValueExW',
'RegCreateKeyExA', 'RegCreateKeyExW', 'RegDeleteKeyA', 'RegDeleteKeyW',
'RegEnumValueA', 'RegEnumValueW', 'RegQueryValueExA', 'RegQueryValueExW',
'RegDeleteValueA', 'RegDeleteValueW', 'RegCloseKey'
},
'Services': {
'OpenSCManagerA', 'OpenSCManagerW', 'CreateServiceA', 'CreateServiceW',
'StartServiceA', 'StartServiceW', 'ControlService', 'DeleteService'
},
'Privileges': {
'AdjustTokenPrivileges', 'LookupPrivilegeValueA', 'LookupPrivilegeValueW',
'OpenProcessToken', 'NetUserAdd', 'NetLocalGroupAddMembers', 'IsAdmin',
'GetUserNameA', 'GetUserNameW'
},
'Native': {
'RtlUnwind', 'RtlVirtualUnwind', 'RtlCaptureContext', 'RtlLookupFunctionEntry',
'NtClose', 'NtQueryInformationProcess', 'RtlAllocateHeap', 'RtlFreeHeap',
'GetSystemTimeAsFileTime', 'GetLocalTime', 'GlobalMemoryStatus',
'GetVersionExA', 'GetVersionExW', 'GetComputerNameA', 'GetComputerNameW',
'GetLastError', 'GetACP', 'RaiseException', 'SetLastError', 'GetEnvironmentStringsW',
'FreeEnvironmentStringsW', 'GetLocaleInfoA', 'lstrlenA', 'GetVersion', 'GetSystemInfo',
'CompareStringA'
},
'DotNet': {
'_CorExeMain', '_CorDllMain'
},
'AntiDebug': {
'IsDebuggerPresent', 'CheckRemoteDebuggerPresent', 'OutputDebugStringA', 'OutputDebugStringW',
'GetTickCount', 'GetTickCount64', 'QueryPerformanceCounter', 'FindWindowA', 'FindWindowW',
'EnumWindows', 'EnumChildWindows', 'GetWindowRect', 'GetClientRect', 'SetWindowDisplayAffinity',
'UnhandledExceptionFilter', 'SetUnhandledExceptionFilter', 'IsProcessorFeaturePresent',
'SetErrorMode', 'GetTimeZoneInformation', 'GetDriveTypeW', 'GetDiskFreeSpaceA'
},
'Keylogging': {
'GetAsyncKeyState', 'GetKeyState', 'GetKeyboardState', 'MapVirtualKeyA', 'MapVirtualKeyW',
'ToAscii', 'ToAsciiEx', 'ToUnicode', 'ToUnicodeEx', 'GetKeyNameTextA', 'GetForegroundWindow',
'KeybdEvent', 'SendInput', 'MapVirtualKeyExA', 'MapVirtualKeyExW', 'CallNextHookEx'
},
'Input': {
'GetCursorPos', 'SetCursorPos', 'MouseEvent', 'GetDoubleClickTime',
'GetCapture', 'SetCapture'
},
'ScreenCapture': {
'BitBlt', 'StretchBlt', 'GetDC', 'GetWindowDC', 'CreateCompatibleDC', 'CreateCompatibleBitmap',
'GdipSaveImageToFile', 'PrintWindow', 'GetDesktopWindow', 'CreateDCW', 'CreateDCA',
'SelectObject', 'DeleteDC', 'DeleteObject', 'GetDeviceCaps', 'GetSystemMetrics'
},
'Graphics': {
'Direct3DCreate9', 'D3D11CreateDevice'
},
'Audio': {
'WaveInOpen', 'WaveInClose', 'WaveInStart', 'WaveInStop'
},
'Clipboard': {
'OpenClipboard', 'CloseClipboard', 'GetClipboardData', 'SetClipboardData'
},
'Camera': {
'CapCreateCaptureWindowA', 'CapCreateCaptureWindowW'
},
'Memory': {
'HeapAlloc', 'HeapFree', 'HeapSize', 'HeapCreate', 'GetProcessHeap',
'LocalAlloc', 'LocalFree', 'GlobalAlloc', 'GlobalFree', 'GlobalUnlock',
'HeapDestroy', 'GlobalLock', 'SysFreeString', 'SysAllocStringLen'
},
'Resource': {
'LoadResource', 'SizeofResource', 'LockResource', 'FreeResource', 'FindResourceA'
},
'WindowControl': {
'ShowWindow', 'DestroyWindow', 'TranslateMessage', 'DispatchMessageA', 'GetWindow',
'PeekMessageA', 'GetWindowLongA', 'CallWindowProcA', 'SetWindowLongA', 'GetWindowTextA',
'ScreenToClient', 'GetActiveWindow', 'CreateWindowExA', 'DefWindowProcA', 'GetMessageA',
'RegisterClassA', 'UnregisterClassA', 'MessageBoxA'
},
'COM': {
'CoCreateInstance', 'CoUninitialize', 'CoInitialize'
}
}
TARGET_DLLS = {
'kernel32.dll', 'user32.dll', 'gdi32.dll', 'advapi32.dll', 'shell32.dll', 'ntdll.dll', 'hal.dll',
'ws2_32.dll', 'wsock32.dll', 'wininet.dll', 'winhttp.dll', 'urlmon.dll', 'crypt32.dll', 'bcrypt.dll',
'psapi.dll', 'dbghelp.dll', 'imagehlp.dll', 'shlwapi.dll', 'ole32.dll', 'oleaut32.dll', 'comctl32.dll',
'mscoree.dll', 'vbscript.dll', 'netapi32.dll', 'iphlpapi.dll', 'wtsapi32.dll', 'version.dll', 'winmm.dll'
}
BASE_SCHEMA = [
"FileEntropy", "SectionMeanEntropy", "IsExe", "IsDll", "IsDriver", "Is64Bit", "Machine", "Magic",
"TimeDateStamp", "CheckSum", "ImageBase", "SizeOfImage", "SizeOfHeaders",
"Characteristics", "DllCharacteristics", "Subsystem", "LoaderFlags",
"MajorLinkerVersion", "MinorLinkerVersion", "SizeOfCode",
"SizeOfInitializedData", "SizeOfUninitializedData", "AddressOfEntryPoint",
"BaseOfCode", "SectionAlignment", "FileAlignment",
"MajorOperatingSystemVersion", "MinorOperatingSystemVersion",
"MajorImageVersion", "MinorImageVersion",
"MajorSubsystemVersion", "MinorSubsystemVersion",
"SizeOfStackReserve", "SizeOfStackCommit", "SizeOfHeapReserve", "SizeOfHeapCommit",
"NumberOfSections", "NumberOfRvaAndSizes", "PointerToSymbolTable", "NumberOfSymbols",
"SizeOfOptionalHeader",
"TextSectionMaxEntropy", "TextSectionMeanEntropy", "TextSizeRatio",
"DataSectionMaxEntropy", "DataSectionMeanEntropy", "DataSizeRatio",
"RsrcSectionMaxEntropy", "RsrcSectionMeanEntropy", "RsrcSizeRatio",
"SectionCount", "ExecutableSections",
"WritableSections", "ReadableSections", "SectionException",
"Char_00000020_Count", "Char_00000020_MeanEntropy",
"Char_00000040_Count", "Char_00000040_MeanEntropy",
"Char_00000080_Count", "Char_00000080_MeanEntropy",
"Char_02000000_Count", "Char_02000000_MeanEntropy",
"Char_20000000_Count", "Char_20000000_MeanEntropy",
"Char_40000000_Count", "Char_40000000_MeanEntropy",
"Char_80000000_Count", "Char_80000000_MeanEntropy",
"IconCount", "ApiCount", "ExportCount", "DebugCount", "ExceptionCount",
"ImportCount", "ImportFunctionCount",
"FileDescriptionLength", "FileVersionLength", "ProductNameLength",
"ProductVersionLength", "CompanyNameLength", "LegalCopyrightLength",
"CommentsLength", "InternalNameLength", "LegalTrademarksLength",
"SpecialBuildLength", "PrivateBuildLength",
"TrustSigned", "IsDebug", "IsPatched", "IsPrivateBuild", "IsPreRelease",
"IsSpecialBuild", "IsAdmin", "IsInstall", "HasTlsCallbacks",
"HasInvalidTimestamp", "HasRelocationDirectory", "HasPacked", "FileTimeException"
]
CAT_SCHEMA = [f"Cat_{k}" for k in sorted(API_CAT_MAPPING.keys())]
DLL_SCHEMA = [f"Dll_{d.replace('.', '_')}" for d in sorted(TARGET_DLLS)]
_ALL_APIS = {api for apis in API_CAT_MAPPING.values() for api in apis}
API_SCHEMA = [f"Api_{api}" for api in sorted(_ALL_APIS)]
SCHEMA = BASE_SCHEMA + CAT_SCHEMA + DLL_SCHEMA + API_SCHEMA
_API_TO_CATS = {}
for _cat, _apis in API_CAT_MAPPING.items():
for _api in _apis:
if _api not in _API_TO_CATS:
_API_TO_CATS[_api] = []
_API_TO_CATS[_api].append(_cat)
PACKERS = {
'upx', 'aspack', 'asprotect', 'pecompact', 'upack', 'fsg', 'mew',
'mpress', 'ezip', 'pklt', 'shrink', 'petite', 'telock',
'themida', 'winlicense', 'tmd', 'vmp', 'enigma', 'obsidium',
'pelock', 'exestealth', 'yoda', 'armadillo', 'zprotect',
'sforce', 'starforce', 'qihoo', 'wisevec', 'megastop',
}
INSTALLER_SIGS = [b"Nullsoft.NSIS", b"Inno Setup", b"7-Zip.7zip", b"InstallShield"]
####################################################################################################
def safe_float(val):
try:
f = float(val)
if math.isinf(f) or math.isnan(f): return 0.0
return f
except:
return 0.0
####################################################################################################
def calc_entropy(data):
if not data: return 0.0
arr = np.frombuffer(data, dtype=np.uint8)
counts = np.bincount(arr, minlength=256)
probs = counts[counts > 0] / len(arr)
return float(-np.sum(probs * np.log2(probs)))
def safe_div(n, d):
return float(n) / d if d > 0 else 0.0
def process_single_file_content(file_bytes, file_path):
fsize = len(file_bytes)
if fsize == 0: return None
fts = {k: 0.0 for k in Features.SCHEMA}
pe = None
try:
pe = pefile.PE(data=file_bytes, fast_load=True)
fts['TrustSigned'] = verify_signature(file_path)
fh = pe.FILE_HEADER
fts['Machine'] = fh.Machine
fts['NumberOfSections'] = fh.NumberOfSections
fts['TimeDateStamp'] = fh.TimeDateStamp
fts['PointerToSymbolTable'] = fh.PointerToSymbolTable
fts['NumberOfSymbols'] = fh.NumberOfSymbols
fts['SizeOfOptionalHeader'] = fh.SizeOfOptionalHeader
fts['Characteristics'] = fh.Characteristics
curr_ts = datetime.datetime.utcnow().timestamp()
fts['HasInvalidTimestamp'] = 1.0 if (fh.TimeDateStamp < 631152000 or fh.TimeDateStamp > curr_ts + 2592000) else 0.0
fts['FileTimeException'] = 1.0 if fh.TimeDateStamp == 0 else 0.0
if hasattr(pe, 'OPTIONAL_HEADER'):
op = pe.OPTIONAL_HEADER
fts['Magic'] = getattr(op, 'Magic', 0)
fts['MajorLinkerVersion'] = getattr(op, 'MajorLinkerVersion', 0)
fts['MinorLinkerVersion'] = getattr(op, 'MinorLinkerVersion', 0)
fts['SizeOfCode'] = getattr(op, 'SizeOfCode', 0)
fts['SizeOfInitializedData'] = getattr(op, 'SizeOfInitializedData', 0)
fts['SizeOfUninitializedData'] = getattr(op, 'SizeOfUninitializedData', 0)
fts['AddressOfEntryPoint'] = getattr(op, 'AddressOfEntryPoint', 0)
fts['BaseOfCode'] = getattr(op, 'BaseOfCode', 0)
fts['ImageBase'] = getattr(op, 'ImageBase', 0)
fts['SectionAlignment'] = getattr(op, 'SectionAlignment', 0)
fts['FileAlignment'] = getattr(op, 'FileAlignment', 0)
fts['MajorOperatingSystemVersion'] = getattr(op, 'MajorOperatingSystemVersion', 0)
fts['MinorOperatingSystemVersion'] = getattr(op, 'MinorOperatingSystemVersion', 0)
fts['MajorImageVersion'] = getattr(op, 'MajorImageVersion', 0)
fts['MinorImageVersion'] = getattr(op, 'MinorImageVersion', 0)
fts['MajorSubsystemVersion'] = getattr(op, 'MajorSubsystemVersion', 0)
fts['MinorSubsystemVersion'] = getattr(op, 'MinorSubsystemVersion', 0)
fts['SizeOfImage'] = getattr(op, 'SizeOfImage', 0)
fts['SizeOfHeaders'] = getattr(op, 'SizeOfHeaders', 0)
fts['CheckSum'] = getattr(op, 'CheckSum', 0)
fts['Subsystem'] = getattr(op, 'Subsystem', 0)
fts['DllCharacteristics'] = getattr(op, 'DllCharacteristics', 0)
fts['SizeOfStackReserve'] = getattr(op, 'SizeOfStackReserve', 0)
fts['SizeOfStackCommit'] = getattr(op, 'SizeOfStackCommit', 0)
fts['SizeOfHeapReserve'] = getattr(op, 'SizeOfHeapReserve', 0)
fts['SizeOfHeapCommit'] = getattr(op, 'SizeOfHeapCommit', 0)
fts['LoaderFlags'] = getattr(op, 'LoaderFlags', 0)
fts['NumberOfRvaAndSizes'] = getattr(op, 'NumberOfRvaAndSizes', 0)
fts['Is64Bit'] = 1.0 if fh.Machine in (0x8664, 0xAA64, 0x0200) else 0.0
fts['IsExe'] = 1.0 if pe.is_exe() else 0.0
fts['IsDll'] = 1.0 if pe.is_dll() else 0.0
fts['IsDriver'] = 1.0 if fts['Subsystem'] == 1 else 0.0
if hasattr(op, 'DATA_DIRECTORY'):
reloc_idx = pefile.DIRECTORY_ENTRY.get('IMAGE_DIRECTORY_ENTRY_BASERELOC', 5)
if len(op.DATA_DIRECTORY) > reloc_idx and op.DATA_DIRECTORY[reloc_idx].Size > 0:
fts['HasRelocationDirectory'] = 1.0
try:
fts['FileEntropy'] = calc_entropy(file_bytes)
except Exception:
pass
sec_entropies = []
text_entropies = []
data_entropies = []
rsrc_entropies = []
char_flags = [0x00000020, 0x00000040, 0x00000080, 0x02000000, 0x20000000, 0x40000000, 0x80000000]
if hasattr(pe, 'sections'):
fts['SectionCount'] = len(pe.sections)
for section in pe.sections:
try:
name = section.Name.decode('ascii', 'ignore').strip('\x00')
except Exception:
name = ""
try:
s_data = section.get_data()
s_entropy = calc_entropy(s_data)
except Exception:
s_data = b""
s_entropy = 0.0
sec_entropies.append(s_entropy)
if any(p in name.lower() for p in Features.PACKERS): fts['HasPacked'] = 1.0
if section.Characteristics & 0x20000000: fts['ExecutableSections'] += 1.0
if section.Characteristics & 0x80000000: fts['WritableSections'] += 1.0
if section.Characteristics & 0x40000000: fts['ReadableSections'] += 1.0
if section.SizeOfRawData + section.PointerToRawData > fsize: fts['SectionException'] = 1.0
for flag in char_flags:
if section.Characteristics & flag:
fts[f'Char_{flag:08X}_Count'] += 1.0
fts[f'Char_{flag:08X}_MeanEntropy'] += s_entropy
ratio = safe_div(section.SizeOfRawData, fsize)
if name.startswith('.text'):
text_entropies.append(s_entropy)
fts['TextSizeRatio'] += ratio
elif name.startswith('.data'):
data_entropies.append(s_entropy)
fts['DataSizeRatio'] += ratio
elif name.startswith('.rsrc'):
rsrc_entropies.append(s_entropy)
fts['RsrcSizeRatio'] += ratio
if b"requireAdministrator" in s_data: fts['IsAdmin'] = 1.0
for sig in Features.INSTALLER_SIGS:
if sig in s_data:
fts['IsInstall'] = 1.0
break
fts['SectionMeanEntropy'] = safe_div(sum(sec_entropies), len(sec_entropies))
fts['TextSectionMaxEntropy'] = max(text_entropies) if text_entropies else 0.0
fts['TextSectionMeanEntropy'] = safe_div(sum(text_entropies), len(text_entropies))
fts['DataSectionMaxEntropy'] = max(data_entropies) if data_entropies else 0.0
fts['DataSectionMeanEntropy'] = safe_div(sum(data_entropies), len(data_entropies))
fts['RsrcSectionMaxEntropy'] = max(rsrc_entropies) if rsrc_entropies else 0.0
fts['RsrcSectionMeanEntropy'] = safe_div(sum(rsrc_entropies), len(rsrc_entropies))
for flag in char_flags:
fts[f'Char_{flag:08X}_MeanEntropy'] = safe_div(fts[f'Char_{flag:08X}_MeanEntropy'], fts[f'Char_{flag:08X}_Count'])
try:
pe.parse_data_directories(directories=[
pefile.DIRECTORY_ENTRY['IMAGE_DIRECTORY_ENTRY_IMPORT'],
pefile.DIRECTORY_ENTRY['IMAGE_DIRECTORY_ENTRY_EXPORT'],
pefile.DIRECTORY_ENTRY['IMAGE_DIRECTORY_ENTRY_RESOURCE'],
pefile.DIRECTORY_ENTRY['IMAGE_DIRECTORY_ENTRY_DEBUG'],
pefile.DIRECTORY_ENTRY['IMAGE_DIRECTORY_ENTRY_TLS']
])
except Exception:
pass
if hasattr(pe, 'DIRECTORY_ENTRY_TLS') and hasattr(pe.DIRECTORY_ENTRY_TLS, 'struct'):
if getattr(pe.DIRECTORY_ENTRY_TLS.struct, 'AddressOfCallBacks', 0) != 0:
fts['HasTlsCallbacks'] = 1.0
if hasattr(pe, 'VS_FIXEDFILEINFO') and len(pe.VS_FIXEDFILEINFO) > 0:
flags = getattr(pe.VS_FIXEDFILEINFO[0], 'FileFlags', 0)
fts['IsDebug'] = 1.0 if flags & 0x1 else 0.0
fts['IsPreRelease'] = 1.0 if flags & 0x2 else 0.0
fts['IsPatched'] = 1.0 if flags & 0x4 else 0.0
fts['IsPrivateBuild'] = 1.0 if flags & 0x8 else 0.0
fts['IsSpecialBuild'] = 1.0 if flags & 0x20 else 0.0
if hasattr(pe, 'FileInfo'):
for fileinfo_list in pe.FileInfo:
for fileinfo in fileinfo_list:
if getattr(fileinfo, 'name', '') in ('StringFileInfo', b'StringFileInfo'):
for st in getattr(fileinfo, 'StringTable', []):
for key, val in st.entries.items():
try:
k = key.decode('utf-8', 'ignore') if isinstance(key, bytes) else str(key)
v = val.decode('utf-8', 'ignore') if isinstance(val, bytes) else str(val)
length = len(v)
if k == 'FileDescription': fts['FileDescriptionLength'] = length
elif k == 'FileVersion': fts['FileVersionLength'] = length
elif k == 'ProductName': fts['ProductNameLength'] = length
elif k == 'ProductVersion': fts['ProductVersionLength'] = length
elif k == 'CompanyName': fts['CompanyNameLength'] = length
elif k == 'LegalCopyright': fts['LegalCopyrightLength'] = length
elif k == 'Comments': fts['CommentsLength'] = length
elif k == 'InternalName': fts['InternalNameLength'] = length
elif k == 'LegalTrademarks': fts['LegalTrademarksLength'] = length
elif k == 'SpecialBuild': fts['SpecialBuildLength'] = length
elif k == 'PrivateBuild': fts['PrivateBuildLength'] = length
except Exception:
continue
if hasattr(pe, 'DIRECTORY_ENTRY_IMPORT'):
fts['ImportCount'] = len(pe.DIRECTORY_ENTRY_IMPORT)
for entry in pe.DIRECTORY_ENTRY_IMPORT:
if getattr(entry, 'dll', None):
try:
dll_name = entry.dll.decode('ascii', 'ignore').lower()
if dll_name in Features.TARGET_DLLS:
fts[f"Dll_{dll_name.replace('.', '_')}"] = 1.0
except Exception:
pass
imports = getattr(entry, 'imports', [])
for imp in imports:
fts['ImportFunctionCount'] += 1.0
if not getattr(imp, 'name', None): continue
try:
func_name = imp.name.decode('ascii', 'ignore')
if func_name in Features._ALL_APIS:
fts[f'Api_{func_name}'] += 1.0
fts['ApiCount'] += 1.0
for cat in Features._API_TO_CATS.get(func_name, []):
fts[f'Cat_{cat}'] += 1.0
except Exception:
continue
if hasattr(pe, 'DIRECTORY_ENTRY_EXPORT') and hasattr(pe.DIRECTORY_ENTRY_EXPORT, 'symbols'):
fts['ExportCount'] = len(pe.DIRECTORY_ENTRY_EXPORT.symbols)
if hasattr(pe, 'DIRECTORY_ENTRY_RESOURCE'):
for entry in pe.DIRECTORY_ENTRY_RESOURCE.entries:
if getattr(entry, 'id', None) == 3 and hasattr(entry, 'directory'):
fts['IconCount'] += len(getattr(entry.directory, 'entries', []))
if hasattr(pe, 'OPTIONAL_HEADER') and hasattr(pe.OPTIONAL_HEADER, 'DATA_DIRECTORY') and len(pe.OPTIONAL_HEADER.DATA_DIRECTORY) > 3:
exc_dir = pe.OPTIONAL_HEADER.DATA_DIRECTORY[3]
if exc_dir.Size > 0:
fts['ExceptionCount'] = exc_dir.Size // 12 if fts['Machine'] in (0x8664, 0xAA64) else 0.0
if hasattr(pe, 'DIRECTORY_ENTRY_DEBUG'):
fts['DebugCount'] = len(pe.DIRECTORY_ENTRY_DEBUG)
for k in fts:
fts[k] = safe_float(fts[k])
return fts
except pefile.PEFormatError:
return None
except Exception:
return None
finally:
if pe:
pe.close()
####################################################################################################
def init_db():
conn = sqlite3.connect(DB_PATH)
conn.execute("PRAGMA synchronous = OFF")
conn.execute("PRAGMA journal_mode = WAL")
conn.execute("PRAGMA cache_size = 10000")
cols = "FileHash TEXT, " + ", ".join([f"{c} REAL" for c in Features.SCHEMA])
conn.execute(f"CREATE TABLE IF NOT EXISTS PeData ({cols}, Label INTEGER)")
conn.execute(f"CREATE UNIQUE INDEX IF NOT EXISTS idx_pedata_hash ON PeData (FileHash)")
conn.commit()
return conn
def load_existing_hashes(conn):
print("[*] Loading existing hashes into memory...")
try:
cursor = conn.execute("SELECT FileHash FROM PeData")
return {row[0] for row in cursor.fetchall()}
except:
return set()
####################################################################################################
def scan_and_save(conn, path, label, existing_hashes):
print(f"\n[*] Scanning: {path} (Label={label})")
print(f"[*] Config: Max File Size = {MAX_FILE_SIZE/1024/1024:.0f} MB")
files = []
if os.path.isfile(path):
files.append(path)
else:
for r, _, fs in os.walk(path):
for f in fs:
if os.path.splitext(f)[1].lower() in TARGET_EXTENSIONS:
files.append(os.path.join(r, f))
total = len(files)
if total == 0:
print("[-] No valid PE files found.")
return
print(f"[*] Found {total} files. Processing...\n")
batch = []
count = 0
errors = 0
skipped = 0
start_time = time.time()
placeholders = ",".join(["?"] * (len(Features.SCHEMA) + 2))
sql = f"INSERT INTO PeData VALUES ({placeholders})"
try:
for i, file_path in enumerate(files):
try:
fsize = os.path.getsize(file_path)
if fsize == 0 or fsize > MAX_FILE_SIZE:
errors += 1
continue
with open(file_path, "rb") as f:
file_bytes = f.read()
sha256 = hashlib.sha256(file_bytes).hexdigest()
if sha256 in existing_hashes:
skipped += 1
else:
res = process_single_file_content(file_bytes, file_path)
if res:
vals = [sha256]
vals.extend([res.get(c, 0.0) for c in Features.SCHEMA])
vals.append(float(label))
batch.append(tuple(vals))
existing_hashes.add(sha256)
count += 1
else:
errors += 1
except Exception:
errors += 1
if len(batch) >= BATCH_SIZE:
try:
conn.executemany(sql, batch)
conn.commit()
batch = []
except Exception as batch_err:
print(f"\n[-] Batch Error: {batch_err}")
batch = []
elapsed = time.time() - start_time
rate = (i + 1) / elapsed if elapsed > 0 else 0
sys.stdout.write(f"\r[{i+1}/{total}] New: {count} | Skip: {skipped} | Err: {errors} | {rate:.1f} files/s")
sys.stdout.flush()
except KeyboardInterrupt:
print("\n\n[-] Interrupted by user.")
except Exception as e:
print(f"\n[-] Unexpected Error: {e}")
finally:
if batch:
try:
conn.executemany(sql, batch)
conn.commit()
except Exception:
pass
print(f"\n\n[+] Scan finished. Total New: {count}, Total Skipped: {skipped}")
####################################################################################################
if __name__ == "__main__":
print("\n---------------- PE Dataset Builder v3.2 ----------------\n")
if os.path.exists(DB_PATH):
try:
conn_check = sqlite3.connect(DB_PATH)
cursor = conn_check.execute("PRAGMA table_info(PeData)")
cols = [row[1] for row in cursor.fetchall()]
if 'FileHash' not in cols or len(cols) != len(Features.SCHEMA) + 2:
print(f"[-] WARNING: DB Schema mismatch. Please delete {DB_PATH} to rebuild.")
conn_check.close()
except: pass
conn = init_db()
existing_hashes = load_existing_hashes(conn)
print(f"[+] Loaded {len(existing_hashes)} hashes from database.\n")
try:
while True:
print("-" * 60)
raw_path = input("\n[*] Enter Path (File or Folder): ").strip()
target_path = raw_path.strip('"').strip("'")
if target_path.lower() in ['exit', 'q']: break
if not target_path or not os.path.exists(target_path):
print("[-] Invalid path.")
continue
while True:
label_input = input("[*] Enter Label (0=Safe, 1=Malware): ").strip()
if label_input.lower() in ['exit', 'q']:
conn.close()
sys.exit()
if label_input in ['0', '1']:
label = int(label_input)
break
print("[-] Please enter 0 or 1.")
scan_and_save(conn, target_path, label, existing_hashes)
except KeyboardInterrupt:
print("\n\n[-] Interrupted.")
finally:
conn.close()
print(f"\n[+] Database saved to: {os.path.abspath(DB_PATH)}")
+216
View File
@@ -0,0 +1,216 @@
import os, sys, time, sqlite3, re, json
import pandas as pd
import numpy as np
import lightgbm as lgb
import onnxmltools
from onnxmltools.convert.common.data_types import FloatTensorType
from sklearn.model_selection import train_test_split
from sklearn.metrics import classification_report, accuracy_score, roc_auc_score
####################################################################################################
DB_PATH = "pe_features.db"
MODEL_FILE = "model.txt"
ONNX_FILE = "model.onnx"
FEATURE_FILE = "features.json"
TEST_SIZE = 0.0001
RANDOM_SEED = 42
LGBM_PARAMS = {
'objective': 'binary',
'metric': ['binary_logloss', 'auc'],
'boosting_type': 'gbdt',
'num_leaves': 512,
'learning_rate': 0.05,
'feature_fraction': 0.8,
'bagging_fraction': 0.8,
'bagging_freq': 5,
'verbose': -1,
'seed': RANDOM_SEED,
'n_jobs': -1,
'max_depth': -1,
'min_data_in_leaf': 10,
'lambda_l1': 0.1,
'lambda_l2': 0.1,
}
####################################################################################################
def clean_col_name(name):
return re.sub(r'[^A-Za-z0-9_]+', '', name)
def get_raw_schema(db_path):
if not os.path.exists(db_path):
return None
conn = sqlite3.connect(db_path)
try:
cursor = conn.cursor()
cursor.execute("PRAGMA table_info(PeData)")
columns = [row[1] for row in cursor.fetchall()]
exclude = {'label', 'id', 'filename', 'filelength', 'rowid', 'probability', 'predictedlabel', 'score', 'filehash', 'timedatestamp'}
return [c for c in columns if c.lower().strip() not in exclude]
except Exception as e:
print(f"[-] Schema extraction error: {e}")
return None
finally:
conn.close()
####################################################################################################
def load_data(db_path, raw_cols):
print(f"[*] Connecting to database: {db_path}")
conn = sqlite3.connect(db_path)
req_cols = list(set(raw_cols + ['Label']))
sql = f"SELECT {', '.join(req_cols)} FROM PeData"
try:
chunks = []
for chunk in pd.read_sql_query(sql, conn, chunksize=20000):
label_col = next((c for c in chunk.columns if c.lower().strip() == 'label'), None)
if not label_col:
print("[-] Critical Error: Label column not found.")
return None, None, None
cols_to_cast = [c for c in chunk.columns if c != label_col]
chunk[cols_to_cast] = chunk[cols_to_cast].astype(np.float32)
chunk[label_col] = chunk[label_col].astype(np.int8)
chunks.append(chunk)
if not chunks:
return None, None, None
df = pd.concat(chunks, ignore_index=True)
label_col = next((c for c in df.columns if c.lower().strip() == 'label'), None)
y = df[label_col].astype(int)
X = df.drop(columns=[label_col], errors='ignore')
valid_features = [c for c in raw_cols if c in X.columns]
X = X[valid_features].copy()
rename_map = {old: clean_col_name(old) for old in X.columns}
X = X.rename(columns=rename_map)
if X.columns.duplicated().any():
print("[-] Warning: Duplicate column names detected after cleaning. Deduplicating...")
X = X.loc[:, ~X.columns.duplicated()]
final_features = X.columns.tolist()
return X, y, final_features
finally:
conn.close()
####################################################################################################
def train_process(X, y):
print(f"[*] Dataset shape: {X.shape}")
X_train, X_test, y_train, y_test = train_test_split(
X, y, test_size=TEST_SIZE, random_state=RANDOM_SEED, stratify=y
)
n_pos = (y_train == 1).sum()
n_neg = (y_train == 0).sum()
base_weight = n_neg / n_pos if n_pos > 0 else 1.0
weight_ratio_target = 0.1
final_pos_weight = base_weight * weight_ratio_target
print(f"[*] Balance Report: Safe={n_neg}, Malware={n_pos}")
print(f"[*] Weight Config: Base Balanced={base_weight:.4f}, Final Adjusted={final_pos_weight:.4f}")
train_data = lgb.Dataset(X_train, label=y_train)
valid_data = lgb.Dataset(X_test, label=y_test, reference=train_data)
params = LGBM_PARAMS.copy()
params['scale_pos_weight'] = final_pos_weight
print("\n[*] Starting LightGBM training...")
start_time = time.time()
model = lgb.train(
params,
train_data,
valid_sets=[valid_data],
num_boost_round=1000,
callbacks=[
lgb.log_evaluation(period=100)
]
)
elapsed = time.time() - start_time
print(f"\n[+] Training finished in {elapsed:.2f}s")
return model, X_test, y_test
####################################################################################################
def export_onnx_model(model, feature_names):
print(f"[*] Exporting ONNX model to {ONNX_FILE}...")
try:
initial_type = [('float_input', FloatTensorType([None, len(feature_names)]))]
onnx_model = onnxmltools.convert_lightgbm(model, initial_types=initial_type)
onnxmltools.utils.save_model(onnx_model, ONNX_FILE)
with open(FEATURE_FILE, 'w') as f:
json.dump(feature_names, f)
print(f"[+] ONNX export success. Features synced to {FEATURE_FILE}")
except Exception as e:
print(f"[-] ONNX export failed: {e}")
####################################################################################################
def evaluate_and_save(model, X_test, y_test):
print("\n------------------- Evaluation Report -------------------\n")
y_prob = model.predict(X_test, num_iteration=model.best_iteration)
y_pred = (y_prob > 0.5).astype(int)
acc = accuracy_score(y_test, y_pred)
auc = roc_auc_score(y_test, y_prob)
print(f"Accuracy : {acc:.4f}")
print(f"ROC AUC : {auc:.4f}")
print("\n" + classification_report(y_test, y_pred, zero_division=0))
print("---------------------------------------------------------\n")
print("[*] Top 20 Features (Importance by Gain):")
feature_names = model.feature_name()
importance = model.feature_importance(importance_type='gain')
feature_imp = pd.DataFrame(sorted(zip(importance, feature_names), reverse=True), columns=['Value', 'Feature'])
print(feature_imp.head(20).to_string(index=False))
print("\n---------------------------------------------------------\n")
model.save_model(MODEL_FILE)
print(f"[+] Native model saved to: {os.path.abspath(MODEL_FILE)}")
export_onnx_model(model, feature_names)
####################################################################################################
if __name__ == "__main__":
print("\n---------------- PE Malware Trainer v3.1 ----------------\n")
if not os.path.exists(DB_PATH):
print(f"[-] Error: Database {DB_PATH} not found.")
sys.exit(1)
raw_schema = get_raw_schema(DB_PATH)
if not raw_schema:
print("[-] Error: Could not read schema.")
sys.exit(1)
X, y, _ = load_data(DB_PATH, raw_schema)
if X is None:
sys.exit(1)
try:
model, X_test, y_test = train_process(X, y)
evaluate_and_save(model, X_test, y_test)
except KeyboardInterrupt:
print("\n\n[-] Training interrupted.")
except Exception as e:
print(f"\n[-] Critical Error: {e}")
+216
View File
@@ -0,0 +1,216 @@
import os, sys, time, sqlite3, re, json
import pandas as pd
import numpy as np
import lightgbm as lgb
import onnxmltools
from onnxmltools.convert.common.data_types import FloatTensorType
from sklearn.model_selection import train_test_split
from sklearn.metrics import classification_report, accuracy_score, roc_auc_score
####################################################################################################
DB_PATH = "pe_features.db"
MODEL_FILE = "model.txt"
ONNX_FILE = "model.onnx"
FEATURE_FILE = "features.json"
TEST_SIZE = 0.0001
RANDOM_SEED = 42
LGBM_PARAMS = {
'objective': 'binary',
'metric': ['binary_logloss', 'auc'],
'boosting_type': 'gbdt',
'num_leaves': 512,
'learning_rate': 0.05,
'feature_fraction': 0.8,
'bagging_fraction': 0.8,
'bagging_freq': 5,
'verbose': -1,
'seed': RANDOM_SEED,
'n_jobs': -1,
'max_depth': -1,
'min_data_in_leaf': 10,
'lambda_l1': 0.1,
'lambda_l2': 0.1,
}
####################################################################################################
def clean_col_name(name):
return re.sub(r'[^A-Za-z0-9_]+', '', name)
def get_raw_schema(db_path):
if not os.path.exists(db_path):
return None
conn = sqlite3.connect(db_path)
try:
cursor = conn.cursor()
cursor.execute("PRAGMA table_info(PeData)")
columns = [row[1] for row in cursor.fetchall()]
exclude = {'label', 'id', 'filename', 'filelength', 'rowid', 'probability', 'predictedlabel', 'score', 'filehash', 'timedatestamp'}
return [c for c in columns if c.lower().strip() not in exclude]
except Exception as e:
print(f"[-] Schema extraction error: {e}")
return None
finally:
conn.close()
####################################################################################################
def load_data(db_path, raw_cols):
print(f"[*] Connecting to database: {db_path}")
conn = sqlite3.connect(db_path)
req_cols = list(set(raw_cols + ['Label']))
sql = f"SELECT {', '.join(req_cols)} FROM PeData"
try:
chunks = []
for chunk in pd.read_sql_query(sql, conn, chunksize=20000):
label_col = next((c for c in chunk.columns if c.lower().strip() == 'label'), None)
if not label_col:
print("[-] Critical Error: Label column not found.")
return None, None, None
cols_to_cast = [c for c in chunk.columns if c != label_col]
chunk[cols_to_cast] = chunk[cols_to_cast].astype(np.float32)
chunk[label_col] = chunk[label_col].astype(np.int8)
chunks.append(chunk)
if not chunks:
return None, None, None
df = pd.concat(chunks, ignore_index=True)
label_col = next((c for c in df.columns if c.lower().strip() == 'label'), None)
y = df[label_col].astype(int)
X = df.drop(columns=[label_col], errors='ignore')
valid_features = [c for c in raw_cols if c in X.columns]
X = X[valid_features].copy()
rename_map = {old: clean_col_name(old) for old in X.columns}
X = X.rename(columns=rename_map)
if X.columns.duplicated().any():
print("[-] Warning: Duplicate column names detected after cleaning. Deduplicating...")
X = X.loc[:, ~X.columns.duplicated()]
final_features = X.columns.tolist()
return X, y, final_features
finally:
conn.close()
####################################################################################################
def train_process(X, y):
print(f"[*] Dataset shape: {X.shape}")
X_train, X_test, y_train, y_test = train_test_split(
X, y, test_size=TEST_SIZE, random_state=RANDOM_SEED, stratify=y
)
n_pos = (y_train == 1).sum()
n_neg = (y_train == 0).sum()
base_weight = n_neg / n_pos if n_pos > 0 else 1.0
weight_ratio_target = 0.001
final_pos_weight = base_weight * weight_ratio_target
print(f"[*] Balance Report: Safe={n_neg}, Malware={n_pos}")
print(f"[*] Weight Config: Base Balanced={base_weight:.4f}, Final Adjusted={final_pos_weight:.4f}")
train_data = lgb.Dataset(X_train, label=y_train)
valid_data = lgb.Dataset(X_test, label=y_test, reference=train_data)
params = LGBM_PARAMS.copy()
params['scale_pos_weight'] = final_pos_weight
print("\n[*] Starting LightGBM training...")
start_time = time.time()
model = lgb.train(
params,
train_data,
valid_sets=[valid_data],
num_boost_round=1000,
callbacks=[
lgb.log_evaluation(period=100)
]
)
elapsed = time.time() - start_time
print(f"\n[+] Training finished in {elapsed:.2f}s")
return model, X_test, y_test
####################################################################################################
def export_onnx_model(model, feature_names):
print(f"[*] Exporting ONNX model to {ONNX_FILE}...")
try:
initial_type = [('float_input', FloatTensorType([None, len(feature_names)]))]
onnx_model = onnxmltools.convert_lightgbm(model, initial_types=initial_type)
onnxmltools.utils.save_model(onnx_model, ONNX_FILE)
with open(FEATURE_FILE, 'w') as f:
json.dump(feature_names, f)
print(f"[+] ONNX export success. Features synced to {FEATURE_FILE}")
except Exception as e:
print(f"[-] ONNX export failed: {e}")
####################################################################################################
def evaluate_and_save(model, X_test, y_test):
print("\n------------------- Evaluation Report -------------------\n")
y_prob = model.predict(X_test, num_iteration=model.best_iteration)
y_pred = (y_prob > 0.5).astype(int)
acc = accuracy_score(y_test, y_pred)
auc = roc_auc_score(y_test, y_prob)
print(f"Accuracy : {acc:.4f}")
print(f"ROC AUC : {auc:.4f}")
print("\n" + classification_report(y_test, y_pred, zero_division=0))
print("---------------------------------------------------------\n")
print("[*] Top 20 Features (Importance by Gain):")
feature_names = model.feature_name()
importance = model.feature_importance(importance_type='gain')
feature_imp = pd.DataFrame(sorted(zip(importance, feature_names), reverse=True), columns=['Value', 'Feature'])
print(feature_imp.head(20).to_string(index=False))
print("\n---------------------------------------------------------\n")
model.save_model(MODEL_FILE)
print(f"[+] Native model saved to: {os.path.abspath(MODEL_FILE)}")
export_onnx_model(model, feature_names)
####################################################################################################
if __name__ == "__main__":
print("\n---------------- PE Malware Trainer v3.1 ----------------\n")
if not os.path.exists(DB_PATH):
print(f"[-] Error: Database {DB_PATH} not found.")
sys.exit(1)
raw_schema = get_raw_schema(DB_PATH)
if not raw_schema:
print("[-] Error: Could not read schema.")
sys.exit(1)
X, y, _ = load_data(DB_PATH, raw_schema)
if X is None:
sys.exit(1)
try:
model, X_test, y_test = train_process(X, y)
evaluate_and_save(model, X_test, y_test)
except KeyboardInterrupt:
print("\n\n[-] Training interrupted.")
except Exception as e:
print(f"\n[-] Critical Error: {e}")
+216
View File
@@ -0,0 +1,216 @@
import os, sys, time, sqlite3, re, json
import pandas as pd
import numpy as np
import lightgbm as lgb
import onnxmltools
from onnxmltools.convert.common.data_types import FloatTensorType
from sklearn.model_selection import train_test_split
from sklearn.metrics import classification_report, accuracy_score, roc_auc_score
####################################################################################################
DB_PATH = "pe_features.db"
MODEL_FILE = "model.txt"
ONNX_FILE = "Pefile_General_T1.onnx"
FEATURE_FILE = "features.json"
TEST_SIZE = 0.0001
RANDOM_SEED = 42
LGBM_PARAMS = {
'objective': 'binary',
'metric': ['binary_logloss', 'auc'],
'boosting_type': 'gbdt',
'num_leaves': 63,
'learning_rate': 0.05,
'feature_fraction': 0.8,
'bagging_fraction': 0.8,
'bagging_freq': 5,
'verbose': -1,
'seed': RANDOM_SEED,
'n_jobs': -1,
'max_depth': -1,
'min_data_in_leaf': 10,
'lambda_l1': 0.1,
'lambda_l2': 0.1,
}
####################################################################################################
def clean_col_name(name):
return re.sub(r'[^A-Za-z0-9_]+', '', name)
def get_raw_schema(db_path):
if not os.path.exists(db_path):
return None
conn = sqlite3.connect(db_path)
try:
cursor = conn.cursor()
cursor.execute("PRAGMA table_info(PeData)")
columns = [row[1] for row in cursor.fetchall()]
exclude = {'label', 'id', 'filename', 'filelength', 'rowid', 'probability', 'predictedlabel', 'score', 'filehash', 'timedatestamp'}
return [c for c in columns if c.lower().strip() not in exclude]
except Exception as e:
print(f"[-] Schema extraction error: {e}")
return None
finally:
conn.close()
####################################################################################################
def load_data(db_path, raw_cols):
print(f"[*] Connecting to database: {db_path}")
conn = sqlite3.connect(db_path)
req_cols = list(set(raw_cols + ['Label']))
sql = f"SELECT {', '.join(req_cols)} FROM PeData"
try:
chunks = []
for chunk in pd.read_sql_query(sql, conn, chunksize=20000):
label_col = next((c for c in chunk.columns if c.lower().strip() == 'label'), None)
if not label_col:
print("[-] Critical Error: Label column not found.")
return None, None, None
cols_to_cast = [c for c in chunk.columns if c != label_col]
chunk[cols_to_cast] = chunk[cols_to_cast].astype(np.float32)
chunk[label_col] = chunk[label_col].astype(np.int8)
chunks.append(chunk)
if not chunks:
return None, None, None
df = pd.concat(chunks, ignore_index=True)
label_col = next((c for c in df.columns if c.lower().strip() == 'label'), None)
y = df[label_col].astype(int)
X = df.drop(columns=[label_col], errors='ignore')
valid_features = [c for c in raw_cols if c in X.columns]
X = X[valid_features].copy()
rename_map = {old: clean_col_name(old) for old in X.columns}
X = X.rename(columns=rename_map)
if X.columns.duplicated().any():
print("[-] Warning: Duplicate column names detected after cleaning. Deduplicating...")
X = X.loc[:, ~X.columns.duplicated()]
final_features = X.columns.tolist()
return X, y, final_features
finally:
conn.close()
####################################################################################################
def train_process(X, y):
print(f"[*] Dataset shape: {X.shape}")
X_train, X_test, y_train, y_test = train_test_split(
X, y, test_size=TEST_SIZE, random_state=RANDOM_SEED, stratify=y
)
n_pos = (y_train == 1).sum()
n_neg = (y_train == 0).sum()
base_weight = n_neg / n_pos if n_pos > 0 else 1.0
weight_ratio_target = 0.001
final_pos_weight = base_weight * weight_ratio_target
print(f"[*] Balance Report: Safe={n_neg}, Malware={n_pos}")
print(f"[*] Weight Config: Base Balanced={base_weight:.4f}, Final Adjusted={final_pos_weight:.4f}")
train_data = lgb.Dataset(X_train, label=y_train)
valid_data = lgb.Dataset(X_test, label=y_test, reference=train_data)
params = LGBM_PARAMS.copy()
params['scale_pos_weight'] = final_pos_weight
print("\n[*] Starting LightGBM training...")
start_time = time.time()
model = lgb.train(
params,
train_data,
valid_sets=[valid_data],
num_boost_round=1000,
callbacks=[
lgb.log_evaluation(period=100)
]
)
elapsed = time.time() - start_time
print(f"\n[+] Training finished in {elapsed:.2f}s")
return model, X_test, y_test
####################################################################################################
def export_onnx_model(model, feature_names):
print(f"[*] Exporting ONNX model to {ONNX_FILE}...")
try:
initial_type = [('float_input', FloatTensorType([None, len(feature_names)]))]
onnx_model = onnxmltools.convert_lightgbm(model, initial_types=initial_type)
onnxmltools.utils.save_model(onnx_model, ONNX_FILE)
with open(FEATURE_FILE, 'w') as f:
json.dump(feature_names, f)
print(f"[+] ONNX export success. Features synced to {FEATURE_FILE}")
except Exception as e:
print(f"[-] ONNX export failed: {e}")
####################################################################################################
def evaluate_and_save(model, X_test, y_test):
print("\n------------------- Evaluation Report -------------------\n")
y_prob = model.predict(X_test, num_iteration=model.best_iteration)
y_pred = (y_prob > 0.5).astype(int)
acc = accuracy_score(y_test, y_pred)
auc = roc_auc_score(y_test, y_prob)
print(f"Accuracy : {acc:.4f}")
print(f"ROC AUC : {auc:.4f}")
print("\n" + classification_report(y_test, y_pred, zero_division=0))
print("---------------------------------------------------------\n")
print("[*] Top 20 Features (Importance by Gain):")
feature_names = model.feature_name()
importance = model.feature_importance(importance_type='gain')
feature_imp = pd.DataFrame(sorted(zip(importance, feature_names), reverse=True), columns=['Value', 'Feature'])
print(feature_imp.head(20).to_string(index=False))
print("\n---------------------------------------------------------\n")
model.save_model(MODEL_FILE)
print(f"[+] Native model saved to: {os.path.abspath(MODEL_FILE)}")
export_onnx_model(model, feature_names)
####################################################################################################
if __name__ == "__main__":
print("\n---------------- PE Malware Trainer v3.1 ----------------\n")
if not os.path.exists(DB_PATH):
print(f"[-] Error: Database {DB_PATH} not found.")
sys.exit(1)
raw_schema = get_raw_schema(DB_PATH)
if not raw_schema:
print("[-] Error: Could not read schema.")
sys.exit(1)
X, y, _ = load_data(DB_PATH, raw_schema)
if X is None:
sys.exit(1)
try:
model, X_test, y_test = train_process(X, y)
evaluate_and_save(model, X_test, y_test)
except KeyboardInterrupt:
print("\n\n[-] Training interrupted.")
except Exception as e:
print(f"\n[-] Critical Error: {e}")
File diff suppressed because it is too large. Load diff
+666
View File
@@ -0,0 +1,666 @@
* {
box-sizing: border-box;
margin: 0;
padding: 0;
font-family: -apple-system, BlinkMacSystemFont, "SF Pro Text", "Segoe UI", Roboto, Helvetica, Arial, sans-serif;
-webkit-tap-highlight-color: transparent;
outline: none;
}
:root {
--bg-window: rgb(245, 245, 247);
--bg-nav: rgb(255, 255, 255);
--bg-panel: rgb(255, 255, 255);
--bg-hover: rgba(0, 0, 0, 0.05);
--text-primary: rgb(29, 29, 31);
--text-secondary: rgb(134, 134, 139);
--border-color: rgb(210, 210, 215);
--accent-color: rgb(0, 122, 255);
--accent-alpha: rgba(0, 122, 255, 0.1);
--accent-shadow: rgba(0, 122, 255, 0.2);
--accent-hover: rgba(0, 122, 255, 0.3);
--shadow-sm: 0 2px 12px rgba(0, 0, 0, 0.04);
}
body {
background-color: transparent;
margin: 0;
padding: 0;
width: 100vw;
height: 100vh;
overflow: hidden;
display: flex;
justify-content: center;
align-items: center;
color: var(--text-primary);
}
.app-container {
width: 100vw;
height: 100vh;
display: flex;
flex-direction: column;
overflow: hidden;
background-color: var(--bg-window);
}
.navigate-window {
height: 44px;
background-color: var(--bg-nav);
display: flex;
justify-content: space-between;
align-items: center;
padding: 0 10px 0 20px;
border-bottom: 1px solid var(--border-color);
transition: background-color 0.3s;
}
.navigate-title {
font-size: 16px;
font-weight: 600;
letter-spacing: -0.2px;
}
.window-controls {
display: flex;
gap: 4px;
-webkit-app-region: no-drag;
}
.ctrl-btn {
width: 46px;
height: 32px;
border: none;
background: transparent;
border-radius: 6px;
cursor: pointer;
display: flex;
justify-content: center;
align-items: center;
transition: background-color 0.15s;
color: var(--text-secondary);
}
.ctrl-btn:hover { background-color: var(--bg-hover); color: var(--text-primary); }
#close_button:hover { background-color: #ff3b30; color: white; }
.theme-svg {
width: 16px;
height: 16px;
}
.main-layout {
display: flex;
height: calc(100% - 44px);
}
.options-window {
width: 64px;
background-color: var(--bg-nav);
display: flex;
flex-direction: column;
align-items: center;
padding: 16px 0;
gap: 12px;
border-right: 1px solid var(--border-color);
transition: background-color 0.3s;
}
.nav-btn {
width: 44px;
height: 44px;
border: none;
background: transparent;
border-radius: 10px;
cursor: pointer;
display: flex;
justify-content: center;
align-items: center;
color: var(--text-secondary);
transition: all 0.2s;
position: relative;
}
.nav-btn svg { width: 22px; height: 22px; }
.nav-btn:hover { background-color: var(--bg-hover); color: var(--text-primary); }
.nav-btn.active { background-color: var(--accent-alpha); color: var(--accent-color); }
.spacer { flex-grow: 1; }
.content-window {
flex-grow: 1;
position: relative;
overflow: hidden;
}
.page {
position: absolute;
top: 0;
left: 0;
width: 100%;
height: 100%;
padding: 28px 32px;
opacity: 0;
visibility: hidden;
transform: translateY(8px);
transition: opacity 0.25s ease, transform 0.25s ease;
display: flex;
flex-direction: column;
}
.page.active {
opacity: 1;
visibility: visible;
transform: translateY(0);
}
.state-widget {
flex: 1;
height: 100%;
display: flex;
flex-direction: column;
justify-content: center;
align-items: center;
gap: 16px;
color: var(--accent-color);
}
.state-title {
font-size: 24px;
font-weight: 600;
color: var(--text-primary);
letter-spacing: -0.5px;
}
.log-widget {
flex-grow: 1;
margin-top: 20px;
}
.license-widget {
flex-grow: 1;
}
.log-text, .license-text {
width: 100%;
height: 100%;
border: 1px solid var(--border-color);
border-radius: 12px;
background-color: var(--bg-panel);
color: var(--text-primary);
resize: none;
padding: 16px;
font-size: 13px;
line-height: 1.6;
white-space: pre;
overflow-x: auto;
}
.progress-widget {
height: 70px;
display: flex;
justify-content: space-between;
align-items: center;
margin-bottom: 20px;
gap: 16px;
}
.header-group {
flex: 1;
min-width: 0;
}
.header-group h2 {
font-size: 24px;
font-weight: 600;
letter-spacing: -0.5px;
white-space: nowrap;
}
.header-group p {
font-size: 14px;
color: var(--text-secondary);
margin-top: 4px;
white-space: nowrap;
overflow-x: auto;
padding-bottom: 4px;
}
.modern-action-group {
display: flex;
gap: 12px;
align-items: center;
flex-shrink: 0;
}
.modern-select {
appearance: none;
}
.modern-select:hover, .modern-select:focus {
border-color: var(--accent-color);
box-shadow: 0 0 0 2px rgba(0, 122, 255, 0.2);
}
.modern-btn {
padding: 8px 20px;
border: 1px solid var(--border-color);
border-radius: 10px;
background-color: transparent;
color: var(--text-primary);
font-size: 14px;
font-weight: 500;
cursor: pointer;
transition: all 0.2s;
}
.modern-btn:hover { background-color: var(--bg-hover); }
.primary-btn {
background-color: var(--accent-color);
color: white;
border: none;
box-shadow: 0 2px 4px var(--accent-shadow);
}
.primary-btn:hover {
box-shadow: 0 4px 8px var(--accent-hover);
transform: translateY(-1px);
background-color: var(--accent-color);
opacity: 0.95;
}
.primary-btn:active {
transform: translateY(0);
opacity: 0.8;
}
.danger-btn { color: #ff3b30; border-color: #ff3b30; }
.danger-btn:hover { background-color: rgba(255, 59, 48, 0.1); }
.hidden { display: none !important; }
.scan-content {
flex-grow: 1;
min-height: 0;
display: flex;
flex-direction: column;
}
.virus-widget {
width: 100%;
flex-grow: 1;
min-height: 0;
border: 1px solid var(--border-color);
border-radius: 12px;
background-color: var(--bg-panel);
}
.virus-list {
list-style: none;
height: 100%;
overflow-y: auto;
overflow-x: auto;
padding: 12px;
}
.virus-list .manage-list-item {
min-width: max-content;
}
.list-widget {
display: flex;
flex-direction: column;
height: 100%;
overflow-y: auto;
padding-right: 12px;
gap: 12px;
}
.list-item {
display: flex;
justify-content: space-between;
align-items: center;
padding: 18px 24px;
border-radius: 16px;
background-color: var(--bg-panel);
border: 1px solid var(--border-color);
transition: background-color 0.2s;
gap: 16px;
}
.item-info {
flex: 1;
min-width: 0;
overflow-x: auto;
padding-bottom: 2px;
}
.list-item[data-target]:hover, .list-item[data-action]:hover {
background-color: var(--bg-hover);
}
.item-info h2 {
font-size: 16px;
font-weight: 600;
letter-spacing: -0.2px;
white-space: nowrap;
}
.item-info p {
font-size: 13px;
color: var(--text-secondary);
margin-top: 4px;
white-space: nowrap;
}
.toggle-switch {
position: relative;
display: inline-block;
width: 50px;
height: 28px;
}
.toggle-switch input { opacity: 0; width: 0; height: 0; }
.slider {
position: absolute;
cursor: pointer;
top: 0; left: 0; right: 0; bottom: 0;
background-color: var(--border-color);
transition: .3s;
border-radius: 28px;
}
.slider:before {
position: absolute;
content: "";
height: 24px; width: 24px;
left: 2px; bottom: 2px;
background-color: white;
transition: .3s;
border-radius: 50%;
box-shadow: 0 2px 4px rgba(0, 0, 0, 0.2);
}
.toggle-switch input:checked + .slider {
background-color: var(--accent-color);
}
.toggle-switch input:checked + .slider:before {
transform: translateX(22px);
}
.icon-action-btn {
width: 32px; height: 32px;
border-radius: 8px;
border: none;
background-color: transparent;
color: var(--text-secondary);
cursor: pointer;
display: flex; justify-content: center; align-items: center;
transition: background-color 0.2s;
}
.icon-action-btn:hover { background-color: var(--bg-hover); color: var(--text-primary); }
.icon-action-btn svg { width: 20px; height: 20px; }
.sub-header-toolbar {
display: flex;
align-items: center;
gap: 12px;
margin-bottom: 16px;
width: 100%;
}
.sub-header-toolbar h2 {
font-size: 24px;
font-weight: 600;
letter-spacing: -0.5px;
white-space: nowrap;
}
.icon-btn {
background: transparent;
border: none;
color: var(--text-secondary);
cursor: pointer;
padding: 6px;
border-radius: 8px;
display: flex; align-items: center; justify-content: center;
transition: background-color 0.2s;
}
.icon-btn:hover { background-color: var(--bg-hover); color: var(--text-primary); }
.icon-btn svg { width: 24px; height: 24px; }
.search-box {
display: flex;
align-items: center;
background-color: var(--bg-panel);
border: 1px solid var(--border-color);
border-radius: 10px;
padding: 0 14px;
width: 200px;
transition: border-color 0.2s, box-shadow 0.2s;
}
.search-box:focus-within {
border-color: var(--accent-color);
box-shadow: 0 0 0 2px var(--accent-shadow);
}
.search-box svg {
width: 18px; height: 18px;
color: var(--text-secondary);
}
.search-box input {
border: none;
background: transparent;
color: var(--text-primary);
padding: 10px;
outline: none;
width: 100%;
font-size: 14px;
}
.flex-grow {
flex-grow: 1;
min-height: 0;
}
.manage-widget {
border: 1px solid var(--border-color);
border-radius: 12px;
background-color: var(--bg-panel);
overflow-y: auto;
overflow-x: auto;
}
.manage-list {
list-style: none;
padding: 0;
margin: 0;
min-width: max-content;
}
.manage-list-item {
display: flex;
align-items: center;
gap: 12px;
padding: 12px 16px;
border-bottom: 1px solid var(--border-color);
transition: background-color 0.2s;
}
.manage-list-item:last-child {
border-bottom: none;
}
.manage-list-item:hover {
background-color: var(--bg-hover);
}
.manage-list-item input[type="checkbox"] {
width: 16px;
height: 16px;
accent-color: var(--accent-color);
cursor: pointer;
}
.manage-list-item-content {
flex-grow: 1;
min-width: 0;
font-size: 14px;
color: var(--text-primary);
white-space: nowrap;
}
.custom-select-wrapper {
position: relative;
min-width: 120px;
user-select: none;
}
select.hidden + .custom-select-wrapper {
display: none !important;
}
.custom-select-trigger {
display: flex;
justify-content: space-between;
align-items: center;
padding: 8px 16px;
border-radius: 10px;
border: 1px solid var(--border-color);
background-color: var(--bg-panel);
color: var(--text-primary);
font-size: 14px;
font-weight: 500;
cursor: pointer;
transition: border-color 0.2s, box-shadow 0.2s;
gap: 12px;
}
.custom-select-wrapper.open .custom-select-trigger,
.custom-select-trigger:hover {
border-color: var(--accent-color);
box-shadow: 0 0 0 2px var(--accent-shadow);
}
.custom-select-icon {
display: flex;
align-items: center;
color: var(--text-secondary);
transition: transform 0.3s ease;
}
.custom-select-wrapper.open .custom-select-icon {
transform: rotate(180deg);
}
.custom-select-icon svg { width: 16px; height: 16px; }
.custom-select-options {
position: absolute;
top: calc(100% + 8px);
left: 0;
right: 0;
background-color: var(--bg-panel);
border: 1px solid var(--border-color);
border-radius: 10px;
box-shadow: var(--shadow-sm);
z-index: 100;
list-style: none;
margin: 0;
max-height: 0;
padding: 0 6px;
border-width: 0;
overflow: hidden;
opacity: 0;
visibility: hidden;
transform: translateY(-10px);
transition: opacity 0.2s ease, transform 0.2s ease, visibility 0s 0.2s, max-height 0s 0.2s, padding 0s 0.2s, border-width 0s 0.2s, box-shadow 0s 0.2s;
}
.custom-select-wrapper.open .custom-select-options {
max-height: 260px;
padding: 6px;
border-width: 1px;
overflow-y: auto;
opacity: 1;
visibility: visible;
transform: translateY(0);
transition: opacity 0.2s ease, transform 0.2s ease, visibility 0s 0s, max-height 0s 0s, padding 0s 0s, border-width 0s 0s, box-shadow 0s 0s;
}
.custom-select-option {
padding: 8px 12px;
border-radius: 6px;
cursor: pointer;
font-size: 14px;
color: var(--text-primary);
transition: background-color 0.2s, color 0.2s;
text-align: center;
}
.custom-select-option:hover {
background-color: var(--bg-hover);
}
.custom-select-option.selected {
background-color: var(--accent-alpha);
color: var(--accent-color);
font-weight: 600;
}
.home-top-widget {
display: flex;
align-items: center;
height: 200px;
gap: 24px;
width: 100%;
}
.quick-actions {
flex: 1;
display: flex;
flex-direction: column;
gap: 12px;
height: 100%;
justify-content: center;
}
#loading_overlay {
position: fixed;
top: 0;
left: 0;
width: 100vw;
height: 100vh;
background-color: #e0e0e0;
display: flex;
justify-content: center;
align-items: center;
z-index: 9999;
transition: opacity 0.4s ease, visibility 0.4s ease;
}
#loading_overlay.fade-out {
opacity: 0;
visibility: hidden;
}
.spinner {
animation: rotate 2s linear infinite;
width: 50px;
height: 50px;
}
.spinner .path {
stroke: #86868b;
stroke-linecap: round;
animation: dash 1.5s ease-in-out infinite;
}
@keyframes rotate {
100% { transform: rotate(360deg); }
}
@keyframes dash {
0% { stroke-dasharray: 1, 150; stroke-dashoffset: 0; }
50% { stroke-dasharray: 90, 150; stroke-dashoffset: -35; }
100% { stroke-dasharray: 90, 150; stroke-dashoffset: -124; }
}
.app-container.fade-in {
opacity: 1 !important;
}
::-webkit-scrollbar { width: 6px; height: 6px; }
::-webkit-scrollbar-track { background: transparent; }
::-webkit-scrollbar-thumb { background: var(--border-color); border-radius: 10px; }
::-webkit-scrollbar-thumb:hover { background: var(--text-secondary); }
Binary file not shown.

After

Width:  |  Height:  |  Size: 6.6 KiB

File diff suppressed because it is too large. Load diff
+421
View File
@@ -0,0 +1,421 @@
<!DOCTYPE html>
<html lang="zh-TW">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>PYAS Security</title>
<link rel="stylesheet" href="/static/css/style.css">
<script defer src="/static/js/main.js"></script>
</head>
<body>
<div id="loading_overlay">
<svg class="spinner" viewBox="0 0 50 50">
<circle class="path" cx="25" cy="25" r="20" fill="none" stroke-width="5"></circle>
</svg>
</div>
<div class="app-container" style="opacity: 0; transition: opacity 0.5s ease;">
<header class="navigate-window pywebview-drag-region">
<div class="navigate-title" data-i18n>PYAS Security</div>
<div class="window-controls">
<button class="ctrl-btn" id="about_button" data-target="about_window">
<svg viewBox="0 0 24 24" class="theme-svg" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><circle cx="12" cy="12" r="10"></circle><line x1="12" y1="16" x2="12" y2="12"></line><line x1="12" y1="8" x2="12.01" y2="8"></line></svg>
</button>
<button class="ctrl-btn" id="minimize_button">
<svg viewBox="0 0 24 24" class="theme-svg" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><line x1="5" y1="12" x2="19" y2="12"></line></svg>
</button>
<button class="ctrl-btn" id="close_button">
<svg viewBox="0 0 24 24" class="theme-svg" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><line x1="18" y1="6" x2="6" y2="18"></line><line x1="6" y1="6" x2="18" y2="18"></line></svg>
</button>
</div>
</header>
<div class="main-layout">
<aside class="options-window pywebview-drag-region">
<button class="nav-btn active" data-target="home_window">
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M3 9l9-7 9 7v11a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2z"></path><polyline points="9 22 9 12 15 12 15 22"></polyline></svg>
</button>
<button class="nav-btn" data-target="scan_window">
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><circle cx="11" cy="11" r="8"></circle><line x1="21" y1="21" x2="16.65" y2="16.65"></line></svg>
</button>
<button class="nav-btn" data-target="tool_window">
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><rect x="3" y="3" width="7" height="7"></rect><rect x="14" y="3" width="7" height="7"></rect><rect x="14" y="14" width="7" height="7"></rect><rect x="3" y="14" width="7" height="7"></rect></svg>
</button>
<button class="nav-btn" data-target="protect_window">
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M12 22s8-4 8-10V5l-8-3-8 3v7c0 6 8 10 8 10z"></path></svg>
</button>
<div class="spacer"></div>
<button class="nav-btn" data-target="setting_window">
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><circle cx="12" cy="12" r="3"></circle><path d="M19.4 15a1.65 1.65 0 0 0 .33 1.82l.06.06a2 2 0 0 1 0 2.83 2 2 0 0 1-2.83 0l-.06-.06a1.65 1.65 0 0 0-1.82-.33 1.65 1.65 0 0 0-1 1.51V21a2 2 0 0 1-2 2 2 2 0 0 1-2-2v-.09A1.65 1.65 0 0 0 9 19.4a1.65 1.65 0 0 0-1.82.33l-.06.06a2 2 0 0 1-2.83 0 2 2 0 0 1 0-2.83l.06-.06a1.65 1.65 0 0 0 .33-1.82 1.65 1.65 0 0 0-1.51-1H3a2 2 0 0 1-2-2 2 2 0 0 1 2-2h.09A1.65 1.65 0 0 0 4.6 9a1.65 1.65 0 0 0-.33-1.82l-.06-.06a2 2 0 0 1 0-2.83 2 2 0 0 1 2.83 0l.06.06a1.65 1.65 0 0 0 1.82.33H9a1.65 1.65 0 0 0 1-1.51V3a2 2 0 0 1 2-2 2 2 0 0 1 2 2v.09a1.65 1.65 0 0 0 1 1.51 1.65 1.65 0 0 0 1.82-.33l.06-.06a2 2 0 0 1 2.83 0 2 2 0 0 1 0 2.83l-.06.06a1.65 1.65 0 0 0-.33 1.82V9a1.65 1.65 0 0 0 1.51 1H21a2 2 0 0 1 2 2 2 2 0 0 1-2 2h-.09a1.65 1.65 0 0 0-1.51 1z"></path></svg>
</button>
</aside>
<main class="content-window">
<section id="home_window" class="page active">
<div class="home-top-widget">
<div class="state-widget">
<svg viewBox="0 0 24 24" class="state-icon theme-svg" style="width: 120px; height: 120px;" fill="none" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"><path d="M12 22s8-4 8-10V5l-8-3-8 3v7c0 6 8 10 8 10z"></path><polyline points="9 12 11 14 15 10"></polyline></svg>
<h2 class="state-title" data-i18n>此裝置已受到防護</h2>
</div>
<div class="quick-actions">
<div class="list-item" style="cursor: pointer;" id="quick_scan_btn" data-action="scan">
<div class="item-info">
<h2 data-i18n>智能掃描</h2><p data-i18n>此選項可以快速掃病毒</p>
</div>
<button class="icon-action-btn"><svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><polyline points="9 18 15 12 9 6"></polyline></svg></button>
</div>
<div class="list-item" style="cursor: pointer;" data-target="log_export_window">
<div class="item-info">
<h2 data-i18n>日誌導出</h2><p data-i18n>此選項可以篩選導出日誌</p>
</div>
<button class="icon-action-btn"><svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><polyline points="9 18 15 12 9 6"></polyline></svg></button>
</div>
</div>
</div>
<div class="log-widget">
<textarea class="log-text" readonly></textarea>
</div>
</section>
<section id="scan_window" class="page">
<div class="progress-widget">
<div class="header-group">
<h2 class="section-title" data-i18n>病毒掃描</h2>
<p class="section-desc" id="progress_text" data-i18n>此選項可以選擇掃描方式</p>
</div>
<div class="action-group modern-action-group">
<button class="action-btn modern-btn danger-btn hidden" id="solve_btn" data-i18n>刪除</button>
<select id="scan_method_select" class="modern-select">
<option value="none" selected disabled data-i18n>選擇</option>
<option value="smart" data-i18n>智能</option>
<option value="file" data-i18n>檔案</option>
<option value="path" data-i18n>路徑</option>
<option value="full" data-i18n>全盤</option>
</select>
<button class="action-btn modern-btn hidden" id="stop_btn" data-i18n>停止</button>
</div>
</div>
<div class="scan-content">
<div class="virus-widget">
<ul class="virus-list" id="virus_list"></ul>
</div>
</div>
</section>
<section id="protect_window" class="page">
<div class="list-widget">
<div class="list-item">
<div class="item-info">
<h2 data-i18n>進程防護</h2><p data-i18n>此選項可以攔截進程病毒</p>
</div>
<label class="toggle-switch"><input type="checkbox" checked><span class="slider"></span></label>
</div>
<div class="list-item">
<div class="item-info">
<h2 data-i18n>檔案防護</h2><p data-i18n>此選項可以攔截檔案變更</p>
</div>
<label class="toggle-switch"><input type="checkbox" checked><span class="slider"></span></label>
</div>
<div class="list-item">
<div class="item-info">
<h2 data-i18n>系統防護</h2><p data-i18n>此選項可以攔截系統項目</p>
</div>
<label class="toggle-switch"><input type="checkbox" checked><span class="slider"></span></label>
</div>
<div class="list-item">
<div class="item-info">
<h2 data-i18n>驅動防護</h2><p data-i18n>此選項可以增強自身防護</p>
</div>
<label class="toggle-switch"><input type="checkbox" checked><span class="slider"></span></label>
</div>
<div class="list-item">
<div class="item-info">
<h2 data-i18n>網路防護</h2><p data-i18n>此選項可以攔截網路通訊</p>
</div>
<label class="toggle-switch"><input type="checkbox" checked><span class="slider"></span></label>
</div>
<div class="list-item" style="cursor: pointer;" data-target="custom_protect_window">
<div class="item-info">
<h2 data-i18n>自訂防護</h2><p data-i18n>此選項可以導入自訂防護</p>
</div>
<button class="icon-action-btn"><svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><polyline points="9 18 15 12 9 6"></polyline></svg></button>
</div>
</div>
</section>
<section id="tool_window" class="page">
<div class="list-widget">
<div class="list-item" style="cursor: pointer;" data-target="taskmgr_window">
<div class="item-info">
<h2 data-i18n>進程管理</h2><p data-i18n>此選項可以實時查看系統進程</p>
</div>
<button class="icon-action-btn"><svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><polyline points="9 18 15 12 9 6"></polyline></svg></button>
</div>
<div class="list-item" style="cursor: pointer;" data-target="junk_window">
<div class="item-info">
<h2 data-i18n>垃圾清理</h2><p data-i18n>此選項可以清理暫存垃圾檔案</p>
</div>
<button class="icon-action-btn"><svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><polyline points="9 18 15 12 9 6"></polyline></svg></button>
</div>
<div class="list-item" style="cursor: pointer;" data-target="repair_window">
<div class="item-info">
<h2 data-i18n>系統修復</h2><p data-i18n>此選項可以修復系統註冊項目</p>
</div>
<button class="icon-action-btn"><svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><polyline points="9 18 15 12 9 6"></polyline></svg></button>
</div>
<div class="list-item" style="cursor: pointer;" data-target="popup_window">
<div class="item-info">
<h2 data-i18n>彈窗攔截</h2><p data-i18n>此選項可以選擇指定窗口攔截</p>
</div>
<button class="icon-action-btn"><svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><polyline points="9 18 15 12 9 6"></polyline></svg></button>
</div>
<div class="list-item" style="cursor: pointer;" data-target="whitelist_window">
<div class="item-info">
<h2 data-i18n>白名單</h2><p data-i18n>此選項可以管理系統白名單</p>
</div>
<button class="icon-action-btn"><svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><polyline points="9 18 15 12 9 6"></polyline></svg></button>
</div>
<div class="list-item" style="cursor: pointer;" data-target="quarantine_window">
<div class="item-info">
<h2 data-i18n>隔離區</h2><p data-i18n>此選項可以管理已隔離的檔案</p>
</div>
<button class="icon-action-btn"><svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><polyline points="9 18 15 12 9 6"></polyline></svg></button>
</div>
</div>
</section>
<section id="setting_window" class="page">
<div class="list-widget">
<div class="list-item">
<div class="item-info">
<h2 data-i18n>增強模式</h2><p data-i18n>此選項可以增強掃描引擎</p>
</div>
<label class="toggle-switch"><input type="checkbox"><span class="slider"></span></label>
</div>
<div class="list-item">
<div class="item-info">
<h2 data-i18n>擴展引擎</h2><p data-i18n>此選項可以使用擴展引擎</p>
</div>
<label class="toggle-switch"><input type="checkbox"><span class="slider"></span></label>
</div>
<div class="list-item">
<div class="item-info">
<h2 data-i18n>雲端服務</h2><p data-i18n>此選項可以上報雲端引擎</p>
</div>
<label class="toggle-switch"><input type="checkbox" checked><span class="slider"></span></label>
</div>
<div class="list-item">
<div class="item-info">
<h2 data-i18n>顯色主題</h2><p data-i18n>此選項可以選擇主題顏色</p>
</div>
<select id="theme_select" class="modern-select">
<option value="white_switch" data-i18n>白色主題</option>
<option value="red_switch" data-i18n>紅色主題</option>
<option value="yellow_switch" data-i18n>黃色主題</option>
<option value="green_switch" data-i18n>綠色主題</option>
<option value="blue_switch" data-i18n>藍色主題</option>
<option value="black_switch" data-i18n>黑色主題</option>
</select>
</div>
<div class="list-item">
<div class="item-info">
<h2 data-i18n>顯示語言</h2><p data-i18n>此選項可以選擇顯示語言</p>
</div>
<select id="lang_select" class="modern-select">
<option value="english_switch">English</option>
<option value="traditional_switch">繁體中文</option>
<option value="simplified_switch">简体中文</option>
<option value="japanese_switch">日本語</option>
<option value="korean_switch">한국어</option>
<option value="french_switch">Français</option>
<option value="spanish_switch">Español</option>
<option value="hindi_switch">हिन्दी</option>
<option value="arabic_switch">العربية</option>
<option value="russian_switch">Русский</option>
<option value="slovenian_switch">Slovenščina</option>
</select>
</div>
<div class="list-item">
<div class="item-info">
<h2 data-i18n>重置選項</h2><p data-i18n>此選項可以重置所有設定</p>
</div>
<button class="action-btn modern-btn danger-btn" data-i18n>執行</button>
</div>
</div>
</section>
<section id="about_window" class="page">
<div class="list-widget">
<div class="list-item">
<div class="item-info">
<h2 data-i18n>產品金鑰</h2><p data-i18n>此選項可以啟用產品金鑰</p>
</div>
<button class="icon-action-btn"><svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M21 2l-2 2m-7.61 7.61a5.5 5.5 0 1 1-7.778 7.778 5.5 5.5 0 0 1 7.777-7.777zm0 0L15.5 7.5m0 0l3 3L22 7l-3-3m-3.5 3.5L19 4"></path></svg></button>
</div>
<div class="list-item">
<div class="item-info">
<h2 data-i18n>官方網站</h2><p data-i18n>此選項可以前往官方網站</p>
</div>
<button class="icon-action-btn"><svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><circle cx="12" cy="12" r="10"></circle><line x1="2" y1="12" x2="22" y2="12"></line><path d="M12 2a15.3 15.3 0 0 1 4 10 15.3 15.3 0 0 1-4 10 15.3 15.3 0 0 1-4-10 15.3 15.3 0 0 1 4-10z"></path></svg></button>
</div>
<div class="list-item">
<div class="item-info">
<h2 data-i18n>檢查更新</h2><p data-i18n>此選項可以檢查可用更新</p>
</div>
<button class="icon-action-btn"><svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><polyline points="23 4 23 10 17 10"></polyline><path d="M20.49 15a9 9 0 1 1-2.12-9.36L23 10"></path></svg></button>
</div>
<div class="license-widget">
<textarea class="license-text" readonly>87owo Publicly Available Source Code License Agreement
Last updated: 2025-12-19
1. License and Ownership
- This software and its source code are source available for public view.
- Access or execution of certain features may require an activation key.
- 87owo reserves all legal rights, no intellectual property is transferred.
- All project archives, source code, and binaries remain fully proprietary.
2. Use and Prohibitions
- You may view, download, and modify the code for local and personal use.
- Redistribution of any original or modified code requires prior consent.
- Commercial hosting or SaaS deployment is prohibited without permission.
- Reverse engineering, license removal, or illegal use is strictly barred.
3. Data and Privacy
- This project may collect operational data for optimization and analysis.
- All collected data is strictly anonymized to ensure the user's privacy.
- Transmission of information is encrypted to prevent unauthorized access.
4. Updates and Terms
- New releases may include critical patches, enhancements, or UI changes.
- Continued use of these services constitutes acceptance of updated terms.
- The author reserves the right to modify these license terms at any time.
5. Warranty and Liability
- All services and assets are provided "as is" without any form of warranty.
- 87owo is not liable for any direct or indirect damages from software use.
- Users assume all risks regarding performance, data, and system integrity.
© 2020-2026 87owo (PYAS Security). All rights reserved.</textarea>
</div>
</div>
</section>
<section id="taskmgr_window" class="page sub-page">
<div class="sub-header-toolbar">
<button class="icon-btn" data-target="tool_window"><svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><polyline points="15 18 9 12 15 6"></polyline></svg></button>
<h2 data-i18n>進程管理</h2>
<div class="spacer"></div>
<div class="search-box">
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><circle cx="11" cy="11" r="8"></circle><line x1="21" y1="21" x2="16.65" y2="16.65"></line></svg>
<input type="text" data-i18n-placeholder="搜尋進程..." placeholder="搜尋進程...">
</div>
<button class="action-btn modern-btn primary-btn" data-i18n>查看</button>
<button class="action-btn modern-btn danger-btn" data-i18n>結束</button>
</div>
<div class="manage-widget flex-grow"><ul class="manage-list"></ul></div>
</section>
<section id="whitelist_window" class="page sub-page">
<div class="sub-header-toolbar">
<button class="icon-btn" data-target="tool_window"><svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><polyline points="15 18 9 12 15 6"></polyline></svg></button>
<h2 data-i18n>白名單管理</h2>
<div class="spacer"></div>
<div class="search-box">
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><circle cx="11" cy="11" r="8"></circle><line x1="21" y1="21" x2="16.65" y2="16.65"></line></svg>
<input type="text" data-i18n-placeholder="搜尋檔案..." placeholder="搜尋檔案...">
</div>
<button class="action-btn modern-btn primary-btn" data-i18n>加入</button>
<button class="action-btn modern-btn" data-i18n>移除</button>
</div>
<div class="manage-widget flex-grow"><ul class="manage-list"></ul></div>
</section>
<section id="quarantine_window" class="page sub-page">
<div class="sub-header-toolbar">
<button class="icon-btn" data-target="tool_window"><svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><polyline points="15 18 9 12 15 6"></polyline></svg></button>
<h2 data-i18n>隔離區管理</h2>
<div class="spacer"></div>
<div class="search-box">
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><circle cx="11" cy="11" r="8"></circle><line x1="21" y1="21" x2="16.65" y2="16.65"></line></svg>
<input type="text" data-i18n-placeholder="搜尋檔案..." placeholder="搜尋檔案...">
</div>
<button class="action-btn modern-btn primary-btn" data-i18n>加入</button>
<button class="action-btn modern-btn" data-i18n>移除</button>
</div>
<div class="manage-widget flex-grow"><ul class="manage-list"></ul></div>
</section>
<section id="junk_window" class="page sub-page">
<div class="sub-header-toolbar">
<button class="icon-btn" data-target="tool_window"><svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><polyline points="15 18 9 12 15 6"></polyline></svg></button>
<h2 data-i18n>垃圾清理</h2>
<div class="spacer"></div>
<div class="search-box">
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><circle cx="11" cy="11" r="8"></circle><line x1="21" y1="21" x2="16.65" y2="16.65"></line></svg>
<input type="text" data-i18n-placeholder="搜尋檔案..." placeholder="搜尋檔案...">
</div>
<button class="action-btn modern-btn primary-btn" data-i18n>掃描</button>
<button class="action-btn modern-btn danger-btn" data-i18n>刪除</button>
</div>
<div class="manage-widget flex-grow"><ul class="manage-list"></ul></div>
</section>
<section id="repair_window" class="page sub-page">
<div class="sub-header-toolbar">
<button class="icon-btn" data-target="tool_window"><svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><polyline points="15 18 9 12 15 6"></polyline></svg></button>
<h2 data-i18n>系統修復</h2>
<div class="spacer"></div>
<button class="action-btn modern-btn primary-btn" data-i18n>掃描</button>
<button class="action-btn modern-btn danger-btn" data-i18n>修復</button>
</div>
<div class="manage-widget flex-grow"><ul class="manage-list"></ul></div>
</section>
<section id="popup_window" class="page sub-page">
<div class="sub-header-toolbar">
<button class="icon-btn" data-target="tool_window"><svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><polyline points="15 18 9 12 15 6"></polyline></svg></button>
<h2 data-i18n>彈窗攔截</h2>
<div class="spacer"></div>
<div class="search-box">
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><circle cx="11" cy="11" r="8"></circle><line x1="21" y1="21" x2="16.65" y2="16.65"></line></svg>
<input type="text" data-i18n-placeholder="搜尋窗口..." placeholder="搜尋窗口...">
</div>
<button class="action-btn modern-btn primary-btn" data-i18n>加入</button>
<button class="action-btn modern-btn" data-i18n>移除</button>
</div>
<div class="manage-widget flex-grow"><ul class="manage-list"></ul></div>
</section>
<section id="custom_protect_window" class="page sub-page">
<div class="sub-header-toolbar">
<button class="icon-btn" data-target="protect_window"><svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><polyline points="15 18 9 12 15 6"></polyline></svg></button>
<h2 data-i18n>自訂防護</h2>
<div class="spacer"></div>
<div class="search-box">
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><circle cx="11" cy="11" r="8"></circle><line x1="21" y1="21" x2="16.65" y2="16.65"></line></svg>
<input type="text" data-i18n-placeholder="搜尋規則..." placeholder="搜尋規則...">
</div>
<button class="action-btn modern-btn primary-btn" data-i18n>加入</button>
<button class="action-btn modern-btn" data-i18n>移除</button>
</div>
<div class="manage-widget flex-grow"><ul class="manage-list"></ul></div>
</section>
<section id="log_export_window" class="page sub-page">
<div class="sub-header-toolbar">
<button class="icon-btn" data-target="home_window"><svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><polyline points="15 18 9 12 15 6"></polyline></svg></button>
<h2 data-i18n>日誌導出</h2>
<div class="spacer"></div>
<div class="search-box">
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><circle cx="11" cy="11" r="8"></circle><line x1="21" y1="21" x2="16.65" y2="16.65"></line></svg>
<input type="text" data-i18n-placeholder="搜尋日誌..." placeholder="搜尋日誌...">
</div>
<button class="action-btn modern-btn primary-btn" data-i18n>導出</button>
<button class="action-btn modern-btn danger-btn" data-i18n>刪除</button>
</div>
<div class="manage-widget flex-grow"><ul class="manage-list"></ul></div>
</section>
</main>
</div>
</div>
</body>
</html>
+1818 -1593
View File
File diff suppressed because it is too large. Load diff
-130
View File
@@ -1,130 +0,0 @@
translate_dict = {
"traditional_switch": {"开启":"開啟","关闭":"關閉","On":"開啟","Off":"關閉"},
"simplified_switch": {"開啟":"开启","關閉":"关闭","On":"开启","Off":"关闭",
"錯誤":"错误","發現":"发现","關閉":"关闭","裝置":"设备","記憶體":"内存",
"軟體":"软件","檔案":"文件","確定":"确认","設定":"设置","關於":"关于",
"總數":"总数","註冊":"注册","圖標":"图标","路徑":"路径","網路":"网络",
"位元":"字节","靈敏":"敏感","複製":"复制","開啟":"开启","靜態":"静态",
"訊":"讯","項":"项","掃":"扫","試":"试","黃":"黄","嗎":"吗","防":"保",
"鑰":"钥","當":"当","後":"后","護":"护","導":"导","區":"区","復":"复",
"攔":"拦","請":"请","統":"统","刪":"删","進":"进","個":"个","選":"选",
"啟":"启","監":"监","並":"并","徑":"径","盤":"盘","隱":"隐","動":"动",
"狀":"状","誌":"志","這":"这","會":"会","題":"题","擇":"择","語":"语",
"無":"无","顯":"显","變":"变","戶":"户","碼":"码","雲":"云","介":"界",
"稱":"称","類":"类","輸":"输","執":"执","數":"数","尋":"寻","網":"网",
"詢":"询","製":"制","員":"员","許":"许","條":"条","藍":"蓝","紅":"红",
"綠":"绿","態":"态","務":"务","連":"连","檢":"检","測":"测","關":"关",
"強":"强","驅":"驱","為":"为","腳":"脚","單":"单","開":"开","擴":"扩",
"惡":"恶","據":"据","庫":"库","經":"经","敗":"败","謝":"谢","別":"别",
"載":"载","誤":"误","報":"报","鏈":"链","竄":"窜","蹤":"踪","與":"与",
"異":"异","實":"实","時":"时","彈":"弹","窗":"窗","暫":"暂","訂":"订",
"資":"资","產":"产","金":"密","離":"离","規":"规","則":"则","來":"来",
"殼":"壳","碼":"码","線":"线","餘":"余",},
"english_switch": {"開啟":"On","關閉":"Off","开启":"On","关闭":"Off",",":", ",
"立即刪除":"Delete Now","安全模式":"Safe Mode","官方網站":"Official Website ",
"腳本":"Script ","鏈式":"Chain ","記憶體":"Memory ","正在加載":"Loading ","加載":"Load ",
"行為":"Behavior ","可疑":"Suspicious ","勒索":"Ransom ","主要":"Main ",
"許可條款":"License Terms ","惡意":"Malicious", "開發":"Developers ",
"啟用":"Enable ","禁用":"Disable ","正在初始化中":"Initializing ",
"正在掃描":"Scanning ","正在刪除":"Deleting ","正在追蹤":"Tracking ",
"最小化到":"Minimize To ","靈敏度":"Sensitivity ","正在檢查":"Checking ",
"確定要":"Sure Want To ","引擎靈敏度":"Sensitivity ","更新":"Update ",
"白色主題":"White","黃色主題":"Yellow","紅色主題":"Red","語言":"Language ",
"藍色主題":"Blue","綠色主題":"Green","自訂主題":"Custom","黑色主題":"Black",
"特別感謝":"Contributors ","版本資訊":"Information","通訊":"Connect ",
"註冊":"Registry ","退出":"Quit ","檔案":"File ","當前":"Currently ",
"狀態":"State ","掃描":"Scan ","工具":"Tools ","防護":"Protect ",
"失敗":"Failed ","請":"Please ","路徑":"Path ","方式":"Method ",
"需要":"Need ","病毒":"Virus ","選擇":"Select ","返回":"Back",
"受到":"Been ","錯誤":"Error","完成":"Complete ","引擎":"Engine ",
"托盤":"Tray ","圖標":"Icon ","不安全":"Unsafe","增強":"Enhance ",
"系統":"System ","雲端":"Cloud ","重啟":"Reboot ","修復":"Repair ",
"裝置":"Device ","發現":"Found ","成功":"Success ","清理":"Cleanup ",
"確定":"OK","執行":"Execute ","警告":"Warning ","使用":"Use ",
"網路":"Network ","變更":"Change ","總數":"Total","結束":"End ",
"選項":"Options ","監控":"Monitor ","連接":"Connect ","可以":"Can ",
"進程":"Process ","攔截":"Block ","引導":"Boot ","刪除":"Delete ",
"分區":"Partition ","設定":"Setting ","關於":"About","模式":"Mode ",
"提高":"Improve ","安全":"Security ","顯色":"Color ","顏色":"Color ",
"主題":"Theme ", "回收":"Collect ", "無用":"Useless ","導入":"Import ",
"風險":"Risk ","介面":"UI ","核心":"Core ","服務":"Services ",
"管理":"Manage","所有":"All ","您":"You ","取消":"Cancel ","未":"Not ",
"垃圾":"Junk ","位元":"byte ","本機":"Computer ","停止":"Stop ",
"到":"To ","白名單":"White List ","軟體":"Software ","正在":"",
"增加":"Add ","移除":"Delete ","項目":"Items ","生成":"Create ","稱":"",
"複製":"Copy ","此":"This ","並":"And ","全盤":"Full ","最新":"Latest ",
"已存在":"Already ","數據庫":"Database ","誤報":"False Positive ",
"高":"High ","新":"New ","和":"And ","可能會":"Possible ","會":"Can ",
"日誌":"Log","已":"Has ","彈窗":"Window ","要":"To ","驅動":"Driver ",
"的":"","顯示":"","個":"","嗎":"","了":"","行":"","立即":"","規則":"Rule ",
"密鑰":"Key ","檢測":"Detect ","製作":"Maker ","運行":"Running",
"擴展":"Extension ","套件":"Kit ","自動":"Auto ","上報":"Report ",
"分析":"Analysis ","自身":"Self ","其他":"Other ","配置":"Config ",
"丟失":"Lost ","網站":"Website ","不兼容":"Not Compatible ","列表":"List ",
"竄改":"Tamper ","第三方":"Other ","實時":"Real Time ","查看":"View ",
"指定":"Specify ","清空":"Empty ","資源回收桶":"Recycle Bin ", "無法":"Cannot ",
"與":"With ","當":"When ","功能":"Function ","異常":"Abnormal ", "來":"To ",
"重置":"Reset ","窗口":"Window ","暫存":"Temp ","自訂":"Custom ",
"不支持":"Not Support ","共":"Total ","秒":"Seconds ","耗時":"Takes ",
"沙箱":"Sandbox ", "虛擬":"Virtual ", "隔離":"Quarantine ", "時":"",
"建立":"Create ","環境":"Environment ","沙箱":"Sandbox ","化":"", "表":"",
"達":"Reached ", "上限":"Limit ", "區":"", "金鑰":"Key ", "產品":"Product ",
"運行庫":".NET CLR ","殼碼":"Shellcode ","線程":"Thread ","注入":"Inject ",
"前往":"Go ","檢查":"Check ","可用":"Available ","靜態":"Static ",
"版本":"Version ","是":"","剩餘":"Remain ","遠控":"Remote ","監控":"Monitor ",
" ":" "},
"white_switch": {
"_white": "_black",},
"red_switch": {
"235, 235, 235": "255, 235, 235",
"245, 245, 245": "255, 225, 225",
"255, 255, 255": "255, 245, 245",
"240, 240, 240": "255, 220, 220",
"250, 250, 250": "255, 210, 210",
"220, 255, 220": "255, 220, 220",
"210, 255, 210": "255, 210, 210",
"_white": "_black"
},
"yellow_switch": {
"235, 235, 235": "255, 255, 235",
"245, 245, 245": "255, 255, 225",
"255, 255, 255": "255, 255, 245",
"240, 240, 240": "255, 255, 220",
"250, 250, 250": "255, 255, 210",
"220, 255, 220": "255, 255, 220",
"210, 255, 210": "255, 255, 210",
"_white": "_black"
},
"green_switch": {
"235, 235, 235": "235, 255, 235",
"245, 245, 245": "225, 255, 225",
"255, 255, 255": "245, 255, 245",
"240, 240, 240": "220, 255, 220",
"250, 250, 250": "210, 255, 210",
"_white": "_black"
},
"blue_switch": {
"235, 235, 235": "235, 235, 255",
"245, 245, 245": "225, 225, 255",
"255, 255, 255": "245, 245, 255",
"240, 240, 240": "220, 220, 255",
"250, 250, 250": "210, 210, 255",
"220, 255, 220": "220, 220, 255",
"210, 255, 210": "210, 210, 255",
"_white": "_black"
},
"black_switch": {
"255, 255, 255": "20, 20, 20",
"245, 245, 245": "40, 40, 40",
"235, 235, 235": "30, 30, 30",
"240, 240, 240": "70, 70, 70",
"250, 250, 250": "80, 80, 80",
"220, 255, 220": "70, 70, 70",
"210, 255, 210": "80, 80, 80",
"0, 0, 0": "255, 255, 255",
"_black": "_white"
}
}
+687 -69
View File
@@ -1,4 +1,4 @@
import os, yara, time, numpy, base64, requests
import os, yara, time, numpy, base64, requests, pefile, math, json, re, datetime
import ctypes, ctypes.wintypes, hashlib, onnxruntime
from PIL import Image
@@ -11,14 +11,25 @@ class GUID(ctypes.Structure):
("Data1", ctypes.wintypes.DWORD),
("Data2", ctypes.wintypes.WORD),
("Data3", ctypes.wintypes.WORD),
("Data4", ctypes.c_ubyte * 8)]
("Data4", ctypes.c_ubyte * 8)
]
class WINTRUST_FILE_INFO(ctypes.Structure):
_fields_ = [
("cbStruct", ctypes.wintypes.DWORD),
("pcwszFilePath", ctypes.wintypes.LPCWSTR),
("hFile", ctypes.wintypes.HANDLE),
("pgKnownSubject", ctypes.wintypes.LPVOID)]
("pgKnownSubject", ctypes.wintypes.LPVOID)
]
class WINTRUST_DATA_UNION(ctypes.Union):
_fields_ = [
("pFile", ctypes.POINTER(WINTRUST_FILE_INFO)),
("pCatalog", ctypes.wintypes.LPVOID),
("pBlob", ctypes.wintypes.LPVOID),
("pSgnr", ctypes.wintypes.LPVOID),
("pCert", ctypes.wintypes.LPVOID)
]
class WINTRUST_DATA(ctypes.Structure):
_fields_ = [
@@ -28,19 +39,20 @@ class WINTRUST_DATA(ctypes.Structure):
("dwUIChoice", ctypes.wintypes.DWORD),
("fdwRevocationChecks", ctypes.wintypes.DWORD),
("dwUnionChoice", ctypes.wintypes.DWORD),
("pFile", ctypes.POINTER(WINTRUST_FILE_INFO)),
("u", WINTRUST_DATA_UNION),
("dwStateAction", ctypes.wintypes.DWORD),
("hWVTStateData", ctypes.wintypes.HANDLE),
("pwszURLReference", ctypes.wintypes.LPCWSTR),
("dwProvFlags", ctypes.wintypes.DWORD),
("dwUIContext", ctypes.wintypes.DWORD),
("pSignatureSettings", ctypes.wintypes.LPVOID)]
("pSignatureSettings", ctypes.wintypes.LPVOID)
]
####################################################################################################
class sign_scanner:
def __init__(self):
self.verify = GUID(0x00AAC56B, 0xCD44, 0x11D0, (0x8C, 0xC2, 0x00, 0xC0, 0x4F, 0xC2, 0x95, 0xEE))
self.verify = GUID(0x00AAC56B, 0xCD44, 0x11D0, (ctypes.c_ubyte * 8)(0x8C, 0xC2, 0x00, 0xC0, 0x4F, 0xC2, 0x95, 0xEE))
def init_windll(self, path):
for name in path:
@@ -48,7 +60,6 @@ class sign_scanner:
setattr(self, name.lower(), ctypes.WinDLL(name, use_last_error=True))
except Exception:
pass
try:
self.WinVerifyTrust = self.wintrust.WinVerifyTrust
self.WinVerifyTrust.restype = ctypes.wintypes.LONG
@@ -56,15 +67,34 @@ class sign_scanner:
except Exception:
pass
####################################################################################################
def sign_verify(self, file_path):
if os.name != 'nt':
return False
try:
fi = WINTRUST_FILE_INFO(ctypes.sizeof(WINTRUST_FILE_INFO), file_path, None, None)
data = WINTRUST_DATA(ctypes.sizeof(WINTRUST_DATA), None, None, 2, 0, 1,
ctypes.pointer(fi), 1, None, None, 0, 0, None)
s = self.WinVerifyTrust(None, ctypes.byref(self.verify), ctypes.byref(data))
data.dwStateAction = 2
self.WinVerifyTrust(None, ctypes.byref(self.verify), ctypes.byref(data))
return s == 0
fi = WINTRUST_FILE_INFO()
fi.cbStruct = ctypes.sizeof(WINTRUST_FILE_INFO)
fi.pcwszFilePath = os.path.abspath(file_path)
fi.hFile = None
fi.pgKnownSubject = None
wt_union = WINTRUST_DATA_UNION()
wt_union.pFile = ctypes.pointer(fi)
td = WINTRUST_DATA()
td.cbStruct = ctypes.sizeof(WINTRUST_DATA)
td.pPolicyCallbackData = None
td.pSIPClientData = None
td.dwUIChoice = 2
td.fdwRevocationChecks = 0
td.dwUnionChoice = 1
td.u = wt_union
td.dwStateAction = 0
td.hWVTStateData = None
td.pwszURLReference = None
td.dwProvFlags = 0
td.dwUIContext = 0
td.pSignatureSettings = None
return self.WinVerifyTrust(None, ctypes.byref(self.verify), ctypes.byref(td)) == 0
except Exception:
return False
@@ -94,6 +124,8 @@ class rule_scanner:
if yara_files:
self.compile_all_rules(yara_files)
####################################################################################################
def load_compiled_rule(self, file):
try:
self.rules = yara.load(file)
@@ -113,6 +145,8 @@ class rule_scanner:
except Exception:
pass
####################################################################################################
def yara_scan(self, file_path):
try:
if not self.rules:
@@ -121,21 +155,20 @@ class rule_scanner:
matches = self.rules.match(filepath=file_path)
if matches:
rule_name = str(matches[0])
types = rule_name.split("_")[0]
label = rule_name.split("_")[1]
level = rule_name.split("_")[2]
return f"{types}/{label}", level
label = rule_name.split("_")[0]
level = rule_name.split("_")[-1]
return f"{label}:WinPE/Unknown.A!rb", level
return False, False
except Exception:
return False, False
####################################################################################################
class model_scanner:
class cnn_scanner:
def __init__(self):
self.models = []
self.labels = ["Pefile/White", "Pefile/General"]
self.detect_set = {"Pefile/General"}
self.labels = ["White:WinPE/Unknown", "General:WinPE/Unknown"]
self.detect_set = {"General:WinPE/Unknown"}
self.resize = (224, 224)
def load_path(self, path, callback=None):
@@ -149,11 +182,13 @@ class model_scanner:
def load_file(self, file):
if file.lower().endswith('.onnx'):
try:
session = onnxruntime.InferenceSession(file)
session = onnxruntime.InferenceSession(file, providers=['CPUExecutionProvider'])
self.models.append(session)
except Exception:
return False
####################################################################################################
def model_scan(self, file_path, full_output=False):
if not self.models:
return (False, False) if not full_output else ([], str(file_path), None)
@@ -186,20 +221,23 @@ class model_scanner:
try:
probs = model.run(None, {input_name: curr_arr})[0]
pred = probs[0]
pred_prob = float(probs[0][0]) if hasattr(probs[0], '__len__') else float(probs[0])
except Exception:
continue
idx = int(numpy.argmax(pred))
label = self.labels[idx] if idx < len(self.labels) else f"Class_{idx}"
conf = round(float(pred[idx]) * 100, 2)
is_malicious = pred_prob > 0.5
idx = 1 if is_malicious else 0
label = self.labels[idx]
conf = pred_prob if is_malicious else (1.0 - pred_prob)
conf = round(conf * 100, 2)
if full_output:
results.append(("Whole File", label, conf, self.pil_to_base64(image)))
if label in self.detect_set:
if best_malicious is None or conf > best_malicious[1]:
best_malicious = (label, int(conf))
best_malicious = (f"{label}.{int(conf)}!dl", int(conf))
if full_output:
results.sort(key=lambda x: (x[1] not in self.detect_set, -x[2]))
@@ -209,6 +247,8 @@ class model_scanner:
return best_malicious if best_malicious else (False, False)
####################################################################################################
def pil_to_base64(self, img):
buf = BytesIO()
img.save(buf, format='PNG')
@@ -232,9 +272,556 @@ class model_scanner:
####################################################################################################
class pe_scanner:
API_CAT_MAPPING = {
'ProcessControl': {
'CreateProcessA', 'CreateProcessW', 'WinExec', 'ShellExecuteA', 'ShellExecuteW',
'ShellExecuteExW', 'ExitProcess', 'TerminateProcess', 'OpenProcess', 'GetExitCodeProcess',
'SetThreadPriority', 'GetThreadPriority', 'GetCurrentProcess', 'GetCurrentProcessId',
'GetModuleFileNameA', 'GetCommandLineA', 'GetModuleFileNameW', 'GetStartupInfoW'
},
'Injection': {
'VirtualAlloc', 'VirtualAllocEx', 'VirtualProtect', 'VirtualProtectEx', 'WriteProcessMemory',
'ReadProcessMemory', 'CreateRemoteThread', 'QueueUserAPC', 'SetThreadContext', 'GetThreadContext',
'Wow64SetThreadContext', 'NtUnmapViewOfSection', 'ZwUnmapViewOfSection', 'RtlCreateUserThread',
'SetWindowsHookExA', 'SetWindowsHookExW', 'UnhookWindowsHookEx', 'LoadLibraryA', 'LoadLibraryW',
'LoadLibraryExA', 'LoadLibraryExW', 'GetProcAddress', 'GetModuleHandleA', 'GetModuleHandleW',
'FreeLibrary', 'CreateFileMappingA', 'CreateFileMappingW', 'MapViewOfFile', 'UnmapViewOfSection',
'VirtualFree', 'VirtualQuery'
},
'Synchronization': {
'WaitForSingleObject', 'WaitForSingleObjectEx', 'WaitForMultipleObjects', 'WaitForMultipleObjectsEx',
'CreateMutexA', 'CreateMutexW', 'OpenMutexW', 'ReleaseMutex', 'CreateEventA', 'CreateEventW',
'OpenEventW', 'SetEvent', 'ResetEvent', 'EnterCriticalSection', 'LeaveCriticalSection',
'InitializeCriticalSection', 'DeleteCriticalSection', 'Sleep', 'SleepEx',
'InitializeCriticalSectionAndSpinCount', 'InterlockedDecrement', 'InterlockedIncrement',
'InterlockedExchange', 'InterlockedCompareExchange', 'SetTimer', 'KillTimer'
},
'MultiThreading': {
'CreateThread', 'ResumeThread', 'SuspendThread', 'ExitThread', 'TerminateThread',
'GetCurrentThread', 'GetCurrentThreadId', 'TlsAlloc', 'TlsSetValue', 'TlsGetValue',
'CreateThreadpoolWork', 'SubmitThreadpoolWork', 'TlsFree'
},
'Network': {
'socket', 'connect', 'send', 'recv', 'bind', 'listen', 'accept', 'gethostbyname', 'getaddrinfo',
'WSAStartup', 'WSACleanup', 'WSAIoctl', 'WSASocketA', 'WSASocketW', 'closesocket', 'htons',
'InternetOpenA', 'InternetOpenW', 'InternetConnectA', 'InternetConnectW', 'InternetOpenUrlA',
'InternetOpenUrlW', 'HttpOpenRequestA', 'HttpOpenRequestW', 'HttpSendRequestA', 'HttpSendRequestW',
'InternetReadFile', 'URLDownloadToFileA', 'URLDownloadToFileW', 'DnsQuery_A', 'DnsQuery_W'
},
'Encryption': {
'CryptAcquireContextA', 'CryptAcquireContextW', 'CryptCreateHash', 'CryptHashData',
'CryptDeriveKey', 'CryptEncrypt', 'CryptDecrypt', 'CryptDestroyKey', 'CryptDestroyHash',
'CryptReleaseContext', 'CryptGenKey', 'CryptImportKey', 'CryptExportKey'
},
'DataObfuscation': {
'RtlDecompressBuffer', 'MultiByteToWideChar', 'WideCharToMultiByte', 'Base64Decode',
'CryptDecodeObject', 'IsDBCSLeadByte', 'CharUpperA', 'CharLowerA',
'GetStringTypeW', 'LCMapStringW', 'IsValidCodePage', 'DecodePointer', 'EncodePointer'
},
'FileIO': {
'CreateFileA', 'CreateFileW', 'WriteFile', 'ReadFile', 'DeleteFileA', 'DeleteFileW',
'CopyFileA', 'CopyFileW', 'MoveFileA', 'MoveFileW', 'FindFirstFileA', 'FindNextFileA',
'GetTempPathA', 'GetTempPathW', 'GetTempFileNameA', 'GetTempFileNameW', 'SetFileAttributesA',
'SetFileAttributesW', 'DeviceIoControl', 'SetFileTime', 'GetFileSize', 'GetFileSizeEx',
'SetFilePointer', 'FlushFileBuffers', 'ConnectNamedPipe', 'PeekNamedPipe',
'CloseHandle', 'GetFileType', 'SetStdHandle', 'SetFilePointerEx', 'FindClose',
'SetHandleCount', 'SetEndOfFile', 'GetFullPathNameA', 'GetFileAttributesA'
},
'Registry': {
'RegOpenKeyExA', 'RegOpenKeyExW', 'RegSetValueExA', 'RegSetValueExW',
'RegCreateKeyExA', 'RegCreateKeyExW', 'RegDeleteKeyA', 'RegDeleteKeyW',
'RegEnumValueA', 'RegEnumValueW', 'RegQueryValueExA', 'RegQueryValueExW',
'RegDeleteValueA', 'RegDeleteValueW', 'RegCloseKey'
},
'Services': {
'OpenSCManagerA', 'OpenSCManagerW', 'CreateServiceA', 'CreateServiceW',
'StartServiceA', 'StartServiceW', 'ControlService', 'DeleteService'
},
'Privileges': {
'AdjustTokenPrivileges', 'LookupPrivilegeValueA', 'LookupPrivilegeValueW',
'OpenProcessToken', 'NetUserAdd', 'NetLocalGroupAddMembers', 'IsAdmin',
'GetUserNameA', 'GetUserNameW'
},
'Native': {
'RtlUnwind', 'RtlVirtualUnwind', 'RtlCaptureContext', 'RtlLookupFunctionEntry',
'NtClose', 'NtQueryInformationProcess', 'RtlAllocateHeap', 'RtlFreeHeap',
'GetSystemTimeAsFileTime', 'GetLocalTime', 'GlobalMemoryStatus',
'GetVersionExA', 'GetVersionExW', 'GetComputerNameA', 'GetComputerNameW',
'GetLastError', 'GetACP', 'RaiseException', 'SetLastError', 'GetEnvironmentStringsW',
'FreeEnvironmentStringsW', 'GetLocaleInfoA', 'lstrlenA', 'GetVersion', 'GetSystemInfo',
'CompareStringA'
},
'DotNet': {
'_CorExeMain', '_CorDllMain'
},
'AntiDebug': {
'IsDebuggerPresent', 'CheckRemoteDebuggerPresent', 'OutputDebugStringA', 'OutputDebugStringW',
'GetTickCount', 'GetTickCount64', 'QueryPerformanceCounter', 'FindWindowA', 'FindWindowW',
'EnumWindows', 'EnumChildWindows', 'GetWindowRect', 'GetClientRect', 'SetWindowDisplayAffinity',
'UnhandledExceptionFilter', 'SetUnhandledExceptionFilter', 'IsProcessorFeaturePresent',
'SetErrorMode', 'GetTimeZoneInformation', 'GetDriveTypeW', 'GetDiskFreeSpaceA'
},
'Keylogging': {
'GetAsyncKeyState', 'GetKeyState', 'GetKeyboardState', 'MapVirtualKeyA', 'MapVirtualKeyW',
'ToAscii', 'ToAsciiEx', 'ToUnicode', 'ToUnicodeEx', 'GetKeyNameTextA', 'GetForegroundWindow',
'KeybdEvent', 'SendInput', 'MapVirtualKeyExA', 'MapVirtualKeyExW', 'CallNextHookEx'
},
'Input': {
'GetCursorPos', 'SetCursorPos', 'MouseEvent', 'GetDoubleClickTime',
'GetCapture', 'SetCapture'
},
'ScreenCapture': {
'BitBlt', 'StretchBlt', 'GetDC', 'GetWindowDC', 'CreateCompatibleDC', 'CreateCompatibleBitmap',
'GdipSaveImageToFile', 'PrintWindow', 'GetDesktopWindow', 'CreateDCW', 'CreateDCA',
'SelectObject', 'DeleteDC', 'DeleteObject', 'GetDeviceCaps', 'GetSystemMetrics'
},
'Graphics': {
'Direct3DCreate9', 'D3D11CreateDevice'
},
'Audio': {
'WaveInOpen', 'WaveInClose', 'WaveInStart', 'WaveInStop'
},
'Clipboard': {
'OpenClipboard', 'CloseClipboard', 'GetClipboardData', 'SetClipboardData'
},
'Camera': {
'CapCreateCaptureWindowA', 'CapCreateCaptureWindowW'
},
'Memory': {
'HeapAlloc', 'HeapFree', 'HeapSize', 'HeapCreate', 'GetProcessHeap',
'LocalAlloc', 'LocalFree', 'GlobalAlloc', 'GlobalFree', 'GlobalUnlock',
'HeapDestroy', 'GlobalLock', 'SysFreeString', 'SysAllocStringLen'
},
'Resource': {
'LoadResource', 'SizeofResource', 'LockResource', 'FreeResource', 'FindResourceA'
},
'WindowControl': {
'ShowWindow', 'DestroyWindow', 'TranslateMessage', 'DispatchMessageA', 'GetWindow',
'PeekMessageA', 'GetWindowLongA', 'CallWindowProcA', 'SetWindowLongA', 'GetWindowTextA',
'ScreenToClient', 'GetActiveWindow', 'CreateWindowExA', 'DefWindowProcA', 'GetMessageA',
'RegisterClassA', 'UnregisterClassA', 'MessageBoxA'
},
'COM': {
'CoCreateInstance', 'CoUninitialize', 'CoInitialize'
}
}
TARGET_DLLS = {
'kernel32.dll', 'user32.dll', 'gdi32.dll', 'advapi32.dll', 'shell32.dll', 'ntdll.dll', 'hal.dll',
'ws2_32.dll', 'wsock32.dll', 'wininet.dll', 'winhttp.dll', 'urlmon.dll', 'crypt32.dll', 'bcrypt.dll',
'psapi.dll', 'dbghelp.dll', 'imagehlp.dll', 'shlwapi.dll', 'ole32.dll', 'oleaut32.dll', 'comctl32.dll',
'mscoree.dll', 'vbscript.dll', 'netapi32.dll', 'iphlpapi.dll', 'wtsapi32.dll', 'version.dll', 'winmm.dll'
}
BASE_SCHEMA = [
"FileEntropy", "SectionMeanEntropy", "IsExe", "IsDll", "IsDriver", "Is64Bit",
"Machine", "Magic", "CheckSum", "ImageBase", "SizeOfImage", "SizeOfHeaders",
"Characteristics", "DllCharacteristics", "Subsystem", "LoaderFlags",
"MajorLinkerVersion", "MinorLinkerVersion", "SizeOfCode",
"SizeOfInitializedData", "SizeOfUninitializedData", "AddressOfEntryPoint",
"BaseOfCode", "SectionAlignment", "FileAlignment",
"MajorOperatingSystemVersion", "MinorOperatingSystemVersion",
"MajorImageVersion", "MinorImageVersion",
"MajorSubsystemVersion", "MinorSubsystemVersion",
"SizeOfStackReserve", "SizeOfStackCommit", "SizeOfHeapReserve", "SizeOfHeapCommit",
"NumberOfSections", "NumberOfRvaAndSizes", "PointerToSymbolTable", "NumberOfSymbols",
"SizeOfOptionalHeader",
"TextSectionMaxEntropy", "TextSectionMeanEntropy", "TextSizeRatio",
"DataSectionMaxEntropy", "DataSectionMeanEntropy", "DataSizeRatio",
"RsrcSectionMaxEntropy", "RsrcSectionMeanEntropy", "RsrcSizeRatio",
"SectionCount", "ExecutableSections",
"WritableSections", "ReadableSections", "SectionException",
"Char_00000020_Count", "Char_00000020_MeanEntropy",
"Char_00000040_Count", "Char_00000040_MeanEntropy",
"Char_00000080_Count", "Char_00000080_MeanEntropy",
"Char_02000000_Count", "Char_02000000_MeanEntropy",
"Char_20000000_Count", "Char_20000000_MeanEntropy",
"Char_40000000_Count", "Char_40000000_MeanEntropy",
"Char_80000000_Count", "Char_80000000_MeanEntropy",
"IconCount", "ApiCount", "ExportCount", "DebugCount", "ExceptionCount",
"ImportCount", "ImportFunctionCount",
"FileDescriptionLength", "FileVersionLength", "ProductNameLength",
"ProductVersionLength", "CompanyNameLength", "LegalCopyrightLength",
"CommentsLength", "InternalNameLength", "LegalTrademarksLength",
"SpecialBuildLength", "PrivateBuildLength",
"TrustSigned", "IsDebug", "IsPatched", "IsPrivateBuild", "IsPreRelease",
"IsSpecialBuild", "IsAdmin", "IsInstall", "HasTlsCallbacks",
"HasInvalidTimestamp", "HasRelocationDirectory", "HasPacked", "FileTimeException"
]
CAT_SCHEMA = [f"Cat_{k}" for k in sorted(API_CAT_MAPPING.keys())]
DLL_SCHEMA = [f"Dll_{d.replace('.', '_')}" for d in sorted(TARGET_DLLS)]
_ALL_APIS = {api for apis in API_CAT_MAPPING.values() for api in apis}
API_SCHEMA = [f"Api_{api}" for api in sorted(_ALL_APIS)]
SCHEMA = BASE_SCHEMA + CAT_SCHEMA + DLL_SCHEMA + API_SCHEMA
_API_TO_CATS = {}
for _cat, _apis in API_CAT_MAPPING.items():
for _api in _apis:
if _api not in _API_TO_CATS:
_API_TO_CATS[_api] = []
_API_TO_CATS[_api].append(_cat)
PACKERS = {
'upx', 'aspack', 'asprotect', 'pecompact', 'upack', 'fsg', 'mew',
'mpress', 'ezip', 'pklt', 'shrink', 'petite', 'telock',
'themida', 'winlicense', 'tmd', 'vmp', 'enigma', 'obsidium',
'pelock', 'exestealth', 'yoda', 'armadillo', 'zprotect',
'sforce', 'starforce', 'qihoo', 'wisevec', 'megastop',
}
INSTALLER_SIGS = [b"Nullsoft.NSIS", b"Inno Setup", b"7-Zip.7zip", b"InstallShield"]
####################################################################################################
def __init__(self):
self.model = None
self.input_name = None
self.feature_order = []
self.signer = sign_scanner()
self.signer.init_windll(["wintrust"])
def load_path(self, path, callback=None):
for root, _, files in os.walk(path):
for file in files:
full_path = os.path.join(root, file)
if callback:
callback(full_path)
if full_path.endswith('.onnx'):
self.load_model(full_path)
def load_model(self, model_path):
if not os.path.exists(model_path):
return
try:
self.model = onnxruntime.InferenceSession(model_path, providers=['CPUExecutionProvider'])
self.input_name = self.model.get_inputs()[0].name
feat_path = os.path.join(os.path.dirname(model_path), "features.json")
model_features = []
if os.path.exists(feat_path):
with open(feat_path, 'r') as f:
model_features = json.load(f)
else:
model_features = [re.sub(r'[^A-Za-z0-9_]+', '', f) for f in self.SCHEMA]
local_map = {re.sub(r'[^A-Za-z0-9_]+', '', raw_name): raw_name for raw_name in self.SCHEMA}
self.feature_order = [local_map.get(req_feat) for req_feat in model_features]
except Exception:
pass
####################################################################################################
def _safe_float(self, val):
try:
f = float(val)
if math.isinf(f) or math.isnan(f): return 0.0
return f
except Exception:
return 0.0
def _safe_div(self, n, d):
return float(n) / d if d > 0 else 0.0
def _calc_entropy(self, data):
if not data: return 0.0
arr = numpy.frombuffer(data, dtype=numpy.uint8)
counts = numpy.bincount(arr, minlength=256)
probs = counts[counts > 0] / len(arr)
return float(-numpy.sum(probs * numpy.log2(probs)))
####################################################################################################
def extract_features(self, file_path):
fts = {k: 0.0 for k in self.SCHEMA}
pe = None
try:
fsize = os.path.getsize(file_path)
if fsize == 0 or fsize > 512 * 1024 * 1024:
return None
with open(file_path, "rb") as f:
file_bytes = f.read()
pe = pefile.PE(data=file_bytes, fast_load=True)
fts['TrustSigned'] = 1 if self.signer.sign_verify(file_path) else 0
fh = pe.FILE_HEADER
fts['Machine'] = fh.Machine
fts['NumberOfSections'] = fh.NumberOfSections
fts['TimeDateStamp'] = fh.TimeDateStamp
fts['PointerToSymbolTable'] = fh.PointerToSymbolTable
fts['NumberOfSymbols'] = fh.NumberOfSymbols
fts['SizeOfOptionalHeader'] = fh.SizeOfOptionalHeader
fts['Characteristics'] = fh.Characteristics
curr_ts = datetime.datetime.now(datetime.timezone.utc).timestamp()
fts['HasInvalidTimestamp'] = 1 if (fh.TimeDateStamp < 631152000 or fh.TimeDateStamp > curr_ts + 2592000) else 0
fts['FileTimeException'] = 1 if fh.TimeDateStamp == 0 else 0
if hasattr(pe, 'OPTIONAL_HEADER'):
op = pe.OPTIONAL_HEADER
fts['Magic'] = getattr(op, 'Magic', 0)
fts['MajorLinkerVersion'] = getattr(op, 'MajorLinkerVersion', 0)
fts['MinorLinkerVersion'] = getattr(op, 'MinorLinkerVersion', 0)
fts['SizeOfCode'] = getattr(op, 'SizeOfCode', 0)
fts['SizeOfInitializedData'] = getattr(op, 'SizeOfInitializedData', 0)
fts['SizeOfUninitializedData'] = getattr(op, 'SizeOfUninitializedData', 0)
fts['AddressOfEntryPoint'] = getattr(op, 'AddressOfEntryPoint', 0)
fts['BaseOfCode'] = getattr(op, 'BaseOfCode', 0)
fts['ImageBase'] = getattr(op, 'ImageBase', 0)
fts['SectionAlignment'] = getattr(op, 'SectionAlignment', 0)
fts['FileAlignment'] = getattr(op, 'FileAlignment', 0)
fts['MajorOperatingSystemVersion'] = getattr(op, 'MajorOperatingSystemVersion', 0)
fts['MinorOperatingSystemVersion'] = getattr(op, 'MinorOperatingSystemVersion', 0)
fts['MajorImageVersion'] = getattr(op, 'MajorImageVersion', 0)
fts['MinorImageVersion'] = getattr(op, 'MinorImageVersion', 0)
fts['MajorSubsystemVersion'] = getattr(op, 'MajorSubsystemVersion', 0)
fts['MinorSubsystemVersion'] = getattr(op, 'MinorSubsystemVersion', 0)
fts['SizeOfImage'] = getattr(op, 'SizeOfImage', 0)
fts['SizeOfHeaders'] = getattr(op, 'SizeOfHeaders', 0)
fts['CheckSum'] = getattr(op, 'CheckSum', 0)
fts['Subsystem'] = getattr(op, 'Subsystem', 0)
fts['DllCharacteristics'] = getattr(op, 'DllCharacteristics', 0)
fts['SizeOfStackReserve'] = getattr(op, 'SizeOfStackReserve', 0)
fts['SizeOfStackCommit'] = getattr(op, 'SizeOfStackCommit', 0)
fts['SizeOfHeapReserve'] = getattr(op, 'SizeOfHeapReserve', 0)
fts['SizeOfHeapCommit'] = getattr(op, 'SizeOfHeapCommit', 0)
fts['LoaderFlags'] = getattr(op, 'LoaderFlags', 0)
fts['NumberOfRvaAndSizes'] = getattr(op, 'NumberOfRvaAndSizes', 0)
fts['Is64Bit'] = 1 if fh.Machine in (0x8664, 0xAA64, 0x0200) else 0
fts['IsExe'] = 1 if pe.is_exe() else 0
fts['IsDll'] = 1 if pe.is_dll() else 0
fts['IsDriver'] = 1 if fts['Subsystem'] == 1 else 0
if hasattr(op, 'DATA_DIRECTORY'):
reloc_idx = pefile.DIRECTORY_ENTRY.get('IMAGE_DIRECTORY_ENTRY_BASERELOC', 5)
if len(op.DATA_DIRECTORY) > reloc_idx and op.DATA_DIRECTORY[reloc_idx].Size > 0:
fts['HasRelocationDirectory'] = 1
try:
fts['FileEntropy'] = self._calc_entropy(file_bytes)
except Exception:
pass
sec_entropies = []
text_entropies = []
data_entropies = []
rsrc_entropies = []
char_flags = [0x00000020, 0x00000040, 0x00000080, 0x02000000, 0x20000000, 0x40000000, 0x80000000]
if hasattr(pe, 'sections'):
fts['SectionCount'] = len(pe.sections)
for section in pe.sections:
try:
name = section.Name.decode('ascii', 'ignore').strip('\x00')
except Exception:
name = ""
try:
s_data = section.get_data()
s_entropy = self._calc_entropy(s_data)
except Exception:
s_data = b""
s_entropy = 0.0
sec_entropies.append(s_entropy)
if any(p in name.lower() for p in self.PACKERS): fts['HasPacked'] = 1
if section.Characteristics & 0x20000000: fts['ExecutableSections'] += 1
if section.Characteristics & 0x80000000: fts['WritableSections'] += 1
if section.Characteristics & 0x40000000: fts['ReadableSections'] += 1
if section.SizeOfRawData + section.PointerToRawData > fsize: fts['SectionException'] = 1
for flag in char_flags:
if section.Characteristics & flag:
fts[f'Char_{flag:08X}_Count'] += 1
fts[f'Char_{flag:08X}_MeanEntropy'] += s_entropy
ratio = self._safe_div(section.SizeOfRawData, fsize)
if name.startswith('.text'):
text_entropies.append(s_entropy)
fts['TextSizeRatio'] += ratio
elif name.startswith('.data'):
data_entropies.append(s_entropy)
fts['DataSizeRatio'] += ratio
elif name.startswith('.rsrc'):
rsrc_entropies.append(s_entropy)
fts['RsrcSizeRatio'] += ratio
if b"requireAdministrator" in s_data: fts['IsAdmin'] = 1
for sig in self.INSTALLER_SIGS:
if sig in s_data:
fts['IsInstall'] = 1
break
fts['SectionMeanEntropy'] = self._safe_div(sum(sec_entropies), len(sec_entropies))
fts['TextSectionMaxEntropy'] = max(text_entropies) if text_entropies else 0.0
fts['TextSectionMeanEntropy'] = self._safe_div(sum(text_entropies), len(text_entropies))
fts['DataSectionMaxEntropy'] = max(data_entropies) if data_entropies else 0.0
fts['DataSectionMeanEntropy'] = self._safe_div(sum(data_entropies), len(data_entropies))
fts['RsrcSectionMaxEntropy'] = max(rsrc_entropies) if rsrc_entropies else 0.0
fts['RsrcSectionMeanEntropy'] = self._safe_div(sum(rsrc_entropies), len(rsrc_entropies))
for flag in char_flags:
fts[f'Char_{flag:08X}_MeanEntropy'] = self._safe_div(fts[f'Char_{flag:08X}_MeanEntropy'], fts[f'Char_{flag:08X}_Count'])
try:
pe.parse_data_directories(directories=[
pefile.DIRECTORY_ENTRY['IMAGE_DIRECTORY_ENTRY_IMPORT'],
pefile.DIRECTORY_ENTRY['IMAGE_DIRECTORY_ENTRY_EXPORT'],
pefile.DIRECTORY_ENTRY['IMAGE_DIRECTORY_ENTRY_RESOURCE'],
pefile.DIRECTORY_ENTRY['IMAGE_DIRECTORY_ENTRY_DEBUG'],
pefile.DIRECTORY_ENTRY['IMAGE_DIRECTORY_ENTRY_TLS']
])
except Exception:
pass
if hasattr(pe, 'DIRECTORY_ENTRY_TLS') and hasattr(pe.DIRECTORY_ENTRY_TLS, 'struct'):
if getattr(pe.DIRECTORY_ENTRY_TLS.struct, 'AddressOfCallBacks', 0) != 0:
fts['HasTlsCallbacks'] = 1
if hasattr(pe, 'VS_FIXEDFILEINFO') and len(pe.VS_FIXEDFILEINFO) > 0:
flags = getattr(pe.VS_FIXEDFILEINFO[0], 'FileFlags', 0)
fts['IsDebug'] = 1 if flags & 0x1 else 0
fts['IsPreRelease'] = 1 if flags & 0x2 else 0
fts['IsPatched'] = 1 if flags & 0x4 else 0
fts['IsPrivateBuild'] = 1 if flags & 0x8 else 0
fts['IsSpecialBuild'] = 1 if flags & 0x20 else 0
if hasattr(pe, 'FileInfo'):
for fileinfo_list in pe.FileInfo:
for fileinfo in fileinfo_list:
if getattr(fileinfo, 'name', '') in ('StringFileInfo', b'StringFileInfo'):
for st in getattr(fileinfo, 'StringTable', []):
for key, val in st.entries.items():
try:
k = key.decode('utf-8', 'ignore') if isinstance(key, bytes) else str(key)
v = val.decode('utf-8', 'ignore') if isinstance(val, bytes) else str(val)
length = len(v)
if k == 'FileDescription': fts['FileDescriptionLength'] = length
elif k == 'FileVersion': fts['FileVersionLength'] = length
elif k == 'ProductName': fts['ProductNameLength'] = length
elif k == 'ProductVersion': fts['ProductVersionLength'] = length
elif k == 'CompanyName': fts['CompanyNameLength'] = length
elif k == 'LegalCopyright': fts['LegalCopyrightLength'] = length
elif k == 'Comments': fts['CommentsLength'] = length
elif k == 'InternalName': fts['InternalNameLength'] = length
elif k == 'LegalTrademarks': fts['LegalTrademarksLength'] = length
elif k == 'SpecialBuild': fts['SpecialBuildLength'] = length
elif k == 'PrivateBuild': fts['PrivateBuildLength'] = length
except Exception:
continue
if hasattr(pe, 'DIRECTORY_ENTRY_IMPORT'):
fts['ImportCount'] = len(pe.DIRECTORY_ENTRY_IMPORT)
for entry in pe.DIRECTORY_ENTRY_IMPORT:
if getattr(entry, 'dll', None):
try:
dll_name = entry.dll.decode('ascii', 'ignore').lower()
if dll_name in self.TARGET_DLLS:
fts[f"Dll_{dll_name.replace('.', '_')}"] = 1.0
except Exception:
pass
imports = getattr(entry, 'imports', [])
for imp in imports:
fts['ImportFunctionCount'] += 1
if not getattr(imp, 'name', None): continue
try:
func_name = imp.name.decode('ascii', 'ignore')
if func_name in self._ALL_APIS:
fts[f'Api_{func_name}'] += 1
fts['ApiCount'] += 1
for cat in self._API_TO_CATS.get(func_name, []):
fts[f'Cat_{cat}'] += 1
except Exception:
continue
if hasattr(pe, 'DIRECTORY_ENTRY_EXPORT') and hasattr(pe.DIRECTORY_ENTRY_EXPORT, 'symbols'):
fts['ExportCount'] = len(pe.DIRECTORY_ENTRY_EXPORT.symbols)
if hasattr(pe, 'DIRECTORY_ENTRY_RESOURCE'):
for entry in pe.DIRECTORY_ENTRY_RESOURCE.entries:
if getattr(entry, 'id', None) == 3 and hasattr(entry, 'directory'):
fts['IconCount'] += len(getattr(entry.directory, 'entries', []))
if hasattr(pe, 'OPTIONAL_HEADER') and hasattr(pe.OPTIONAL_HEADER, 'DATA_DIRECTORY') and len(pe.OPTIONAL_HEADER.DATA_DIRECTORY) > 3:
exc_dir = pe.OPTIONAL_HEADER.DATA_DIRECTORY[3]
if exc_dir.Size > 0:
fts['ExceptionCount'] = exc_dir.Size // 12 if fts['Machine'] in (0x8664, 0xAA64) else 0
if hasattr(pe, 'DIRECTORY_ENTRY_DEBUG'):
fts['DebugCount'] = len(pe.DIRECTORY_ENTRY_DEBUG)
for k in fts:
fts[k] = self._safe_float(fts[k])
return fts
except Exception:
return None
finally:
if pe:
pe.close()
####################################################################################################
def pe_scan(self, file_path):
if not self.model:
return False, False
try:
data = self.extract_features(file_path)
if not data:
return False, False
vec_data = []
for key in self.feature_order:
if key:
vec_data.append(data.get(key, 0.0))
else:
vec_data.append(0.0)
vec = numpy.array(vec_data, dtype=numpy.float32).reshape(1, -1)
outputs = self.model.run(None, {self.input_name: vec})
result = outputs[1]
prob = 0.0
if isinstance(result, list) and len(result) > 0:
prob_dict = result[0]
if hasattr(prob_dict, 'get'):
prob = float(prob_dict.get(1, prob_dict.get('1', 0.0)))
elif isinstance(result, numpy.ndarray):
if result.ndim == 2 and result.shape[1] > 1:
prob = float(result[0][1])
score = int(prob * 100)
if prob > 0.5:
return f"General:WinPE/Unknown.{score}!ml", score
return False, False
except Exception:
return False, False
####################################################################################################
class cloud_scanner:
def __init__(self):
pass
self.session = None
self.api_host = None
self.api_key = None
self.timeout = 30
def _init_session(self, api_host, api_key):
if self.session is None or self.api_host != api_host or self.api_key != api_key:
self.api_host = api_host.rstrip('/')
self.api_key = api_key
self.session = requests.Session()
self.session.headers.update({"X-API-Key": api_key, "User-Agent": "PYAS-Engine/1.2"})
def _request(self, method, endpoint, **kwargs):
try:
r = self.session.request(method, f"{self.api_host}{endpoint}", timeout=self.timeout, **kwargs)
if r.status_code == 200:
return r
except Exception:
pass
return None
def calc_hash(self, file_path, block_size=65536):
try:
@@ -246,64 +833,95 @@ class cloud_scanner:
except Exception:
return ""
def upload_file(self, file_path, api_host, api_key):
####################################################################################################
def rescan(self, sha256, api_host, api_key):
self._init_session(api_host, api_key)
r = self._request("POST", f"/api/rescan/{sha256}")
return r is not None and r.json().get('status') == 'success'
def upload_file(self, file_path, api_host, api_key, chunk_size=5242880):
try:
self._init_session(api_host, api_key)
sha256 = self.calc_hash(file_path)
if not sha256:
return False, None
base = api_host.rstrip('/')
base_headers = {"X-API-Key": api_key, "User-Agent": "PYAS-Client/1.0"}
status_req = self._request("GET", f"/api/processing_status/{sha256}")
if status_req and status_req.json().get('status') != 'missing':
self.rescan(sha256, api_host, api_key)
return True, sha256
def _req(m, ep, files=None, **k):
try:
return requests.request(m, f"{base}{ep}", headers=base_headers, files=files, timeout=120, **k)
except:
return None
file_size = os.path.getsize(file_path)
if file_size > 104857600:
return False, sha256
r = _req("GET", f"/api/processing_status/{sha256}")
status = r.json().get('status') if r and r.status_code == 200 else 'missing'
if status in ['failed', 'error']:
status = 'missing'
if status == 'missing':
if file_size > chunk_size:
total_chunks = (file_size + chunk_size - 1) // chunk_size
upload_id = os.urandom(16).hex()
with open(file_path, 'rb') as f:
r = _req("POST", "/api/upload", files={'file': f})
if not r or r.status_code != 200:
return False, sha256
for i in range(total_chunks):
chunk_data = f.read(chunk_size)
headers = {
"X-Chunk-Index": str(i),
"X-Total-Chunks": str(total_chunks),
"X-Upload-ID": upload_id
}
chunk_success = False
for _ in range(3):
r = self._request("POST", "/api/upload", files={'file': (os.path.basename(file_path), chunk_data)}, headers=headers)
if r:
chunk_success = True
break
time.sleep(1)
if not chunk_success:
return False, sha256
return True, sha256
return True, sha256
with open(file_path, 'rb') as f:
file_data = f.read()
for _ in range(3):
r = self._request("POST", "/api/upload", files={'file': (os.path.basename(file_path), file_data)})
if r:
return True, sha256
time.sleep(1)
return False, sha256
except Exception:
return False, None
def get_result(self, sha256, api_host, api_key):
####################################################################################################
def get_result(self, sha256, api_host, api_key, max_retries=6, interval=10):
try:
if not sha256:
return False
self._init_session(api_host, api_key)
base = api_host.rstrip('/')
headers = {"X-API-Key": api_key, "User-Agent": "PYAS-Client/1.0"}
is_done = False
for _ in range(max_retries):
r = self._request("GET", f"/api/processing_status/{sha256}")
if r:
st = r.json().get('status', 'error')
if st == 'done':
is_done = True
break
if st in ['error', 'failed']:
return False
time.sleep(interval)
def _req(m, ep, **k):
try:
return requests.request(m, f"{base}{ep}", headers=headers, timeout=10, **k)
except:
return None
if not is_done:
return False
for _ in range(30):
r = _req("GET", f"/api/processing_status/{sha256}")
st = r.json().get('status') if r else 'error'
if st == 'done':
break
if st in ['error', 'failed']:
return False
time.sleep(1)
r = _req("GET", f"/api/report/{sha256}")
if r and r.status_code == 200:
r = self._request("GET", f"/api/report/{sha256}")
if r:
data = r.json().get('data', {})
res = data.get('detection', {}).get('results', {}).get('PYAS', {})
label = res.get('label', 'Unsupport')
metadata = data.get('metadata', {})
label = metadata.get('label', 'Unsupport')
score = metadata.get('score', 0)
sims = data.get('similar', [])
is_malicious = 'General' in label
@@ -312,11 +930,11 @@ class cloud_scanner:
for s in sims:
if s.get('similarity', 0) > 80:
valid_sim_count += 1
if "General" in s.get('label'):
if "General" in s.get('label', ''):
sim_malicious_count += 1
if is_malicious and (valid_sim_count == 0 or sim_malicious_count == valid_sim_count):
return True
return f"General:WinPE/Unknown.{score}!cl"
except Exception:
pass
return False
-1576
View File
File diff suppressed because it is too large. Load diff
-14520
View File
File diff suppressed because it is too large. Load diff
+2 -2
View File
@@ -2,9 +2,9 @@ import pyinstaller_versionfile
pyinstaller_versionfile.create_versionfile(
output_file="versionfile.txt",
version='3.3.8',
version='3.5.0.0',
company_name="PYAS Security",
file_description="Python Antivirus Software",
file_description="PYAS Security Antivirus",
internal_name="PYAS",
legal_copyright="PYAS Security",
original_filename="PYAS.exe",
+40 -11
View File
@@ -43,11 +43,25 @@ constexpr auto PYAS_POOL_TAG = 'SAYP';
#define PROCESS_QUERY_LIMITED_INFORMATION (0x1000)
#endif
#ifndef PROCESS_SET_QUOTA
#define PROCESS_SET_QUOTA (0x0100)
#define PROCESS_SET_QUOTA (0x0100)
#endif
#ifndef THREAD_TERMINATE
#define THREAD_TERMINATE (0x0001)
#endif
#ifndef THREAD_SUSPEND_RESUME
#define THREAD_SUSPEND_RESUME (0x0002)
#endif
#ifndef THREAD_SET_CONTEXT
#define THREAD_SET_CONTEXT (0x0010)
#endif
#ifndef THREAD_SET_INFORMATION
#define THREAD_SET_INFORMATION (0x0020)
#endif
#ifndef THREAD_SET_THREAD_TOKEN
#define THREAD_SET_THREAD_TOKEN (0x0080)
#endif
#ifndef POOL_FLAG_NON_PAGED
#define POOL_FLAG_NON_PAGED 0x0000000000000040UI64
#define POOL_FLAG_NON_PAGED 0x0000000000000040UI64
#endif
#ifndef FSCTL_MANAGE_BYPASS_IO
@@ -87,6 +101,11 @@ typedef struct _PYAS_MESSAGE {
WCHAR Path[MAX_PATH_LEN];
} PYAS_MESSAGE, * PPYAS_MESSAGE;
typedef struct _PYAS_USER_MESSAGE {
ULONG Command;
WCHAR Path[MAX_PATH_LEN];
} PYAS_USER_MESSAGE, * PPYAS_USER_MESSAGE;
typedef struct _DRIVER_DATA {
PDRIVER_OBJECT DriverObject;
PFLT_FILTER FilterHandle;
@@ -100,12 +119,18 @@ typedef struct _DRIVER_DATA {
EX_RUNDOWN_REF PortRundown;
} DRIVER_DATA, * PDRIVER_DATA;
typedef struct _RULE_NODE {
struct _RULE_NODE* Next;
UNICODE_STRING Pattern;
} RULE_NODE, * PRULE_NODE;
extern DRIVER_DATA GlobalData;
FLT_PREOP_CALLBACK_STATUS ProtectFile_PreCreate(PFLT_CALLBACK_DATA Data, PCFLT_RELATED_OBJECTS FltObjects, PVOID* CompletionContext);
FLT_PREOP_CALLBACK_STATUS ProtectFile_PreWrite(PFLT_CALLBACK_DATA Data, PCFLT_RELATED_OBJECTS FltObjects, PVOID* CompletionContext);
FLT_PREOP_CALLBACK_STATUS ProtectFile_PreSetInfo(PFLT_CALLBACK_DATA Data, PCFLT_RELATED_OBJECTS FltObjects, PVOID* CompletionContext);
FLT_PREOP_CALLBACK_STATUS ProtectFile_FileSystemControl(PFLT_CALLBACK_DATA Data, PCFLT_RELATED_OBJECTS FltObjects, PVOID* CompletionContext);
FLT_PREOP_CALLBACK_STATUS ProtectFile_SetSecurity(PFLT_CALLBACK_DATA Data, PCFLT_RELATED_OBJECTS FltObjects, PVOID* CompletionContext);
FLT_PREOP_CALLBACK_STATUS ProtectBoot_PreDeviceControl(PFLT_CALLBACK_DATA Data, PCFLT_RELATED_OBJECTS FltObjects, PVOID* CompletionContext);
NTSTATUS InitializeProcessProtection();
@@ -113,28 +138,32 @@ VOID UninitializeProcessProtection();
NTSTATUS InitializeRegistryProtection(PDRIVER_OBJECT DriverObject);
VOID UninitializeRegistryProtection();
NTSTATUS LoadRulesFromDisk(PUNICODE_STRING RegistryPath);
VOID UnloadRules();
VOID InitializeRulesEngine();
VOID UninitializeRulesEngine();
VOID AddDynamicWhitelist(PUNICODE_STRING RuleStr);
VOID RemoveDynamicWhitelist(PUNICODE_STRING RuleStr);
VOID ImageLoadNotify(PUNICODE_STRING FullImageName, HANDLE ProcessId, PIMAGE_INFO ImageInfo);
NTSTATUS GetProcessImageName(HANDLE ProcessId, PUNICODE_STRING* ImageName);
BOOLEAN IsProcessTrusted(HANDLE ProcessId);
BOOLEAN IsTargetProtected(HANDLE ProcessId);
BOOLEAN IsInstallerProcess(HANDLE ProcessId);
BOOLEAN WildcardMatch(PCWSTR Pattern, PCWSTR String, USHORT StringLengthBytes);
BOOLEAN CheckRegistryRule(PCUNICODE_STRING KeyName);
BOOLEAN CheckFileExtensionRule(PCUNICODE_STRING FileName);
BOOLEAN CheckProtectedPathRule(PCUNICODE_STRING FileName);
BOOLEAN CheckRansomActivity(HANDLE ProcessId, PUNICODE_STRING FileName, PVOID Buffer, ULONG Length, BOOLEAN IsWrite);
BOOLEAN CheckRansomActivity(HANDLE ProcessId, PUNICODE_STRING FileName, PVOID WriteBuffer, ULONG WriteLength, BOOLEAN IsWrite);
NTSTATUS SendMessageToUser(ULONG Code, ULONG Pid, PWCHAR Path, USHORT PathSize);
FORCEINLINE PVOID PyasAllocate(SIZE_T Size) {
#if (NTDDI_VERSION >= NTDDI_WIN10_VB)
return ExAllocatePool2(POOL_FLAG_NON_PAGED, Size, PYAS_POOL_TAG);
#else
PVOID Buffer = ExAllocatePoolWithTag(NonPagedPoolNx, Size, PYAS_POOL_TAG);
if (Buffer) RtlZeroMemory(Buffer, Size);
return Buffer;
#endif
}
FORCEINLINE VOID PyasFree(PVOID Ptr) {
+83 -15
View File
@@ -27,7 +27,19 @@ static VOID InstanceTeardownComplete(PCFLT_RELATED_OBJECTS FltObjects, FLT_INSTA
}
static NTSTATUS DriverUnload(FLT_FILTER_UNLOAD_FLAGS Flags) {
UNREFERENCED_PARAMETER(Flags);
if (!(Flags & FLTFL_FILTER_UNLOAD_MANDATORY)) {
return STATUS_FLT_DO_NOT_DETACH;
}
if (GlobalData.ServerPort) {
FltCloseCommunicationPort(GlobalData.ServerPort);
GlobalData.ServerPort = NULL;
}
if (GlobalData.FilterHandle) {
FltUnregisterFilter(GlobalData.FilterHandle);
GlobalData.FilterHandle = NULL;
}
PsRemoveLoadImageNotifyRoutine(ImageLoadNotify);
UninitializeRegistryProtection();
@@ -35,11 +47,39 @@ static NTSTATUS DriverUnload(FLT_FILTER_UNLOAD_FLAGS Flags) {
ExWaitForRundownProtectionRelease(&GlobalData.PortRundown);
if (GlobalData.ServerPort) {
FltCloseCommunicationPort(GlobalData.ServerPort);
UnloadRules();
UninitializeRulesEngine();
return STATUS_SUCCESS;
}
static NTSTATUS PortMessage(PVOID PortCookie, PVOID InputBuffer, ULONG InputBufferLength, PVOID OutputBuffer, ULONG OutputBufferLength, PULONG ReturnOutputBufferLength) {
UNREFERENCED_PARAMETER(PortCookie);
UNREFERENCED_PARAMETER(OutputBuffer);
UNREFERENCED_PARAMETER(OutputBufferLength);
if (InputBuffer && InputBufferLength >= sizeof(PYAS_USER_MESSAGE)) {
PPYAS_USER_MESSAGE msg = (PPYAS_USER_MESSAGE)InputBuffer;
if (msg->Command == 1 || msg->Command == 2) {
msg->Path[MAX_PATH_LEN - 1] = L'\0';
UNICODE_STRING us;
RtlInitUnicodeString(&us, msg->Path);
if (us.Length > 0) {
if (msg->Command == 1) {
AddDynamicWhitelist(&us);
}
else {
RemoveDynamicWhitelist(&us);
}
}
}
}
if (GlobalData.FilterHandle) {
FltUnregisterFilter(GlobalData.FilterHandle);
if (ReturnOutputBufferLength) {
*ReturnOutputBufferLength = 0;
}
return STATUS_SUCCESS;
}
@@ -68,10 +108,6 @@ static VOID PortDisconnect(PVOID ConnectionCookie) {
ExAcquireFastMutex(&GlobalData.PortMutex);
GlobalData.ClientPort = NULL;
GlobalData.PyasPid = 0;
if (GlobalData.PyasProcess) {
GlobalData.PyasProcess = NULL;
}
ExReleaseFastMutex(&GlobalData.PortMutex);
ExWaitForRundownProtectionRelease(&GlobalData.PortRundown);
@@ -81,6 +117,7 @@ CONST FLT_OPERATION_REGISTRATION Callbacks[] = {
{ IRP_MJ_CREATE, 0, ProtectFile_PreCreate, NULL },
{ IRP_MJ_WRITE, 0, ProtectFile_PreWrite, NULL },
{ IRP_MJ_SET_INFORMATION, 0, ProtectFile_PreSetInfo, NULL },
{ IRP_MJ_SET_SECURITY, 0, ProtectFile_SetSecurity, NULL },
{ IRP_MJ_FILE_SYSTEM_CONTROL, 0, ProtectFile_FileSystemControl, NULL },
{ IRP_MJ_DEVICE_CONTROL, 0, ProtectBoot_PreDeviceControl, NULL },
{ IRP_MJ_OPERATION_END }
@@ -103,8 +140,6 @@ CONST FLT_REGISTRATION FilterRegistration = {
};
extern "C" NTSTATUS DriverEntry(PDRIVER_OBJECT DriverObject, PUNICODE_STRING RegistryPath) {
UNREFERENCED_PARAMETER(RegistryPath);
NTSTATUS status = STATUS_SUCCESS;
PSECURITY_DESCRIPTOR sd = NULL;
OBJECT_ATTRIBUTES oa = { 0 };
@@ -117,19 +152,32 @@ extern "C" NTSTATUS DriverEntry(PDRIVER_OBJECT DriverObject, PUNICODE_STRING Reg
ExInitializeRundownProtection(&GlobalData.PortRundown);
InitializeRulesEngine();
status = LoadRulesFromDisk(RegistryPath);
status = FltRegisterFilter(DriverObject, &FilterRegistration, &GlobalData.FilterHandle);
if (!NT_SUCCESS(status)) return status;
if (!NT_SUCCESS(status)) {
UnloadRules();
UninitializeRulesEngine();
return status;
}
status = FltBuildDefaultSecurityDescriptor(&sd, FLT_PORT_ALL_ACCESS);
if (NT_SUCCESS(status)) {
RtlInitUnicodeString(&name, PYAS_PORT_NAME);
InitializeObjectAttributes(&oa, &name, OBJ_KERNEL_HANDLE | OBJ_CASE_INSENSITIVE, NULL, sd);
status = FltCreateCommunicationPort(GlobalData.FilterHandle, &GlobalData.ServerPort, &oa, NULL, PortConnect, PortDisconnect, NULL, 1);
status = FltCreateCommunicationPort(GlobalData.FilterHandle, &GlobalData.ServerPort, &oa, NULL, PortConnect, PortDisconnect, PortMessage, 1);
FltFreeSecurityDescriptor(sd);
}
if (!NT_SUCCESS(status)) {
FltUnregisterFilter(GlobalData.FilterHandle);
if (GlobalData.FilterHandle) {
FltUnregisterFilter(GlobalData.FilterHandle);
GlobalData.FilterHandle = NULL;
}
UnloadRules();
UninitializeRulesEngine();
return status;
}
@@ -137,5 +185,25 @@ extern "C" NTSTATUS DriverEntry(PDRIVER_OBJECT DriverObject, PUNICODE_STRING Reg
InitializeRegistryProtection(DriverObject);
PsSetLoadImageNotifyRoutine(ImageLoadNotify);
return FltStartFiltering(GlobalData.FilterHandle);
status = FltStartFiltering(GlobalData.FilterHandle);
if (!NT_SUCCESS(status)) {
PsRemoveLoadImageNotifyRoutine(ImageLoadNotify);
UninitializeRegistryProtection();
UninitializeProcessProtection();
if (GlobalData.ServerPort) {
FltCloseCommunicationPort(GlobalData.ServerPort);
GlobalData.ServerPort = NULL;
}
if (GlobalData.FilterHandle) {
FltUnregisterFilter(GlobalData.FilterHandle);
GlobalData.FilterHandle = NULL;
}
UnloadRules();
UninitializeRulesEngine();
}
return status;
}
+1 -1
View File
@@ -3,7 +3,7 @@ Signature = "$WINDOWS NT$"
Class = ActivityMonitor
ClassGuid = {b86dff51-a31e-4bac-b3cf-e8cfe75c9fc2}
Provider = %ManufacturerName%
DriverVer = 12/29/2025,1.0.0.0
DriverVer = 04/12/2026,3.5.0.0
CatalogFile = PYAS_Driver.cat
PnpLockdown = 1
+1 -2
View File
@@ -12,7 +12,6 @@ FLT_PREOP_CALLBACK_STATUS ProtectBoot_PreDeviceControl(PFLT_CALLBACK_DATA Data,
UNREFERENCED_PARAMETER(CompletionContext);
if (Data->RequestorMode == KernelMode) return FLT_PREOP_SUCCESS_NO_CALLBACK;
if (KeGetCurrentIrql() > APC_LEVEL) return FLT_PREOP_SUCCESS_NO_CALLBACK;
ULONG IoControlCode = Data->Iopb->Parameters.DeviceIoControl.Common.IoControlCode;
@@ -23,7 +22,7 @@ FLT_PREOP_CALLBACK_STATUS ProtectBoot_PreDeviceControl(PFLT_CALLBACK_DATA Data,
IoControlCode == IOCTL_DISK_FORMAT_TRACKS_EX) {
HANDLE Pid = PsGetCurrentProcessId();
if (!IsProcessTrusted(Pid) && !IsInstallerProcess(Pid)) {
if (!IsProcessTrusted(Pid)) {
UNICODE_STRING MsgStr = RTL_CONSTANT_STRING(L"Disk_Wiper_Attempt");
SendMessageToUser(4001, (ULONG)(ULONG_PTR)Pid, MsgStr.Buffer, MsgStr.Length);
Data->IoStatus.Status = STATUS_ACCESS_DENIED;
+116 -59
View File
@@ -34,15 +34,23 @@ FLT_PREOP_CALLBACK_STATUS ProtectFile_PreCreate(PFLT_CALLBACK_DATA Data, PCFLT_R
FileName = nameInfo->Name;
if (!FileName.Buffer || FileName.Length == 0) {
goto cleanup;
FltReleaseFileNameInformation(nameInfo);
return FLT_PREOP_SUCCESS_NO_CALLBACK;
}
HANDLE Pid = PsGetCurrentProcessId();
BOOLEAN Trusted = IsProcessTrusted(Pid);
ULONG CreateDisposition = (Data->Iopb->Parameters.Create.Options >> 24) & 0xFF;
BOOLEAN IsCreateAction = (CreateDisposition == FILE_CREATE ||
CreateDisposition == FILE_SUPERSEDE ||
CreateDisposition == FILE_OVERWRITE ||
CreateDisposition == FILE_OVERWRITE_IF ||
CreateDisposition == FILE_OPEN_IF);
if (IsHoneyToken(&FileName)) {
if (!Trusted) {
if (Data->Iopb->Parameters.Create.SecurityContext->DesiredAccess & (FILE_WRITE_DATA | DELETE)) {
if ((Data->Iopb->Parameters.Create.SecurityContext->DesiredAccess & (FILE_WRITE_DATA | DELETE)) || IsCreateAction) {
SendMessageToUser(5001, (ULONG)(ULONG_PTR)Pid, FileName.Buffer, FileName.Length);
Data->IoStatus.Status = STATUS_ACCESS_DENIED;
callbackStatus = FLT_PREOP_COMPLETE;
@@ -57,7 +65,7 @@ FLT_PREOP_CALLBACK_STATUS ProtectFile_PreCreate(PFLT_CALLBACK_DATA Data, PCFLT_R
if (Data->Iopb->Parameters.Create.SecurityContext->DesiredAccess &
(FILE_WRITE_DATA | FILE_APPEND_DATA | GENERIC_WRITE | WRITE_DAC | WRITE_OWNER)) {
if (!Trusted && !IsInstallerProcess(Pid)) {
if (!Trusted) {
UNICODE_STRING MsgStr = RTL_CONSTANT_STRING(L"Disk_Wiper_Attempt");
SendMessageToUser(4001, (ULONG)(ULONG_PTR)Pid, MsgStr.Buffer, MsgStr.Length);
Data->IoStatus.Status = STATUS_ACCESS_DENIED;
@@ -69,7 +77,8 @@ FLT_PREOP_CALLBACK_STATUS ProtectFile_PreCreate(PFLT_CALLBACK_DATA Data, PCFLT_R
if (CheckProtectedPathRule(&FileName)) {
if (!Trusted) {
if (Data->Iopb->Parameters.Create.SecurityContext->DesiredAccess & (FILE_WRITE_DATA | DELETE | WRITE_DAC | GENERIC_WRITE)) {
if ((Data->Iopb->Parameters.Create.SecurityContext->DesiredAccess &
(FILE_WRITE_DATA | FILE_APPEND_DATA | FILE_WRITE_ATTRIBUTES | DELETE | WRITE_DAC | GENERIC_WRITE)) || IsCreateAction) {
SendMessageToUser(2001, (ULONG)(ULONG_PTR)Pid, FileName.Buffer, FileName.Length);
Data->IoStatus.Status = STATUS_ACCESS_DENIED;
callbackStatus = FLT_PREOP_COMPLETE;
@@ -114,37 +123,80 @@ FLT_PREOP_CALLBACK_STATUS ProtectFile_PreSetInfo(PFLT_CALLBACK_DATA Data, PCFLT_
}
PFLT_FILE_NAME_INFORMATION nameInfo = NULL;
FLT_PREOP_CALLBACK_STATUS callbackStatus = FLT_PREOP_SUCCESS_NO_CALLBACK;
NTSTATUS status = FltGetFileNameInformation(Data, FLT_FILE_NAME_NORMALIZED | FLT_FILE_NAME_QUERY_DEFAULT, &nameInfo);
if (!NT_SUCCESS(status) || !nameInfo) return FLT_PREOP_SUCCESS_NO_CALLBACK;
if (!NT_SUCCESS(status) || !nameInfo) goto cleanup;
status = FltParseFileNameInformation(nameInfo);
if (!NT_SUCCESS(status) || !nameInfo->Name.Buffer) {
FltReleaseFileNameInformation(nameInfo);
return FLT_PREOP_SUCCESS_NO_CALLBACK;
}
if (!NT_SUCCESS(status) || !nameInfo->Name.Buffer) goto cleanup;
HANDLE Pid = PsGetCurrentProcessId();
if (!IsProcessTrusted(Pid)) {
if (CheckFileExtensionRule(&nameInfo->Name) || CheckProtectedPathRule(&nameInfo->Name)) {
if (CheckProtectedPathRule(&nameInfo->Name)) {
SendMessageToUser(2001, (ULONG)(ULONG_PTR)Pid, nameInfo->Name.Buffer, nameInfo->Name.Length);
Data->IoStatus.Status = STATUS_ACCESS_DENIED;
callbackStatus = FLT_PREOP_COMPLETE;
goto cleanup;
}
if (CheckFileExtensionRule(&nameInfo->Name)) {
if (CheckRansomActivity(Pid, &nameInfo->Name, NULL, 0, FALSE)) {
SendMessageToUser(5001, (ULONG)(ULONG_PTR)Pid, nameInfo->Name.Buffer, nameInfo->Name.Length);
Data->IoStatus.Status = STATUS_ACCESS_DENIED;
FltReleaseFileNameInformation(nameInfo);
return FLT_PREOP_COMPLETE;
callbackStatus = FLT_PREOP_COMPLETE;
goto cleanup;
}
}
}
cleanup:
if (nameInfo) {
FltReleaseFileNameInformation(nameInfo);
}
return FLT_PREOP_SUCCESS_NO_CALLBACK;
return callbackStatus;
}
FLT_PREOP_CALLBACK_STATUS ProtectFile_SetSecurity(PFLT_CALLBACK_DATA Data, PCFLT_RELATED_OBJECTS FltObjects, PVOID* CompletionContext) {
UNREFERENCED_PARAMETER(FltObjects);
UNREFERENCED_PARAMETER(CompletionContext);
if (Data->RequestorMode == KernelMode) return FLT_PREOP_SUCCESS_NO_CALLBACK;
if (KeGetCurrentIrql() > APC_LEVEL) return FLT_PREOP_SUCCESS_NO_CALLBACK;
PFLT_FILE_NAME_INFORMATION nameInfo = NULL;
FLT_PREOP_CALLBACK_STATUS callbackStatus = FLT_PREOP_SUCCESS_NO_CALLBACK;
NTSTATUS status = FltGetFileNameInformation(Data, FLT_FILE_NAME_NORMALIZED | FLT_FILE_NAME_QUERY_DEFAULT, &nameInfo);
if (!NT_SUCCESS(status) || !nameInfo) goto cleanup;
status = FltParseFileNameInformation(nameInfo);
if (!NT_SUCCESS(status) || !nameInfo->Name.Buffer) goto cleanup;
SECURITY_INFORMATION SecurityInfo = Data->Iopb->Parameters.SetSecurity.SecurityInformation;
if (SecurityInfo & (OWNER_SECURITY_INFORMATION | DACL_SECURITY_INFORMATION | GROUP_SECURITY_INFORMATION)) {
if (CheckProtectedPathRule(&nameInfo->Name)) {
HANDLE Pid = PsGetCurrentProcessId();
if (!IsProcessTrusted(Pid)) {
SendMessageToUser(2001, (ULONG)(ULONG_PTR)Pid, nameInfo->Name.Buffer, nameInfo->Name.Length);
Data->IoStatus.Status = STATUS_ACCESS_DENIED;
callbackStatus = FLT_PREOP_COMPLETE;
goto cleanup;
}
}
}
cleanup:
if (nameInfo) {
FltReleaseFileNameInformation(nameInfo);
}
return callbackStatus;
}
FLT_PREOP_CALLBACK_STATUS ProtectFile_PreWrite(PFLT_CALLBACK_DATA Data, PCFLT_RELATED_OBJECTS FltObjects, PVOID* CompletionContext) {
UNREFERENCED_PARAMETER(FltObjects);
UNREFERENCED_PARAMETER(CompletionContext);
UNREFERENCED_PARAMETER(FltObjects);
if (Data->RequestorMode == KernelMode) return FLT_PREOP_SUCCESS_NO_CALLBACK;
@@ -156,46 +208,45 @@ FLT_PREOP_CALLBACK_STATUS ProtectFile_PreWrite(PFLT_CALLBACK_DATA Data, PCFLT_RE
if (WriteLength == 0) return FLT_PREOP_SUCCESS_NO_CALLBACK;
PFLT_FILE_NAME_INFORMATION nameInfo = NULL;
FLT_PREOP_CALLBACK_STATUS callbackStatus = FLT_PREOP_SUCCESS_NO_CALLBACK;
NTSTATUS status = FltGetFileNameInformation(Data, FLT_FILE_NAME_NORMALIZED | FLT_FILE_NAME_QUERY_DEFAULT, &nameInfo);
FLT_PREOP_CALLBACK_STATUS callbackStatus = FLT_PREOP_SUCCESS_NO_CALLBACK;
if (!NT_SUCCESS(status) || !nameInfo) goto cleanup;
if (NT_SUCCESS(status) && nameInfo) {
status = FltParseFileNameInformation(nameInfo);
if (NT_SUCCESS(status) && nameInfo->Name.Buffer) {
status = FltParseFileNameInformation(nameInfo);
if (!NT_SUCCESS(status) || !nameInfo->Name.Buffer) goto cleanup;
if (CheckFileExtensionRule(&nameInfo->Name)) {
HANDLE Pid = PsGetCurrentProcessId();
if (CheckFileExtensionRule(&nameInfo->Name)) {
HANDLE Pid = PsGetCurrentProcessId();
if (!IsProcessTrusted(Pid)) {
if (!IsProcessTrusted(Pid)) {
PVOID SystemBuffer = NULL;
PMDL Mdl = Data->Iopb->Parameters.Write.MdlAddress;
PVOID SystemBuffer = NULL;
PMDL Mdl = Data->Iopb->Parameters.Write.MdlAddress;
if (!Mdl) {
if (Data->Iopb->Parameters.Write.WriteBuffer && KeGetCurrentIrql() <= APC_LEVEL) {
if (NT_SUCCESS(FltLockUserBuffer(Data))) {
Mdl = Data->Iopb->Parameters.Write.MdlAddress;
}
}
}
if (Mdl) {
SystemBuffer = MmGetSystemAddressForMdlSafe(Mdl, NormalPagePriority | MdlMappingNoExecute);
}
if (SystemBuffer) {
if (CheckRansomActivity(Pid, &nameInfo->Name, SystemBuffer, WriteLength, TRUE)) {
if (!IsInstallerProcess(Pid)) {
SendMessageToUser(5001, (ULONG)(ULONG_PTR)Pid, nameInfo->Name.Buffer, nameInfo->Name.Length);
Data->IoStatus.Status = STATUS_ACCESS_DENIED;
callbackStatus = FLT_PREOP_COMPLETE;
}
}
if (!Mdl) {
if (Data->Iopb->Parameters.Write.WriteBuffer && KeGetCurrentIrql() <= APC_LEVEL) {
if (NT_SUCCESS(FltLockUserBuffer(Data))) {
Mdl = Data->Iopb->Parameters.Write.MdlAddress;
}
}
}
if (Mdl) {
SystemBuffer = MmGetSystemAddressForMdlSafe(Mdl, NormalPagePriority | MdlMappingNoExecute);
}
if (SystemBuffer) {
if (CheckRansomActivity(Pid, &nameInfo->Name, SystemBuffer, WriteLength, TRUE)) {
SendMessageToUser(5001, (ULONG)(ULONG_PTR)Pid, nameInfo->Name.Buffer, nameInfo->Name.Length);
Data->IoStatus.Status = STATUS_ACCESS_DENIED;
callbackStatus = FLT_PREOP_COMPLETE;
}
}
}
}
cleanup:
if (nameInfo) {
FltReleaseFileNameInformation(nameInfo);
}
return callbackStatus;
@@ -212,25 +263,31 @@ FLT_PREOP_CALLBACK_STATUS ProtectFile_FileSystemControl(PFLT_CALLBACK_DATA Data,
PFS_BPIO_INPUT InputBuffer = (PFS_BPIO_INPUT)Data->Iopb->Parameters.FileSystemControl.Neither.InputBuffer;
if (!InputBuffer) return FLT_PREOP_SUCCESS_NO_CALLBACK;
__try {
if (InputBuffer->Operation == FS_BPIO_OP_ENABLE) {
PFLT_FILE_NAME_INFORMATION nameInfo = NULL;
if (NT_SUCCESS(FltGetFileNameInformation(Data, FLT_FILE_NAME_NORMALIZED | FLT_FILE_NAME_QUERY_DEFAULT, &nameInfo)) && nameInfo) {
if (NT_SUCCESS(FltParseFileNameInformation(nameInfo))) {
if (CheckFileExtensionRule(&nameInfo->Name) || CheckProtectedPathRule(&nameInfo->Name)) {
FS_BPIO_OPERATIONS Operation;
Data->IoStatus.Status = STATUS_NOT_SUPPORTED;
FltReleaseFileNameInformation(nameInfo);
return FLT_PREOP_COMPLETE;
}
}
FltReleaseFileNameInformation(nameInfo);
}
}
__try {
Operation = InputBuffer->Operation;
}
__except (EXCEPTION_EXECUTE_HANDLER) {
return FLT_PREOP_SUCCESS_NO_CALLBACK;
}
return FLT_PREOP_SUCCESS_NO_CALLBACK;
PFLT_FILE_NAME_INFORMATION nameInfo = NULL;
FLT_PREOP_CALLBACK_STATUS callbackStatus = FLT_PREOP_SUCCESS_NO_CALLBACK;
if (Operation == FS_BPIO_OP_ENABLE) {
if (NT_SUCCESS(FltGetFileNameInformation(Data, FLT_FILE_NAME_NORMALIZED | FLT_FILE_NAME_QUERY_DEFAULT, &nameInfo)) && nameInfo) {
if (NT_SUCCESS(FltParseFileNameInformation(nameInfo))) {
if (CheckFileExtensionRule(&nameInfo->Name) || CheckProtectedPathRule(&nameInfo->Name)) {
Data->IoStatus.Status = STATUS_NOT_SUPPORTED;
callbackStatus = FLT_PREOP_COMPLETE;
}
}
}
}
if (nameInfo) {
FltReleaseFileNameInformation(nameInfo);
}
return callbackStatus;
}
+64 -16
View File
@@ -1,15 +1,18 @@
#include "DriverCommon.h"
PVOID ObRegistrationHandle = NULL;
OB_CALLBACK_REGISTRATION ObRegistration;
OB_OPERATION_REGISTRATION ObCallbacks[1];
static PVOID ObRegistrationHandle = NULL;
static OB_CALLBACK_REGISTRATION ObRegistration;
static OB_OPERATION_REGISTRATION ObCallbacks[2];
static BOOLEAN IsSystemImage(PUNICODE_STRING FullImageName) {
if (!FullImageName || !FullImageName->Buffer) return FALSE;
if (WildcardMatch(L"*\\Windows\\System32\\*", FullImageName->Buffer, FullImageName->Length) ||
WildcardMatch(L"*\\Windows\\SysWOW64\\*", FullImageName->Buffer, FullImageName->Length) ||
WildcardMatch(L"*\\Windows\\WinSxS\\*", FullImageName->Buffer, FullImageName->Length)) {
WildcardMatch(L"*\\Windows\\WinSxS\\*", FullImageName->Buffer, FullImageName->Length) ||
WildcardMatch(L"*\\Windows\\Microsoft.NET\\*", FullImageName->Buffer, FullImageName->Length) ||
WildcardMatch(L"*\\Common Files\\Microsoft Shared\\*", FullImageName->Buffer, FullImageName->Length) ||
WildcardMatch(L"*\\Program Files*\\*", FullImageName->Buffer, FullImageName->Length)) {
return TRUE;
}
return FALSE;
@@ -32,7 +35,7 @@ VOID ImageLoadNotify(PUNICODE_STRING FullImageName, HANDLE ProcessId, PIMAGE_INF
}
static BOOLEAN IsBlacklistedAdminTool(HANDLE ProcessId) {
if (KeGetCurrentIrql() > APC_LEVEL) return FALSE;
if (KeGetCurrentIrql() != PASSIVE_LEVEL) return FALSE;
NTSTATUS status;
BOOLEAN isBlacklisted = FALSE;
@@ -72,22 +75,19 @@ static OB_PREOP_CALLBACK_STATUS PreOpenProcess(PVOID RegistrationContext, POB_PR
HANDLE SourcePid = PsGetCurrentProcessId();
if (SourcePid == TargetPid) return OB_PREOP_SUCCESS;
if ((ULONG)(ULONG_PTR)SourcePid == GlobalData.PyasPid) return OB_PREOP_SUCCESS;
if (SourcePid == (HANDLE)4) return OB_PREOP_SUCCESS;
BOOLEAN bProtected = IsTargetProtected(TargetPid);
if (IsTargetProtected(TargetPid)) {
BOOLEAN bIsTrusted = IsProcessTrusted(SourcePid);
if (bProtected) {
BOOLEAN bIsInstaller = IsInstallerProcess(SourcePid);
if (bIsInstaller) {
if (bIsTrusted) {
if (IsBlacklistedAdminTool(SourcePid)) {
bIsInstaller = FALSE;
bIsTrusted = FALSE;
}
}
if (bIsInstaller) return OB_PREOP_SUCCESS;
if (bIsTrusted) return OB_PREOP_SUCCESS;
ACCESS_MASK DenyMask = PROCESS_TERMINATE |
PROCESS_VM_OPERATION |
@@ -100,11 +100,54 @@ static OB_PREOP_CALLBACK_STATUS PreOpenProcess(PVOID RegistrationContext, POB_PR
PROCESS_SET_QUOTA;
OperationInformation->Parameters->CreateHandleInformation.DesiredAccess &= ~DenyMask;
OperationInformation->Parameters->CreateHandleInformation.OriginalDesiredAccess &= ~DenyMask;
if (OperationInformation->Operation == OB_OPERATION_HANDLE_DUPLICATE) {
OperationInformation->Parameters->DuplicateHandleInformation.DesiredAccess &= ~DenyMask;
OperationInformation->Parameters->DuplicateHandleInformation.OriginalDesiredAccess &= ~DenyMask;
}
}
return OB_PREOP_SUCCESS;
}
static OB_PREOP_CALLBACK_STATUS PreOpenThread(PVOID RegistrationContext, POB_PRE_OPERATION_INFORMATION OperationInformation) {
UNREFERENCED_PARAMETER(RegistrationContext);
if (OperationInformation->KernelHandle) return OB_PREOP_SUCCESS;
PETHREAD TargetThread = (PETHREAD)OperationInformation->Object;
if (!TargetThread) return OB_PREOP_SUCCESS;
PEPROCESS TargetProcess = IoThreadToProcess(TargetThread);
if (!TargetProcess) return OB_PREOP_SUCCESS;
HANDLE TargetPid = PsGetProcessId(TargetProcess);
HANDLE SourcePid = PsGetCurrentProcessId();
if (SourcePid == TargetPid) return OB_PREOP_SUCCESS;
if ((ULONG)(ULONG_PTR)SourcePid == GlobalData.PyasPid) return OB_PREOP_SUCCESS;
if (SourcePid == (HANDLE)4) return OB_PREOP_SUCCESS;
if (IsTargetProtected(TargetPid)) {
BOOLEAN bIsTrusted = IsProcessTrusted(SourcePid);
if (bIsTrusted) {
if (IsBlacklistedAdminTool(SourcePid)) {
bIsTrusted = FALSE;
}
}
if (bIsTrusted) return OB_PREOP_SUCCESS;
ACCESS_MASK DenyMask = THREAD_TERMINATE |
THREAD_SUSPEND_RESUME |
THREAD_SET_CONTEXT |
THREAD_SET_INFORMATION |
THREAD_SET_THREAD_TOKEN;
OperationInformation->Parameters->CreateHandleInformation.DesiredAccess &= ~DenyMask;
if (OperationInformation->Operation == OB_OPERATION_HANDLE_DUPLICATE) {
OperationInformation->Parameters->DuplicateHandleInformation.DesiredAccess &= ~DenyMask;
}
}
@@ -120,8 +163,13 @@ NTSTATUS InitializeProcessProtection() {
ObCallbacks[0].PreOperation = PreOpenProcess;
ObCallbacks[0].PostOperation = NULL;
ObCallbacks[1].ObjectType = PsThreadType;
ObCallbacks[1].Operations = OB_OPERATION_HANDLE_CREATE | OB_OPERATION_HANDLE_DUPLICATE;
ObCallbacks[1].PreOperation = PreOpenThread;
ObCallbacks[1].PostOperation = NULL;
ObRegistration.Version = OB_FLT_REGISTRATION_VERSION;
ObRegistration.OperationRegistrationCount = 1;
ObRegistration.OperationRegistrationCount = 2;
ObRegistration.Altitude = Altitude;
ObRegistration.RegistrationContext = NULL;
ObRegistration.OperationRegistration = ObCallbacks;
+209 -16
View File
@@ -1,6 +1,73 @@
#include "DriverCommon.h"
LARGE_INTEGER Cookie;
static LARGE_INTEGER Cookie;
static BOOLEAN IsBamRegistryPath(PCUNICODE_STRING KeyName) {
if (!KeyName || !KeyName->Buffer) return FALSE;
if (WildcardMatch(L"*\\Services\\bam\\*", KeyName->Buffer, KeyName->Length)) {
return TRUE;
}
return FALSE;
}
static BOOLEAN IsWriteAccess(ACCESS_MASK Access) {
if (Access & (KEY_SET_VALUE | KEY_CREATE_SUB_KEY | KEY_CREATE_LINK |
DELETE | WRITE_DAC | WRITE_OWNER | GENERIC_WRITE)) {
return TRUE;
}
return FALSE;
}
static PWCHAR GetFullPath(PVOID RootObject, PUNICODE_STRING CompleteName, PULONG OutLength) {
PWCHAR Buffer = NULL;
ULONG TotalSize = 0;
PCUNICODE_STRING RootName = NULL;
BOOLEAN NeedFreeRootName = FALSE;
if (RootObject) {
NTSTATUS status = CmCallbackGetKeyObjectIDEx(&Cookie, RootObject, NULL, &RootName, 0);
if (NT_SUCCESS(status) && RootName) {
NeedFreeRootName = TRUE;
}
}
ULONG RootLen = (RootName && RootName->Buffer) ? RootName->Length : 0;
ULONG RelLen = (CompleteName && CompleteName->Buffer) ? CompleteName->Length : 0;
TotalSize = RootLen + sizeof(WCHAR) + RelLen + sizeof(WCHAR);
Buffer = (PWCHAR)PyasAllocate(TotalSize);
if (!Buffer) {
if (NeedFreeRootName) CmCallbackReleaseKeyObjectIDEx(RootName);
return NULL;
}
RtlZeroMemory(Buffer, TotalSize);
PWCHAR Current = Buffer;
if (RootLen > 0) {
RtlCopyMemory(Current, RootName->Buffer, RootLen);
Current += (RootLen / sizeof(WCHAR));
if (Current > Buffer && *(Current - 1) != L'\\') {
*Current = L'\\';
Current++;
}
}
if (RelLen > 0) {
if (RootLen > 0 && CompleteName->Buffer[0] == L'\\') {
RtlCopyMemory(Current, CompleteName->Buffer + 1, RelLen - sizeof(WCHAR));
}
else {
RtlCopyMemory(Current, CompleteName->Buffer, RelLen);
}
}
if (NeedFreeRootName) CmCallbackReleaseKeyObjectIDEx(RootName);
if (OutLength) *OutLength = (ULONG)wcslen(Buffer) * sizeof(WCHAR);
return Buffer;
}
static NTSTATUS RegistryCallback(PVOID CallbackContext, PVOID Argument1, PVOID Argument2) {
UNREFERENCED_PARAMETER(CallbackContext);
@@ -8,32 +75,158 @@ static NTSTATUS RegistryCallback(PVOID CallbackContext, PVOID Argument1, PVOID A
if (KeGetCurrentIrql() > APC_LEVEL) return STATUS_SUCCESS;
REG_NOTIFY_CLASS Class = (REG_NOTIFY_CLASS)(ULONG_PTR)Argument1;
NTSTATUS status = STATUS_SUCCESS;
if (Class == RegNtPreSetValueKey || Class == RegNtPreDeleteKey || Class == RegNtPreCreateKey) {
REG_SET_VALUE_KEY_INFORMATION* Info = (REG_SET_VALUE_KEY_INFORMATION*)Argument2;
PCUNICODE_STRING KeyName = NULL;
if (Class == RegNtPreCreateKey || Class == RegNtPreOpenKey || Class == RegNtPreOpenKeyEx) {
PVOID RootObject = NULL;
PUNICODE_STRING CompleteName = NULL;
ACCESS_MASK DesiredAccess = 0;
if (!NT_SUCCESS(CmCallbackGetKeyObjectIDEx(&Cookie, Info->Object, NULL, &KeyName, 0))) {
if (Class == RegNtPreCreateKey) {
REG_CREATE_KEY_INFORMATION* Info = (REG_CREATE_KEY_INFORMATION*)Argument2;
RootObject = Info->RootObject;
CompleteName = Info->CompleteName;
DesiredAccess = Info->DesiredAccess;
}
else {
REG_OPEN_KEY_INFORMATION* Info = (REG_OPEN_KEY_INFORMATION*)Argument2;
RootObject = Info->RootObject;
CompleteName = Info->CompleteName;
DesiredAccess = Info->DesiredAccess;
}
if (!IsWriteAccess(DesiredAccess)) {
return STATUS_SUCCESS;
}
if (KeyName && KeyName->Buffer) {
if (CheckRegistryRule(KeyName)) {
ULONG PathLen = 0;
PWCHAR FullPathBuffer = GetFullPath(RootObject, CompleteName, &PathLen);
if (FullPathBuffer) {
UNICODE_STRING FullPath;
RtlInitUnicodeString(&FullPath, FullPathBuffer);
if (CheckRegistryRule(&FullPath)) {
HANDLE Pid = PsGetCurrentProcessId();
if (!IsProcessTrusted(Pid)) {
if (!IsInstallerProcess(Pid)) {
SendMessageToUser(3001, (ULONG)(ULONG_PTR)Pid, KeyName->Buffer, KeyName->Length);
CmCallbackReleaseKeyObjectIDEx(KeyName);
return STATUS_ACCESS_DENIED;
}
SendMessageToUser(3001, (ULONG)(ULONG_PTR)Pid, FullPath.Buffer, FullPath.Length);
status = STATUS_ACCESS_DENIED;
}
}
PyasFree(FullPathBuffer);
}
if (KeyName) {
CmCallbackReleaseKeyObjectIDEx(KeyName);
}
return status;
}
if (Class == RegNtPreDeleteKey) {
REG_DELETE_KEY_INFORMATION* Info = (REG_DELETE_KEY_INFORMATION*)Argument2;
PCUNICODE_STRING KeyName = NULL;
if (NT_SUCCESS(CmCallbackGetKeyObjectIDEx(&Cookie, Info->Object, NULL, &KeyName, 0))) {
if (KeyName) {
if (CheckRegistryRule(KeyName)) {
HANDLE Pid = PsGetCurrentProcessId();
if (!IsProcessTrusted(Pid)) {
SendMessageToUser(3001, (ULONG)(ULONG_PTR)Pid, KeyName->Buffer, KeyName->Length);
status = STATUS_ACCESS_DENIED;
}
}
CmCallbackReleaseKeyObjectIDEx(KeyName);
}
}
return status;
}
if (Class == RegNtPreSetValueKey) {
REG_SET_VALUE_KEY_INFORMATION* Info = (REG_SET_VALUE_KEY_INFORMATION*)Argument2;
PCUNICODE_STRING KeyName = NULL;
if (NT_SUCCESS(CmCallbackGetKeyObjectIDEx(&Cookie, Info->Object, NULL, &KeyName, 0))) {
if (KeyName) {
if (KeyName->Buffer) {
if (!IsBamRegistryPath(KeyName)) {
ULONG FullSize = KeyName->Length + sizeof(WCHAR) +
(Info->ValueName ? Info->ValueName->Length : 0) +
sizeof(WCHAR);
PWCHAR Buffer = (PWCHAR)PyasAllocate(FullSize);
if (Buffer) {
UNICODE_STRING FullPath;
FullPath.Buffer = Buffer;
FullPath.Length = 0;
FullPath.MaximumLength = (USHORT)FullSize;
RtlCopyUnicodeString(&FullPath, KeyName);
if (Info->ValueName && Info->ValueName->Length > 0) {
if (FullPath.Length > 0 && FullPath.Buffer[(FullPath.Length / sizeof(WCHAR)) - 1] != L'\\') {
RtlAppendUnicodeToString(&FullPath, L"\\");
}
RtlAppendUnicodeStringToString(&FullPath, Info->ValueName);
}
if (CheckRegistryRule(&FullPath)) {
HANDLE Pid = PsGetCurrentProcessId();
if (!IsProcessTrusted(Pid)) {
SendMessageToUser(3001, (ULONG)(ULONG_PTR)Pid, FullPath.Buffer, FullPath.Length);
status = STATUS_ACCESS_DENIED;
}
}
PyasFree(Buffer);
}
}
}
CmCallbackReleaseKeyObjectIDEx(KeyName);
}
}
return status;
}
if (Class == RegNtPreDeleteValueKey) {
REG_DELETE_VALUE_KEY_INFORMATION* Info = (REG_DELETE_VALUE_KEY_INFORMATION*)Argument2;
PCUNICODE_STRING KeyName = NULL;
if (NT_SUCCESS(CmCallbackGetKeyObjectIDEx(&Cookie, Info->Object, NULL, &KeyName, 0))) {
if (KeyName) {
if (KeyName->Buffer) {
if (!IsBamRegistryPath(KeyName)) {
ULONG FullSize = KeyName->Length + sizeof(WCHAR) +
(Info->ValueName ? Info->ValueName->Length : 0) +
sizeof(WCHAR);
PWCHAR Buffer = (PWCHAR)PyasAllocate(FullSize);
if (Buffer) {
UNICODE_STRING FullPath;
FullPath.Buffer = Buffer;
FullPath.Length = 0;
FullPath.MaximumLength = (USHORT)FullSize;
RtlCopyUnicodeString(&FullPath, KeyName);
if (Info->ValueName && Info->ValueName->Length > 0) {
if (FullPath.Length > 0 && FullPath.Buffer[(FullPath.Length / sizeof(WCHAR)) - 1] != L'\\') {
RtlAppendUnicodeToString(&FullPath, L"\\");
}
RtlAppendUnicodeStringToString(&FullPath, Info->ValueName);
}
if (CheckRegistryRule(&FullPath)) {
HANDLE Pid = PsGetCurrentProcessId();
if (!IsProcessTrusted(Pid)) {
SendMessageToUser(3001, (ULONG)(ULONG_PTR)Pid, FullPath.Buffer, FullPath.Length);
status = STATUS_ACCESS_DENIED;
}
}
PyasFree(Buffer);
}
}
}
CmCallbackReleaseKeyObjectIDEx(KeyName);
}
}
return status;
}
return STATUS_SUCCESS;
}
File diff suppressed because it is too large. Load diff
+20
View File
@@ -0,0 +1,20 @@
#include <windows.h>
#include <ntverp.h>
#define VER_FILETYPE VFT_DRV
#define VER_FILESUBTYPE VFT2_DRV_SYSTEM
#define VER_FILEDESCRIPTION_STR "PYAS Security Driver Protect"
#define VER_INTERNALNAME_STR "PYAS"
#define VER_LEGALCOPYRIGHT_STR "PYAS Security"
#define VER_ORIGINALFILENAME_STR "PYAS_Driver.sys"
#define VER_PRODUCTNAME_STR "PYAS"
#define VER_FILEVERSION 3,5.0,0
#define VER_FILEVERSION_STR "3,5.0,0"
#define VER_PRODUCTVERSION 3,5.0,0
#define VER_PRODUCTVERSION_STR "3,5.0,0"
#define VER_COMPANYNAME_STR "PYAS Security"
#include "common.ver"
+215
View File
@@ -0,0 +1,215 @@
{
"Rule_Registry_BlockList": [
"\\REGISTRY\\MACHINE\\BCD00000000",
"\\REGISTRY\\MACHINE\\BCD00000000\\*",
"*\\Image File Execution Options\\*",
"*\\Classes\\.exe",
"*\\Classes\\.bat",
"*\\Classes\\.cmd",
"*\\Classes\\.com",
"*\\Classes\\exefile\\*",
"*\\Classes\\batfile\\*",
"*\\Classes\\cmdfile\\*",
"*\\Classes\\comfile\\*",
"*\\mouclass",
"*\\kbdclass",
"*\\DisableAntiSpyware",
"*\\DisableWindowsUpdateAccess",
"*\\EnableLUA",
"*\\ConsentPromptBehaviorAdmin",
"*\\NoControlPanel",
"*\\NoDrives",
"*\\NoFileMenu",
"*\\NoFind",
"*\\NoStartMenuPinnedList",
"*\\NoSetFolders",
"*\\NoSetFolderOptions",
"*\\NoViewOnDrive",
"*\\NoClose",
"*\\NoDesktop",
"*\\NoLogoff",
"*\\NoFolderOptions",
"*\\RestrictRun",
"*\\NoViewContextMenu",
"*\\HideClock",
"*\\NoStartMenuMyGames",
"*\\NoStartMenuMyMusic",
"*\\DisableCMD",
"*\\NoAddingComponents",
"*\\NoWinKeys",
"*\\NoStartMenuLogOff",
"*\\NoSimpleNetIDList",
"*\\NoLowDiskSpaceChecks",
"*\\DisableLockWorkstation",
"*\\Restrict_Run",
"*\\DisableTaskMgr",
"*\\DisableRegistryTools",
"*\\DisableChangePassword",
"*\\Wallpaper",
"*\\NoComponents",
"*\\NoStartMenuMorePrograms",
"*\\NoActiveDesktop",
"*\\NoSetActiveDesktop",
"*\\NoRecentDocsMenu",
"*\\NoWindowsUpdate",
"*\\NoChangeStartMenu",
"*\\NoFavoritesMenu",
"*\\NoRecentDocsHistory",
"*\\NoSetTaskbar",
"*\\NoSMHelp",
"*\\NoTrayContextMenu",
"*\\NoManageMyComputerVerb",
"*\\NoRealMode",
"*\\NoRun",
"*\\ClearRecentDocsOnExit",
"*\\NoActiveDesktopChanges",
"*\\NoStartMenuNetworkPlaces",
"*\\NoLockScreen",
"*\\HideFastUserSwitching",
"*\\ConsentPromptBehaviorUser",
"*\\EnableSecureUIAPaths",
"*\\NoSRM",
"*\\NoInteractiveServices",
"*\\ShutdownWithoutLogon",
"*\\AutoAdminLogon",
"*\\BootExecute",
"*\\SubSystems",
"*\\FirmwareBootDevice",
"*\\BootDriverFlags",
"*\\SystemStartOptions",
"*\\CrashDumpEnabled",
"*\\DisableAutomaticRestartOnFailure",
"*\\SystemBiosVersion",
"*\\NoTheme",
"*\\SystemSetupInProgress",
"*\\CmdLine",
"*\\SetupType",
"*\\AutoReboot",
"*\\Userinit",
"*\\UIHost",
"*\\Debugger",
"*\\DefaultUserName",
"*\\DefaultPassword",
"*\\AltDefaultUserName",
"*\\AltDefaultPassword",
"*\\DisableCAD",
"*\\LegalNoticeCaption",
"*\\LegalNoticeText",
],
"Rule_Registry_TrustedList": [
],
"Rule_Process_TrustedPaths": [
"\\Device\\HarddiskVolume*\\Windows\\*",
"\\Device\\HarddiskVolume*\\Program Files\\*",
"\\Device\\HarddiskVolume*\\Program Files (x86)\\*",
"\\Device\\HarddiskVolume*\\ProgramData\\Microsoft\\Windows Defender\\*",
"\\Device\\HarddiskVolume*\\ProgramData\\Microsoft\\Windows Defender Advanced Threat Protection\\*",
"*\\Google\\Chrome\\Application\\chrome.exe",
"*\\Microsoft\\Edge\\Application\\msedge.exe",
"*\\Mozilla Firefox\\firefox.exe",
"*\\Steam\\steam.exe",
"*\\steam\\*\\steam.exe",
"*\\Battle.net\\*\\Battle.net.exe",
"*\\Epic Games\\Launcher\\Portal\\Binaries\\*\\EpicGamesLauncher.exe",
"*\\ThunderEv.exe",
"*\\ThunderStart.exe",
"*\\resources\\bin\\SDK\\DownloadSDKServer.exe",
"*\\KuGou.exe",
"*\\XLGame.exe",
"*\\XLLiveUD.exe",
"*\\PC4399_WPFLauncher.exe",
"*\\bin\\APlayer.exe",
],
"Rule_Process_ExploitableBlacklist": [
"*\\cmd.exe",
"*\\powershell.exe",
"*\\pwsh.exe",
"*\\wscript.exe",
"*\\cscript.exe",
"*\\mshta.exe",
"*\\regsvr32.exe",
"*\\certutil.exe",
"*\\bitsadmin.exe",
"*\\wmic.exe",
"*\\hh.exe",
"*\\cmstp.exe",
"*\\installutil.exe",
"*\\regasm.exe",
"*\\regsvcs.exe",
"*\\msbuild.exe",
"*\\csc.exe",
"*\\vbc.exe",
"*\\jsc.exe",
"*\\bash.exe",
"*\\scrcons.exe",
"*\\schtasks.exe",
"*\\at.exe",
"*\\reg.exe",
"*\\sc.exe",
"*\\net.exe",
"*\\net1.exe",
"*\\whoami.exe",
"*\\ipconfig.exe",
"*\\taskkill.exe",
"*\\ProcessHacker.exe",
"*\\procmon.exe",
"*\\procexp.exe",
],
"Rule_File_ProtectedPaths": [
"*\\PYAS.exe",
"*\\PYAS_Driver.sys",
"*\\ProgramData\\PYAS\\*.json",
"*\\Plugins\\Rules\\Rules_Driver_P1.json",
"*\\Engine\\Pattern\\*.onnx",
"*\\Engine\\Properties\\*.onnx",
"\\Device\\HarddiskVolume*\\bootmgr",
"\\Device\\HarddiskVolume*\\boot.ini",
"\\Device\\HarddiskVolume*\\BOOTNXT",
"\\Device\\HarddiskVolume*\\EFI\\*",
"\\Device\\HarddiskVolume*\\Boot\\*",
"\\Device\\HarddiskVolume*\\Recovery\\*",
"\\Device\\HarddiskVolume*\\System Volume Information\\*",
"\\Device\\HarddiskVolume*\\Windows\\System32\\config\\*",
"*\\CON",
"*\\CON\\*",
"*\\PRN",
"*\\PRN\\*",
"*\\AUX",
"*\\AUX\\*",
"*\\NUL",
"*\\NUL\\*",
"*\\COM?",
"*\\COM?\\*",
"*\\LPT?",
"*\\LPT?\\*",
"*\\OSDATA",
"*\\OSDATA\\*",
"*\\*<*",
"*\\*>*",
],
"Rule_File_ExceptionPaths": [
"*\\Users\\*\\steam\\*",
"*\\AppData\\*",
"*\\Temp\\*",
"*\\Local\\Temp\\*",
"*\\ventoy\\Boot\\*",
"*\\AdminRegionConfig.*",
"*\\nt_qq\\nt_*\\*",
],
"Rule_File_RansomwareExtensions": [
".zip", ".7z", ".rar", ".tar", ".gz",
".ppt", ".pptx", ".wps", ".txt", ".rtf",
".pdf", ".xls", ".xlsx", ".doc", ".docx",
".jpg", ".jpeg", ".png", ".webp", ".gif",
".mp3", ".wav", ".aac", ".ogg", ".flac",
".mp4", ".avi", ".mov", ".wmv", ".mkv",
]
}
+105 -133
View File
@@ -2,21 +2,32 @@
Antivirus software written in Python and C++ that blocks threats through deep learning and behavioral monitoring!
<img width="2245" height="1477" alt="PYAS_UI" src="https://github.com/user-attachments/assets/87d40261-7655-49ad-a19c-1ffcca60584f" />
<img width="2440" height="1600" alt="PYAS_All" src="https://github.com/user-attachments/assets/1991aad3-64cc-4266-ac67-dab70c891ce7" />
## Requirements
Python 3.10 is recommended. Other Python versions may require different pip commands.
```
pip install numpy==1.26.4
pip install onnxruntime==1.18.1
pip install pefile==2023.2.7
pip install Pillow==11.0.0
pip install pyperclip==1.9.0
pip install PySide6==6.9.1
pip install requests==2.32.4
pip install yara-python==4.5.4
pip install pystray
pip install pefile
pip install requests
pip install pywebview
pip install Pillow
pip install yara-python
pip install numpy
pip install onnxruntime
```
Non-essential requirements installation, only used for model training or other functions.
```
pip install pandas
pip install scikit-learn
pip install tensorflow
pip install tf2onnx
pip install lightgbm
pip install onnxmltools
```
## File Information
@@ -25,33 +36,34 @@ The following lists the storage locations of all relevant code and other related
```
PYAS/
├── Engine/
│ ├── Models/
│ │ ├── convert.py # Convert executable files or other files to images
│ │ ├── train.py # TensorFlow CNN model training complete code
│ │ └── ... # Other models folders and files
│ │
├── Embed/ # Deploy an embedded command line antivirus engine
│ ├── Dockerfile
│ └── ...
│
├── Engine/ # Complete code for Yara signatures and AI model training
│ ├── Heuristic/
│ │ └── ... # Yara Rules
│ ├── Pattern/
│ │ └── ... # CNN Model
│ └── Properties/
│ └── ... # LightGBM Model
│
├── Interface/ # Interface Interaction and Icons with WebView2
│ ├── static/
│ │ └── ... # style.css, main.js, icon.ico
│ └── templates/
│ └── ... # index.html
│
├── Plugins/ # Main filter driver protect and rules
│ ├── Filter/
│ │ └── ... # DriverCommon.h, DriverEntry.cpp, ProtectRegistry.cpp, ...
│ └── Rules/
│ ├── rules.yar # Yara virus signature rule matching
│ ├── rules.ips # IP network address rule matching
│ └── ... # Other rules folders and files
│
├── Plugins/
│ └── Filter/
│ ├── DriverEntry.cpp # Main driver entry and initialization logic
│ ├── DriverCommon.h # Global driver definitions, constants, and functions
│ ├── ProtectBoot.cpp # Disk boot sector write protection
│ ├── ProtectRegistry.cpp # Registry modification protection
│ ├── ProtectRules.cpp # White, block list, and matching logic for files registry
│ └── ... # Other driver folders and files
│
├── PYAS.py # Main application entry point and UI to engine interface
├── PYAS_Config.py # Configuration loading, saving, and global parameters
├── PYAS_Engine.py # Core scanning engine: YARA, IP, ONNX model execution
├── PYAS_Interface.py # User interface components and event handling
├── PYAS_Resource.py # Static image and icon resource management
│ └── ... # Rules_Driver_P1.json
|
├── PYAS.py # Main antivirus application entry point
├── PYAS_Engine.py # Core scanning engine algorithm
├── PYAS_Version.py # Version metadata for packaging and updates
└── ... # Other supplementary folders and files
└── ...
```
## Architecture diagram
@@ -61,122 +73,78 @@ PYAS Security antivirus software general architecture diagram.
```mermaid
graph TD
%% Global Styles
classDef ui fill:#e1f5fe,stroke:#01579b,stroke-width:2px;
classDef logic fill:#fff9c4,stroke:#fbc02d,stroke-width:2px;
classDef engine fill:#e8f5e9,stroke:#2e7d32,stroke-width:2px;
classDef thread fill:#f3e5f5,stroke:#7b1fa2,stroke-width:2px;
classDef kernel fill:#ffebee,stroke:#c62828,stroke-width:2px;
classDef data fill:#eceff1,stroke:#455a64,stroke-width:2px,stroke-dasharray: 5 5;
classDef userMode fill:#e3f2fd,stroke:#1565c0,stroke-width:2px,color:#0d47a1
classDef kernelMode fill:#fff3e0,stroke:#e65100,stroke-width:2px,color:#e65100
classDef storage fill:#f3e5f5,stroke:#7b1fa2,stroke-width:2px,color:#4a148c
classDef interaction fill:#e8f5e9,stroke:#2e7d32,stroke-width:2px,stroke-dasharray: 5 5,color:#1b5e20
User((User))
subgraph Presentation_Layer ["Presentation Layer (View)"]
direction TB
UI_Static["PYAS_Interface.py<br/>(Ui_main_window Class)"]:::ui
UI_Controller["PYAS.py<br/>(MainWindow_Controller Class)"]:::ui
TrayIcon["System Tray Icon"]:::ui
subgraph UserMode ["User Mode (Python / Web)"]
UI["Web UI (pywebview)"]:::userMode
PYASCore["PYAS Core (PYAS.py)"]:::userMode
User <--> UI_Controller
UI_Static --- UI_Controller
UI_Controller --- TrayIcon
end
subgraph Core_Logic_Layer ["Core Logic & Controller"]
direction TB
Init_Env["Initialization<br/>(init_environ, init_variable,<br/>init_windll)"]:::logic
Config_Mgr["Configuration Manager<br/>(load_config, save_config)"]:::logic
subgraph System_Tools ["System Tools"]
Sys_Repair["System Repair<br/>(repair_system_mbr,<br/>repair_system_image, etc.)"]:::logic
Sys_Clean["System Cleaner<br/>(clean_button, traverse_temp)"]:::logic
Proc_Mgr["Process Manager<br/>(list_process, kill_process)"]:::logic
List_Mgr["List Manager<br/>(manage_named_list:<br/>Whitelist/Quarantine)"]:::logic
subgraph Engines ["Scanning Engines (PYAS_Engine.py)"]
PEScanner["PE/ML Scanner (pe_scanner)"]:::userMode
CNNScanner["CNN Scanner (cnn_scanner)"]:::userMode
RuleScanner["YARA Scanner (rule_scanner)"]:::userMode
CloudScanner["Cloud API (cloud_scanner)"]:::userMode
SignScanner["Signature Verify (sign_scanner)"]:::userMode
end
UI_Controller --> Init_Env
Init_Env --> Config_Mgr
UI_Controller --> System_Tools
end
subgraph Protection_Layer ["Real-time Protection Threads (Daemons)"]
direction TB
Thread_Proc["Process Guard Thread<br/>(protect_proc_thread)<br/>[WMI/Snapshot]"]:::thread
Thread_File["File Guard Thread<br/>(protect_file_thread)<br/>[ReadDirectoryChangesW]"]:::thread
Thread_Net["Network Guard Thread<br/>(protect_net_thread)<br/>[GetExtendedTcpTable]"]:::thread
Thread_Sys["System Guard Thread<br/>(protect_system_thread)<br/>[Reg/MBR Check]"]:::thread
Thread_Popup["Popup Blocker Thread<br/>(popup_intercept_thread)<br/>[EnumWindows]"]:::thread
Thread_Pipe["Driver IPC Thread<br/>(pipe_server_thread)<br/>[NamedPipe Server]"]:::thread
Init_Env --> Thread_Proc
Init_Env --> Thread_File
Init_Env --> Thread_Net
Init_Env --> Thread_Sys
Init_Env --> Thread_Popup
Init_Env --> Thread_Pipe
end
subgraph Engine_Layer ["Detection Engine (PYAS_Engine.py)"]
direction TB
Scan_Worker["Scan Worker<br/>(scan_worker / scan_engine)"]:::engine
subgraph Scanners
Sign_Check["Signature Scanner<br/>(sign_scanner)<br/>[WinVerifyTrust]"]:::engine
AI_Model["AI Model Scanner<br/>(model_scanner)<br/>[ONNX/ResNet]"]:::engine
Yara_Rules["Rule Scanner<br/>(rule_scanner)<br/>[Yara/IP Lists]"]:::engine
CommClient["Filter Communication Client"]:::interaction
end
subgraph KernelMode ["Kernel Mode (C++ Minifilter Driver)"]
CommServer["ALPC Port (\\PYAS_Output_Pipe)"]:::interaction
DriverCore["Driver Entry (DriverEntry.cpp)"]:::kernelMode
RuleEngine["Rules & Trust Cache (ProtectRules.cpp)"]:::kernelMode
subgraph Protections ["Protection Subsystems"]
FileProtect["File Minifilter (ProtectFile.cpp)"]:::kernelMode
ProcessProtect["ObRegisterCallbacks (ProtectProcess.cpp)"]:::kernelMode
RegProtect["CmRegisterCallbackEx (ProtectRegistry.cpp)"]:::kernelMode
BootProtect["IRP_MJ_DEVICE_CONTROL (ProtectBoot.cpp)"]:::kernelMode
end
Thread_Proc --> Scan_Worker
Thread_File --> Scan_Worker
Thread_Net --> Scan_Worker
UI_Controller -->|Manual Scan| Scan_Worker
Scan_Worker --> Sign_Check
Scan_Worker --> AI_Model
Scan_Worker --> Yara_Rules
end
subgraph Kernel_OS_Layer ["Windows Kernel & Driver"]
direction TB
WinAPI["Windows API (ctypes)<br/>(kernel32, user32, ntdll,<br/>advapi32, iphlpapi, psapi)"]:::kernel
Driver_Sys["Kernel Driver<br/>(PYAS_Driver.sys)"]:::kernel
OS_FS["File System (NTFS)"]:::kernel
OS_Net["Network Stack"]:::kernel
Init_Env -- Load DLLs --> WinAPI
Thread_File -- Monitor --> OS_FS
Thread_Net -- Monitor --> OS_Net
Thread_Proc -- Suspend/Kill --> WinAPI
Thread_Pipe <==>|"Named Pipe IPC<br/>(\\.\pipe\PYAS_Output_Pipe)"| Driver_Sys
Driver_Sys -- Callback/Block --> WinAPI
subgraph OS ["Windows OS & Storage"]
FS["File System (FltMgr)"]:::storage
Reg["Registry (CmMgr)"]:::storage
ProcMgr["Process/Thread Manager (ObMgr)"]:::storage
Disk["Physical Disk / MBR"]:::storage
end
subgraph Data_Persistence ["Data & Resources"]
Config_File[("Config.json")]:::data
Model_Files[("Model Files<br/>(.onnx)")]:::data
Rule_Files[("Rule Files<br/>(.yar)")]:::data
Quarantine_Dir[("/Quarantine/")]:::data
CloudAPI["PYAS Cloud Server"]:::storage
Config_Mgr <--> Config_File
AI_Model <--> Model_Files
Yara_Rules <--> Rule_Files
List_Mgr <--> Quarantine_Dir
end
%% User Mode Internal
UI <-->|JS Evaluate / JSON| PYASCore
PYASCore -->|Extract Features & Scan| Engines
CloudScanner -.->|HTTPS REST API| CloudAPI
PYASCore <-->|FilterSendMessage / FilterGetMessage| CommClient
%% User-Kernel Boundary
CommClient <-->|PYAS_MESSAGE / PYAS_USER_MESSAGE| CommServer
%% Kernel Mode Internal
CommServer <-->|Message Dispatch| DriverCore
DriverCore --> RuleEngine
RuleEngine --> Protections
%% Kernel-OS Boundary
FileProtect -->|IRP_MJ_CREATE, IRP_MJ_WRITE...| FS
ProcessProtect -->|PreOpenProcess, PreOpenThread| ProcMgr
RegProtect -->|RegNtPreCreateKey, RegNtPreSetValueKey...| Reg
BootProtect -->|IOCTL_DISK_FORMAT_TRACKS...| Disk
```
## Support System
Please ensure your computer supports Microsoft Visual C++ 2015-2022 Redistributable and Edge Webview2.
| Config | Permissions | System version | Processor | Memory | Storage |
|-----------|---------------|----------------------|-----------|--------|---------|
| Minimum | Administrator | >= Windows 10 (20H1) | 1 GHz | 200MB | 100MB |
| Minimum | Administrator | >= Windows 10 (20H1) | 1 GHz | 300MB | 100MB |
| Recommend | Administrator | >= Windows 10 (21H2) | 3 GHz | 500MB | 200MB |
## Packaged Releases
Download the installer. If it is incompatible with your system, you can repackage it yourself.
Packaged Download: https://github.com/87owo/PYAS/releases
## Official Website
If you are interested in this project, you can visit the website to see other related content.
@@ -185,10 +153,14 @@ Source Available : https://github.com/87owo/PYAS
Official Website : https://pyas-security.com/antivirus
Online Analyze : https://pyas-security.com/analyze
Packaged Download: https://github.com/87owo/PYAS/releases
## Project License
For any questions, needs, or bug feedback, please contact us through the following website.
Source Issues : https://github.com/87owo/PYAS/issues
Official Email : mailto:service.pyas@gmail.com
Official Email : service@pyas-security.com