Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
364f48a6f0 | ||
|
|
cfebf6df0e | ||
|
|
688855b72d | ||
|
|
77b275a04b | ||
|
|
98b142df12 | ||
|
|
c5fe845f18 | ||
|
|
e5e9629d31 | ||
|
|
b65b9e6bfb | ||
|
|
93ae8b02e3 | ||
|
|
5c183ea200 | ||
|
|
661765f08c | ||
|
|
60fa19c681 | ||
|
|
606eddba53 | ||
|
|
71605ade31 | ||
|
|
ee409e4872 | ||
|
|
91024abc4a | ||
|
|
99de36a6cf | ||
|
|
ac1e48dc30 | ||
|
|
494bab3380 | ||
|
|
6696ca93ce | ||
|
|
b7e80fb8ed | ||
|
|
ce5b642d3e | ||
|
|
76d414164a | ||
|
|
b6d13047ba | ||
|
|
f1ba295796 | ||
|
|
415abef4fc | ||
|
|
95af2014d8 | ||
|
|
f75b039daf | ||
|
|
65b1ac78f3 | ||
|
|
b067591882 | ||
|
|
7941d76fd7 | ||
|
|
7bff601beb | ||
|
|
7b4da32bee | ||
|
|
bbf2974703 | ||
|
|
f5abaf06de | ||
|
|
88023dd09e | ||
|
|
eec2996fab | ||
|
|
97ca200c29 | ||
|
|
f0600ea3a3 | ||
|
|
19b3eb51d7 | ||
|
|
871dabf142 | ||
|
|
58795b9349 | ||
|
|
1ef769842b | ||
|
|
6051570414 | ||
|
|
da5d5d0dc7 | ||
|
|
782aa22d4d | ||
|
|
0084867f38 | ||
|
|
5a27858f86 | ||
|
|
7905d8375f | ||
|
|
ba8aa63d75 | ||
|
|
ac82778e26 | ||
|
|
55d2661e38 | ||
|
|
4803b89294 | ||
|
|
e48adbd87d | ||
|
|
227314d83c | ||
|
|
3c0e21eb02 | ||
|
|
86d6153ef1 | ||
|
|
78cf8bded3 | ||
|
|
26d12181cd | ||
|
|
8d184b011c | ||
|
|
ed3a1444ec | ||
|
|
d3e70a47cd | ||
|
|
3171151219 | ||
|
|
e407867635 | ||
|
|
6d9cfab55f | ||
|
|
f0bce029f0 | ||
|
|
d47960a0cf | ||
|
|
8416018afe | ||
|
|
e93fc70169 | ||
|
|
5b3474c781 | ||
|
|
8d2a9f3b2b | ||
|
|
73d09f9b09 | ||
|
|
4e7c6efa2a | ||
|
|
81a884bc61 | ||
|
|
ac4f960352 | ||
|
|
e4b3e4e438 | ||
|
|
d6ee476296 | ||
|
|
c23741ba9a | ||
|
|
7bac5b4740 | ||
|
|
1fe440168e | ||
|
|
26ad57138b | ||
|
|
92e1fdaf0f | ||
|
|
357540d0ad | ||
|
|
405622a91e | ||
|
|
5686c73c56 | ||
|
|
76e2f787ac | ||
|
|
02b67f4e6a | ||
|
|
a2ab527761 | ||
|
|
f1142ae742 | ||
|
|
decb222dac | ||
|
|
00c598e6f7 | ||
|
|
e9843dd3a5 | ||
|
|
37f390742b | ||
|
|
533e7dfd15 | ||
|
|
ddcbe1cd51 | ||
|
|
4eabd04bc6 | ||
|
|
c8f2da6ac9 | ||
|
|
8820ac4bf3 | ||
|
|
236b200f39 | ||
|
|
6789e32674 | ||
|
|
9eb55deedf | ||
|
|
6e76b5b8eb | ||
|
|
491b0da83d | ||
|
|
5d2c17e940 | ||
|
|
0497c4a9e7 | ||
|
|
6d0765848b | ||
|
|
40c4ae71bd | ||
|
|
8edba10dff | ||
|
|
85aa7bc652 | ||
|
|
854ad1db07 | ||
|
|
955600a1e1 | ||
|
|
b739134d85 | ||
|
|
723152566d | ||
|
|
678b045e0e | ||
|
|
681f4df49a | ||
|
|
b19f7d7098 | ||
|
|
daaae3ec10 | ||
|
|
c14957afff | ||
|
|
843a4998f1 | ||
|
|
9637e74edc | ||
|
|
b1bdf4f671 | ||
|
|
238ed8e364 | ||
|
|
8aa1509863 | ||
|
|
aac182e30b | ||
|
|
ac7e953acc | ||
|
|
6ae0f27e05 | ||
|
|
f6e5a92400 | ||
|
|
5e5ff3428c | ||
|
|
1cdbb9c402 | ||
|
|
8caacd1870 | ||
|
|
cfbc4c9c20 | ||
|
|
76dbf1c5d0 | ||
|
|
c0e7c4afa5 | ||
|
|
9da0970d85 | ||
|
|
d6f2ad32a7 | ||
|
|
8dbc87a331 | ||
|
|
77eb1ce44e | ||
|
|
e12f4ae61a | ||
|
|
c23a73625f | ||
|
|
3a66946686 | ||
|
|
7fef43701f | ||
|
|
7f5eb34ac6 | ||
|
|
ba4369f829 | ||
|
|
81af2e51fc | ||
|
|
dff09064c8 | ||
|
|
ee0bb32cce | ||
|
|
4a215c308b | ||
|
|
79493a96ea | ||
|
|
3cca1c4671 | ||
|
|
72e9923d08 | ||
|
|
406e152891 | ||
|
|
e9eb569d50 | ||
|
|
640b389e44 | ||
|
|
314e3349d6 | ||
|
|
0ae4d72bc9 | ||
|
|
8920a5fb21 | ||
|
|
b338ff5dd9 | ||
|
|
87e2fcb37c | ||
|
|
bab43e3b17 | ||
|
|
e602088330 | ||
|
|
2f6db92077 | ||
|
|
cee33c0f77 | ||
|
|
4fcdaa10b6 | ||
|
|
949f74ab04 | ||
|
|
ef68a89b45 | ||
|
|
10ecb35cca | ||
|
|
696ac22f77 | ||
|
|
8d13f98030 | ||
|
|
1dddf13a9d | ||
|
|
855472239a | ||
|
|
dc5d317623 | ||
|
|
91011e9e2a | ||
|
|
bb09b75d3b | ||
|
|
b2f9adad66 | ||
|
|
0c2c84d2a9 | ||
|
|
1733e7cc86 | ||
|
|
831d9a9895 | ||
|
|
70ba3a629d | ||
|
|
29e9702d21 | ||
|
|
f8ef756bd4 | ||
|
|
233b28e8a5 | ||
|
|
da77c17be5 | ||
|
|
8dfa76822e | ||
|
|
4163991b66 | ||
|
|
78323c9a08 | ||
|
|
eb517e86f3 | ||
|
|
e24f3823cb | ||
|
|
fd3e8ee28a | ||
|
|
a980ea7bd6 | ||
|
|
20ce73c54f | ||
|
|
d8f22f7f28 | ||
|
|
591055edaf | ||
|
|
fbf0e06c04 | ||
|
|
9daf327e80 | ||
|
|
4441978e10 | ||
|
|
d8eb770735 | ||
|
|
cefbf79c6e | ||
|
|
fc8762094e | ||
|
|
801c3aadcc | ||
|
|
4296f1ec43 | ||
|
|
a7f469747c | ||
|
|
68d0352343 | ||
|
|
01e6d09f7c | ||
|
|
6582abfd4a | ||
|
|
f8f4b303d7 | ||
|
|
79f05a23a8 | ||
|
|
95845c5c85 | ||
|
|
cd1a24290e | ||
|
|
21261bfb17 | ||
|
|
79abf51cb8 | ||
|
|
e538b837d5 | ||
|
|
7b995e1223 | ||
|
|
c7826b58a3 | ||
|
|
5786dbdb75 | ||
|
|
0f42c19ec7 | ||
|
|
87d44a64f3 | ||
|
|
77dd4ace33 | ||
|
|
0907d71957 | ||
|
|
e7a30646e3 | ||
|
|
98d21b3391 | ||
|
|
35d9203b9c | ||
|
|
e6e384a7f7 | ||
|
|
7d954c14ae | ||
|
|
41388962e2 | ||
|
|
6d51f6b6a9 | ||
|
|
c7af9a3702 | ||
|
|
da7c61202c | ||
|
|
c24e872a33 | ||
|
|
d0809ec313 | ||
|
|
c1a796da38 | ||
|
|
98c27800e0 | ||
|
|
7a18eadd13 | ||
|
|
b07adb7b30 | ||
|
|
4d7c3cce4d | ||
|
|
4ff0ab9cd8 | ||
|
|
264dd50969 | ||
|
|
26d0ad1a6e | ||
|
|
90284018e5 | ||
|
|
46eeec16d4 | ||
|
|
083fb0dccc | ||
|
|
d46f145806 | ||
|
|
868eddfd2b | ||
|
|
8295e6bb5b | ||
|
|
424914b842 | ||
|
|
8c5027df0f | ||
|
|
0b7817d992 | ||
|
|
ca6c9bfd05 | ||
|
|
0a1abd8040 | ||
|
|
ed69a4fdf6 | ||
|
|
2ae19a6d4e | ||
|
|
404bc50dd4 | ||
|
|
cada5eeeaf | ||
|
|
5e9bd40e8b | ||
|
|
0788bab0dd | ||
|
|
4e8e0dbea7 | ||
|
|
b8c5279137 | ||
|
|
a3255b1681 | ||
|
|
f151379f8f | ||
|
|
7784544583 | ||
|
|
f742937296 | ||
|
|
5fd9242291 | ||
|
|
9f343196a9 | ||
|
|
5468eded04 | ||
|
|
1b97758fc9 | ||
|
|
b2e8e49447 | ||
|
|
3eec781139 | ||
|
|
b6f1977fa0 | ||
|
|
e0aa080f00 | ||
|
|
66aa76c836 | ||
|
|
bb9444e1ae | ||
|
|
6c9bfa7755 | ||
|
|
c4fa849290 | ||
|
|
4807e2c410 | ||
|
|
5388efdebc | ||
|
|
1d656de7ba | ||
|
|
eaa1d725a9 | ||
|
|
dea2150d63 | ||
|
|
80f9d6cf75 | ||
|
|
7430d38594 | ||
|
|
6804e4af9f | ||
|
|
138756c31d | ||
|
|
349bc293c6 | ||
|
|
2f87cd10f2 | ||
|
|
fc50eafcca | ||
|
|
b48e20a727 | ||
|
|
ce7ed2e6eb | ||
|
|
043d71e0ff | ||
|
|
1855ae0e1d | ||
|
|
77323a50df | ||
|
|
1c2f6b85ca | ||
|
|
2b5c478e4b | ||
|
|
90fd83a81f | ||
|
|
8fa2a166f3 | ||
|
|
e8cc45ebe7 | ||
|
|
eb87117b91 | ||
|
|
3799dfa1e8 | ||
|
|
163c7fd680 | ||
|
|
44ca762f69 | ||
|
|
8db311ba33 | ||
|
|
99d8604db4 | ||
|
|
90aa88daeb | ||
|
|
ae6eebad4d | ||
|
|
cd31ce1c38 | ||
|
|
ed3846e451 | ||
|
|
0c79cf8e37 | ||
|
|
81c7e16c0e | ||
|
|
16c03095b8 | ||
|
|
b5dab3aa22 | ||
|
|
e4a3c871f8 | ||
|
|
a2fe947b08 | ||
|
|
8df49ecfc1 | ||
|
|
526b9bd4c7 | ||
|
|
c67ff3d936 | ||
|
|
417b17da98 | ||
|
|
4ca19a4eb0 | ||
|
|
e834dfb7ee | ||
|
|
5e1ff74fb3 | ||
|
|
7a1d0f2ca7 | ||
|
|
b305522106 | ||
|
|
ff3495645a | ||
|
|
ddc000321f | ||
|
|
fe4e6af3b9 | ||
|
|
a1a83cc32c | ||
|
|
e37665218b | ||
|
|
2a876f4136 | ||
|
|
290d8e8a64 | ||
|
|
121bce529f | ||
|
|
1232d98573 | ||
|
|
208d9150b0 | ||
|
|
351bf7725e | ||
|
|
a547776766 | ||
|
|
86a8d20757 | ||
|
|
760c420f95 | ||
|
|
44c2edf58e | ||
|
|
e88207c232 | ||
|
|
f6b2450fcd | ||
|
|
a124ecd3ed | ||
|
|
34fcc3d29e | ||
|
|
5f3011eebc | ||
|
|
d4464edb6c | ||
|
|
1dc89a80db | ||
|
|
bd23599770 | ||
|
|
bda6ff80da | ||
|
|
16bee9825f | ||
|
|
5e4a4c4e8f | ||
|
|
0a03093333 | ||
|
|
960b1d53f7 | ||
|
|
f09dee6a52 | ||
|
|
301e1e56d1 | ||
|
|
6b2dbd382d | ||
|
|
e6f526c766 | ||
|
|
06cca669a0 | ||
|
|
f89ccea208 | ||
|
|
0728f2d995 | ||
|
|
c7a7a61c9a | ||
|
|
aca11e66a8 | ||
|
|
46c7a001c2 | ||
|
|
d6aa3c50a3 | ||
|
|
caaadc5cf3 | ||
|
|
14d703903c | ||
|
|
76fa14ab01 | ||
|
|
d1afe255e5 | ||
|
|
8f27739331 | ||
|
|
7a6aba5a2a | ||
|
|
ca0ec1f99d | ||
|
|
4e167486a1 | ||
|
|
503bbcd995 | ||
|
|
b160d2f634 | ||
|
|
2e7628a837 | ||
|
|
1259948ed7 | ||
|
|
81881ae1a4 | ||
|
|
0b5c581aca | ||
|
|
eb3614ed0c | ||
|
|
eb50513277 | ||
|
|
c3af73b8cf | ||
|
|
b552164a13 | ||
|
|
b6dc9ecadb | ||
|
|
0723dc8dbb | ||
|
|
0b1fffb5bf | ||
|
|
a39d87e5c5 | ||
|
|
0b0c3310cf | ||
|
|
f75c3750a5 | ||
|
|
3da760721b | ||
|
|
a9ad3feeec | ||
|
|
479c4ade67 | ||
|
|
84afa3e714 | ||
|
|
9a69665bab | ||
|
|
fea34ee937 | ||
|
|
ff2412c65d | ||
|
|
658953860a | ||
|
|
b473783527 | ||
|
|
7e590d4925 | ||
|
|
232fd3e2f2 | ||
|
|
9ea25fb77c | ||
|
|
6c47136de3 | ||
|
|
6fae29b488 | ||
|
|
7ed8cc9380 | ||
|
|
142e9d1028 | ||
|
|
c96c30ff9a | ||
|
|
337f1cc100 | ||
|
|
c3cb89dbea | ||
|
|
445bd1f668 | ||
|
|
1bded908c1 | ||
|
|
8d9dea4b08 | ||
|
|
4e98d6d8ce | ||
|
|
55b5cd939e | ||
|
|
d72f5a43c8 | ||
|
|
b406d8d30b | ||
|
|
f9c91c9ba0 | ||
|
|
0213d7eeb4 | ||
|
|
048c5c0970 | ||
|
|
728542fce8 | ||
|
|
81ca24c7a2 | ||
|
|
32ec272253 | ||
|
|
758982b660 |
No files matched your search
@@ -1,34 +0,0 @@
|
||||
@echo off
|
||||
setlocal
|
||||
|
||||
:: Check for permissions
|
||||
>nul 2>&1 "%SYSTEMROOT%\system32\icacls.exe" "%SYSTEMROOT%\system32\config\system"
|
||||
|
||||
if %errorlevel% NEQ 0 goto NoAdmin
|
||||
goto gotAdmin
|
||||
|
||||
:NoAdmin
|
||||
echo You do not have administrator rights, please run this script as administrator.
|
||||
pause
|
||||
exit /B
|
||||
|
||||
:gotAdmin
|
||||
if exist "%temp%\getadmin.vbs" del "%temp%\getadmin.vbs"
|
||||
|
||||
set "script_dir=%~dp0"
|
||||
set "script_dir=%script_dir:~0,-1%"
|
||||
|
||||
bcdedit -debug on
|
||||
bcdedit /set testsigning on
|
||||
bcdedit /deletevalue {current} safeboot
|
||||
|
||||
powershell.exe -ExecutionPolicy Bypass -Command ^
|
||||
"$endDate = (Get-Date).AddYears(100); ^
|
||||
$cert = New-SelfSignedCertificate -DnsName PYAS -CertStoreLocation cert:\LocalMachine\My -Type CodeSigning -NotAfter $endDate; ^
|
||||
$pwd = ConvertTo-SecureString -String 'PYAS' -Force -AsPlainText; ^
|
||||
Export-PfxCertificate -Cert $cert -FilePath '%script_dir%\PYAS.pfx' -Password $pwd;"
|
||||
"%script_dir%\signtool.exe" sign /f "%script_dir%\PYAS.pfx" /p PYAS "%script_dir%\PYAS_Driver.sys"
|
||||
sc create PYAS_Driver type= kernel start= demand binPath= "%script_dir%\PYAS_Driver.sys"
|
||||
|
||||
shutdown -r -t 0
|
||||
endlocal
|
||||
Whitespace-only changes.
Binary file not shown.
@@ -1,25 +0,0 @@
|
||||
@echo off
|
||||
setlocal
|
||||
|
||||
:: Check for permissions
|
||||
>nul 2>&1 "%SYSTEMROOT%\system32\icacls.exe" "%SYSTEMROOT%\system32\config\system"
|
||||
|
||||
if %errorlevel% NEQ 0 goto NoAdmin
|
||||
goto gotAdmin
|
||||
|
||||
:NoAdmin
|
||||
echo You do not have administrator rights, please run this script as administrator.
|
||||
pause
|
||||
exit /B
|
||||
|
||||
:gotAdmin
|
||||
if exist "%temp%\getadmin.vbs" del "%temp%\getadmin.vbs"
|
||||
|
||||
sc stop PYAS_Driver
|
||||
sc delete PYAS_Driver
|
||||
|
||||
bcdedit /set testsigning off
|
||||
bcdedit -debug off
|
||||
|
||||
shutdown -r -t 0
|
||||
endlocal
|
||||
Binary file not shown.
Binary file not shown.
@@ -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"]
|
||||
@@ -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'])
|
||||
@@ -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
|
||||
Binary file not shown.
@@ -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)
|
||||
@@ -0,0 +1,4 @@
|
||||
onnxruntime
|
||||
numpy
|
||||
pefile
|
||||
signify[full]
|
||||
@@ -772,31 +772,26 @@ rule General_28 {
|
||||
author = "PYAS Security"
|
||||
date = "2024-06-12"
|
||||
strings:
|
||||
$s1 = "GNU C11 6.3.0" ascii
|
||||
$s2 = "GCC: (MinGW.org GCC-6.3.0-1) 6.3.0" fullword ascii
|
||||
$s3 = "___mingw_readdir" fullword ascii
|
||||
$s4 = "___mingw_closedir" fullword ascii
|
||||
$s5 = "___mingw_seekdir" fullword ascii
|
||||
$s6 = "%d is not a prime number." fullword ascii
|
||||
$s7 = "___mingw_rewinddir" fullword ascii
|
||||
$s8 = "__mingw32_init_mainargs" fullword ascii
|
||||
$s9 = "___mingw_telldir" fullword ascii
|
||||
$s10 = "Enter an integer: " fullword ascii
|
||||
$s11 = "___mingw_opendir" fullword ascii
|
||||
$s12 = "/home/keith/" ascii
|
||||
$s13 = "../../../src/gcc-6.3.0/libgcc" ascii
|
||||
$s14 = "___mingw_dirname" fullword ascii
|
||||
$s15 = "_isPrime`" fullword ascii
|
||||
$s16 = "%d is a prime number." fullword ascii
|
||||
$s17 = ".weak.__Jv_RegisterClasses.___EH_FRAME_BEGIN__" fullword ascii
|
||||
$s18 = "-mtune=generic" ascii
|
||||
$s19 = "-march=i586" ascii
|
||||
$s20 = "-g -g -g -O2 -O2 -O2" ascii
|
||||
$s21 = "-fbuilding-libgcc" ascii
|
||||
$s22 = "-fno-stack-protector" ascii
|
||||
$x1 = "schemas.microsoft.com" wide
|
||||
$x2 = "Client Worker Thread" ascii
|
||||
$s1 = "A6BFEA43-501F-456F-A845-983D3AD7B8F0" wide
|
||||
$s2 = "upgrader" wide
|
||||
$s3 = "rtvscan.exe" wide
|
||||
$s4 = "Enable-WindowsOptionalFeature -FeatureName" wide
|
||||
$s5 = "remupd.exe" fullword wide
|
||||
$s6 = "Mcshield.exe" fullword wide
|
||||
$s7 = "mssecess.exe" fullword wide
|
||||
$s8 = "RavMonD.exe" fullword wide
|
||||
$s9 = "KvMonXP.exe" fullword wide
|
||||
$s10 = "baiduSafeTray.exe" fullword wide
|
||||
$s11 = "Qavanijeb.exe" fullword wide
|
||||
$s12 = "%s --> Error: %d, EC: %d" fullword ascii
|
||||
$s13 = "wscript.exe //E:vbscript" wide
|
||||
$s14 = "upgrader_64" wide
|
||||
$s15 = "Process Data Error" fullword wide
|
||||
condition:
|
||||
uint16(0) == 0x5a4d and filesize < 500KB and
|
||||
8 of them
|
||||
uint16(0) == 0x5a4d and filesize < 1000KB and
|
||||
1 of ($x*) and 6 of them
|
||||
}
|
||||
|
||||
rule General_29 {
|
||||
@@ -1442,31 +1437,3 @@ rule General_52 {
|
||||
uint16(0) == 0x5a4d and filesize < 2000KB and
|
||||
8 of them
|
||||
}
|
||||
|
||||
rule General_53 {
|
||||
meta:
|
||||
description = "53"
|
||||
author = "PYAS Security"
|
||||
date = "2024-06-12"
|
||||
strings:
|
||||
$x1 = "schemas.microsoft.com" wide
|
||||
$x2 = "Client Worker Thread" ascii
|
||||
$s1 = "A6BFEA43-501F-456F-A845-983D3AD7B8F0" wide
|
||||
$s2 = "upgrader" wide
|
||||
$s3 = "rtvscan.exe" wide
|
||||
$s4 = "Enable-WindowsOptionalFeature -FeatureName" wide
|
||||
$s5 = "remupd.exe" fullword wide
|
||||
$s6 = "Mcshield.exe" fullword wide
|
||||
$s7 = "mssecess.exe" fullword wide
|
||||
$s8 = "RavMonD.exe" fullword wide
|
||||
$s9 = "KvMonXP.exe" fullword wide
|
||||
$s10 = "baiduSafeTray.exe" fullword wide
|
||||
$s11 = "Qavanijeb.exe" fullword wide
|
||||
$s12 = "%s --> Error: %d, EC: %d" fullword ascii
|
||||
$s13 = "wscript.exe //E:vbscript" wide
|
||||
$s14 = "upgrader_64" wide
|
||||
$s15 = "Process Data Error" fullword wide
|
||||
condition:
|
||||
uint16(0) == 0x5a4d and filesize < 1000KB and
|
||||
1 of ($x*) and 6 of them
|
||||
}
|
||||
@@ -1,12 +0,0 @@
|
||||
{
|
||||
"Suffix": [".com", ".exe", ".dll", ".sys", ".scr",
|
||||
".bat", ".cmd", ".ps1", ".vbs", ".wsf"],
|
||||
|
||||
"Labels": ["Pefile/White", "Script/White", "Pefile/General", "Script/General"],
|
||||
|
||||
"Detect": ["Pefile/General", "Script/General"],
|
||||
|
||||
"Pixels": [224, 224],
|
||||
|
||||
"Values": 90
|
||||
}
|
||||
Binary file not shown.
@@ -0,0 +1,55 @@
|
||||
from PIL import Image
|
||||
import numpy, os
|
||||
|
||||
####################################################################################################
|
||||
|
||||
def preprocess_image(file_data, size, channels=1):
|
||||
width, height = size
|
||||
wah = int(numpy.ceil(numpy.sqrt(len(file_data) / channels)))
|
||||
arr = numpy.frombuffer(file_data, dtype=numpy.uint8)
|
||||
img = numpy.zeros(wah*wah*channels, dtype=numpy.uint8)
|
||||
img[:len(file_data)] = arr
|
||||
image = Image.fromarray(img.reshape((wah, wah)), 'L')
|
||||
return image.resize((width, height), Image.Resampling.NEAREST)
|
||||
|
||||
####################################################################################################
|
||||
|
||||
def get_type(file_path):
|
||||
suffix = {".com", ".dll", ".drv", ".exe", ".ocx", ".scr", ".sys", ".mui", ".cpl"}
|
||||
try:
|
||||
file_content = None
|
||||
if not os.path.splitext(file_path)[-1].lower() in suffix:
|
||||
return file_content
|
||||
with open(file_path, 'rb') as f:
|
||||
file_content = f.read()
|
||||
except:
|
||||
pass
|
||||
return file_content
|
||||
|
||||
####################################################################################################
|
||||
|
||||
def file_to_images(input_dir, output_dir):
|
||||
for root, dirs, files in os.walk(input_dir):
|
||||
for file in files:
|
||||
try:
|
||||
file_path = os.path.join(root, file)
|
||||
file_content = get_type(file_path)
|
||||
if file_content:
|
||||
print(file_path)
|
||||
relative_path = os.path.relpath(root, input_dir)
|
||||
output_path_dir = os.path.join(output_dir, relative_path)
|
||||
|
||||
if not os.path.exists(output_path_dir):
|
||||
os.makedirs(output_path_dir)
|
||||
image = preprocess_image(file_content, (224, 224))
|
||||
safe_name = os.path.basename(file_path).replace('/', '_').replace('\\', '_')
|
||||
image.save(os.path.join(output_path_dir, f"{safe_name}.png"))
|
||||
except Exception as e:
|
||||
print(e)
|
||||
|
||||
####################################################################################################
|
||||
|
||||
input_directory = input('Enter the folder path: ')
|
||||
output_directory = "./Image_Output"
|
||||
file_to_images(input_directory, output_directory)
|
||||
input('Conversion Complete')
|
||||
@@ -0,0 +1,34 @@
|
||||
import os, hashlib
|
||||
|
||||
####################################################################################################
|
||||
|
||||
def calc_sha256(path):
|
||||
with open(path, "rb") as f:
|
||||
return hashlib.sha256(f.read()).digest()
|
||||
|
||||
def dedup_realtime(root):
|
||||
seen = set()
|
||||
deleted = 0
|
||||
for dirpath, _, files in os.walk(root):
|
||||
for name in files:
|
||||
p = os.path.join(dirpath, name)
|
||||
try:
|
||||
h = calc_sha256(p)
|
||||
except Exception:
|
||||
continue
|
||||
if h in seen:
|
||||
try:
|
||||
os.remove(p)
|
||||
print(p)
|
||||
deleted += 1
|
||||
except Exception:
|
||||
pass
|
||||
else:
|
||||
seen.add(h)
|
||||
print(f"deleted {deleted}")
|
||||
|
||||
####################################################################################################
|
||||
|
||||
if __name__ == "__main__":
|
||||
dedup_realtime(input("Input file path: "))
|
||||
input('Deduplicate Complete')
|
||||
@@ -0,0 +1,165 @@
|
||||
import tensorflow as tf
|
||||
import os, tf2onnx, numpy, random
|
||||
from PIL import Image
|
||||
from tensorflow.keras import *
|
||||
from collections import Counter
|
||||
|
||||
####################################################################################################
|
||||
|
||||
Image.MAX_IMAGE_PIXELS = None
|
||||
tf.keras.mixed_precision.set_global_policy('mixed_float16')
|
||||
|
||||
####################################################################################################
|
||||
|
||||
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)
|
||||
|
||||
combined = list(zip(file_paths, labels))
|
||||
random.shuffle(combined)
|
||||
file_paths[:], labels[:] = zip(*combined)
|
||||
|
||||
num_val = int(val_split * len(file_paths))
|
||||
train_files = file_paths[num_val:]
|
||||
train_labels = labels[num_val:]
|
||||
val_files = file_paths[:num_val]
|
||||
val_labels = labels[:num_val]
|
||||
|
||||
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_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_parallel_calls=tf.data.AUTOTUNE)
|
||||
val_ds = val_ds.batch(batch_size).prefetch(tf.data.AUTOTUNE)
|
||||
|
||||
train_ds.class_indices = class_indices
|
||||
return train_ds, val_ds
|
||||
|
||||
####################################################################################################
|
||||
|
||||
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.cast(label, tf.float32)
|
||||
return img, label
|
||||
|
||||
def get_file_list(directory):
|
||||
file_paths, labels, class_indices = [], [], {}
|
||||
for i, cls in enumerate(sorted([d for d in os.listdir(directory) if os.path.isdir(os.path.join(directory, d))])):
|
||||
class_indices[cls] = i
|
||||
cls_dir = os.path.join(directory, cls)
|
||||
|
||||
for root, _, files in os.walk(cls_dir):
|
||||
for file in files:
|
||||
if file.lower().endswith(('.jpg', '.jpeg', '.png', '.bmp', '.gif')):
|
||||
file_paths.append(os.path.join(root, file))
|
||||
labels.append(i)
|
||||
return file_paths, labels, class_indices
|
||||
|
||||
####################################################################################################
|
||||
|
||||
def multi_conv(x, strides):
|
||||
convs = []
|
||||
for k in [(1, 7), (1, 1), (3, 3), (5, 5)]:
|
||||
c = layers.DepthwiseConv2D(kernel_size=k, strides=strides, padding='same', activation='gelu')(x)
|
||||
convs.append(c)
|
||||
|
||||
x = layers.Concatenate()(convs)
|
||||
x = layers.BatchNormalization()(x)
|
||||
return layers.Activation('gelu')(x)
|
||||
|
||||
def gated_conv(x, strides):
|
||||
c = layers.DepthwiseConv2D(3, strides=strides, padding='same', activation='gelu')(x)
|
||||
g = layers.DepthwiseConv2D(3, strides=strides, padding='same', activation='sigmoid')(x)
|
||||
return layers.Multiply()([c, g])
|
||||
|
||||
def coordinate_attention(x, reduction):
|
||||
input_shape = tf.shape(x)
|
||||
h, w = input_shape[1], input_shape[2]
|
||||
filters = x.shape[-1]
|
||||
mip = max(8, filters // reduction)
|
||||
|
||||
x_h = layers.Lambda(lambda t: tf.reduce_mean(t, axis=2, keepdims=True))(x)
|
||||
x_w = layers.Lambda(lambda t: tf.reduce_mean(t, axis=1, keepdims=True))(x)
|
||||
x_w_t = layers.Permute((2, 1, 3))(x_w)
|
||||
|
||||
x_cat = layers.Concatenate(axis=1)([x_h, x_w_t])
|
||||
x_cat = layers.Conv2D(mip, 1, padding='valid', use_bias=False)(x_cat)
|
||||
x_cat = layers.BatchNormalization()(x_cat)
|
||||
x_cat = layers.Activation('gelu')(x_cat)
|
||||
|
||||
x_cat_h, x_cat_w = layers.Lambda(lambda t: tf.split(t[0], [t[1], t[2]], axis=1))([x_cat, h, w])
|
||||
x_h_sigmoid = layers.Conv2D(filters, 1, padding='valid', activation='sigmoid')(x_cat_h)
|
||||
x_cat_w = layers.Permute((2, 1, 3))(x_cat_w)
|
||||
x_w_sigmoid = layers.Conv2D(filters, 1, padding='valid', activation='sigmoid')(x_cat_w)
|
||||
|
||||
return layers.Multiply()([x, x_h_sigmoid, x_w_sigmoid])
|
||||
|
||||
def build_model(input_shape):
|
||||
i = layers.Input(shape=input_shape)
|
||||
x = multi_conv(i, strides=1)
|
||||
x = gated_conv(x, strides=2)
|
||||
x = multi_conv(x, strides=2)
|
||||
x = multi_conv(x, strides=2)
|
||||
x = multi_conv(x, strides=2)
|
||||
x = multi_conv(x, strides=2)
|
||||
|
||||
x = coordinate_attention(x, reduction=4)
|
||||
x = layers.GlobalMaxPooling2D()(x)
|
||||
|
||||
x = layers.Dense(x.shape[-1] // 2, activation='gelu', dtype='float32')(x)
|
||||
o = layers.Dense(1, activation='sigmoid', dtype='float32')(x)
|
||||
return models.Model(i, o)
|
||||
|
||||
####################################################################################################
|
||||
|
||||
def calculate_lr_cosine_sq(epoch, total_epochs=30, lr_start=5e-4, lr_end=5e-7):
|
||||
cos_val = 0.5 * (1 + numpy.cos(epoch / total_epochs * numpy.pi))
|
||||
return lr_end + (lr_start - lr_end) * (cos_val ** 2)
|
||||
|
||||
def lr_scheduler(epoch):
|
||||
return calculate_lr_cosine_sq(epoch)
|
||||
|
||||
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())
|
||||
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,) + 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())
|
||||
|
||||
####################################################################################################
|
||||
|
||||
train_ds, val_ds = load_dataset(r'.\Image_Full_Pefile')
|
||||
for imgs, _ in train_ds.take(1):
|
||||
train_ds.image_shape = imgs.shape[1:]
|
||||
break
|
||||
|
||||
try:
|
||||
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)
|
||||
|
||||
print(f"Total parameters: {model.count_params()}")
|
||||
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, class_weight={0: 10.0, 1: 1.0})
|
||||
input('Training Complete')
|
||||
@@ -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)}")
|
||||
@@ -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}")
|
||||
@@ -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}")
|
||||
@@ -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
@@ -1,117 +0,0 @@
|
||||
Windows Registry Editor Version 5.00
|
||||
|
||||
; Repair system restrictions
|
||||
[HKEY_LOCAL_MACHINE\SOFTWARE\Policies\Microsoft\MMC]
|
||||
; NoControlPanel
|
||||
"NoControlPanel"=dword:00000000
|
||||
; NoDrives
|
||||
"NoDrives"=dword:00000000
|
||||
; NoFileMenu
|
||||
"NoFileMenu"=dword:00000000
|
||||
; NoFind
|
||||
"NoFind"=dword:00000000
|
||||
; NoRealMode
|
||||
"NoRealMode"=dword:00000000
|
||||
; NoRecentDocsMenu
|
||||
"NoRecentDocsMenu"=dword:00000000
|
||||
; NoSetFolders
|
||||
"NoSetFolders"=dword:00000000
|
||||
; NoSetFolderOptions
|
||||
"NoSetFolderOptions"=dword:00000000
|
||||
; NoViewOnDrive
|
||||
"NoViewOnDrive"=dword:00000000
|
||||
; NoClose
|
||||
"NoClose"=dword:00000000
|
||||
; NoRun
|
||||
"NoRun"=dword:00000000
|
||||
; NoDesktop
|
||||
"NoDesktop"=dword:00000000
|
||||
; NoLogOff
|
||||
"NoLogOff"=dword:00000000
|
||||
; NoFolderOptions
|
||||
"NoFolderOptions"=dword:00000000
|
||||
; RestrictRun
|
||||
"RestrictRun"=dword:00000000
|
||||
; NoViewContextMenu
|
||||
"NoViewContexMenu"=dword:00000000
|
||||
; HideClock
|
||||
"HideClock"=dword:00000000
|
||||
; NoStartMenuMorePrograms
|
||||
"NoStartMenuMorePrograms"=dword:00000000
|
||||
; NoStartMenuMyGames
|
||||
"NoStartMenuMyGames"=dword:00000000
|
||||
; NoStartMenuMyMusic
|
||||
"NoStartMenuMyMusic"=dword:00000000
|
||||
; DisableCMD
|
||||
"DisableCMD"=dword:00000000
|
||||
; NoWinKeys
|
||||
"NoWinKeys"=dword:00000000
|
||||
; StartMenuLogOff
|
||||
"StartMenuLogOff"=dword:00000000
|
||||
; NoSimpleNetlDList
|
||||
"NoSimpleNetlDList"=dword:00000000
|
||||
; NoLowDiskSpaceChecks
|
||||
"NoLowDiskSpaceChecks"=dword:00000000
|
||||
; DisableLockWorkstation
|
||||
"DisableLockWorkstation"=dword:00000000
|
||||
; Restrict_Run
|
||||
"Restrict_Run"=dword:00000000
|
||||
; DisableTaskMgr
|
||||
"DisableTaskMgr"=dword:00000000
|
||||
; DisableRegistryTools
|
||||
"DisableRegistryTools"=dword:00000000
|
||||
; DisableChangePassword
|
||||
"DisableChangePassword"=dword:00000000
|
||||
; Wallpaper
|
||||
"Wallpaper"=dword:00000000
|
||||
; NoComponents
|
||||
"NoComponents"=dword:00000000
|
||||
; NoAddingComponents
|
||||
"NoAddingComponents"=dword:00000000
|
||||
; NoStartMenuPinnedList
|
||||
"NoStartMenuPinnedList"=dword:00000000
|
||||
; NoActiveDesktop
|
||||
"NoActiveDesktop"=dword:00000000
|
||||
; NoSetActiveDesktop
|
||||
"NoSetActiveDesktop"=dword:00000000
|
||||
; NoActiveDesktopChanges
|
||||
"NoActiveDesktopChanges"=dword:00000000
|
||||
; NoChangeStartMenu
|
||||
"NoChangeStartMenu"=dword:00000000
|
||||
; NoFavoritesMenu
|
||||
"NoFavoritesMenu"=dword:00000000
|
||||
; NoRecentDocsHistory
|
||||
"NoRecentDocsHistory"=dword:00000000
|
||||
; NoSetTaskbar
|
||||
"NoSetTaskbar"=dword:00000000
|
||||
; NoSMHelp
|
||||
"NoSMHelp"=dword:00000000
|
||||
; NoTrayContextMenu
|
||||
"NoTrayContextMenu"=dword:00000000
|
||||
; NoViewContextMenu
|
||||
"NoViewContextMenu"=dword:00000000
|
||||
; NoManageMyComputerVerb
|
||||
"NoManageMyComputerVerb"=dword:00000000
|
||||
; NoWindowsUpdate
|
||||
"NoWindowsUpdate"=dword:00000000
|
||||
; ClearRecentDocsOnExit
|
||||
"ClearRecentDocsOnExit"=dword:00000000
|
||||
; NoStartMenuNetworkPlaces
|
||||
"NoStartMenuNetworkPlaces"=dword:00000000
|
||||
|
||||
; Repair Image File Execution Options
|
||||
[HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\Windows NT\CurrentVersion\Image File Execution Options]
|
||||
; (Delete all subkeys here if necessary)
|
||||
|
||||
; Repair executable file icon
|
||||
[HKEY_CLASSES_ROOT\exefile\DefaultIcon]
|
||||
@="%1"
|
||||
[HKEY_LOCAL_MACHINE\SOFTWARE\Classes\exefile\DefaultIcon]
|
||||
@="%1"
|
||||
|
||||
; Repair executable file type
|
||||
[HKEY_CLASSES_ROOT\.exe]
|
||||
@="exefile"
|
||||
[HKEY_CLASSES_ROOT\exefile\shell\open\command]
|
||||
@="\"%1\" %*"
|
||||
|
||||
@@ -1 +0,0 @@
|
||||
sfc /scannow
|
||||
@@ -1 +0,0 @@
|
||||
None
|
||||
@@ -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
@@ -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>
|
||||
-21
@@ -1,21 +0,0 @@
|
||||
MIT License
|
||||
|
||||
Copyright (c) 2020-2024 87owo (PYAS Security)
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
in the Software without restriction, including without limitation the rights
|
||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
copies of the Software, and to permit persons to whom the Software is
|
||||
furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in all
|
||||
copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
SOFTWARE.
|
||||
+31
@@ -0,0 +1,31 @@
|
||||
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.
|
||||
+928
-97
File diff suppressed because it is too large.
Load diff
-1910
File diff suppressed because it is too large.
Load diff
@@ -1,60 +0,0 @@
|
||||
translate_dict = {
|
||||
"zh_TW": {"已开启":"已開啟","已关闭":"已關閉","On":"已開啟","Off":"已關閉"},
|
||||
"zh_CN": {"已開啟":"已开启","已關閉":"已关闭","On":"已开启","Off":"已关闭",
|
||||
"軟體":"软件","檔案":"文件","確定":"确认","設定":"设置","關於":"关于",
|
||||
"錯誤":"错误","實時":"实时","發現":"发现","關閉":"关闭","裝置":"设备",
|
||||
"總數":"总数","註冊":"注册","圖標":"图标","路徑":"路径","網路":"网络",
|
||||
"位元":"字节","彈窗":"弹窗","靈敏":"敏感","複製":"复制","開啟":"开启",
|
||||
"訊":"讯","項":"项","掃":"扫","試":"试","黃":"黄","嗎":"吗","防":"保",
|
||||
"鑰":"钥","當":"当","後":"后","護":"护","導":"导","區":"区","復":"复",
|
||||
"攔":"拦","請":"请","統":"统","刪":"删","進":"进","個":"个","選":"选",
|
||||
"啟":"启","監":"监","並":"并","徑":"径","盤":"盘","隱":"隐","動":"动",
|
||||
"狀":"状","誌":"志","這":"这","會":"会","題":"题","擇":"择","語":"语",
|
||||
"無":"无","顯":"显","變":"变","戶":"户","碼":"码","雲":"云","介":"界",
|
||||
"稱":"称","類":"类","輸":"输","執":"执","數":"数","尋":"寻","網":"网",
|
||||
"詢":"询","製":"制","員":"员","許":"许","條":"条","藍":"蓝","紅":"红",
|
||||
"綠":"绿","態":"态","務":"务","連":"连","檢":"检","測":"测","關":"关",
|
||||
"強":"强","驅":"驱","為":"为","腳":"脚","單":"单","開":"开","擴":"扩",
|
||||
"惡":"恶","據":"据","庫":"库","經":"经","敗":"败","謝":"谢","別":"别",
|
||||
"載":"载","誤":"误","報":"报","鏈":"链","竄":"窜","蹤":"踪","記憶體":"内存",},
|
||||
"en_US": {"已開啟":"On","已關閉":"Off","已开启":"On","已关闭":"Off",",":", ",
|
||||
"立即刪除":"Delete Now","安全模式":"Safe Mode","官方網站":"Website ",
|
||||
"腳本":"Script ","鏈式":"Chain ","記憶體":"Memory ","加載":"Load ",
|
||||
"行為":"Behavior ","可疑":"Suspicious ","勒索":"Ransom ",
|
||||
"許可條款":"License Terms ","惡意":"Malicious","可疑":"Suspicious",
|
||||
"啟用":"Enable ","禁用":"Disable ","正在初始化中":"Initializing ",
|
||||
"正在掃描":"Scanning ","正在刪除":"Deleting ","正在追蹤":"Tracking ",
|
||||
"最小化到":"Minimize To ","靈敏度":"Sensitivity ","正在檢查":"Checking ",
|
||||
"確定要":"Sure Want To ","引擎靈敏度":"Sensitivity ","更新":"Update ",
|
||||
"白色主題":"White","黃色主題":"Yellow","紅色主題":"Red","語言":"Language ",
|
||||
"藍色主題":"Blue","綠色主題":"Green","自定主題":"Custom","最新":"Latest ",
|
||||
"特別感謝":"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 ","可以":"To ",
|
||||
"進程":"Process ","攔截":"Block ","引導":"Boot ","刪除":"Delete ",
|
||||
"分區":"Partition ","設定":"Settings","關於":"About","模式":"Mode ",
|
||||
"提高":"Improve ","安全":"Security ","顯色":"Color ","主題":"Themes ",
|
||||
"風險":"Risk ","介面":"GUI ","核心":"Core ","服務":"Services ",
|
||||
"管理":"Manage","所有":"All ","您":"You ","取消":"Cancel ","未":"Not ",
|
||||
"垃圾":"Junk ","位元":"byte ","本機":"Computer ","停止":"Stop ",
|
||||
"增加到":"Add To ","白名單":"White List ","軟體":"Software ","正在":"",
|
||||
"增加":"Add ","移除":"Delete","項目":"Items ","生成":"Create ","稱":"",
|
||||
"複製":"Copy ","開啟":"Open ","此":"This ","並":"And ","全盤":"Full ",
|
||||
"已經在":"Already ","數據庫":"Database ","誤報":"False Positive ",
|
||||
"高":"High ","新":"New ","和":"And ","可能會":"Possible ","會":"Can ",
|
||||
"日誌":"Log","已":"Has ","彈窗":"Window ","要":"To ","驅動":"Driver ",
|
||||
"的":"","顯示":"","個":"","嗎":"","中":"","了":"","行":"","立即":"",
|
||||
"密鑰":"Key ","檢測":"Detect ","製作":"Maker ","運行":"Running",
|
||||
"擴展":"Extension ","套件":"Kit ","自動":"Auto ","上報":"Report ",
|
||||
"分析":"Analysis ","自身":"Self ","其他":"Other ","配置":"Config ",
|
||||
"丟失":"Lost ","網站":"Website ","不兼容":"Not Compatible ",
|
||||
"竄改":"Tamper ","第三方":"Other ","與":"With "," ":" "}}
|
||||
-9936
File diff suppressed because it is too large.
Load diff
@@ -1,12 +0,0 @@
|
||||
slist = [".exe",".dll",".sys",".com",".scr",
|
||||
".bat",".ps1",".vbs",".cmd",".js"]
|
||||
|
||||
alist = [".exe",".dll",".sys",".com",".scr",
|
||||
".zip",".7z",".rar",".tar",".gz",
|
||||
".js",".bat",".cmd",".ps1",".vbs",
|
||||
".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",
|
||||
".aux",".cur",".mui",".ttf",".efi"]
|
||||
+2
-2
@@ -2,9 +2,9 @@ import pyinstaller_versionfile
|
||||
|
||||
pyinstaller_versionfile.create_versionfile(
|
||||
output_file="versionfile.txt",
|
||||
version='3.2.6',
|
||||
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",
|
||||
|
||||
@@ -0,0 +1,171 @@
|
||||
#pragma once
|
||||
#include <fltKernel.h>
|
||||
#include <ntddk.h>
|
||||
#include <ntdddisk.h>
|
||||
#include <ntddscsi.h>
|
||||
#include <ntstrsafe.h>
|
||||
|
||||
constexpr auto PYAS_PORT_NAME = L"\\PYAS_Output_Pipe";
|
||||
constexpr auto MAX_PATH_LEN = 1024;
|
||||
constexpr auto PYAS_POOL_TAG = 'SAYP';
|
||||
|
||||
#ifndef PROCESS_TERMINATE
|
||||
#define PROCESS_TERMINATE (0x0001)
|
||||
#endif
|
||||
#ifndef PROCESS_CREATE_THREAD
|
||||
#define PROCESS_CREATE_THREAD (0x0002)
|
||||
#endif
|
||||
#ifndef PROCESS_VM_OPERATION
|
||||
#define PROCESS_VM_OPERATION (0x0008)
|
||||
#endif
|
||||
#ifndef PROCESS_VM_READ
|
||||
#define PROCESS_VM_READ (0x0010)
|
||||
#endif
|
||||
#ifndef PROCESS_VM_WRITE
|
||||
#define PROCESS_VM_WRITE (0x0020)
|
||||
#endif
|
||||
#ifndef PROCESS_DUP_HANDLE
|
||||
#define PROCESS_DUP_HANDLE (0x0040)
|
||||
#endif
|
||||
#ifndef PROCESS_CREATE_PROCESS
|
||||
#define PROCESS_CREATE_PROCESS (0x0080)
|
||||
#endif
|
||||
#ifndef PROCESS_SET_INFORMATION
|
||||
#define PROCESS_SET_INFORMATION (0x0200)
|
||||
#endif
|
||||
#ifndef PROCESS_QUERY_INFORMATION
|
||||
#define PROCESS_QUERY_INFORMATION (0x0400)
|
||||
#endif
|
||||
#ifndef PROCESS_SUSPEND_RESUME
|
||||
#define PROCESS_SUSPEND_RESUME (0x0800)
|
||||
#endif
|
||||
#ifndef PROCESS_QUERY_LIMITED_INFORMATION
|
||||
#define PROCESS_QUERY_LIMITED_INFORMATION (0x1000)
|
||||
#endif
|
||||
#ifndef PROCESS_SET_QUOTA
|
||||
#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
|
||||
#endif
|
||||
|
||||
#ifndef FSCTL_MANAGE_BYPASS_IO
|
||||
#define FSCTL_MANAGE_BYPASS_IO CTL_CODE(FILE_DEVICE_FILE_SYSTEM, 188, METHOD_NEITHER, FILE_ANY_ACCESS)
|
||||
|
||||
typedef enum _FS_BPIO_OPERATIONS {
|
||||
FS_BPIO_OP_ENABLE = 1,
|
||||
FS_BPIO_OP_DISABLE = 2,
|
||||
FS_BPIO_OP_QUERY = 3,
|
||||
FS_BPIO_OP_VOLUME_STACK_PAUSE = 4,
|
||||
FS_BPIO_OP_VOLUME_STACK_RESUME = 5,
|
||||
FS_BPIO_OP_STREAM_PAUSE = 6,
|
||||
FS_BPIO_OP_STREAM_RESUME = 7,
|
||||
FS_BPIO_OP_GET_INFO = 8
|
||||
} FS_BPIO_OPERATIONS;
|
||||
|
||||
typedef struct _FS_BPIO_INPUT {
|
||||
FS_BPIO_OPERATIONS Operation;
|
||||
ULONG InFlags;
|
||||
ULONGLONG Reserved1;
|
||||
ULONGLONG Reserved2;
|
||||
} FS_BPIO_INPUT, * PFS_BPIO_INPUT;
|
||||
|
||||
typedef struct _FS_BPIO_OUTPUT {
|
||||
FS_BPIO_OPERATIONS Operation;
|
||||
ULONG OutFlags;
|
||||
ULONGLONG Reserved1;
|
||||
ULONGLONG Reserved2;
|
||||
NTSTATUS Status;
|
||||
} FS_BPIO_OUTPUT, * PFS_BPIO_OUTPUT;
|
||||
|
||||
#endif
|
||||
|
||||
typedef struct _PYAS_MESSAGE {
|
||||
ULONG MessageCode;
|
||||
ULONG ProcessId;
|
||||
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;
|
||||
PFLT_PORT ServerPort;
|
||||
PFLT_PORT ClientPort;
|
||||
PEPROCESS PyasProcess;
|
||||
ULONG PyasPid;
|
||||
LARGE_INTEGER Cookie;
|
||||
FAST_MUTEX PortMutex;
|
||||
FAST_MUTEX TrackerMutex;
|
||||
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();
|
||||
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 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 WriteBuffer, ULONG WriteLength, BOOLEAN IsWrite);
|
||||
|
||||
NTSTATUS SendMessageToUser(ULONG Code, ULONG Pid, PWCHAR Path, USHORT PathSize);
|
||||
|
||||
FORCEINLINE PVOID PyasAllocate(SIZE_T Size) {
|
||||
return ExAllocatePool2(POOL_FLAG_NON_PAGED, Size, PYAS_POOL_TAG);
|
||||
}
|
||||
|
||||
FORCEINLINE VOID PyasFree(PVOID Ptr) {
|
||||
if (Ptr) ExFreePoolWithTag(Ptr, PYAS_POOL_TAG);
|
||||
}
|
||||
@@ -0,0 +1,209 @@
|
||||
#include "DriverCommon.h"
|
||||
|
||||
DRIVER_DATA GlobalData;
|
||||
|
||||
static NTSTATUS InstanceSetup(PCFLT_RELATED_OBJECTS FltObjects, FLT_INSTANCE_SETUP_FLAGS Flags, DEVICE_TYPE VolumeDeviceType, FLT_FILESYSTEM_TYPE VolumeFilesystemType) {
|
||||
UNREFERENCED_PARAMETER(FltObjects);
|
||||
UNREFERENCED_PARAMETER(Flags);
|
||||
UNREFERENCED_PARAMETER(VolumeDeviceType);
|
||||
UNREFERENCED_PARAMETER(VolumeFilesystemType);
|
||||
return STATUS_SUCCESS;
|
||||
}
|
||||
|
||||
static NTSTATUS InstanceQueryTeardown(PCFLT_RELATED_OBJECTS FltObjects, FLT_INSTANCE_QUERY_TEARDOWN_FLAGS Flags) {
|
||||
UNREFERENCED_PARAMETER(FltObjects);
|
||||
UNREFERENCED_PARAMETER(Flags);
|
||||
return STATUS_SUCCESS;
|
||||
}
|
||||
|
||||
static VOID InstanceTeardownStart(PCFLT_RELATED_OBJECTS FltObjects, FLT_INSTANCE_TEARDOWN_FLAGS Flags) {
|
||||
UNREFERENCED_PARAMETER(FltObjects);
|
||||
UNREFERENCED_PARAMETER(Flags);
|
||||
}
|
||||
|
||||
static VOID InstanceTeardownComplete(PCFLT_RELATED_OBJECTS FltObjects, FLT_INSTANCE_TEARDOWN_FLAGS Flags) {
|
||||
UNREFERENCED_PARAMETER(FltObjects);
|
||||
UNREFERENCED_PARAMETER(Flags);
|
||||
}
|
||||
|
||||
static NTSTATUS DriverUnload(FLT_FILTER_UNLOAD_FLAGS 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();
|
||||
UninitializeProcessProtection();
|
||||
|
||||
ExWaitForRundownProtectionRelease(&GlobalData.PortRundown);
|
||||
|
||||
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 (ReturnOutputBufferLength) {
|
||||
*ReturnOutputBufferLength = 0;
|
||||
}
|
||||
return STATUS_SUCCESS;
|
||||
}
|
||||
|
||||
static NTSTATUS PortConnect(PFLT_PORT ClientPort, PVOID ServerPortCookie, PVOID ConnectionContext, ULONG SizeOfContext, PVOID* ConnectionPortCookie) {
|
||||
UNREFERENCED_PARAMETER(ServerPortCookie);
|
||||
UNREFERENCED_PARAMETER(ConnectionContext);
|
||||
UNREFERENCED_PARAMETER(SizeOfContext);
|
||||
UNREFERENCED_PARAMETER(ConnectionPortCookie);
|
||||
|
||||
ExAcquireFastMutex(&GlobalData.PortMutex);
|
||||
|
||||
GlobalData.ClientPort = ClientPort;
|
||||
GlobalData.PyasPid = (ULONG)(ULONG_PTR)PsGetCurrentProcessId();
|
||||
GlobalData.PyasProcess = PsGetCurrentProcess();
|
||||
|
||||
ExReInitializeRundownProtection(&GlobalData.PortRundown);
|
||||
|
||||
ExReleaseFastMutex(&GlobalData.PortMutex);
|
||||
|
||||
return STATUS_SUCCESS;
|
||||
}
|
||||
|
||||
static VOID PortDisconnect(PVOID ConnectionCookie) {
|
||||
UNREFERENCED_PARAMETER(ConnectionCookie);
|
||||
|
||||
ExAcquireFastMutex(&GlobalData.PortMutex);
|
||||
GlobalData.ClientPort = NULL;
|
||||
ExReleaseFastMutex(&GlobalData.PortMutex);
|
||||
|
||||
ExWaitForRundownProtectionRelease(&GlobalData.PortRundown);
|
||||
}
|
||||
|
||||
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 }
|
||||
};
|
||||
|
||||
CONST FLT_REGISTRATION FilterRegistration = {
|
||||
sizeof(FLT_REGISTRATION),
|
||||
FLT_REGISTRATION_VERSION,
|
||||
0,
|
||||
NULL,
|
||||
Callbacks,
|
||||
DriverUnload,
|
||||
InstanceSetup,
|
||||
InstanceQueryTeardown,
|
||||
InstanceTeardownStart,
|
||||
InstanceTeardownComplete,
|
||||
NULL,
|
||||
NULL,
|
||||
NULL
|
||||
};
|
||||
|
||||
extern "C" NTSTATUS DriverEntry(PDRIVER_OBJECT DriverObject, PUNICODE_STRING RegistryPath) {
|
||||
NTSTATUS status = STATUS_SUCCESS;
|
||||
PSECURITY_DESCRIPTOR sd = NULL;
|
||||
OBJECT_ATTRIBUTES oa = { 0 };
|
||||
UNICODE_STRING name = { 0 };
|
||||
|
||||
RtlZeroMemory(&GlobalData, sizeof(GlobalData));
|
||||
GlobalData.DriverObject = DriverObject;
|
||||
ExInitializeFastMutex(&GlobalData.PortMutex);
|
||||
ExInitializeFastMutex(&GlobalData.TrackerMutex);
|
||||
|
||||
ExInitializeRundownProtection(&GlobalData.PortRundown);
|
||||
|
||||
InitializeRulesEngine();
|
||||
|
||||
status = LoadRulesFromDisk(RegistryPath);
|
||||
|
||||
status = FltRegisterFilter(DriverObject, &FilterRegistration, &GlobalData.FilterHandle);
|
||||
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, PortMessage, 1);
|
||||
FltFreeSecurityDescriptor(sd);
|
||||
}
|
||||
|
||||
if (!NT_SUCCESS(status)) {
|
||||
if (GlobalData.FilterHandle) {
|
||||
FltUnregisterFilter(GlobalData.FilterHandle);
|
||||
GlobalData.FilterHandle = NULL;
|
||||
}
|
||||
UnloadRules();
|
||||
UninitializeRulesEngine();
|
||||
return status;
|
||||
}
|
||||
|
||||
InitializeProcessProtection();
|
||||
InitializeRegistryProtection(DriverObject);
|
||||
PsSetLoadImageNotifyRoutine(ImageLoadNotify);
|
||||
|
||||
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;
|
||||
}
|
||||
@@ -0,0 +1,52 @@
|
||||
[Version]
|
||||
Signature = "$WINDOWS NT$"
|
||||
Class = ActivityMonitor
|
||||
ClassGuid = {b86dff51-a31e-4bac-b3cf-e8cfe75c9fc2}
|
||||
Provider = %ManufacturerName%
|
||||
DriverVer = 04/12/2026,3.5.0.0
|
||||
CatalogFile = PYAS_Driver.cat
|
||||
PnpLockdown = 1
|
||||
|
||||
[DestinationDirs]
|
||||
DefaultDestDir = 12
|
||||
PYAS_Driver.DriverFiles = 12
|
||||
|
||||
[SourceDisksNames]
|
||||
1 = %DiskName%,,,""
|
||||
|
||||
[SourceDisksFiles]
|
||||
PYAS_Driver.sys = 1,,
|
||||
|
||||
[DefaultInstall.NTamd64]
|
||||
OptionDesc = %ServiceDescription%
|
||||
CopyFiles = PYAS_Driver.DriverFiles
|
||||
|
||||
[DefaultInstall.NTamd64.Services]
|
||||
AddService = %ServiceName%,,PYAS_Driver.Service
|
||||
|
||||
[PYAS_Driver.DriverFiles]
|
||||
PYAS_Driver.sys
|
||||
|
||||
[PYAS_Driver.Service]
|
||||
DisplayName = %ServiceName%
|
||||
Description = %ServiceDescription%
|
||||
ServiceBinary = %12%\PYAS_Driver.sys
|
||||
Dependencies = FltMgr
|
||||
ServiceType = 2
|
||||
StartType = 3
|
||||
ErrorControl = 1
|
||||
LoadOrderGroup = "FSFilter Activity Monitor"
|
||||
AddReg = PYAS_Driver.AddReg
|
||||
|
||||
[PYAS_Driver.AddReg]
|
||||
HKR,"Parameters","DebugFlags",0x00010001,0x0
|
||||
HKR,"Parameters","SupportedFeatures",0x00010001,0x3
|
||||
HKR,"Parameters\Instances","DefaultInstance",0x00000000,"PYAS Instance"
|
||||
HKR,"Parameters\Instances\PYAS Instance","Altitude",0x00000000,"320000"
|
||||
HKR,"Parameters\Instances\PYAS Instance","Flags",0x00010001,0x0
|
||||
|
||||
[Strings]
|
||||
ManufacturerName = "PYAS Security"
|
||||
DiskName = "PYAS_Driver Source Disk"
|
||||
ServiceName = "PYAS_Driver"
|
||||
ServiceDescription = "PYAS Security Driver"
|
||||
@@ -0,0 +1,35 @@
|
||||
|
||||
Microsoft Visual Studio Solution File, Format Version 12.00
|
||||
# Visual Studio Version 17
|
||||
VisualStudioVersion = 17.14.36518.9 d17.14
|
||||
MinimumVisualStudioVersion = 10.0.40219.1
|
||||
Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "PYAS_Driver", "PYAS_Driver.vcxproj", "{699C79A8-A904-1976-F72A-CDD7E811CFD2}"
|
||||
EndProject
|
||||
Global
|
||||
GlobalSection(SolutionConfigurationPlatforms) = preSolution
|
||||
Debug|ARM64 = Debug|ARM64
|
||||
Debug|x64 = Debug|x64
|
||||
Release|ARM64 = Release|ARM64
|
||||
Release|x64 = Release|x64
|
||||
EndGlobalSection
|
||||
GlobalSection(ProjectConfigurationPlatforms) = postSolution
|
||||
{699C79A8-A904-1976-F72A-CDD7E811CFD2}.Debug|ARM64.ActiveCfg = Debug|ARM64
|
||||
{699C79A8-A904-1976-F72A-CDD7E811CFD2}.Debug|ARM64.Build.0 = Debug|ARM64
|
||||
{699C79A8-A904-1976-F72A-CDD7E811CFD2}.Debug|ARM64.Deploy.0 = Debug|ARM64
|
||||
{699C79A8-A904-1976-F72A-CDD7E811CFD2}.Debug|x64.ActiveCfg = Debug|x64
|
||||
{699C79A8-A904-1976-F72A-CDD7E811CFD2}.Debug|x64.Build.0 = Debug|x64
|
||||
{699C79A8-A904-1976-F72A-CDD7E811CFD2}.Debug|x64.Deploy.0 = Debug|x64
|
||||
{699C79A8-A904-1976-F72A-CDD7E811CFD2}.Release|ARM64.ActiveCfg = Release|ARM64
|
||||
{699C79A8-A904-1976-F72A-CDD7E811CFD2}.Release|ARM64.Build.0 = Release|ARM64
|
||||
{699C79A8-A904-1976-F72A-CDD7E811CFD2}.Release|ARM64.Deploy.0 = Release|ARM64
|
||||
{699C79A8-A904-1976-F72A-CDD7E811CFD2}.Release|x64.ActiveCfg = Release|x64
|
||||
{699C79A8-A904-1976-F72A-CDD7E811CFD2}.Release|x64.Build.0 = Release|x64
|
||||
{699C79A8-A904-1976-F72A-CDD7E811CFD2}.Release|x64.Deploy.0 = Release|x64
|
||||
EndGlobalSection
|
||||
GlobalSection(SolutionProperties) = preSolution
|
||||
HideSolutionNode = FALSE
|
||||
EndGlobalSection
|
||||
GlobalSection(ExtensibilityGlobals) = postSolution
|
||||
SolutionGuid = {D166699A-50D5-470E-9559-39B8E842528D}
|
||||
EndGlobalSection
|
||||
EndGlobal
|
||||
@@ -0,0 +1,128 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<Project DefaultTargets="Build" ToolsVersion="12.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
|
||||
<ItemGroup Label="ProjectConfigurations">
|
||||
<ProjectConfiguration Include="Debug|x64">
|
||||
<Configuration>Debug</Configuration>
|
||||
<Platform>x64</Platform>
|
||||
</ProjectConfiguration>
|
||||
<ProjectConfiguration Include="Release|x64">
|
||||
<Configuration>Release</Configuration>
|
||||
<Platform>x64</Platform>
|
||||
</ProjectConfiguration>
|
||||
<ProjectConfiguration Include="Debug|ARM64">
|
||||
<Configuration>Debug</Configuration>
|
||||
<Platform>ARM64</Platform>
|
||||
</ProjectConfiguration>
|
||||
<ProjectConfiguration Include="Release|ARM64">
|
||||
<Configuration>Release</Configuration>
|
||||
<Platform>ARM64</Platform>
|
||||
</ProjectConfiguration>
|
||||
</ItemGroup>
|
||||
<PropertyGroup Label="Globals">
|
||||
<ProjectGuid>{699C79A8-A904-1976-F72A-CDD7E811CFD2}</ProjectGuid>
|
||||
<TemplateGuid>{dd38f7fc-d7bd-488b-9242-7d8754cde80d}</TemplateGuid>
|
||||
<TargetFrameworkVersion>v4.5</TargetFrameworkVersion>
|
||||
<MinimumVisualStudioVersion>12.0</MinimumVisualStudioVersion>
|
||||
<Configuration>Debug</Configuration>
|
||||
<Platform Condition="'$(Platform)' == ''">x64</Platform>
|
||||
<RootNamespace>PYAS_Driver</RootNamespace>
|
||||
<WindowsTargetPlatformVersion>$(LatestTargetPlatformVersion)</WindowsTargetPlatformVersion>
|
||||
</PropertyGroup>
|
||||
<Import Project="$(VCTargetsPath)\Microsoft.Cpp.Default.props" />
|
||||
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|x64'" Label="Configuration">
|
||||
<TargetVersion>Windows10</TargetVersion>
|
||||
<UseDebugLibraries>true</UseDebugLibraries>
|
||||
<PlatformToolset>WindowsKernelModeDriver10.0</PlatformToolset>
|
||||
<ConfigurationType>Driver</ConfigurationType>
|
||||
<DriverType>WDM</DriverType>
|
||||
<Driver_SpectreMitigation>false</Driver_SpectreMitigation>
|
||||
</PropertyGroup>
|
||||
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Release|x64'" Label="Configuration">
|
||||
<TargetVersion>Windows10</TargetVersion>
|
||||
<UseDebugLibraries>false</UseDebugLibraries>
|
||||
<PlatformToolset>WindowsKernelModeDriver10.0</PlatformToolset>
|
||||
<ConfigurationType>Driver</ConfigurationType>
|
||||
<DriverType>WDM</DriverType>
|
||||
<Driver_SpectreMitigation>false</Driver_SpectreMitigation>
|
||||
</PropertyGroup>
|
||||
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|ARM64'" Label="Configuration">
|
||||
<TargetVersion>Windows10</TargetVersion>
|
||||
<UseDebugLibraries>true</UseDebugLibraries>
|
||||
<PlatformToolset>WindowsKernelModeDriver10.0</PlatformToolset>
|
||||
<ConfigurationType>Driver</ConfigurationType>
|
||||
<DriverType>WDM</DriverType>
|
||||
</PropertyGroup>
|
||||
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Release|ARM64'" Label="Configuration">
|
||||
<TargetVersion>Windows10</TargetVersion>
|
||||
<UseDebugLibraries>false</UseDebugLibraries>
|
||||
<PlatformToolset>WindowsKernelModeDriver10.0</PlatformToolset>
|
||||
<ConfigurationType>Driver</ConfigurationType>
|
||||
<DriverType>WDM</DriverType>
|
||||
</PropertyGroup>
|
||||
<Import Project="$(VCTargetsPath)\Microsoft.Cpp.props" />
|
||||
<ImportGroup Label="ExtensionSettings">
|
||||
</ImportGroup>
|
||||
<ImportGroup Label="PropertySheets">
|
||||
<Import Project="$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props" Condition="exists('$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props')" Label="LocalAppDataPlatform" />
|
||||
</ImportGroup>
|
||||
<PropertyGroup Label="UserMacros" />
|
||||
<PropertyGroup />
|
||||
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|x64'">
|
||||
<DebuggerFlavor>DbgengKernelDebugger</DebuggerFlavor>
|
||||
<EnableInf2cat>false</EnableInf2cat>
|
||||
<Inf2CatUseLocalTime>true</Inf2CatUseLocalTime>
|
||||
</PropertyGroup>
|
||||
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Release|x64'">
|
||||
<DebuggerFlavor>DbgengKernelDebugger</DebuggerFlavor>
|
||||
<Inf2CatUseLocalTime>true</Inf2CatUseLocalTime>
|
||||
</PropertyGroup>
|
||||
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|ARM64'">
|
||||
<DebuggerFlavor>DbgengKernelDebugger</DebuggerFlavor>
|
||||
</PropertyGroup>
|
||||
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Release|ARM64'">
|
||||
<DebuggerFlavor>DbgengKernelDebugger</DebuggerFlavor>
|
||||
</PropertyGroup>
|
||||
<ItemDefinitionGroup Condition="'$(Configuration)|$(Platform)'=='Debug|x64'">
|
||||
<DriverSign>
|
||||
<FileDigestAlgorithm>sha256</FileDigestAlgorithm>
|
||||
</DriverSign>
|
||||
<ClCompile>
|
||||
<LanguageStandard>stdcpp20</LanguageStandard>
|
||||
<TreatWarningAsError>false</TreatWarningAsError>
|
||||
</ClCompile>
|
||||
<Link>
|
||||
<AdditionalDependencies>$(DDK_LIB_PATH)fltmgr.lib;%(AdditionalDependencies)</AdditionalDependencies>
|
||||
</Link>
|
||||
</ItemDefinitionGroup>
|
||||
<ItemDefinitionGroup Condition="'$(Configuration)|$(Platform)'=='Release|x64'">
|
||||
<DriverSign>
|
||||
<FileDigestAlgorithm>sha256</FileDigestAlgorithm>
|
||||
</DriverSign>
|
||||
<ClCompile>
|
||||
<TreatWarningAsError>false</TreatWarningAsError>
|
||||
</ClCompile>
|
||||
<Link>
|
||||
<AdditionalDependencies>$(DDK_LIB_PATH)fltmgr.lib;%(AdditionalDependencies)</AdditionalDependencies>
|
||||
</Link>
|
||||
</ItemDefinitionGroup>
|
||||
<ItemGroup>
|
||||
<Inf Include="PYAS_Driver.inf" />
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<FilesToPackage Include="$(TargetPath)" />
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<ClInclude Include="DriverCommon.h" />
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<ClCompile Include="DriverEntry.cpp" />
|
||||
<ClCompile Include="ProtectBoot.cpp" />
|
||||
<ClCompile Include="ProtectFile.cpp" />
|
||||
<ClCompile Include="ProtectProcess.cpp" />
|
||||
<ClCompile Include="ProtectRegistry.cpp" />
|
||||
<ClCompile Include="ProtectRules.cpp" />
|
||||
</ItemGroup>
|
||||
<Import Project="$(VCTargetsPath)\Microsoft.Cpp.targets" />
|
||||
<ImportGroup Label="ExtensionTargets">
|
||||
</ImportGroup>
|
||||
</Project>
|
||||
@@ -0,0 +1,51 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<Project ToolsVersion="4.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
|
||||
<ItemGroup>
|
||||
<Filter Include="Source Files">
|
||||
<UniqueIdentifier>{4FC737F1-C7A5-4376-A066-2A32D752A2FF}</UniqueIdentifier>
|
||||
<Extensions>cpp;c;cc;cxx;def;odl;idl;hpj;bat;asm;asmx</Extensions>
|
||||
</Filter>
|
||||
<Filter Include="Header Files">
|
||||
<UniqueIdentifier>{93995380-89BD-4b04-88EB-625FBE52EBFB}</UniqueIdentifier>
|
||||
<Extensions>h;hpp;hxx;hm;inl;inc;xsd</Extensions>
|
||||
</Filter>
|
||||
<Filter Include="Resource Files">
|
||||
<UniqueIdentifier>{67DA6AB6-F800-4c08-8B7A-83BB121AAD01}</UniqueIdentifier>
|
||||
<Extensions>rc;ico;cur;bmp;dlg;rc2;rct;bin;rgs;gif;jpg;jpeg;jpe;resx;tiff;tif;png;wav;mfcribbon-ms</Extensions>
|
||||
</Filter>
|
||||
<Filter Include="Driver Files">
|
||||
<UniqueIdentifier>{8E41214B-6785-4CFE-B992-037D68949A14}</UniqueIdentifier>
|
||||
<Extensions>inf;inv;inx;mof;mc;</Extensions>
|
||||
</Filter>
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<Inf Include="PYAS_Driver.inf">
|
||||
<Filter>Driver Files</Filter>
|
||||
</Inf>
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<ClInclude Include="DriverCommon.h">
|
||||
<Filter>Header Files</Filter>
|
||||
</ClInclude>
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<ClCompile Include="ProtectRules.cpp">
|
||||
<Filter>Source Files</Filter>
|
||||
</ClCompile>
|
||||
<ClCompile Include="ProtectFile.cpp">
|
||||
<Filter>Source Files</Filter>
|
||||
</ClCompile>
|
||||
<ClCompile Include="ProtectBoot.cpp">
|
||||
<Filter>Source Files</Filter>
|
||||
</ClCompile>
|
||||
<ClCompile Include="ProtectRegistry.cpp">
|
||||
<Filter>Source Files</Filter>
|
||||
</ClCompile>
|
||||
<ClCompile Include="ProtectProcess.cpp">
|
||||
<Filter>Source Files</Filter>
|
||||
</ClCompile>
|
||||
<ClCompile Include="DriverEntry.cpp">
|
||||
<Filter>Source Files</Filter>
|
||||
</ClCompile>
|
||||
</ItemGroup>
|
||||
</Project>
|
||||
@@ -0,0 +1,33 @@
|
||||
#include "DriverCommon.h"
|
||||
|
||||
#ifndef IOCTL_DISK_FORMAT_TRACKS
|
||||
#define IOCTL_DISK_FORMAT_TRACKS CTL_CODE(IOCTL_DISK_BASE, 0x0006, METHOD_BUFFERED, FILE_READ_ACCESS | FILE_WRITE_ACCESS)
|
||||
#endif
|
||||
#ifndef IOCTL_DISK_FORMAT_TRACKS_EX
|
||||
#define IOCTL_DISK_FORMAT_TRACKS_EX CTL_CODE(IOCTL_DISK_BASE, 0x000b, METHOD_BUFFERED, FILE_READ_ACCESS | FILE_WRITE_ACCESS)
|
||||
#endif
|
||||
|
||||
FLT_PREOP_CALLBACK_STATUS ProtectBoot_PreDeviceControl(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;
|
||||
|
||||
ULONG IoControlCode = Data->Iopb->Parameters.DeviceIoControl.Common.IoControlCode;
|
||||
|
||||
if (IoControlCode == IOCTL_DISK_SET_DRIVE_LAYOUT_EX ||
|
||||
IoControlCode == IOCTL_SCSI_PASS_THROUGH_DIRECT ||
|
||||
IoControlCode == IOCTL_DISK_FORMAT_TRACKS ||
|
||||
IoControlCode == IOCTL_DISK_FORMAT_TRACKS_EX) {
|
||||
|
||||
HANDLE Pid = PsGetCurrentProcessId();
|
||||
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;
|
||||
return FLT_PREOP_COMPLETE;
|
||||
}
|
||||
}
|
||||
return FLT_PREOP_SUCCESS_NO_CALLBACK;
|
||||
}
|
||||
@@ -0,0 +1,293 @@
|
||||
#include "DriverCommon.h"
|
||||
|
||||
static BOOLEAN IsHoneyToken(PUNICODE_STRING Path) {
|
||||
if (!Path || !Path->Buffer) return FALSE;
|
||||
if (WildcardMatch(L"*PYAS_Honey*", Path->Buffer, Path->Length) ||
|
||||
WildcardMatch(L"*Backup_Secret*", Path->Buffer, Path->Length)) {
|
||||
return TRUE;
|
||||
}
|
||||
return FALSE;
|
||||
}
|
||||
|
||||
FLT_PREOP_CALLBACK_STATUS ProtectFile_PreCreate(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;
|
||||
UNICODE_STRING FileName = { 0 };
|
||||
NTSTATUS status = STATUS_SUCCESS;
|
||||
FLT_PREOP_CALLBACK_STATUS callbackStatus = FLT_PREOP_SUCCESS_NO_CALLBACK;
|
||||
|
||||
status = FltGetFileNameInformation(Data, FLT_FILE_NAME_NORMALIZED | FLT_FILE_NAME_QUERY_DEFAULT, &nameInfo);
|
||||
if (!NT_SUCCESS(status) || !nameInfo) {
|
||||
return FLT_PREOP_SUCCESS_NO_CALLBACK;
|
||||
}
|
||||
|
||||
status = FltParseFileNameInformation(nameInfo);
|
||||
if (!NT_SUCCESS(status)) {
|
||||
FltReleaseFileNameInformation(nameInfo);
|
||||
return FLT_PREOP_SUCCESS_NO_CALLBACK;
|
||||
}
|
||||
|
||||
FileName = nameInfo->Name;
|
||||
if (!FileName.Buffer || FileName.Length == 0) {
|
||||
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)) || IsCreateAction) {
|
||||
SendMessageToUser(5001, (ULONG)(ULONG_PTR)Pid, FileName.Buffer, FileName.Length);
|
||||
Data->IoStatus.Status = STATUS_ACCESS_DENIED;
|
||||
callbackStatus = FLT_PREOP_COMPLETE;
|
||||
goto cleanup;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (WildcardMatch(L"\\Device\\PhysicalDrive*", FileName.Buffer, FileName.Length) ||
|
||||
(WildcardMatch(L"\\Device\\Harddisk*", FileName.Buffer, FileName.Length) && !WildcardMatch(L"*Volume*", FileName.Buffer, FileName.Length))) {
|
||||
|
||||
if (Data->Iopb->Parameters.Create.SecurityContext->DesiredAccess &
|
||||
(FILE_WRITE_DATA | FILE_APPEND_DATA | GENERIC_WRITE | WRITE_DAC | WRITE_OWNER)) {
|
||||
|
||||
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;
|
||||
callbackStatus = FLT_PREOP_COMPLETE;
|
||||
goto cleanup;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (CheckProtectedPathRule(&FileName)) {
|
||||
if (!Trusted) {
|
||||
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;
|
||||
goto cleanup;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (CheckFileExtensionRule(&FileName)) {
|
||||
if (!Trusted) {
|
||||
if (Data->Iopb->Parameters.Create.SecurityContext->DesiredAccess & (DELETE | FILE_DELETE_CHILD)) {
|
||||
if (CheckRansomActivity(Pid, &FileName, NULL, 0, FALSE)) {
|
||||
SendMessageToUser(5001, (ULONG)(ULONG_PTR)Pid, FileName.Buffer, FileName.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_PreSetInfo(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;
|
||||
|
||||
FILE_INFORMATION_CLASS infoClass = Data->Iopb->Parameters.SetFileInformation.FileInformationClass;
|
||||
|
||||
if (infoClass != FileDispositionInformation &&
|
||||
infoClass != FileRenameInformation &&
|
||||
infoClass != FileRenameInformationEx) {
|
||||
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;
|
||||
|
||||
HANDLE Pid = PsGetCurrentProcessId();
|
||||
if (!IsProcessTrusted(Pid)) {
|
||||
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;
|
||||
callbackStatus = FLT_PREOP_COMPLETE;
|
||||
goto cleanup;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
cleanup:
|
||||
if (nameInfo) {
|
||||
FltReleaseFileNameInformation(nameInfo);
|
||||
}
|
||||
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(CompletionContext);
|
||||
UNREFERENCED_PARAMETER(FltObjects);
|
||||
|
||||
if (Data->RequestorMode == KernelMode) return FLT_PREOP_SUCCESS_NO_CALLBACK;
|
||||
|
||||
if (Data->Iopb->IrpFlags & (IRP_PAGING_IO | IRP_SYNCHRONOUS_PAGING_IO | IRP_NOCACHE)) {
|
||||
return FLT_PREOP_SUCCESS_NO_CALLBACK;
|
||||
}
|
||||
|
||||
ULONG WriteLength = Data->Iopb->Parameters.Write.Length;
|
||||
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);
|
||||
|
||||
if (!NT_SUCCESS(status) || !nameInfo) goto cleanup;
|
||||
|
||||
status = FltParseFileNameInformation(nameInfo);
|
||||
if (!NT_SUCCESS(status) || !nameInfo->Name.Buffer) goto cleanup;
|
||||
|
||||
if (CheckFileExtensionRule(&nameInfo->Name)) {
|
||||
HANDLE Pid = PsGetCurrentProcessId();
|
||||
|
||||
if (!IsProcessTrusted(Pid)) {
|
||||
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)) {
|
||||
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;
|
||||
}
|
||||
|
||||
FLT_PREOP_CALLBACK_STATUS ProtectFile_FileSystemControl(PFLT_CALLBACK_DATA Data, PCFLT_RELATED_OBJECTS FltObjects, PVOID* CompletionContext) {
|
||||
UNREFERENCED_PARAMETER(FltObjects);
|
||||
UNREFERENCED_PARAMETER(CompletionContext);
|
||||
|
||||
if (Data->Iopb->Parameters.FileSystemControl.Common.FsControlCode != FSCTL_MANAGE_BYPASS_IO) {
|
||||
return FLT_PREOP_SUCCESS_NO_CALLBACK;
|
||||
}
|
||||
|
||||
PFS_BPIO_INPUT InputBuffer = (PFS_BPIO_INPUT)Data->Iopb->Parameters.FileSystemControl.Neither.InputBuffer;
|
||||
if (!InputBuffer) return FLT_PREOP_SUCCESS_NO_CALLBACK;
|
||||
|
||||
FS_BPIO_OPERATIONS Operation;
|
||||
|
||||
__try {
|
||||
Operation = InputBuffer->Operation;
|
||||
}
|
||||
__except (EXCEPTION_EXECUTE_HANDLER) {
|
||||
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;
|
||||
}
|
||||
@@ -0,0 +1,185 @@
|
||||
#include "DriverCommon.h"
|
||||
|
||||
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\\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;
|
||||
}
|
||||
|
||||
VOID ImageLoadNotify(PUNICODE_STRING FullImageName, HANDLE ProcessId, PIMAGE_INFO ImageInfo) {
|
||||
UNREFERENCED_PARAMETER(ImageInfo);
|
||||
|
||||
if (KeGetCurrentIrql() > APC_LEVEL) return;
|
||||
if (!FullImageName || !FullImageName->Buffer) return;
|
||||
if (ProcessId == (HANDLE)0) return;
|
||||
|
||||
if (IsSystemImage(FullImageName)) return;
|
||||
|
||||
if (IsTargetProtected(ProcessId)) {
|
||||
if (CheckFileExtensionRule(FullImageName)) {
|
||||
SendMessageToUser(6001, (ULONG)(ULONG_PTR)ProcessId, FullImageName->Buffer, FullImageName->Length);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
static BOOLEAN IsBlacklistedAdminTool(HANDLE ProcessId) {
|
||||
if (KeGetCurrentIrql() != PASSIVE_LEVEL) return FALSE;
|
||||
|
||||
NTSTATUS status;
|
||||
BOOLEAN isBlacklisted = FALSE;
|
||||
PEPROCESS Process = NULL;
|
||||
PUNICODE_STRING imageFileName = NULL;
|
||||
|
||||
status = PsLookupProcessByProcessId(ProcessId, &Process);
|
||||
if (!NT_SUCCESS(status)) return FALSE;
|
||||
|
||||
status = SeLocateProcessImageName(Process, &imageFileName);
|
||||
|
||||
if (NT_SUCCESS(status) && imageFileName) {
|
||||
if (imageFileName->Buffer) {
|
||||
if (WildcardMatch(L"*Taskmgr.exe", imageFileName->Buffer, imageFileName->Length) ||
|
||||
WildcardMatch(L"*taskkill.exe", imageFileName->Buffer, imageFileName->Length) ||
|
||||
WildcardMatch(L"*ProcessHacker.exe", imageFileName->Buffer, imageFileName->Length) ||
|
||||
WildcardMatch(L"*procmon.exe", imageFileName->Buffer, imageFileName->Length)) {
|
||||
isBlacklisted = TRUE;
|
||||
}
|
||||
}
|
||||
ExFreePool(imageFileName);
|
||||
}
|
||||
|
||||
ObDereferenceObject(Process);
|
||||
return isBlacklisted;
|
||||
}
|
||||
|
||||
static OB_PREOP_CALLBACK_STATUS PreOpenProcess(PVOID RegistrationContext, POB_PRE_OPERATION_INFORMATION OperationInformation) {
|
||||
UNREFERENCED_PARAMETER(RegistrationContext);
|
||||
|
||||
if (OperationInformation->KernelHandle) return OB_PREOP_SUCCESS;
|
||||
|
||||
PEPROCESS TargetProcess = (PEPROCESS)OperationInformation->Object;
|
||||
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 = PROCESS_TERMINATE |
|
||||
PROCESS_VM_OPERATION |
|
||||
PROCESS_VM_WRITE |
|
||||
PROCESS_CREATE_THREAD |
|
||||
PROCESS_VM_READ |
|
||||
PROCESS_DUP_HANDLE |
|
||||
PROCESS_SUSPEND_RESUME |
|
||||
PROCESS_SET_INFORMATION |
|
||||
PROCESS_SET_QUOTA;
|
||||
|
||||
OperationInformation->Parameters->CreateHandleInformation.DesiredAccess &= ~DenyMask;
|
||||
|
||||
if (OperationInformation->Operation == OB_OPERATION_HANDLE_DUPLICATE) {
|
||||
OperationInformation->Parameters->DuplicateHandleInformation.DesiredAccess &= ~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;
|
||||
}
|
||||
}
|
||||
|
||||
return OB_PREOP_SUCCESS;
|
||||
}
|
||||
|
||||
NTSTATUS InitializeProcessProtection() {
|
||||
static UNICODE_STRING Altitude = { 0 };
|
||||
RtlInitUnicodeString(&Altitude, L"320000");
|
||||
|
||||
ObCallbacks[0].ObjectType = PsProcessType;
|
||||
ObCallbacks[0].Operations = OB_OPERATION_HANDLE_CREATE | OB_OPERATION_HANDLE_DUPLICATE;
|
||||
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 = 2;
|
||||
ObRegistration.Altitude = Altitude;
|
||||
ObRegistration.RegistrationContext = NULL;
|
||||
ObRegistration.OperationRegistration = ObCallbacks;
|
||||
|
||||
return ObRegisterCallbacks(&ObRegistration, &ObRegistrationHandle);
|
||||
}
|
||||
|
||||
VOID UninitializeProcessProtection() {
|
||||
if (ObRegistrationHandle) {
|
||||
ObUnRegisterCallbacks(ObRegistrationHandle);
|
||||
ObRegistrationHandle = NULL;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,244 @@
|
||||
#include "DriverCommon.h"
|
||||
|
||||
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);
|
||||
|
||||
if (KeGetCurrentIrql() > APC_LEVEL) return STATUS_SUCCESS;
|
||||
|
||||
REG_NOTIFY_CLASS Class = (REG_NOTIFY_CLASS)(ULONG_PTR)Argument1;
|
||||
NTSTATUS status = STATUS_SUCCESS;
|
||||
|
||||
if (Class == RegNtPreCreateKey || Class == RegNtPreOpenKey || Class == RegNtPreOpenKeyEx) {
|
||||
PVOID RootObject = NULL;
|
||||
PUNICODE_STRING CompleteName = NULL;
|
||||
ACCESS_MASK DesiredAccess = 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;
|
||||
}
|
||||
|
||||
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)) {
|
||||
SendMessageToUser(3001, (ULONG)(ULONG_PTR)Pid, FullPath.Buffer, FullPath.Length);
|
||||
status = STATUS_ACCESS_DENIED;
|
||||
}
|
||||
}
|
||||
PyasFree(FullPathBuffer);
|
||||
}
|
||||
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;
|
||||
}
|
||||
|
||||
NTSTATUS InitializeRegistryProtection(PDRIVER_OBJECT DriverObject) {
|
||||
UNICODE_STRING Altitude;
|
||||
RtlInitUnicodeString(&Altitude, L"320000");
|
||||
return CmRegisterCallbackEx(RegistryCallback, &Altitude, DriverObject, NULL, &Cookie, NULL);
|
||||
}
|
||||
|
||||
VOID UninitializeRegistryProtection() {
|
||||
if (Cookie.QuadPart != 0) {
|
||||
CmUnRegisterCallback(Cookie);
|
||||
Cookie.QuadPart = 0;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,851 @@
|
||||
#include "DriverCommon.h"
|
||||
|
||||
constexpr auto MAX_TRACKERS = 64;
|
||||
constexpr auto RANSOM_TIME_WINDOW_MS = 3000;
|
||||
constexpr auto RANSOM_COUNT_THRESHOLD = 5;
|
||||
constexpr auto HIGH_ENTROPY_THRESHOLD = 10;
|
||||
|
||||
constexpr auto TRUST_CACHE_SIZE = 128;
|
||||
constexpr auto TRUST_CACHE_TTL_SEC = 300;
|
||||
|
||||
typedef struct _RANSOM_TRACKER {
|
||||
PEPROCESS Process;
|
||||
ULONG ActivityCount;
|
||||
LARGE_INTEGER LastActivityTime;
|
||||
} RANSOM_TRACKER, * PRANSOM_TRACKER;
|
||||
|
||||
typedef struct _TRUST_CACHE_ENTRY {
|
||||
PEPROCESS Process;
|
||||
BOOLEAN IsTrusted;
|
||||
LARGE_INTEGER CacheTime;
|
||||
} TRUST_CACHE_ENTRY, * PTRUST_CACHE_ENTRY;
|
||||
|
||||
static RANSOM_TRACKER RansomTrackers[MAX_TRACKERS];
|
||||
static TRUST_CACHE_ENTRY TrustCache[TRUST_CACHE_SIZE];
|
||||
static FAST_MUTEX TrustCacheLock;
|
||||
static BOOLEAN g_CacheInitialized = FALSE;
|
||||
|
||||
static ERESOURCE RuleLock;
|
||||
static PRULE_NODE g_RegistryBlockList = NULL;
|
||||
static PRULE_NODE g_RegistryTrustedList = NULL;
|
||||
static PRULE_NODE g_ProcessTrustedPaths = NULL;
|
||||
static PRULE_NODE g_ProcessExploitable = NULL;
|
||||
static PRULE_NODE g_FileProtectedPaths = NULL;
|
||||
static PRULE_NODE g_FileExceptionPaths = NULL;
|
||||
static PRULE_NODE g_FileRansomExts = NULL;
|
||||
|
||||
const PCWSTR Helper_NaturallyCompressedExtensions[] = {
|
||||
L".zip", L".7z", L".rar", L".tar", L".gz",
|
||||
L".jpg", L".jpeg", L".png", L".webp", L".gif",
|
||||
L".mp3", L".wav", L".aac", L".ogg", L".flac",
|
||||
L".mp4", L".avi", L".mov", L".wmv", L".mkv",
|
||||
L".docx", L".xlsx", L".pptx", L".pdf", L".wps",
|
||||
L".apk", L".jar", L".class", L".db", L".sqlite"
|
||||
};
|
||||
|
||||
static VOID FreeList(PRULE_NODE* Head) {
|
||||
PRULE_NODE Current = *Head;
|
||||
while (Current) {
|
||||
PRULE_NODE Next = Current->Next;
|
||||
if (Current->Pattern.Buffer) PyasFree(Current->Pattern.Buffer);
|
||||
PyasFree(Current);
|
||||
Current = Next;
|
||||
}
|
||||
*Head = NULL;
|
||||
}
|
||||
|
||||
static VOID AddRule(PRULE_NODE* Head, PUNICODE_STRING RuleStr) {
|
||||
if (!RuleStr || !RuleStr->Buffer) return;
|
||||
|
||||
PRULE_NODE Check = *Head;
|
||||
while (Check) {
|
||||
if (RtlEqualUnicodeString(&Check->Pattern, RuleStr, TRUE)) {
|
||||
return;
|
||||
}
|
||||
Check = Check->Next;
|
||||
}
|
||||
|
||||
PRULE_NODE Node = (PRULE_NODE)PyasAllocate(sizeof(RULE_NODE));
|
||||
if (!Node) return;
|
||||
|
||||
SIZE_T Size = RuleStr->Length + sizeof(WCHAR);
|
||||
Node->Pattern.Buffer = (PWCHAR)PyasAllocate(Size);
|
||||
if (!Node->Pattern.Buffer) {
|
||||
PyasFree(Node);
|
||||
return;
|
||||
}
|
||||
|
||||
RtlCopyMemory(Node->Pattern.Buffer, RuleStr->Buffer, RuleStr->Length);
|
||||
Node->Pattern.Buffer[RuleStr->Length / sizeof(WCHAR)] = L'\0';
|
||||
Node->Pattern.Length = RuleStr->Length;
|
||||
Node->Pattern.MaximumLength = (USHORT)Size;
|
||||
|
||||
Node->Next = *Head;
|
||||
*Head = Node;
|
||||
}
|
||||
|
||||
static BOOLEAN HasSuffix(PCUNICODE_STRING String, PCWSTR Suffix) {
|
||||
if (!String || !String->Buffer || !Suffix) return FALSE;
|
||||
|
||||
SIZE_T StringLenChars = String->Length / sizeof(WCHAR);
|
||||
SIZE_T SuffixLenChars = 0;
|
||||
|
||||
while (Suffix[SuffixLenChars] != L'\0') {
|
||||
SuffixLenChars++;
|
||||
}
|
||||
|
||||
if (StringLenChars < SuffixLenChars) return FALSE;
|
||||
|
||||
PCWSTR Ptr = String->Buffer + (StringLenChars - SuffixLenChars);
|
||||
|
||||
for (SIZE_T i = 0; i < SuffixLenChars; i++) {
|
||||
if (RtlDowncaseUnicodeChar(Ptr[i]) != RtlDowncaseUnicodeChar(Suffix[i])) {
|
||||
return FALSE;
|
||||
}
|
||||
}
|
||||
return TRUE;
|
||||
}
|
||||
|
||||
BOOLEAN WildcardMatch(PCWSTR Pattern, PCWSTR String, USHORT StringLengthBytes) {
|
||||
if (!Pattern || !String) return FALSE;
|
||||
|
||||
USHORT StringLenChars = StringLengthBytes / sizeof(WCHAR);
|
||||
PCWSTR mp = NULL;
|
||||
PCWSTR cp = NULL;
|
||||
PCWSTR StringEnd = String + StringLenChars;
|
||||
|
||||
while (String < StringEnd) {
|
||||
if (*Pattern == L'*') {
|
||||
mp = ++Pattern;
|
||||
cp = String + 1;
|
||||
}
|
||||
else if (*Pattern == L'?' || (RtlDowncaseUnicodeChar(*Pattern) == RtlDowncaseUnicodeChar(*String))) {
|
||||
Pattern++;
|
||||
String++;
|
||||
}
|
||||
else if (mp) {
|
||||
Pattern = mp;
|
||||
String = cp++;
|
||||
}
|
||||
else {
|
||||
return FALSE;
|
||||
}
|
||||
}
|
||||
while (*Pattern == L'*') {
|
||||
Pattern++;
|
||||
}
|
||||
return !*Pattern;
|
||||
}
|
||||
|
||||
static ULONG ProcessJsonUnescape(PWCHAR Buffer, ULONG LengthChars) {
|
||||
if (!Buffer || LengthChars == 0) return 0;
|
||||
|
||||
ULONG WriteIdx = 0;
|
||||
ULONG ReadIdx = 0;
|
||||
|
||||
while (ReadIdx < LengthChars) {
|
||||
if (Buffer[ReadIdx] == L'\\' && (ReadIdx + 1 < LengthChars)) {
|
||||
WCHAR NextChar = Buffer[ReadIdx + 1];
|
||||
if (NextChar == L'\\' || NextChar == L'"' || NextChar == L'/') {
|
||||
Buffer[WriteIdx++] = NextChar;
|
||||
ReadIdx += 2;
|
||||
}
|
||||
else if (NextChar == L'n') { Buffer[WriteIdx++] = L'\n'; ReadIdx += 2; }
|
||||
else if (NextChar == L'r') { Buffer[WriteIdx++] = L'\r'; ReadIdx += 2; }
|
||||
else if (NextChar == L't') { Buffer[WriteIdx++] = L'\t'; ReadIdx += 2; }
|
||||
else {
|
||||
Buffer[WriteIdx++] = Buffer[ReadIdx++];
|
||||
}
|
||||
}
|
||||
else {
|
||||
Buffer[WriteIdx++] = Buffer[ReadIdx++];
|
||||
}
|
||||
}
|
||||
|
||||
Buffer[WriteIdx] = L'\0';
|
||||
return WriteIdx * sizeof(WCHAR);
|
||||
}
|
||||
|
||||
static VOID SkipWhitespace(PCHAR* Ptr, PCHAR End) {
|
||||
while (*Ptr < End) {
|
||||
char c = **Ptr;
|
||||
if (c == ' ' || c == '\t' || c == '\n' || c == '\r') {
|
||||
(*Ptr)++;
|
||||
}
|
||||
else {
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
static VOID ParseAndLoadRules(PCHAR JsonContent, ULONG ContentLength, PCSTR KeyName, PRULE_NODE* ListHead) {
|
||||
if (!JsonContent || !KeyName || !ListHead) return;
|
||||
|
||||
PCHAR Ptr = JsonContent;
|
||||
PCHAR End = JsonContent + ContentLength;
|
||||
SIZE_T KeyLen = 0;
|
||||
while (KeyName[KeyLen] != '\0') KeyLen++;
|
||||
|
||||
while (Ptr < End) {
|
||||
if (*Ptr == '"') {
|
||||
if ((SIZE_T)(End - Ptr) > KeyLen && RtlCompareMemory(Ptr + 1, KeyName, KeyLen) == KeyLen) {
|
||||
if (*(Ptr + 1 + KeyLen) == '"') {
|
||||
Ptr += 1 + KeyLen + 1;
|
||||
|
||||
SkipWhitespace(&Ptr, End);
|
||||
if (Ptr >= End || *Ptr != ':') continue;
|
||||
Ptr++;
|
||||
|
||||
SkipWhitespace(&Ptr, End);
|
||||
if (Ptr >= End || *Ptr != '[') continue;
|
||||
Ptr++;
|
||||
|
||||
while (Ptr < End) {
|
||||
SkipWhitespace(&Ptr, End);
|
||||
if (Ptr >= End) break;
|
||||
|
||||
if (*Ptr == ']') {
|
||||
Ptr++;
|
||||
return;
|
||||
}
|
||||
|
||||
if (*Ptr == '"') {
|
||||
PCHAR StartQuote = ++Ptr;
|
||||
while (Ptr < End) {
|
||||
if (*Ptr == '"' && *(Ptr - 1) != '\\') break;
|
||||
Ptr++;
|
||||
}
|
||||
|
||||
if (Ptr < End && *Ptr == '"') {
|
||||
ULONG UTF8Len = (ULONG)(Ptr - StartQuote);
|
||||
if (UTF8Len > 0) {
|
||||
ULONG WideSize = 0;
|
||||
RtlUTF8ToUnicodeN(NULL, 0, &WideSize, StartQuote, UTF8Len);
|
||||
|
||||
if (WideSize > 0) {
|
||||
PWCHAR WideBuffer = (PWCHAR)PyasAllocate(WideSize + sizeof(WCHAR));
|
||||
if (WideBuffer) {
|
||||
ULONG ResultSize = 0;
|
||||
RtlUTF8ToUnicodeN(WideBuffer, WideSize, &ResultSize, StartQuote, UTF8Len);
|
||||
ULONG FinalSize = ProcessJsonUnescape(WideBuffer, ResultSize / sizeof(WCHAR));
|
||||
|
||||
UNICODE_STRING Us;
|
||||
Us.Buffer = WideBuffer;
|
||||
Us.Length = (USHORT)FinalSize;
|
||||
Us.MaximumLength = (USHORT)(WideSize + sizeof(WCHAR));
|
||||
|
||||
AddRule(ListHead, &Us);
|
||||
PyasFree(WideBuffer);
|
||||
}
|
||||
}
|
||||
}
|
||||
Ptr++;
|
||||
}
|
||||
}
|
||||
else if (*Ptr == ',') {
|
||||
Ptr++;
|
||||
}
|
||||
else {
|
||||
Ptr++;
|
||||
}
|
||||
}
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
Ptr++;
|
||||
}
|
||||
}
|
||||
|
||||
VOID InitializeRulesEngine() {
|
||||
ExInitializeResourceLite(&RuleLock);
|
||||
ExInitializeFastMutex(&TrustCacheLock);
|
||||
RtlZeroMemory(TrustCache, sizeof(TrustCache));
|
||||
g_CacheInitialized = TRUE;
|
||||
}
|
||||
|
||||
VOID UninitializeRulesEngine() {
|
||||
ExDeleteResourceLite(&RuleLock);
|
||||
g_CacheInitialized = FALSE;
|
||||
}
|
||||
|
||||
static VOID RemoveRule(PRULE_NODE* Head, PUNICODE_STRING RuleStr) {
|
||||
if (!RuleStr || !RuleStr->Buffer) return;
|
||||
PRULE_NODE Current = *Head;
|
||||
PRULE_NODE Previous = NULL;
|
||||
|
||||
while (Current) {
|
||||
if (RtlEqualUnicodeString(&Current->Pattern, RuleStr, TRUE)) {
|
||||
PRULE_NODE ToDelete = Current;
|
||||
|
||||
if (Previous) {
|
||||
Previous->Next = Current->Next;
|
||||
}
|
||||
else {
|
||||
*Head = Current->Next;
|
||||
}
|
||||
|
||||
Current = Current->Next;
|
||||
|
||||
if (ToDelete->Pattern.Buffer) PyasFree(ToDelete->Pattern.Buffer);
|
||||
PyasFree(ToDelete);
|
||||
}
|
||||
else {
|
||||
Previous = Current;
|
||||
Current = Current->Next;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
VOID AddDynamicWhitelist(PUNICODE_STRING RuleStr) {
|
||||
ExAcquireResourceExclusiveLite(&RuleLock, TRUE);
|
||||
AddRule(&g_ProcessTrustedPaths, RuleStr);
|
||||
AddRule(&g_FileExceptionPaths, RuleStr);
|
||||
|
||||
ExAcquireFastMutex(&TrustCacheLock);
|
||||
RtlZeroMemory(TrustCache, sizeof(TrustCache));
|
||||
ExReleaseFastMutex(&TrustCacheLock);
|
||||
ExReleaseResourceLite(&RuleLock);
|
||||
}
|
||||
|
||||
VOID RemoveDynamicWhitelist(PUNICODE_STRING RuleStr) {
|
||||
ExAcquireResourceExclusiveLite(&RuleLock, TRUE);
|
||||
RemoveRule(&g_ProcessTrustedPaths, RuleStr);
|
||||
RemoveRule(&g_FileExceptionPaths, RuleStr);
|
||||
|
||||
ExAcquireFastMutex(&TrustCacheLock);
|
||||
RtlZeroMemory(TrustCache, sizeof(TrustCache));
|
||||
ExReleaseFastMutex(&TrustCacheLock);
|
||||
ExReleaseResourceLite(&RuleLock);
|
||||
}
|
||||
|
||||
NTSTATUS LoadRulesFromDisk(PUNICODE_STRING RegistryPath) {
|
||||
NTSTATUS status = STATUS_SUCCESS;
|
||||
HANDLE FileHandle = NULL;
|
||||
IO_STATUS_BLOCK IoStatus = { 0 };
|
||||
OBJECT_ATTRIBUTES oa = { 0 };
|
||||
UNICODE_STRING ImagePathName;
|
||||
OBJECT_ATTRIBUTES RegOa = { 0 };
|
||||
HANDLE RegHandle = NULL;
|
||||
PKEY_VALUE_PARTIAL_INFORMATION Info = NULL;
|
||||
ULONG ResultLength = 0;
|
||||
PWCHAR PathBuffer = NULL;
|
||||
SIZE_T PathBufferSize = 0;
|
||||
UNICODE_STRING FinalPath = { 0 };
|
||||
|
||||
RtlInitUnicodeString(&ImagePathName, L"ImagePath");
|
||||
|
||||
InitializeObjectAttributes(&RegOa, RegistryPath, OBJ_CASE_INSENSITIVE | OBJ_KERNEL_HANDLE, NULL, NULL);
|
||||
status = ZwOpenKey(&RegHandle, KEY_READ, &RegOa);
|
||||
if (!NT_SUCCESS(status)) return status;
|
||||
|
||||
status = ZwQueryValueKey(RegHandle, &ImagePathName, KeyValuePartialInformation, NULL, 0, &ResultLength);
|
||||
if (status != STATUS_BUFFER_TOO_SMALL) {
|
||||
ZwClose(RegHandle);
|
||||
return status;
|
||||
}
|
||||
|
||||
Info = (PKEY_VALUE_PARTIAL_INFORMATION)PyasAllocate(ResultLength);
|
||||
if (!Info) {
|
||||
ZwClose(RegHandle);
|
||||
return STATUS_INSUFFICIENT_RESOURCES;
|
||||
}
|
||||
|
||||
status = ZwQueryValueKey(RegHandle, &ImagePathName, KeyValuePartialInformation, Info, ResultLength, &ResultLength);
|
||||
ZwClose(RegHandle);
|
||||
if (!NT_SUCCESS(status)) {
|
||||
PyasFree(Info);
|
||||
return status;
|
||||
}
|
||||
|
||||
if (Info->Type == REG_EXPAND_SZ || Info->Type == REG_SZ) {
|
||||
PathBufferSize = ResultLength + 1024;
|
||||
PathBuffer = (PWCHAR)PyasAllocate(PathBufferSize);
|
||||
|
||||
if (PathBuffer) {
|
||||
RtlZeroMemory(PathBuffer, PathBufferSize);
|
||||
PWCHAR Src = (PWCHAR)Info->Data;
|
||||
|
||||
if (NT_SUCCESS(RtlStringCbCopyW(PathBuffer, PathBufferSize, Src))) {
|
||||
PWCHAR LastSlash = NULL;
|
||||
PWCHAR Current = PathBuffer;
|
||||
while (*Current) {
|
||||
if (*Current == L'\\') LastSlash = Current;
|
||||
Current++;
|
||||
}
|
||||
|
||||
if (LastSlash) {
|
||||
*LastSlash = L'\0';
|
||||
|
||||
SIZE_T CurrentPathLen = wcslen(PathBuffer);
|
||||
const WCHAR FilterSuffix[] = L"\\Filter";
|
||||
SIZE_T FilterLen = (sizeof(FilterSuffix) / sizeof(WCHAR)) - 1;
|
||||
|
||||
if (CurrentPathLen >= FilterLen) {
|
||||
PWCHAR SuffixStart = PathBuffer + CurrentPathLen - FilterLen;
|
||||
BOOLEAN Match = TRUE;
|
||||
for (SIZE_T i = 0; i < FilterLen; i++) {
|
||||
if (RtlDowncaseUnicodeChar(SuffixStart[i]) != RtlDowncaseUnicodeChar(FilterSuffix[i])) {
|
||||
Match = FALSE;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (Match) {
|
||||
*SuffixStart = L'\0';
|
||||
}
|
||||
}
|
||||
|
||||
RtlStringCbCatW(PathBuffer, PathBufferSize, L"\\Rules\\Rules_Driver_P1.json");
|
||||
|
||||
if (wcsncmp(PathBuffer, L"\\??\\", 4) != 0 &&
|
||||
wcsncmp(PathBuffer, L"\\SystemRoot", 11) != 0 &&
|
||||
wcsncmp(PathBuffer, L"\\DosDevices\\", 12) != 0) {
|
||||
|
||||
PWCHAR TmpBuffer = (PWCHAR)PyasAllocate(PathBufferSize + 16);
|
||||
if (TmpBuffer) {
|
||||
RtlStringCbCopyW(TmpBuffer, PathBufferSize + 16, L"\\??\\");
|
||||
RtlStringCbCatW(TmpBuffer, PathBufferSize + 16, PathBuffer);
|
||||
PyasFree(PathBuffer);
|
||||
PathBuffer = TmpBuffer;
|
||||
}
|
||||
}
|
||||
RtlInitUnicodeString(&FinalPath, PathBuffer);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
PyasFree(Info);
|
||||
|
||||
if (!FinalPath.Buffer) {
|
||||
if (PathBuffer) PyasFree(PathBuffer);
|
||||
return STATUS_UNSUCCESSFUL;
|
||||
}
|
||||
|
||||
InitializeObjectAttributes(&oa, &FinalPath, OBJ_CASE_INSENSITIVE | OBJ_KERNEL_HANDLE, NULL, NULL);
|
||||
status = ZwCreateFile(&FileHandle, GENERIC_READ | SYNCHRONIZE, &oa, &IoStatus, NULL, FILE_ATTRIBUTE_NORMAL, FILE_SHARE_READ, FILE_OPEN, FILE_SYNCHRONOUS_IO_NONALERT, NULL, 0);
|
||||
|
||||
if (NT_SUCCESS(status)) {
|
||||
FILE_STANDARD_INFORMATION FileInfo = { 0 };
|
||||
status = ZwQueryInformationFile(FileHandle, &IoStatus, &FileInfo, sizeof(FileInfo), FileStandardInformation);
|
||||
|
||||
if (NT_SUCCESS(status) && FileInfo.EndOfFile.LowPart > 0) {
|
||||
PVOID FileBuffer = PyasAllocate(FileInfo.EndOfFile.LowPart + 1);
|
||||
if (FileBuffer) {
|
||||
status = ZwReadFile(FileHandle, NULL, NULL, NULL, &IoStatus, FileBuffer, FileInfo.EndOfFile.LowPart, NULL, NULL);
|
||||
if (NT_SUCCESS(status)) {
|
||||
((PCHAR)FileBuffer)[FileInfo.EndOfFile.LowPart] = '\0';
|
||||
|
||||
ExAcquireResourceExclusiveLite(&RuleLock, TRUE);
|
||||
ParseAndLoadRules((PCHAR)FileBuffer, FileInfo.EndOfFile.LowPart, "Rule_Registry_BlockList", &g_RegistryBlockList);
|
||||
ParseAndLoadRules((PCHAR)FileBuffer, FileInfo.EndOfFile.LowPart, "Rule_Registry_TrustedList", &g_RegistryTrustedList);
|
||||
ParseAndLoadRules((PCHAR)FileBuffer, FileInfo.EndOfFile.LowPart, "Rule_Process_TrustedPaths", &g_ProcessTrustedPaths);
|
||||
ParseAndLoadRules((PCHAR)FileBuffer, FileInfo.EndOfFile.LowPart, "Rule_Process_ExploitableBlacklist", &g_ProcessExploitable);
|
||||
ParseAndLoadRules((PCHAR)FileBuffer, FileInfo.EndOfFile.LowPart, "Rule_File_ProtectedPaths", &g_FileProtectedPaths);
|
||||
ParseAndLoadRules((PCHAR)FileBuffer, FileInfo.EndOfFile.LowPart, "Rule_File_ExceptionPaths", &g_FileExceptionPaths);
|
||||
ParseAndLoadRules((PCHAR)FileBuffer, FileInfo.EndOfFile.LowPart, "Rule_File_RansomwareExtensions", &g_FileRansomExts);
|
||||
ExReleaseResourceLite(&RuleLock);
|
||||
}
|
||||
PyasFree(FileBuffer);
|
||||
}
|
||||
}
|
||||
ZwClose(FileHandle);
|
||||
}
|
||||
|
||||
if (PathBuffer) PyasFree(PathBuffer);
|
||||
return status;
|
||||
}
|
||||
|
||||
VOID UnloadRules() {
|
||||
ExAcquireResourceExclusiveLite(&RuleLock, TRUE);
|
||||
FreeList(&g_RegistryBlockList);
|
||||
FreeList(&g_RegistryTrustedList);
|
||||
FreeList(&g_ProcessTrustedPaths);
|
||||
FreeList(&g_ProcessExploitable);
|
||||
FreeList(&g_FileProtectedPaths);
|
||||
FreeList(&g_FileExceptionPaths);
|
||||
FreeList(&g_FileRansomExts);
|
||||
ExReleaseResourceLite(&RuleLock);
|
||||
}
|
||||
|
||||
NTSTATUS GetProcessImageName(HANDLE ProcessId, PUNICODE_STRING* ImageName) {
|
||||
NTSTATUS status;
|
||||
PEPROCESS Process = NULL;
|
||||
|
||||
*ImageName = NULL;
|
||||
status = PsLookupProcessByProcessId(ProcessId, &Process);
|
||||
if (!NT_SUCCESS(status)) return status;
|
||||
|
||||
status = SeLocateProcessImageName(Process, ImageName);
|
||||
ObDereferenceObject(Process);
|
||||
|
||||
return status;
|
||||
}
|
||||
|
||||
static BOOLEAN IsWindowsSystemApp(PCWSTR Buffer, USHORT Length) {
|
||||
if (WildcardMatch(L"*\\Windows\\SystemApps\\*", Buffer, Length)) return TRUE;
|
||||
if (WildcardMatch(L"*\\Windows\\ImmersiveControlPanel\\*", Buffer, Length)) return TRUE;
|
||||
if (WildcardMatch(L"*\\Windows\\explorer.exe", Buffer, Length)) return TRUE;
|
||||
return FALSE;
|
||||
}
|
||||
|
||||
BOOLEAN IsProcessTrusted(HANDLE ProcessId) {
|
||||
if ((ULONG)(ULONG_PTR)ProcessId == GlobalData.PyasPid) return TRUE;
|
||||
if (ProcessId == (HANDLE)4) return TRUE;
|
||||
|
||||
if (KeGetCurrentIrql() != PASSIVE_LEVEL) return FALSE;
|
||||
|
||||
PEPROCESS Process = NULL;
|
||||
if (!NT_SUCCESS(PsLookupProcessByProcessId(ProcessId, &Process))) {
|
||||
return FALSE;
|
||||
}
|
||||
|
||||
if (g_CacheInitialized) {
|
||||
ExAcquireFastMutex(&TrustCacheLock);
|
||||
ULONG Hash = ((ULONG)(ULONG_PTR)ProcessId) & (TRUST_CACHE_SIZE - 1);
|
||||
if (TrustCache[Hash].Process == Process) {
|
||||
LARGE_INTEGER Now;
|
||||
KeQuerySystemTime(&Now);
|
||||
if ((Now.QuadPart - TrustCache[Hash].CacheTime.QuadPart) < (TRUST_CACHE_TTL_SEC * 10000000LL)) {
|
||||
BOOLEAN cachedResult = TrustCache[Hash].IsTrusted;
|
||||
ExReleaseFastMutex(&TrustCacheLock);
|
||||
ObDereferenceObject(Process);
|
||||
return cachedResult;
|
||||
}
|
||||
}
|
||||
ExReleaseFastMutex(&TrustCacheLock);
|
||||
}
|
||||
|
||||
PUNICODE_STRING imageFileName = NULL;
|
||||
NTSTATUS status = GetProcessImageName(ProcessId, &imageFileName);
|
||||
BOOLEAN isTrusted = FALSE;
|
||||
|
||||
ExAcquireResourceSharedLite(&RuleLock, TRUE);
|
||||
|
||||
if (NT_SUCCESS(status) && imageFileName && imageFileName->Buffer) {
|
||||
PRULE_NODE Node = g_ProcessExploitable;
|
||||
while (Node) {
|
||||
if (WildcardMatch(Node->Pattern.Buffer, imageFileName->Buffer, imageFileName->Length)) {
|
||||
goto update_cache;
|
||||
}
|
||||
Node = Node->Next;
|
||||
}
|
||||
|
||||
if (IsWindowsSystemApp(imageFileName->Buffer, imageFileName->Length)) {
|
||||
isTrusted = TRUE;
|
||||
goto update_cache;
|
||||
}
|
||||
|
||||
Node = g_ProcessTrustedPaths;
|
||||
while (Node) {
|
||||
if (WildcardMatch(Node->Pattern.Buffer, imageFileName->Buffer, imageFileName->Length)) {
|
||||
isTrusted = TRUE;
|
||||
goto update_cache;
|
||||
}
|
||||
Node = Node->Next;
|
||||
}
|
||||
}
|
||||
|
||||
update_cache:
|
||||
if (g_CacheInitialized) {
|
||||
ExAcquireFastMutex(&TrustCacheLock);
|
||||
ULONG Hash = ((ULONG)(ULONG_PTR)ProcessId) & (TRUST_CACHE_SIZE - 1);
|
||||
TrustCache[Hash].Process = Process;
|
||||
TrustCache[Hash].IsTrusted = isTrusted;
|
||||
KeQuerySystemTime(&TrustCache[Hash].CacheTime);
|
||||
ExReleaseFastMutex(&TrustCacheLock);
|
||||
}
|
||||
|
||||
ExReleaseResourceLite(&RuleLock);
|
||||
|
||||
if (imageFileName) ExFreePool(imageFileName);
|
||||
ObDereferenceObject(Process);
|
||||
return isTrusted;
|
||||
}
|
||||
|
||||
BOOLEAN IsTargetProtected(HANDLE ProcessId) {
|
||||
if ((ULONG)(ULONG_PTR)ProcessId == GlobalData.PyasPid) return TRUE;
|
||||
return FALSE;
|
||||
}
|
||||
|
||||
BOOLEAN CheckRegistryRule(PCUNICODE_STRING KeyName) {
|
||||
if (!KeyName || !KeyName->Buffer) return FALSE;
|
||||
if (KeyName->Length < 4 * sizeof(WCHAR)) return FALSE;
|
||||
|
||||
if (WildcardMatch(L"*{645FF040-5081-101B-9F08-00AA002F954E}\\DefaultIcon", KeyName->Buffer, KeyName->Length)) {
|
||||
return FALSE;
|
||||
}
|
||||
|
||||
ExAcquireResourceSharedLite(&RuleLock, TRUE);
|
||||
|
||||
PRULE_NODE AllowNode = g_RegistryTrustedList;
|
||||
while (AllowNode) {
|
||||
if (WildcardMatch(AllowNode->Pattern.Buffer, KeyName->Buffer, KeyName->Length)) {
|
||||
ExReleaseResourceLite(&RuleLock);
|
||||
return FALSE;
|
||||
}
|
||||
AllowNode = AllowNode->Next;
|
||||
}
|
||||
|
||||
PRULE_NODE Node = g_RegistryBlockList;
|
||||
BOOLEAN Match = FALSE;
|
||||
while (Node) {
|
||||
if (WildcardMatch(Node->Pattern.Buffer, KeyName->Buffer, KeyName->Length)) {
|
||||
Match = TRUE;
|
||||
break;
|
||||
}
|
||||
Node = Node->Next;
|
||||
}
|
||||
ExReleaseResourceLite(&RuleLock);
|
||||
return Match;
|
||||
}
|
||||
|
||||
BOOLEAN CheckFileExtensionRule(PCUNICODE_STRING FileName) {
|
||||
if (!FileName || !FileName->Buffer) return FALSE;
|
||||
|
||||
ExAcquireResourceSharedLite(&RuleLock, TRUE);
|
||||
|
||||
PRULE_NODE ExNode = g_FileExceptionPaths;
|
||||
while (ExNode) {
|
||||
if (WildcardMatch(ExNode->Pattern.Buffer, FileName->Buffer, FileName->Length)) {
|
||||
ExReleaseResourceLite(&RuleLock);
|
||||
return FALSE;
|
||||
}
|
||||
ExNode = ExNode->Next;
|
||||
}
|
||||
|
||||
PRULE_NODE Node = g_FileRansomExts;
|
||||
BOOLEAN Match = FALSE;
|
||||
while (Node) {
|
||||
if (HasSuffix(FileName, Node->Pattern.Buffer)) {
|
||||
Match = TRUE;
|
||||
break;
|
||||
}
|
||||
Node = Node->Next;
|
||||
}
|
||||
ExReleaseResourceLite(&RuleLock);
|
||||
return Match;
|
||||
}
|
||||
|
||||
BOOLEAN CheckProtectedPathRule(PCUNICODE_STRING FileName) {
|
||||
if (!FileName || !FileName->Buffer) return FALSE;
|
||||
|
||||
if (WildcardMatch(L"*\\Windows\\System32\\config\\systemprofile*", FileName->Buffer, FileName->Length)) {
|
||||
return FALSE;
|
||||
}
|
||||
|
||||
ExAcquireResourceSharedLite(&RuleLock, TRUE);
|
||||
|
||||
PRULE_NODE ExNode = g_FileExceptionPaths;
|
||||
while (ExNode) {
|
||||
if (WildcardMatch(ExNode->Pattern.Buffer, FileName->Buffer, FileName->Length)) {
|
||||
ExReleaseResourceLite(&RuleLock);
|
||||
return FALSE;
|
||||
}
|
||||
ExNode = ExNode->Next;
|
||||
}
|
||||
|
||||
PRULE_NODE Node = g_FileProtectedPaths;
|
||||
BOOLEAN Match = FALSE;
|
||||
while (Node) {
|
||||
if (WildcardMatch(Node->Pattern.Buffer, FileName->Buffer, FileName->Length)) {
|
||||
Match = TRUE;
|
||||
break;
|
||||
}
|
||||
Node = Node->Next;
|
||||
}
|
||||
ExReleaseResourceLite(&RuleLock);
|
||||
return Match;
|
||||
}
|
||||
|
||||
static BOOLEAN IsHighEntropy(PVOID Buffer, ULONG Length) {
|
||||
if (!Buffer || Length < 256) return FALSE;
|
||||
|
||||
ULONG Histogram[256] = { 0 };
|
||||
PUCHAR Ptr = (PUCHAR)Buffer;
|
||||
ULONG ScanLen = (Length > 1024) ? 1024 : Length;
|
||||
|
||||
__try {
|
||||
for (ULONG i = 0; i < ScanLen; i++) {
|
||||
Histogram[Ptr[i]]++;
|
||||
}
|
||||
}
|
||||
__except (EXCEPTION_EXECUTE_HANDLER) {
|
||||
return FALSE;
|
||||
}
|
||||
|
||||
ULONG MaxFreq = 0;
|
||||
for (int i = 0; i < 256; i++) {
|
||||
if (Histogram[i] > MaxFreq) MaxFreq = Histogram[i];
|
||||
}
|
||||
|
||||
ULONG ExpectedAvg = ScanLen / 256;
|
||||
return (MaxFreq < (ExpectedAvg + HIGH_ENTROPY_THRESHOLD));
|
||||
}
|
||||
|
||||
static BOOLEAN IsNaturallyCompressed(PCUNICODE_STRING FileName) {
|
||||
if (!FileName || !FileName->Buffer) return FALSE;
|
||||
for (int i = 0; i < sizeof(Helper_NaturallyCompressedExtensions) / sizeof(Helper_NaturallyCompressedExtensions[0]); i++) {
|
||||
if (HasSuffix(FileName, Helper_NaturallyCompressedExtensions[i])) return TRUE;
|
||||
}
|
||||
return FALSE;
|
||||
}
|
||||
|
||||
static BOOLEAN IsNoisyRansomPath(PCUNICODE_STRING FileName) {
|
||||
if (!FileName || !FileName->Buffer) return FALSE;
|
||||
|
||||
if (WildcardMatch(L"*\\Windows\\Temp\\*", FileName->Buffer, FileName->Length) ||
|
||||
WildcardMatch(L"*\\Windows\\SystemTemp\\*", FileName->Buffer, FileName->Length) ||
|
||||
WildcardMatch(L"*\\Windows\\Prefetch\\*", FileName->Buffer, FileName->Length) ||
|
||||
WildcardMatch(L"*\\Windows\\Logs\\*", FileName->Buffer, FileName->Length) ||
|
||||
WildcardMatch(L"*\\Profiles\\*", FileName->Buffer, FileName->Length) ||
|
||||
WildcardMatch(L"*\\Program*\\*", FileName->Buffer, FileName->Length) ||
|
||||
WildcardMatch(L"*\\AppData\\Local\\*", FileName->Buffer, FileName->Length) ||
|
||||
WildcardMatch(L"*\\User Data\\*", FileName->Buffer, FileName->Length) ||
|
||||
WildcardMatch(L"*$Recycle.Bin*", FileName->Buffer, FileName->Length)) {
|
||||
return TRUE;
|
||||
}
|
||||
return FALSE;
|
||||
}
|
||||
|
||||
static BOOLEAN IsExplorerProcess(HANDLE ProcessId) {
|
||||
if (KeGetCurrentIrql() != PASSIVE_LEVEL) return FALSE;
|
||||
PUNICODE_STRING imageFileName = NULL;
|
||||
NTSTATUS status = GetProcessImageName(ProcessId, &imageFileName);
|
||||
BOOLEAN isExplorer = FALSE;
|
||||
if (NT_SUCCESS(status) && imageFileName && imageFileName->Buffer) {
|
||||
if (HasSuffix(imageFileName, L"\\explorer.exe")) isExplorer = TRUE;
|
||||
ExFreePool(imageFileName);
|
||||
}
|
||||
return isExplorer;
|
||||
}
|
||||
|
||||
BOOLEAN CheckRansomActivity(HANDLE ProcessId, PUNICODE_STRING FileName, PVOID WriteBuffer, ULONG WriteLength, BOOLEAN IsWrite) {
|
||||
if (IsNoisyRansomPath(FileName)) {
|
||||
return FALSE;
|
||||
}
|
||||
|
||||
if (!IsWrite) {
|
||||
if (IsExplorerProcess(ProcessId)) return FALSE;
|
||||
}
|
||||
|
||||
BOOLEAN SuspiciousWrite = FALSE;
|
||||
if (IsWrite && WriteBuffer && WriteLength > 0) {
|
||||
if (!IsNaturallyCompressed(FileName) && IsHighEntropy(WriteBuffer, WriteLength)) {
|
||||
SuspiciousWrite = TRUE;
|
||||
}
|
||||
}
|
||||
|
||||
if (IsWrite && !SuspiciousWrite) {
|
||||
return FALSE;
|
||||
}
|
||||
|
||||
PEPROCESS Process = NULL;
|
||||
if (!NT_SUCCESS(PsLookupProcessByProcessId(ProcessId, &Process))) {
|
||||
return FALSE;
|
||||
}
|
||||
|
||||
LARGE_INTEGER Now = { 0 };
|
||||
KeQuerySystemTime(&Now);
|
||||
BOOLEAN Result = FALSE;
|
||||
|
||||
ExAcquireFastMutex(&GlobalData.TrackerMutex);
|
||||
|
||||
PRANSOM_TRACKER Tracker = NULL;
|
||||
PRANSOM_TRACKER CandidateSlot = NULL;
|
||||
PRANSOM_TRACKER LruSlot = &RansomTrackers[0];
|
||||
|
||||
for (int i = 0; i < MAX_TRACKERS; i++) {
|
||||
PRANSOM_TRACKER Current = &RansomTrackers[i];
|
||||
|
||||
if (Current->LastActivityTime.QuadPart < LruSlot->LastActivityTime.QuadPart) {
|
||||
LruSlot = Current;
|
||||
}
|
||||
|
||||
if (Current->Process == Process) {
|
||||
Tracker = Current;
|
||||
break;
|
||||
}
|
||||
|
||||
BOOLEAN IsExpired = FALSE;
|
||||
if (Current->Process != NULL) {
|
||||
LARGE_INTEGER Diff;
|
||||
Diff.QuadPart = Now.QuadPart - Current->LastActivityTime.QuadPart;
|
||||
if (Diff.QuadPart > (RANSOM_TIME_WINDOW_MS * 10000LL)) {
|
||||
IsExpired = TRUE;
|
||||
}
|
||||
}
|
||||
|
||||
if ((Current->Process == NULL || IsExpired) && !CandidateSlot) {
|
||||
CandidateSlot = Current;
|
||||
}
|
||||
}
|
||||
|
||||
if (!Tracker) {
|
||||
if (CandidateSlot) {
|
||||
Tracker = CandidateSlot;
|
||||
}
|
||||
else {
|
||||
Tracker = LruSlot;
|
||||
}
|
||||
|
||||
Tracker->Process = Process;
|
||||
Tracker->ActivityCount = 0;
|
||||
Tracker->LastActivityTime = Now;
|
||||
}
|
||||
else {
|
||||
LARGE_INTEGER Diff;
|
||||
Diff.QuadPart = Now.QuadPart - Tracker->LastActivityTime.QuadPart;
|
||||
|
||||
if (Diff.QuadPart > (RANSOM_TIME_WINDOW_MS * 10000LL)) {
|
||||
Tracker->ActivityCount = 0;
|
||||
}
|
||||
Tracker->LastActivityTime = Now;
|
||||
}
|
||||
|
||||
ULONG Weight = 2;
|
||||
Tracker->ActivityCount += Weight;
|
||||
|
||||
if (Tracker->ActivityCount >= RANSOM_COUNT_THRESHOLD) {
|
||||
Result = TRUE;
|
||||
}
|
||||
ExReleaseFastMutex(&GlobalData.TrackerMutex);
|
||||
ObDereferenceObject(Process);
|
||||
return Result;
|
||||
}
|
||||
|
||||
NTSTATUS SendMessageToUser(ULONG Code, ULONG Pid, PWCHAR Path, USHORT PathSize) {
|
||||
if (KeGetCurrentIrql() > APC_LEVEL) return STATUS_UNSUCCESSFUL;
|
||||
|
||||
PYAS_MESSAGE msg;
|
||||
RtlZeroMemory(&msg, sizeof(msg));
|
||||
msg.MessageCode = Code;
|
||||
msg.ProcessId = Pid;
|
||||
|
||||
if (Path && PathSize > 0) {
|
||||
size_t MaxSize = sizeof(msg.Path) - sizeof(WCHAR);
|
||||
size_t BytesToCopy = PathSize;
|
||||
if (BytesToCopy > MaxSize) BytesToCopy = MaxSize;
|
||||
|
||||
__try {
|
||||
RtlCopyMemory(msg.Path, Path, BytesToCopy);
|
||||
msg.Path[BytesToCopy / sizeof(WCHAR)] = L'\0';
|
||||
}
|
||||
__except (EXCEPTION_EXECUTE_HANDLER) {
|
||||
return STATUS_ACCESS_VIOLATION;
|
||||
}
|
||||
}
|
||||
|
||||
if (!ExAcquireRundownProtection(&GlobalData.PortRundown)) {
|
||||
return STATUS_PORT_DISCONNECTED;
|
||||
}
|
||||
|
||||
NTSTATUS status = STATUS_PORT_DISCONNECTED;
|
||||
if (GlobalData.ClientPort) {
|
||||
LARGE_INTEGER timeout;
|
||||
timeout.QuadPart = -(5 * 10000);
|
||||
status = FltSendMessage(GlobalData.FilterHandle, &GlobalData.ClientPort, &msg, sizeof(msg), NULL, NULL, &timeout);
|
||||
}
|
||||
|
||||
ExReleaseRundownProtection(&GlobalData.PortRundown);
|
||||
return status;
|
||||
}
|
||||
@@ -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"
|
||||
@@ -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",
|
||||
]
|
||||
}
|
||||
@@ -1,74 +1,166 @@
|
||||
# PYAS (Python Antivirus Software)
|
||||
# PYAS
|
||||
|
||||

|
||||
Antivirus software written in Python and C++ that blocks threats through deep learning and behavioral monitoring!
|
||||
|
||||
## Readme Language
|
||||
|
||||
繁體中文 : https://github.com/87owo/PYAS/blob/main/README_zh_TW.md
|
||||
|
||||
简体中文 : https://github.com/87owo/PYAS/blob/main/README_zh_CN.md
|
||||
<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 pefile==2023.2.7
|
||||
pip install pyperclip==1.8.2
|
||||
pip install PyQt5==5.15.9
|
||||
pip install yara-python==4.5.1
|
||||
pip install numpy==2.1.1
|
||||
pip install onnxruntime==1.18.1
|
||||
pip install pillow==10.4.0
|
||||
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
|
||||
|
||||
The following lists the storage locations of all relevant code and other related documents.
|
||||
|
||||
```
|
||||
PYAS/
|
||||
├── Driver/ (Protect driver and Microsoft runtime)
|
||||
├── Embed/ # Deploy an embedded command line antivirus engine
|
||||
│ ├── Dockerfile
|
||||
│ └── ...
|
||||
│
|
||||
├── Engine/ (Deep learn model and Yara database)
|
||||
│ └── ...
|
||||
├── Engine/ # Complete code for Yara signatures and AI model training
|
||||
│ ├── Heuristic/
|
||||
│ │ └── ... # Yara Rules
|
||||
│ ├── Pattern/
|
||||
│ │ └── ... # CNN Model
|
||||
│ └── Properties/
|
||||
│ └── ... # LightGBM Model
|
||||
│
|
||||
├── Exten/ (Exten tools and System repair tools)
|
||||
│ └── ...
|
||||
├── Interface/ # Interface Interaction and Icons with WebView2
|
||||
│ ├── static/
|
||||
│ │ └── ... # style.css, main.js, icon.ico
|
||||
│ └── templates/
|
||||
│ └── ... # index.html
|
||||
│
|
||||
├── PYAS.py (Main PYAS program with qtui interaction and protect function)
|
||||
├── PYAS_Engine.py (Deep learning predict and yara rules file scanner)
|
||||
├── PYAS_Suffixes.py (including scan file suffixes and common suffixes)
|
||||
├── PYAS_Interface.py (Qt designer ui, must be matched with the main program)
|
||||
├── PYAS_Resource.py (status pictures, icon packages, and button icons)
|
||||
├── PYAS_Language.py (Traditional Chinese, Simplified Chinese and English)
|
||||
├── PYAS_Version.py (file information, version information, original name)
|
||||
├── Plugins/ # Main filter driver protect and rules
|
||||
│ ├── Filter/
|
||||
│ │ └── ... # DriverCommon.h, DriverEntry.cpp, ProtectRegistry.cpp, ...
|
||||
│ └── Rules/
|
||||
│ └── ... # 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
|
||||
└── ...
|
||||
```
|
||||
|
||||
## Official Website
|
||||
## Architecture diagram
|
||||
|
||||
https://pyantivirus.wixsite.com/pyas
|
||||
PYAS Security antivirus software general architecture diagram.
|
||||
|
||||
https://github.com/87owo/PYAS
|
||||
```mermaid
|
||||
graph TD
|
||||
%% Global Styles
|
||||
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
|
||||
|
||||
## Microsoft Runtime
|
||||
subgraph UserMode ["User Mode (Python / Web)"]
|
||||
UI["Web UI (pywebview)"]:::userMode
|
||||
PYASCore["PYAS Core (PYAS.py)"]:::userMode
|
||||
|
||||
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
|
||||
|
||||
CommClient["Filter Communication Client"]:::interaction
|
||||
end
|
||||
|
||||
https://github.com/87owo/Microsoft_Runtime/releases
|
||||
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
|
||||
|
||||
## PYAS Driver
|
||||
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
|
||||
end
|
||||
|
||||
https://github.com/0sha0/PYAS_Protection
|
||||
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
|
||||
|
||||
## MIT license
|
||||
CloudAPI["PYAS Cloud Server"]:::storage
|
||||
|
||||
https://github.com/87owo/PYAS/blob/main/LICENSE.md
|
||||
%% 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
|
||||
|
||||
Windows 8.1, 10, 11 (64-bit), Ram 500MB, Rom 200MB or higher
|
||||
Please ensure your computer supports Microsoft Visual C++ 2015-2022 Redistributable and Edge Webview2.
|
||||
|
||||
Other systems may experience software malfunctions or crash
|
||||
| Config | Permissions | System version | Processor | Memory | Storage |
|
||||
|-----------|---------------|----------------------|-----------|--------|---------|
|
||||
| Minimum | Administrator | >= Windows 10 (20H1) | 1 GHz | 300MB | 100MB |
|
||||
| Recommend | Administrator | >= Windows 10 (21H2) | 3 GHz | 500MB | 200MB |
|
||||
|
||||
## Special Thanks
|
||||
## Official Website
|
||||
|
||||
Wix, mtkiao129, 0sha0, AV-T Team of LisectGroup
|
||||
If you are interested in this project, you can visit the website to see other related content.
|
||||
|
||||
Copyright© 2020~2024 PYAS Security By 87owo
|
||||
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 : service@pyas-security.com
|
||||
@@ -1,68 +0,0 @@
|
||||
# PYAS (Python 杀毒软件)
|
||||
|
||||

|
||||
|
||||
## 安装要求
|
||||
|
||||
```
|
||||
pip install pefile==2023.2.7
|
||||
pip install pyperclip==1.8.2
|
||||
pip install PyQt5==5.15.9
|
||||
pip install yara-python==4.5.1
|
||||
pip install numpy==2.1.1
|
||||
pip install onnxruntime==1.18.1
|
||||
pip install pillow==10.4.0
|
||||
```
|
||||
|
||||
## 文件信息
|
||||
|
||||
```
|
||||
PYAS/
|
||||
├── Driver/ (驱动保护和微软运行库)
|
||||
│ └── ...
|
||||
│
|
||||
├── Engine/ (深度学习和规则数据库)
|
||||
│ └── ...
|
||||
│
|
||||
├── Exten/ (扩展功能与系统修复工具)
|
||||
│ └── ...
|
||||
│
|
||||
├── PYAS.py (PYAS主程序含界面交互与保护功能)
|
||||
├── PYAS_Engine.py (深度学习预测和规则扫描器)
|
||||
├── PYAS_Suffixes.py (获取扫描或事件文件的后缀)
|
||||
├── PYAS_Interface.py (搭配主程序的Qt设计介面)
|
||||
├── PYAS_Resource.py (状态图片和图标和按钮图片)
|
||||
├── PYAS_Language.py (繁体中文和简体中文和英文翻译)
|
||||
├── PYAS_Version.py (版本和讯息名称和打包文件名)
|
||||
└── ...
|
||||
```
|
||||
|
||||
## 官方网页
|
||||
|
||||
https://pyantivirus.wixsite.com/pyas
|
||||
|
||||
https://github.com/87owo/PYAS
|
||||
|
||||
## 微软运行库
|
||||
|
||||
https://github.com/87owo/Microsoft_Runtime/releases
|
||||
|
||||
## 驱动程序
|
||||
|
||||
https://github.com/0sha0/PYAS_Protection
|
||||
|
||||
## 开源协议
|
||||
|
||||
https://github.com/87owo/PYAS/blob/main/LICENSE.md
|
||||
|
||||
## 支援系统
|
||||
|
||||
Windows 8.1, 10, 11 (64-bit), 内存 500MB, 存储 200MB 或更高
|
||||
|
||||
其他系统或版本可能会遇到软体功能故障或程序崩溃
|
||||
|
||||
## 特别感谢
|
||||
|
||||
Wix, mtkiao129, 0sha0, AV-T Team of LisectGroup
|
||||
|
||||
Copyright© 2020~2024 PYAS Security By 87owo
|
||||
@@ -1,68 +0,0 @@
|
||||
# PYAS (Python 防毒軟體)
|
||||
|
||||

|
||||
|
||||
## 安裝要求
|
||||
|
||||
```
|
||||
pip install pefile==2023.2.7
|
||||
pip install pyperclip==1.8.2
|
||||
pip install PyQt5==5.15.9
|
||||
pip install yara-python==4.5.1
|
||||
pip install numpy==2.1.1
|
||||
pip install onnxruntime==1.18.1
|
||||
pip install pillow==10.4.0
|
||||
```
|
||||
|
||||
## 檔案資訊
|
||||
|
||||
```
|
||||
PYAS/
|
||||
├── Driver/ (驅動防護和微軟運行庫)
|
||||
│ └── ...
|
||||
│
|
||||
├── Engine/ (深度學習和規則數據庫)
|
||||
│ └── ...
|
||||
│
|
||||
├── Exten/ (擴展功能與系統修復工具)
|
||||
│ └── ...
|
||||
│
|
||||
├── PYAS.py (PYAS主程序含介面交互與防護功能)
|
||||
├── PYAS_Engine.py (深度學習預測和規則掃描器)
|
||||
├── PYAS_Suffixes.py (獲取掃描或事件檔案的副檔名)
|
||||
├── PYAS_Interface.py (搭配主程序的Qt設計介面)
|
||||
├── PYAS_Resource.py (狀態圖片圖標和按鈕圖片)
|
||||
├── PYAS_Language.py (繁體中文和簡體中文和英文翻譯)
|
||||
├── PYAS_Version.py (檔案版本和訊息名稱和打包檔名)
|
||||
└── ...
|
||||
```
|
||||
|
||||
## 官方網頁
|
||||
|
||||
https://pyantivirus.wixsite.com/pyas
|
||||
|
||||
https://github.com/87owo/PYAS
|
||||
|
||||
## 微軟運行庫
|
||||
|
||||
https://github.com/87owo/Microsoft_Runtime/releases
|
||||
|
||||
## 驅動程式
|
||||
|
||||
https://github.com/0sha0/PYAS_Protection
|
||||
|
||||
## 開源協議
|
||||
|
||||
https://github.com/87owo/PYAS/blob/main/LICENSE.md
|
||||
|
||||
## 支援系統
|
||||
|
||||
Windows 8.1, 10, 11 (64-bit),記憶體 500MB, 磁碟容量 200MB 或更高
|
||||
|
||||
其他系統或版本可能會遇到軟體功能故障或程序崩潰
|
||||
|
||||
## 特別感謝
|
||||
|
||||
Wix, mtkiao129, 0sha0, AV-T Team of LisectGroup
|
||||
|
||||
Copyright© 2020~2024 PYAS Security By 87owo
|
||||
Reference in new issue
Block a user