Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
a04d74a949 | ||
|
|
b93a7ebd94 | ||
|
|
cbc7b0340f | ||
|
|
ea7a197848 | ||
|
|
82d4df889e | ||
|
|
df3a2a1010 | ||
|
|
67cd6c2f59 | ||
|
|
b1788e7df9 | ||
|
|
1cb8a81518 | ||
|
|
e1b61f2675 | ||
|
|
834d44856c | ||
|
|
c088eeb5bc | ||
|
|
921510b7b7 | ||
|
|
17a54f7a8a | ||
|
|
43ff924efc | ||
|
|
f9bd4b6628 | ||
|
|
702aa22139 | ||
|
|
57d53f63e5 | ||
|
|
835ada4a53 | ||
|
|
7f204f3e1b | ||
|
|
70e13b8414 | ||
|
|
df9bb8cbb8 | ||
|
|
cebdb32502 | ||
|
|
58bd61343a | ||
|
|
90faf1007e | ||
|
|
f8dbd12b69 | ||
|
|
cf6ba66dd2 | ||
|
|
2080b5d10a | ||
|
|
dd180977d9 | ||
|
|
221dcbaf1b | ||
|
|
1a2098f4fb | ||
|
|
73eab17110 | ||
|
|
978a336358 | ||
|
|
6a0fd4e427 | ||
|
|
5fd4efd920 | ||
|
|
43be2987f5 | ||
|
|
06cc048fdc | ||
|
|
5752471420 | ||
|
|
3dfe12b90a | ||
|
|
ad1e3501b1 | ||
|
|
1b46572e4c | ||
|
|
9d8e42d34c | ||
|
|
3f6e9dab3e |
No files matched your search
+287
-567
@@ -1,12 +1,23 @@
|
||||
import os, sys, time, ctypes, sqlite3, datetime, pefile, math, hashlib
|
||||
import numpy as np
|
||||
import ctypes.wintypes
|
||||
import os, sys, math, time, datetime, pefile, hashlib
|
||||
import ctypes, ctypes.wintypes, multiprocessing
|
||||
|
||||
try:
|
||||
import orjson
|
||||
JSON_DUMPS = lambda x: orjson.dumps(x).decode('utf-8')
|
||||
JSON_LOADS = orjson.loads
|
||||
HAS_ORJSON = True
|
||||
except ImportError:
|
||||
import json
|
||||
JSON_DUMPS = json.dumps
|
||||
JSON_LOADS = json.loads
|
||||
HAS_ORJSON = False
|
||||
|
||||
####################################################################################################
|
||||
|
||||
DB_PATH = "pe_features.db"
|
||||
JSONL_PATH = "pe_features.jsonl"
|
||||
BATCH_SIZE = 1000
|
||||
MAX_FILE_SIZE = 4 * 1024 * 1024 * 1024
|
||||
MAX_WORKERS = os.cpu_count() - 2
|
||||
TARGET_EXTENSIONS = {
|
||||
'.exe', '.dll', '.sys', '.ocx', '.scr', '.efi', '.acm', '.ax', '.cpl', '.drv', '.com', '.mui', '.pyd'
|
||||
}
|
||||
@@ -60,24 +71,23 @@ V2_GUID = GUID(0x00AAC56B, 0xCD44, 0x11D0, (ctypes.c_ubyte * 8)(0x8C, 0xC2, 0x00
|
||||
####################################################################################################
|
||||
|
||||
def verify_signature(file_path):
|
||||
if os.name != 'nt':
|
||||
return 0
|
||||
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
|
||||
@@ -94,628 +104,338 @@ def verify_signature(file_path):
|
||||
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)
|
||||
except Exception:
|
||||
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'
|
||||
}
|
||||
}
|
||||
class FeatureExtractor:
|
||||
@staticmethod
|
||||
def _safe_float(val):
|
||||
try:
|
||||
f = float(val)
|
||||
return f if not (math.isinf(f) or math.isnan(f)) else 0.0
|
||||
except Exception:
|
||||
return 0.0
|
||||
|
||||
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'
|
||||
}
|
||||
@staticmethod
|
||||
def _calc_entropy(data):
|
||||
if not data: return 0.0
|
||||
import numpy as np
|
||||
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)))
|
||||
|
||||
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
|
||||
@classmethod
|
||||
def extract(cls, file_bytes, file_path, fsize):
|
||||
if fsize == 0: return None
|
||||
base, dlls, apis, pe = {}, set(), set(), None
|
||||
|
||||
try:
|
||||
fts['FileEntropy'] = calc_entropy(file_bytes)
|
||||
except Exception:
|
||||
pass
|
||||
pe = pefile.PE(data=file_bytes, fast_load=True)
|
||||
base['TrustSigned'] = verify_signature(file_path)
|
||||
base['FileEntropy'] = cls._calc_entropy(file_bytes)
|
||||
base['FileSize'] = fsize
|
||||
|
||||
fh = pe.FILE_HEADER
|
||||
base['Machine'] = getattr(fh, 'Machine', 0)
|
||||
base['NumberOfSections'] = getattr(fh, 'NumberOfSections', 0)
|
||||
base['TimeDateStamp'] = getattr(fh, 'TimeDateStamp', 0)
|
||||
base['PointerToSymbolTable'] = getattr(fh, 'PointerToSymbolTable', 0)
|
||||
base['NumberOfSymbols'] = getattr(fh, 'NumberOfSymbols', 0)
|
||||
base['SizeOfOptionalHeader'] = getattr(fh, 'SizeOfOptionalHeader', 0)
|
||||
base['Characteristics'] = getattr(fh, 'Characteristics', 0)
|
||||
|
||||
sec_entropies = []
|
||||
text_entropies = []
|
||||
data_entropies = []
|
||||
rsrc_entropies = []
|
||||
char_flags = [0x00000020, 0x00000040, 0x00000080, 0x02000000, 0x20000000, 0x40000000, 0x80000000]
|
||||
curr_ts = datetime.datetime.utcnow().timestamp()
|
||||
base['HasInvalidTimestamp'] = 1.0 if (base['TimeDateStamp'] < 631152000 or base['TimeDateStamp'] > curr_ts + 2592000) else 0.0
|
||||
base['FileTimeException'] = 1.0 if base['TimeDateStamp'] == 0 else 0.0
|
||||
base['Is64Bit'] = 1.0 if base['Machine'] in (0x8664, 0xAA64, 0x0200) else 0.0
|
||||
base['IsExe'] = 1.0 if pe.is_exe() else 0.0
|
||||
base['IsDll'] = 1.0 if pe.is_dll() else 0.0
|
||||
|
||||
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 = ""
|
||||
base['ExceptionCount'] = 0.0
|
||||
|
||||
if hasattr(pe, 'OPTIONAL_HEADER'):
|
||||
op = pe.OPTIONAL_HEADER
|
||||
fields = ['Magic', 'MajorLinkerVersion', 'MinorLinkerVersion', 'SizeOfCode', 'SizeOfInitializedData', 'SizeOfUninitializedData', 'AddressOfEntryPoint', 'BaseOfCode', 'ImageBase', 'SectionAlignment', 'FileAlignment', 'MajorOperatingSystemVersion', 'MinorOperatingSystemVersion', 'MajorImageVersion', 'MinorImageVersion', 'MajorSubsystemVersion', 'MinorSubsystemVersion', 'SizeOfImage', 'SizeOfHeaders', 'CheckSum', 'Subsystem', 'DllCharacteristics', 'SizeOfStackReserve', 'SizeOfStackCommit', 'SizeOfHeapReserve', 'SizeOfHeapCommit', 'LoaderFlags', 'NumberOfRvaAndSizes']
|
||||
for f in fields: base[f] = getattr(op, f, 0)
|
||||
base['IsDriver'] = 1.0 if base.get('Subsystem') == 1 else 0.0
|
||||
|
||||
if hasattr(op, 'DATA_DIRECTORY'):
|
||||
for i, directory in enumerate(op.DATA_DIRECTORY):
|
||||
base[f'DataDirectory_{i}_Size'] = directory.Size
|
||||
base[f'DataDirectory_{i}_VA'] = directory.VirtualAddress
|
||||
|
||||
try:
|
||||
s_data = section.get_data()
|
||||
s_entropy = calc_entropy(s_data)
|
||||
except Exception:
|
||||
s_data = b""
|
||||
s_entropy = 0.0
|
||||
exc_idx = pefile.DIRECTORY_ENTRY.get('IMAGE_DIRECTORY_ENTRY_EXCEPTION', 3)
|
||||
if len(op.DATA_DIRECTORY) > exc_idx and op.DATA_DIRECTORY[exc_idx].Size > 0:
|
||||
base['ExceptionCount'] = op.DATA_DIRECTORY[exc_idx].Size // 12 if base['Machine'] in (0x8664, 0xAA64) else 0.0
|
||||
|
||||
sec_entropies.append(s_entropy)
|
||||
char_flags = [0x00000020, 0x00000040, 0x00000080, 0x02000000, 0x20000000, 0x40000000, 0x80000000]
|
||||
for flag in char_flags:
|
||||
base[f'Char_{flag:08X}_Count'] = 0.0
|
||||
base[f'Char_{flag:08X}_MeanEntropy'] = 0.0
|
||||
|
||||
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
|
||||
if hasattr(pe, 'sections'):
|
||||
base['SectionCount'] = len(pe.sections)
|
||||
entropies, raw_sizes, v_sizes = [], [], []
|
||||
exec_sec, write_sec, read_sec, sec_exc = 0, 0, 0, 0
|
||||
|
||||
for section in pe.sections:
|
||||
try:
|
||||
s_data = section.get_data()
|
||||
s_entropy = cls._calc_entropy(s_data)
|
||||
except Exception:
|
||||
s_entropy = 0.0
|
||||
|
||||
entropies.append(s_entropy)
|
||||
raw_sizes.append(section.SizeOfRawData)
|
||||
v_sizes.append(section.Misc_VirtualSize)
|
||||
|
||||
if section.Characteristics & 0x20000000: exec_sec += 1
|
||||
if section.Characteristics & 0x80000000: write_sec += 1
|
||||
if section.Characteristics & 0x40000000: read_sec += 1
|
||||
if section.SizeOfRawData + section.PointerToRawData > fsize: sec_exc = 1
|
||||
|
||||
for flag in char_flags:
|
||||
if section.Characteristics & flag:
|
||||
base[f'Char_{flag:08X}_Count'] += 1.0
|
||||
base[f'Char_{flag:08X}_MeanEntropy'] += s_entropy
|
||||
|
||||
base['SectionMaxEntropy'] = max(entropies) if entropies else 0.0
|
||||
base['SectionMinEntropy'] = min(entropies) if entropies else 0.0
|
||||
base['SectionMeanEntropy'] = sum(entropies) / len(entropies) if entropies else 0.0
|
||||
base['SectionMaxRawSize'] = max(raw_sizes) if raw_sizes else 0.0
|
||||
base['SectionMinRawSize'] = min(raw_sizes) if raw_sizes else 0.0
|
||||
base['SectionMeanRawSize'] = sum(raw_sizes) / len(raw_sizes) if raw_sizes else 0.0
|
||||
base['SectionMaxVSize'] = max(v_sizes) if v_sizes else 0.0
|
||||
base['SectionMinVSize'] = min(v_sizes) if v_sizes else 0.0
|
||||
base['SectionMeanVSize'] = sum(v_sizes) / len(v_sizes) if v_sizes else 0.0
|
||||
base['ExecutableSections'] = exec_sec
|
||||
base['WritableSections'] = write_sec
|
||||
base['ReadableSections'] = read_sec
|
||||
base['SectionException'] = float(sec_exc)
|
||||
|
||||
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
|
||||
if base[f'Char_{flag:08X}_Count'] > 0:
|
||||
base[f'Char_{flag:08X}_MeanEntropy'] = base[f'Char_{flag:08X}_MeanEntropy'] / base[f'Char_{flag:08X}_Count']
|
||||
|
||||
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
|
||||
string_keys = ['FileDescription', 'FileVersion', 'ProductName', 'ProductVersion', 'CompanyName', 'LegalCopyright', 'Comments', 'InternalName', 'LegalTrademarks', 'SpecialBuild', 'PrivateBuild']
|
||||
for key in string_keys:
|
||||
base[f'{key}Length'] = 0.0
|
||||
|
||||
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, '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)
|
||||
if k in string_keys:
|
||||
v = val.decode('utf-8', 'ignore') if isinstance(val, bytes) else str(val)
|
||||
base[f'{k}Length'] = float(len(v))
|
||||
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
|
||||
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'):
|
||||
base['HasTlsCallbacks'] = 1.0 if getattr(pe.DIRECTORY_ENTRY_TLS.struct, 'AddressOfCallBacks', 0) != 0 else 0.0
|
||||
|
||||
if hasattr(pe, 'DIRECTORY_ENTRY_DEBUG'):
|
||||
fts['DebugCount'] = len(pe.DIRECTORY_ENTRY_DEBUG)
|
||||
if hasattr(pe, 'VS_FIXEDFILEINFO') and len(pe.VS_FIXEDFILEINFO) > 0:
|
||||
flags = getattr(pe.VS_FIXEDFILEINFO[0], 'FileFlags', 0)
|
||||
base['IsDebug'] = 1.0 if flags & 0x1 else 0.0
|
||||
base['IsPreRelease'] = 1.0 if flags & 0x2 else 0.0
|
||||
base['IsPatched'] = 1.0 if flags & 0x4 else 0.0
|
||||
base['IsPrivateBuild'] = 1.0 if flags & 0x8 else 0.0
|
||||
base['IsSpecialBuild'] = 1.0 if flags & 0x20 else 0.0
|
||||
|
||||
for k in fts:
|
||||
fts[k] = safe_float(fts[k])
|
||||
|
||||
return fts
|
||||
if hasattr(pe, 'DIRECTORY_ENTRY_IMPORT'):
|
||||
base['ImportCount'] = len(pe.DIRECTORY_ENTRY_IMPORT)
|
||||
func_count = 0
|
||||
for entry in pe.DIRECTORY_ENTRY_IMPORT:
|
||||
if getattr(entry, 'dll', None):
|
||||
try: dlls.add(entry.dll.decode('ascii', 'ignore').lower())
|
||||
except Exception: pass
|
||||
for imp in getattr(entry, 'imports', []):
|
||||
func_count += 1
|
||||
if getattr(imp, 'name', None):
|
||||
try: apis.add(imp.name.decode('ascii', 'ignore'))
|
||||
except Exception: pass
|
||||
base['ImportFunctionCount'] = func_count
|
||||
|
||||
except pefile.PEFormatError:
|
||||
return None
|
||||
except Exception:
|
||||
return None
|
||||
finally:
|
||||
if pe:
|
||||
pe.close()
|
||||
base['ExportCount'] = len(pe.DIRECTORY_ENTRY_EXPORT.symbols) if (hasattr(pe, 'DIRECTORY_ENTRY_EXPORT') and hasattr(pe.DIRECTORY_ENTRY_EXPORT, 'symbols')) else 0.0
|
||||
|
||||
if hasattr(pe, 'DIRECTORY_ENTRY_RESOURCE'):
|
||||
icon_count = 0
|
||||
for entry in pe.DIRECTORY_ENTRY_RESOURCE.entries:
|
||||
if getattr(entry, 'id', None) == 3 and hasattr(entry, 'directory'):
|
||||
icon_count += len(getattr(entry.directory, 'entries', []))
|
||||
base['IconCount'] = icon_count
|
||||
else:
|
||||
base['IconCount'] = 0.0
|
||||
|
||||
base['DebugCount'] = len(pe.DIRECTORY_ENTRY_DEBUG) if hasattr(pe, 'DIRECTORY_ENTRY_DEBUG') else 0.0
|
||||
|
||||
for k in base: base[k] = cls._safe_float(base[k])
|
||||
return {"Base": base, "DLLs": list(dlls), "APIs": list(apis)}
|
||||
|
||||
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():
|
||||
hashes = set()
|
||||
if os.path.exists(JSONL_PATH):
|
||||
with open(JSONL_PATH, 'r', encoding='utf-8') as f:
|
||||
for line in f:
|
||||
try:
|
||||
data = JSON_LOADS(line)
|
||||
if 'FileHash' in data: hashes.add(data['FileHash'])
|
||||
except Exception: pass
|
||||
return hashes
|
||||
|
||||
def load_existing_hashes(conn):
|
||||
print("[*] Loading existing hashes into memory...")
|
||||
####################################################################################################
|
||||
|
||||
GLOBAL_HASHES = set()
|
||||
|
||||
def worker_init(shared_hashes):
|
||||
global GLOBAL_HASHES
|
||||
GLOBAL_HASHES = shared_hashes
|
||||
|
||||
def _extract_worker_wrapper(args):
|
||||
return _extract_worker(*args)
|
||||
|
||||
def _extract_worker(file_path, label):
|
||||
try:
|
||||
cursor = conn.execute("SELECT FileHash FROM PeData")
|
||||
return {row[0] for row in cursor.fetchall()}
|
||||
except:
|
||||
return set()
|
||||
fsize = os.path.getsize(file_path)
|
||||
if fsize == 0 or fsize > MAX_FILE_SIZE:
|
||||
return 'error', None
|
||||
|
||||
with open(file_path, "rb") as f:
|
||||
file_bytes = f.read()
|
||||
|
||||
sha256 = hashlib.sha256(file_bytes).hexdigest()
|
||||
|
||||
if sha256 in GLOBAL_HASHES:
|
||||
return 'skipped', None
|
||||
|
||||
res = FeatureExtractor.extract(file_bytes, file_path, fsize)
|
||||
if res:
|
||||
final_res = {"Label": label, "FileHash": sha256, **res}
|
||||
return 'success', (sha256, JSON_DUMPS(final_res) + "\n")
|
||||
|
||||
return 'error', None
|
||||
except Exception:
|
||||
return 'error', None
|
||||
|
||||
####################################################################################################
|
||||
|
||||
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))
|
||||
def scan_and_save(path, label, existing_hashes):
|
||||
print(f"\n[*] Scanning path: {path} (Label={label})")
|
||||
files = [path] if os.path.isfile(path) else [os.path.join(r, f) for r, _, fs in os.walk(path) for f in fs if os.path.splitext(f)[1].lower() in TARGET_EXTENSIONS]
|
||||
|
||||
total = len(files)
|
||||
if total == 0:
|
||||
print("[-] No valid PE files found.")
|
||||
return
|
||||
|
||||
print(f"[*] Found {total} files. Processing...\n")
|
||||
print(f"[*] Found {total} files. Starting {MAX_WORKERS}-process extraction...\n")
|
||||
if HAS_ORJSON:
|
||||
print("[+] orjson acceleration enabled.")
|
||||
|
||||
batch = []
|
||||
count = 0
|
||||
errors = 0
|
||||
skipped = 0
|
||||
count, errors, skipped, processed_files = 0, 0, 0, 0
|
||||
start_time = time.time()
|
||||
batch_buffer = []
|
||||
|
||||
placeholders = ",".join(["?"] * (len(Features.SCHEMA) + 2))
|
||||
sql = f"INSERT INTO PeData VALUES ({placeholders})"
|
||||
pool = multiprocessing.Pool(processes=MAX_WORKERS, initializer=worker_init, initargs=(existing_hashes,))
|
||||
tasks = [(fpath, label) for fpath in files]
|
||||
|
||||
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:
|
||||
for status, data in pool.imap_unordered(_extract_worker_wrapper, tasks):
|
||||
processed_files += 1
|
||||
|
||||
if status == 'error':
|
||||
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 = []
|
||||
|
||||
elif status == 'skipped':
|
||||
skipped += 1
|
||||
elif status == 'success':
|
||||
sha256, json_str = data
|
||||
if sha256 not in existing_hashes:
|
||||
existing_hashes.add(sha256)
|
||||
batch_buffer.append(json_str)
|
||||
count += 1
|
||||
else:
|
||||
skipped += 1
|
||||
|
||||
if len(batch_buffer) >= BATCH_SIZE:
|
||||
with open(JSONL_PATH, 'a', encoding='utf-8') as f_out:
|
||||
f_out.writelines(batch_buffer)
|
||||
batch_buffer.clear()
|
||||
|
||||
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")
|
||||
rate = processed_files / elapsed if elapsed > 0 else 0
|
||||
sys.stdout.write(f"\r[{processed_files}/{total}] Added: {count} | Skipped: {skipped} | Errors: {errors} | Speed: {rate:.1f} files/s")
|
||||
sys.stdout.flush()
|
||||
|
||||
pool.close()
|
||||
pool.join()
|
||||
|
||||
except KeyboardInterrupt:
|
||||
print("\n\n[-] Interrupted by user.")
|
||||
print("\n\n[-] User aborted. Force terminating all child processes...")
|
||||
pool.terminate()
|
||||
pool.join()
|
||||
except Exception as e:
|
||||
print(f"\n[-] Unexpected Error: {e}")
|
||||
print(f"\n[-] Critical error: {e}")
|
||||
pool.terminate()
|
||||
pool.join()
|
||||
finally:
|
||||
if batch:
|
||||
if batch_buffer:
|
||||
try:
|
||||
conn.executemany(sql, batch)
|
||||
conn.commit()
|
||||
except Exception:
|
||||
pass
|
||||
with open(JSONL_PATH, 'a', encoding='utf-8') as f_out:
|
||||
f_out.writelines(batch_buffer)
|
||||
except Exception as save_err:
|
||||
print(f"\n[-] Failed to save remaining buffer: {save_err}")
|
||||
|
||||
print(f"\n\n[+] Scan finished. Total New: {count}, Total Skipped: {skipped}")
|
||||
print(f"\n\n[+] Scan complete. Total added: {count}, Total skipped: {skipped}")
|
||||
|
||||
####################################################################################################
|
||||
|
||||
if __name__ == "__main__":
|
||||
print("\n---------------- PE Dataset Builder v3.2 ----------------\n")
|
||||
multiprocessing.freeze_support()
|
||||
|
||||
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()
|
||||
print("\n---------------- PE Dataset Builder v4.0 ----------------\n")
|
||||
|
||||
existing_hashes = load_existing_hashes(conn)
|
||||
print(f"[+] Loaded {len(existing_hashes)} hashes from database.\n")
|
||||
existing_hashes = load_existing_hashes()
|
||||
print(f"[+] Found {len(existing_hashes)} existing hashes.\n")
|
||||
|
||||
try:
|
||||
while True:
|
||||
print("-" * 60)
|
||||
raw_path = input("\n[*] Enter Path (File or Folder): ").strip()
|
||||
target_path = raw_path.strip('"').strip("'")
|
||||
|
||||
print("-" * 57)
|
||||
target_path = input("\n[*] Enter path (file or directory): ").strip().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
|
||||
label_input = input("[*] Enter label (0=Safe, 1=Malware): ").strip()
|
||||
if label_input.lower() in ['exit', 'q']: sys.exit()
|
||||
if label_input in ['0', '1']: break
|
||||
print("[-] Please enter 0 or 1.")
|
||||
|
||||
scan_and_save(conn, target_path, label, existing_hashes)
|
||||
scan_and_save(target_path, int(label_input), existing_hashes)
|
||||
|
||||
except KeyboardInterrupt:
|
||||
print("\n\n[-] Interrupted.")
|
||||
finally:
|
||||
conn.close()
|
||||
print(f"\n[+] Database saved to: {os.path.abspath(DB_PATH)}")
|
||||
print("\n\n[-] Aborted.")
|
||||
@@ -1,109 +1,128 @@
|
||||
import os, sys, time, sqlite3, re, json
|
||||
import pandas as pd
|
||||
import os, sys, time, json, onnxmltools
|
||||
import numpy as np
|
||||
import pandas as pd
|
||||
import lightgbm as lgb
|
||||
import onnxmltools
|
||||
import scipy.sparse as sp
|
||||
|
||||
from collections import defaultdict
|
||||
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
|
||||
from sklearn.metrics import accuracy_score, roc_auc_score
|
||||
|
||||
####################################################################################################
|
||||
|
||||
DB_PATH = "pe_features.db"
|
||||
JSONL_PATH = "pe_features.jsonl"
|
||||
MODEL_FILE = "model.txt"
|
||||
ONNX_FILE = "Pefile_General_S1.onnx"
|
||||
FEATURE_FILE = "features.json"
|
||||
TEST_SIZE = 0.0001
|
||||
RANDOM_SEED = 42
|
||||
|
||||
EXCLUDE_FEATURES = {
|
||||
'label', 'filehash',
|
||||
}
|
||||
|
||||
LGBM_PARAMS = {
|
||||
'objective': 'binary',
|
||||
'metric': ['binary_logloss', 'auc'],
|
||||
'boosting_type': 'gbdt',
|
||||
'num_leaves': 512,
|
||||
'learning_rate': 0.05,
|
||||
'num_leaves': 256,
|
||||
'feature_fraction': 0.8,
|
||||
'bagging_fraction': 0.8,
|
||||
'bagging_fraction': 1.0,
|
||||
'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,
|
||||
'min_data_in_leaf': 20,
|
||||
'lambda_l1': 0.01,
|
||||
'lambda_l2': 0.01,
|
||||
}
|
||||
|
||||
####################################################################################################
|
||||
|
||||
def clean_col_name(name):
|
||||
return re.sub(r'[^A-Za-z0-9_]+', '', name)
|
||||
def analyze_schema(jsonl_path):
|
||||
print("[*] Stage 1: Scanning global schema...")
|
||||
base_keys = set()
|
||||
dll_counts = defaultdict(int)
|
||||
api_counts = defaultdict(int)
|
||||
num_lines = 0
|
||||
|
||||
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()
|
||||
with open(jsonl_path, 'r') as f:
|
||||
for line in f:
|
||||
try:
|
||||
data = json.loads(line)
|
||||
base_keys.update(data.get('Base', {}).keys())
|
||||
for d in data.get('DLLs', []):
|
||||
dll_counts[d] += 1
|
||||
for a in data.get('APIs', []):
|
||||
api_counts[a] += 1
|
||||
num_lines += 1
|
||||
except Exception:
|
||||
continue
|
||||
|
||||
####################################################################################################
|
||||
|
||||
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"
|
||||
base_schema = sorted([k for k in base_keys if k.lower().strip() not in EXCLUDE_FEATURES])
|
||||
dll_schema = sorted([f"Dll_{k}" for k, v in dll_counts.items() if v >= 100])
|
||||
api_schema = sorted([f"Api_{k}" for k, v in api_counts.items() if v >= 100])
|
||||
|
||||
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()
|
||||
return base_schema, dll_schema, api_schema, num_lines
|
||||
|
||||
####################################################################################################
|
||||
|
||||
def train_process(X, y):
|
||||
def load_data_efficiently(jsonl_path, base_schema, dll_schema, api_schema, num_lines):
|
||||
print(f"[*] Stage 2: Building sparse feature matrix dynamically (Expected samples: {num_lines})...")
|
||||
final_features = base_schema + dll_schema + api_schema
|
||||
num_features = len(final_features)
|
||||
feature_idx = {feat: i for i, feat in enumerate(final_features)}
|
||||
|
||||
rows, cols, vals = [], [], []
|
||||
y = np.zeros(num_lines, dtype=np.int8)
|
||||
|
||||
with open(jsonl_path, 'r') as f:
|
||||
row_idx = 0
|
||||
for line in f:
|
||||
try:
|
||||
data = json.loads(line)
|
||||
y[row_idx] = data.get('Label', 0)
|
||||
|
||||
for k, v in data.get('Base', {}).items():
|
||||
idx = feature_idx.get(k)
|
||||
if idx is not None:
|
||||
val = float(v)
|
||||
if val != 0.0:
|
||||
rows.append(row_idx)
|
||||
cols.append(idx)
|
||||
vals.append(val)
|
||||
|
||||
for d in data.get('DLLs', []):
|
||||
idx = feature_idx.get(f"Dll_{d}")
|
||||
if idx is not None:
|
||||
rows.append(row_idx)
|
||||
cols.append(idx)
|
||||
vals.append(1.0)
|
||||
|
||||
for a in data.get('APIs', []):
|
||||
idx = feature_idx.get(f"Api_{a}")
|
||||
if idx is not None:
|
||||
rows.append(row_idx)
|
||||
cols.append(idx)
|
||||
vals.append(1.0)
|
||||
|
||||
row_idx += 1
|
||||
except Exception:
|
||||
continue
|
||||
|
||||
y = y[:row_idx]
|
||||
X = sp.csr_matrix((vals, (rows, cols)), shape=(row_idx, num_features), dtype=np.float32)
|
||||
|
||||
return X, pd.Series(y), final_features
|
||||
|
||||
####################################################################################################
|
||||
|
||||
def _calculate_dynamic_lr(current_iter: int) -> float:
|
||||
return max(0.01, 0.1 - (current_iter // 80) * 0.01)
|
||||
|
||||
def train_process(X, y, feature_names):
|
||||
print(f"[*] Dataset shape: {X.shape}")
|
||||
|
||||
X_train, X_test, y_train, y_test = train_test_split(
|
||||
@@ -117,11 +136,11 @@ def train_process(X, y):
|
||||
weight_ratio_target = 0.005
|
||||
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}")
|
||||
print(f"[*] Sample distribution: Safe={n_neg}, Malware={n_pos}")
|
||||
print(f"[*] Weight correction: Base={base_weight:.4f}, Final={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)
|
||||
train_data = lgb.Dataset(X_train, label=y_train, feature_name=feature_names)
|
||||
valid_data = lgb.Dataset(X_test, label=y_test, reference=train_data, feature_name=feature_names)
|
||||
|
||||
params = LGBM_PARAMS.copy()
|
||||
params['scale_pos_weight'] = final_pos_weight
|
||||
@@ -133,14 +152,15 @@ def train_process(X, y):
|
||||
params,
|
||||
train_data,
|
||||
valid_sets=[valid_data],
|
||||
num_boost_round=1000,
|
||||
num_boost_round=500,
|
||||
callbacks=[
|
||||
lgb.log_evaluation(period=100)
|
||||
lgb.log_evaluation(period=50),
|
||||
lgb.reset_parameter(learning_rate=_calculate_dynamic_lr)
|
||||
]
|
||||
)
|
||||
|
||||
elapsed = time.time() - start_time
|
||||
print(f"\n[+] Training finished in {elapsed:.2f}s")
|
||||
print(f"\n[+] Training complete, time elapsed {elapsed:.2f} seconds")
|
||||
|
||||
return model, X_test, y_test
|
||||
|
||||
@@ -156,12 +176,10 @@ def export_onnx_model(model, feature_names):
|
||||
with open(FEATURE_FILE, 'w') as f:
|
||||
json.dump(feature_names, f)
|
||||
|
||||
print(f"[+] ONNX export success. Features synced to {FEATURE_FILE}")
|
||||
print(f"[+] ONNX export successful. Feature order saved 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)
|
||||
@@ -170,12 +188,25 @@ def evaluate_and_save(model, X_test, y_test):
|
||||
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))
|
||||
tp = np.sum((y_test == 1) & (y_pred == 1))
|
||||
fn = np.sum((y_test == 1) & (y_pred == 0))
|
||||
tn = np.sum((y_test == 0) & (y_pred == 0))
|
||||
fp = np.sum((y_test == 0) & (y_pred == 1))
|
||||
|
||||
total_malware = tp + fn
|
||||
total_safe = tn + fp
|
||||
|
||||
detection_rate = (tp / total_malware * 100) if total_malware > 0 else 0.0
|
||||
fpr = (fp / total_safe * 100) if total_safe > 0 else 0.0
|
||||
|
||||
print(f"Accuracy : {acc:.6f}")
|
||||
print(f"ROC AUC : {auc:.6f}\n")
|
||||
|
||||
print(f"True Positive (Class 1) : {detection_rate:.3f}% ({tp}/{total_malware})")
|
||||
print(f"False Positive (Class 0) : {fpr:.3f}% ({fp}/{total_safe})")
|
||||
|
||||
print("---------------------------------------------------------\n")
|
||||
print("[*] Top 20 Features (Importance by Gain):")
|
||||
print("\n---------------------------------------------------------\n")
|
||||
print("[*] Top 20 Features:")
|
||||
|
||||
feature_names = model.feature_name()
|
||||
importance = model.feature_importance(importance_type='gain')
|
||||
@@ -192,25 +223,27 @@ def evaluate_and_save(model, X_test, y_test):
|
||||
####################################################################################################
|
||||
|
||||
if __name__ == "__main__":
|
||||
print("\n---------------- PE Malware Trainer v3.1 ----------------\n")
|
||||
print("\n---------------- PE Malware Trainer v4.0 ----------------\n")
|
||||
|
||||
if not os.path.exists(DB_PATH):
|
||||
print(f"[-] Error: Database {DB_PATH} not found.")
|
||||
if not os.path.exists(JSONL_PATH):
|
||||
print(f"[-] Error: Feature file {JSONL_PATH} not found.")
|
||||
sys.exit(1)
|
||||
|
||||
raw_schema = get_raw_schema(DB_PATH)
|
||||
if not raw_schema:
|
||||
print("[-] Error: Could not read schema.")
|
||||
base_schema, dll_schema, api_schema, num_lines = analyze_schema(JSONL_PATH)
|
||||
if num_lines == 0:
|
||||
print("[-] Error: Feature file is empty.")
|
||||
sys.exit(1)
|
||||
|
||||
X, y, _ = load_data(DB_PATH, raw_schema)
|
||||
if X is None:
|
||||
sys.exit(1)
|
||||
print(f"[*] Detected Base features: {len(base_schema)}")
|
||||
print(f"[*] Filtered DLL features: {len(dll_schema)}")
|
||||
print(f"[*] Filtered API features: {len(api_schema)}")
|
||||
|
||||
X, y, final_features = load_data_efficiently(JSONL_PATH, base_schema, dll_schema, api_schema, num_lines)
|
||||
|
||||
try:
|
||||
model, X_test, y_test = train_process(X, y)
|
||||
model, X_test, y_test = train_process(X, y, final_features)
|
||||
evaluate_and_save(model, X_test, y_test)
|
||||
except KeyboardInterrupt:
|
||||
print("\n\n[-] Training interrupted.")
|
||||
print("\n\n[-] Training aborted.")
|
||||
except Exception as e:
|
||||
print(f"\n[-] Critical Error: {e}")
|
||||
print(f"\n[-] System exception: {e}")
|
||||
@@ -1,216 +0,0 @@
|
||||
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}")
|
||||
+131
-98
@@ -1,109 +1,128 @@
|
||||
import os, sys, time, sqlite3, re, json
|
||||
import pandas as pd
|
||||
import os, sys, time, json, onnxmltools
|
||||
import numpy as np
|
||||
import pandas as pd
|
||||
import lightgbm as lgb
|
||||
import onnxmltools
|
||||
import scipy.sparse as sp
|
||||
|
||||
from collections import defaultdict
|
||||
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
|
||||
from sklearn.metrics import accuracy_score, roc_auc_score
|
||||
|
||||
####################################################################################################
|
||||
|
||||
DB_PATH = "pe_features.db"
|
||||
JSONL_PATH = "pe_features.jsonl"
|
||||
MODEL_FILE = "model.txt"
|
||||
ONNX_FILE = "Pefile_General_T1.onnx"
|
||||
FEATURE_FILE = "features.json"
|
||||
TEST_SIZE = 0.0001
|
||||
RANDOM_SEED = 42
|
||||
|
||||
EXCLUDE_FEATURES = {
|
||||
'label', 'filehash',
|
||||
}
|
||||
|
||||
LGBM_PARAMS = {
|
||||
'objective': 'binary',
|
||||
'metric': ['binary_logloss', 'auc'],
|
||||
'boosting_type': 'gbdt',
|
||||
'num_leaves': 64,
|
||||
'learning_rate': 0.05,
|
||||
'feature_fraction': 0.8,
|
||||
'bagging_fraction': 0.8,
|
||||
'bagging_fraction': 1.0,
|
||||
'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,
|
||||
'min_data_in_leaf': 20,
|
||||
'lambda_l1': 0.01,
|
||||
'lambda_l2': 0.01,
|
||||
}
|
||||
|
||||
####################################################################################################
|
||||
|
||||
def clean_col_name(name):
|
||||
return re.sub(r'[^A-Za-z0-9_]+', '', name)
|
||||
def analyze_schema(jsonl_path):
|
||||
print("[*] Stage 1: Scanning global schema...")
|
||||
base_keys = set()
|
||||
dll_counts = defaultdict(int)
|
||||
api_counts = defaultdict(int)
|
||||
num_lines = 0
|
||||
|
||||
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()
|
||||
with open(jsonl_path, 'r') as f:
|
||||
for line in f:
|
||||
try:
|
||||
data = json.loads(line)
|
||||
base_keys.update(data.get('Base', {}).keys())
|
||||
for d in data.get('DLLs', []):
|
||||
dll_counts[d] += 1
|
||||
for a in data.get('APIs', []):
|
||||
api_counts[a] += 1
|
||||
num_lines += 1
|
||||
except Exception:
|
||||
continue
|
||||
|
||||
####################################################################################################
|
||||
|
||||
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"
|
||||
base_schema = sorted([k for k in base_keys if k.lower().strip() not in EXCLUDE_FEATURES])
|
||||
dll_schema = sorted([f"Dll_{k}" for k, v in dll_counts.items() if v >= 100])
|
||||
api_schema = sorted([f"Api_{k}" for k, v in api_counts.items() if v >= 100])
|
||||
|
||||
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()
|
||||
return base_schema, dll_schema, api_schema, num_lines
|
||||
|
||||
####################################################################################################
|
||||
|
||||
def train_process(X, y):
|
||||
def load_data_efficiently(jsonl_path, base_schema, dll_schema, api_schema, num_lines):
|
||||
print(f"[*] Stage 2: Building sparse feature matrix dynamically (Expected samples: {num_lines})...")
|
||||
final_features = base_schema + dll_schema + api_schema
|
||||
num_features = len(final_features)
|
||||
feature_idx = {feat: i for i, feat in enumerate(final_features)}
|
||||
|
||||
rows, cols, vals = [], [], []
|
||||
y = np.zeros(num_lines, dtype=np.int8)
|
||||
|
||||
with open(jsonl_path, 'r') as f:
|
||||
row_idx = 0
|
||||
for line in f:
|
||||
try:
|
||||
data = json.loads(line)
|
||||
y[row_idx] = data.get('Label', 0)
|
||||
|
||||
for k, v in data.get('Base', {}).items():
|
||||
idx = feature_idx.get(k)
|
||||
if idx is not None:
|
||||
val = float(v)
|
||||
if val != 0.0:
|
||||
rows.append(row_idx)
|
||||
cols.append(idx)
|
||||
vals.append(val)
|
||||
|
||||
for d in data.get('DLLs', []):
|
||||
idx = feature_idx.get(f"Dll_{d}")
|
||||
if idx is not None:
|
||||
rows.append(row_idx)
|
||||
cols.append(idx)
|
||||
vals.append(1.0)
|
||||
|
||||
for a in data.get('APIs', []):
|
||||
idx = feature_idx.get(f"Api_{a}")
|
||||
if idx is not None:
|
||||
rows.append(row_idx)
|
||||
cols.append(idx)
|
||||
vals.append(1.0)
|
||||
|
||||
row_idx += 1
|
||||
except Exception:
|
||||
continue
|
||||
|
||||
y = y[:row_idx]
|
||||
X = sp.csr_matrix((vals, (rows, cols)), shape=(row_idx, num_features), dtype=np.float32)
|
||||
|
||||
return X, pd.Series(y), final_features
|
||||
|
||||
####################################################################################################
|
||||
|
||||
def _calculate_dynamic_lr(current_iter: int) -> float:
|
||||
return max(0.01, 0.1 - (current_iter // 80) * 0.01)
|
||||
|
||||
def train_process(X, y, feature_names):
|
||||
print(f"[*] Dataset shape: {X.shape}")
|
||||
|
||||
X_train, X_test, y_train, y_test = train_test_split(
|
||||
@@ -117,11 +136,11 @@ def train_process(X, y):
|
||||
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}")
|
||||
print(f"[*] Sample distribution: Safe={n_neg}, Malware={n_pos}")
|
||||
print(f"[*] Weight correction: Base={base_weight:.4f}, Final={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)
|
||||
train_data = lgb.Dataset(X_train, label=y_train, feature_name=feature_names)
|
||||
valid_data = lgb.Dataset(X_test, label=y_test, reference=train_data, feature_name=feature_names)
|
||||
|
||||
params = LGBM_PARAMS.copy()
|
||||
params['scale_pos_weight'] = final_pos_weight
|
||||
@@ -133,14 +152,15 @@ def train_process(X, y):
|
||||
params,
|
||||
train_data,
|
||||
valid_sets=[valid_data],
|
||||
num_boost_round=1000,
|
||||
num_boost_round=500,
|
||||
callbacks=[
|
||||
lgb.log_evaluation(period=100)
|
||||
lgb.log_evaluation(period=50),
|
||||
lgb.reset_parameter(learning_rate=_calculate_dynamic_lr)
|
||||
]
|
||||
)
|
||||
|
||||
elapsed = time.time() - start_time
|
||||
print(f"\n[+] Training finished in {elapsed:.2f}s")
|
||||
print(f"\n[+] Training complete, time elapsed {elapsed:.2f} seconds")
|
||||
|
||||
return model, X_test, y_test
|
||||
|
||||
@@ -156,12 +176,10 @@ def export_onnx_model(model, feature_names):
|
||||
with open(FEATURE_FILE, 'w') as f:
|
||||
json.dump(feature_names, f)
|
||||
|
||||
print(f"[+] ONNX export success. Features synced to {FEATURE_FILE}")
|
||||
print(f"[+] ONNX export successful. Feature order saved 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)
|
||||
@@ -170,12 +188,25 @@ def evaluate_and_save(model, X_test, y_test):
|
||||
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))
|
||||
tp = np.sum((y_test == 1) & (y_pred == 1))
|
||||
fn = np.sum((y_test == 1) & (y_pred == 0))
|
||||
tn = np.sum((y_test == 0) & (y_pred == 0))
|
||||
fp = np.sum((y_test == 0) & (y_pred == 1))
|
||||
|
||||
total_malware = tp + fn
|
||||
total_safe = tn + fp
|
||||
|
||||
detection_rate = (tp / total_malware * 100) if total_malware > 0 else 0.0
|
||||
fpr = (fp / total_safe * 100) if total_safe > 0 else 0.0
|
||||
|
||||
print(f"Accuracy : {acc:.6f}")
|
||||
print(f"ROC AUC : {auc:.6f}\n")
|
||||
|
||||
print(f"True Positive (Class 1) : {detection_rate:.3f}% ({tp}/{total_malware})")
|
||||
print(f"False Positive (Class 0) : {fpr:.3f}% ({fp}/{total_safe})")
|
||||
|
||||
print("---------------------------------------------------------\n")
|
||||
print("[*] Top 20 Features (Importance by Gain):")
|
||||
print("\n---------------------------------------------------------\n")
|
||||
print("[*] Top 20 Features:")
|
||||
|
||||
feature_names = model.feature_name()
|
||||
importance = model.feature_importance(importance_type='gain')
|
||||
@@ -192,25 +223,27 @@ def evaluate_and_save(model, X_test, y_test):
|
||||
####################################################################################################
|
||||
|
||||
if __name__ == "__main__":
|
||||
print("\n---------------- PE Malware Trainer v3.1 ----------------\n")
|
||||
print("\n---------------- PE Malware Trainer v4.0 ----------------\n")
|
||||
|
||||
if not os.path.exists(DB_PATH):
|
||||
print(f"[-] Error: Database {DB_PATH} not found.")
|
||||
if not os.path.exists(JSONL_PATH):
|
||||
print(f"[-] Error: Feature file {JSONL_PATH} not found.")
|
||||
sys.exit(1)
|
||||
|
||||
raw_schema = get_raw_schema(DB_PATH)
|
||||
if not raw_schema:
|
||||
print("[-] Error: Could not read schema.")
|
||||
base_schema, dll_schema, api_schema, num_lines = analyze_schema(JSONL_PATH)
|
||||
if num_lines == 0:
|
||||
print("[-] Error: Feature file is empty.")
|
||||
sys.exit(1)
|
||||
|
||||
X, y, _ = load_data(DB_PATH, raw_schema)
|
||||
if X is None:
|
||||
sys.exit(1)
|
||||
print(f"[*] Detected Base features: {len(base_schema)}")
|
||||
print(f"[*] Filtered DLL features: {len(dll_schema)}")
|
||||
print(f"[*] Filtered API features: {len(api_schema)}")
|
||||
|
||||
X, y, final_features = load_data_efficiently(JSONL_PATH, base_schema, dll_schema, api_schema, num_lines)
|
||||
|
||||
try:
|
||||
model, X_test, y_test = train_process(X, y)
|
||||
model, X_test, y_test = train_process(X, y, final_features)
|
||||
evaluate_and_save(model, X_test, y_test)
|
||||
except KeyboardInterrupt:
|
||||
print("\n\n[-] Training interrupted.")
|
||||
print("\n\n[-] Training aborted.")
|
||||
except Exception as e:
|
||||
print(f"\n[-] Critical Error: {e}")
|
||||
print(f"\n[-] System exception: {e}")
|
||||
@@ -56,7 +56,7 @@ body {
|
||||
}
|
||||
|
||||
.navigate-title {
|
||||
font-size: 16px;
|
||||
font-size: 17px;
|
||||
font-weight: 600;
|
||||
letter-spacing: -0.2px;
|
||||
}
|
||||
@@ -797,6 +797,44 @@ body.resizing-active * {
|
||||
color: var(--text-primary);
|
||||
}
|
||||
|
||||
.custom-context-menu {
|
||||
position: absolute;
|
||||
z-index: 10000;
|
||||
background-color: var(--bg-panel);
|
||||
border: 1px solid var(--border-color);
|
||||
border-radius: 8px;
|
||||
box-shadow: 0 4px 12px rgba(0, 0, 0, 0.1);
|
||||
padding: 4px;
|
||||
display: none;
|
||||
min-width: 160px;
|
||||
}
|
||||
|
||||
.custom-context-menu.show {
|
||||
display: block;
|
||||
}
|
||||
|
||||
.custom-context-menu-item {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 10px;
|
||||
padding: 8px 12px;
|
||||
border-radius: 6px;
|
||||
cursor: pointer;
|
||||
font-size: 13px;
|
||||
color: var(--text-primary);
|
||||
transition: background-color 0.2s;
|
||||
}
|
||||
|
||||
.custom-context-menu-item:hover {
|
||||
background-color: var(--bg-hover);
|
||||
}
|
||||
|
||||
.custom-context-menu-item svg {
|
||||
width: 16px;
|
||||
height: 16px;
|
||||
color: var(--text-secondary);
|
||||
}
|
||||
|
||||
::-webkit-scrollbar { width: 6px; height: 6px; }
|
||||
::-webkit-scrollbar-track { background: transparent; }
|
||||
::-webkit-scrollbar-thumb { background: var(--border-color); border-radius: 10px; }
|
||||
|
||||
+1474
-1364
File diff suppressed because it is too large.
Load diff
+756
-910
File diff suppressed because it is too large.
Load diff
@@ -60,13 +60,13 @@
|
||||
<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>
|
||||
<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>
|
||||
<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>
|
||||
@@ -81,7 +81,7 @@
|
||||
<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>
|
||||
<p class="section-desc" id="progress_text" data-i18n>此選項可以選擇進階掃描方式</p>
|
||||
</div>
|
||||
<div class="action-group modern-action-group">
|
||||
<select id="scan_method_select" class="modern-select">
|
||||
@@ -105,37 +105,37 @@
|
||||
<div class="list-widget">
|
||||
<div class="list-item">
|
||||
<div class="item-info">
|
||||
<h2 data-i18n>進程防護</h2><p data-i18n>此選項可以攔截進程病毒</p>
|
||||
<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>
|
||||
<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>
|
||||
<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>
|
||||
<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>
|
||||
<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>
|
||||
<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>
|
||||
@@ -158,7 +158,7 @@
|
||||
</div>
|
||||
<div class="list-item" style="cursor: pointer;" data-target="repair_window">
|
||||
<div class="item-info">
|
||||
<h2 data-i18n>系統修復</h2><p data-i18n>此選項可以修復系統註冊項目</p>
|
||||
<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>
|
||||
@@ -170,13 +170,13 @@
|
||||
</div>
|
||||
<div class="list-item" style="cursor: pointer;" data-target="whitelist_window">
|
||||
<div class="item-info">
|
||||
<h2 data-i18n>白名單</h2><p data-i18n>此選項可以管理系統白名單</p>
|
||||
<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>
|
||||
<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>
|
||||
@@ -187,25 +187,25 @@
|
||||
<div class="list-widget">
|
||||
<div class="list-item">
|
||||
<div class="item-info">
|
||||
<h2 data-i18n>增強模式</h2><p data-i18n>此選項可以增強掃描引擎</p>
|
||||
<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>
|
||||
<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>
|
||||
<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>
|
||||
<h2 data-i18n>選單掃描</h2><p data-i18n>此選項可以啟用右鍵掃描功能</p>
|
||||
</div>
|
||||
<label class="toggle-switch"><input type="checkbox"><span class="slider"></span></label>
|
||||
</div>
|
||||
|
||||
+167
-395
@@ -162,208 +162,6 @@ class rule_scanner:
|
||||
####################################################################################################
|
||||
|
||||
class pe_scanner:
|
||||
API_CAT_MAPPING = {
|
||||
'ProcessControl': {
|
||||
'CreateProcessA', 'CreateProcessW', 'WinExec', 'ShellExecuteA', 'ShellExecuteW',
|
||||
'ShellExecuteExW', 'ExitProcess', 'TerminateProcess', 'OpenProcess', 'GetExitCodeProcess',
|
||||
'SetThreadPriority', 'GetThreadPriority', 'GetCurrentProcess', 'GetCurrentProcessId',
|
||||
'GetModuleFileNameA', 'GetCommandLineA', 'GetModuleFileNameW', 'GetStartupInfoW'
|
||||
},
|
||||
'Injection': {
|
||||
'VirtualAlloc', 'VirtualAllocEx', 'VirtualProtect', 'VirtualProtectEx', 'WriteProcessMemory',
|
||||
'ReadProcessMemory', 'CreateRemoteThread', 'QueueUserAPC', 'SetThreadContext', 'GetThreadContext',
|
||||
'Wow64SetThreadContext', 'NtUnmapViewOfSection', 'ZwUnmapViewOfSection', 'RtlCreateUserThread',
|
||||
'SetWindowsHookExA', 'SetWindowsHookExW', 'UnhookWindowsHookEx', 'LoadLibraryA', 'LoadLibraryW',
|
||||
'LoadLibraryExA', 'LoadLibraryExW', 'GetProcAddress', 'GetModuleHandleA', 'GetModuleHandleW',
|
||||
'FreeLibrary', 'CreateFileMappingA', 'CreateFileMappingW', 'MapViewOfFile', 'UnmapViewOfSection',
|
||||
'VirtualFree', 'VirtualQuery'
|
||||
},
|
||||
'Synchronization': {
|
||||
'WaitForSingleObject', 'WaitForSingleObjectEx', 'WaitForMultipleObjects', 'WaitForMultipleObjectsEx',
|
||||
'CreateMutexA', 'CreateMutexW', 'OpenMutexW', 'ReleaseMutex', 'CreateEventA', 'CreateEventW',
|
||||
'OpenEventW', 'SetEvent', 'ResetEvent', 'EnterCriticalSection', 'LeaveCriticalSection',
|
||||
'InitializeCriticalSection', 'DeleteCriticalSection', 'Sleep', 'SleepEx',
|
||||
'InitializeCriticalSectionAndSpinCount', 'InterlockedDecrement', 'InterlockedIncrement',
|
||||
'InterlockedExchange', 'InterlockedCompareExchange', 'SetTimer', 'KillTimer'
|
||||
},
|
||||
'MultiThreading': {
|
||||
'CreateThread', 'ResumeThread', 'SuspendThread', 'ExitThread', 'TerminateThread',
|
||||
'GetCurrentThread', 'GetCurrentThreadId', 'TlsAlloc', 'TlsSetValue', 'TlsGetValue',
|
||||
'CreateThreadpoolWork', 'SubmitThreadpoolWork', 'TlsFree'
|
||||
},
|
||||
'Network': {
|
||||
'socket', 'connect', 'send', 'recv', 'bind', 'listen', 'accept', 'gethostbyname', 'getaddrinfo',
|
||||
'WSAStartup', 'WSACleanup', 'WSAIoctl', 'WSASocketA', 'WSASocketW', 'closesocket', 'htons',
|
||||
'InternetOpenA', 'InternetOpenW', 'InternetConnectA', 'InternetConnectW', 'InternetOpenUrlA',
|
||||
'InternetOpenUrlW', 'HttpOpenRequestA', 'HttpOpenRequestW', 'HttpSendRequestA', 'HttpSendRequestW',
|
||||
'InternetReadFile', 'URLDownloadToFileA', 'URLDownloadToFileW', 'DnsQuery_A', 'DnsQuery_W'
|
||||
},
|
||||
'Encryption': {
|
||||
'CryptAcquireContextA', 'CryptAcquireContextW', 'CryptCreateHash', 'CryptHashData',
|
||||
'CryptDeriveKey', 'CryptEncrypt', 'CryptDecrypt', 'CryptDestroyKey', 'CryptDestroyHash',
|
||||
'CryptReleaseContext', 'CryptGenKey', 'CryptImportKey', 'CryptExportKey'
|
||||
},
|
||||
'DataObfuscation': {
|
||||
'RtlDecompressBuffer', 'MultiByteToWideChar', 'WideCharToMultiByte', 'Base64Decode',
|
||||
'CryptDecodeObject', 'IsDBCSLeadByte', 'CharUpperA', 'CharLowerA',
|
||||
'GetStringTypeW', 'LCMapStringW', 'IsValidCodePage', 'DecodePointer', 'EncodePointer'
|
||||
},
|
||||
'FileIO': {
|
||||
'CreateFileA', 'CreateFileW', 'WriteFile', 'ReadFile', 'DeleteFileA', 'DeleteFileW',
|
||||
'CopyFileA', 'CopyFileW', 'MoveFileA', 'MoveFileW', 'FindFirstFileA', 'FindNextFileA',
|
||||
'GetTempPathA', 'GetTempPathW', 'GetTempFileNameA', 'GetTempFileNameW', 'SetFileAttributesA',
|
||||
'SetFileAttributesW', 'DeviceIoControl', 'SetFileTime', 'GetFileSize', 'GetFileSizeEx',
|
||||
'SetFilePointer', 'FlushFileBuffers', 'ConnectNamedPipe', 'PeekNamedPipe',
|
||||
'CloseHandle', 'GetFileType', 'SetStdHandle', 'SetFilePointerEx', 'FindClose',
|
||||
'SetHandleCount', 'SetEndOfFile', 'GetFullPathNameA', 'GetFileAttributesA'
|
||||
},
|
||||
'Registry': {
|
||||
'RegOpenKeyExA', 'RegOpenKeyExW', 'RegSetValueExA', 'RegSetValueExW',
|
||||
'RegCreateKeyExA', 'RegCreateKeyExW', 'RegDeleteKeyA', 'RegDeleteKeyW',
|
||||
'RegEnumValueA', 'RegEnumValueW', 'RegQueryValueExA', 'RegQueryValueExW',
|
||||
'RegDeleteValueA', 'RegDeleteValueW', 'RegCloseKey'
|
||||
},
|
||||
'Services': {
|
||||
'OpenSCManagerA', 'OpenSCManagerW', 'CreateServiceA', 'CreateServiceW',
|
||||
'StartServiceA', 'StartServiceW', 'ControlService', 'DeleteService'
|
||||
},
|
||||
'Privileges': {
|
||||
'AdjustTokenPrivileges', 'LookupPrivilegeValueA', 'LookupPrivilegeValueW',
|
||||
'OpenProcessToken', 'NetUserAdd', 'NetLocalGroupAddMembers', 'IsAdmin',
|
||||
'GetUserNameA', 'GetUserNameW'
|
||||
},
|
||||
'Native': {
|
||||
'RtlUnwind', 'RtlVirtualUnwind', 'RtlCaptureContext', 'RtlLookupFunctionEntry',
|
||||
'NtClose', 'NtQueryInformationProcess', 'RtlAllocateHeap', 'RtlFreeHeap',
|
||||
'GetSystemTimeAsFileTime', 'GetLocalTime', 'GlobalMemoryStatus',
|
||||
'GetVersionExA', 'GetVersionExW', 'GetComputerNameA', 'GetComputerNameW',
|
||||
'GetLastError', 'GetACP', 'RaiseException', 'SetLastError', 'GetEnvironmentStringsW',
|
||||
'FreeEnvironmentStringsW', 'GetLocaleInfoA', 'lstrlenA', 'GetVersion', 'GetSystemInfo',
|
||||
'CompareStringA'
|
||||
},
|
||||
'DotNet': {
|
||||
'_CorExeMain', '_CorDllMain'
|
||||
},
|
||||
'AntiDebug': {
|
||||
'IsDebuggerPresent', 'CheckRemoteDebuggerPresent', 'OutputDebugStringA', 'OutputDebugStringW',
|
||||
'GetTickCount', 'GetTickCount64', 'QueryPerformanceCounter', 'FindWindowA', 'FindWindowW',
|
||||
'EnumWindows', 'EnumChildWindows', 'GetWindowRect', 'GetClientRect', 'SetWindowDisplayAffinity',
|
||||
'UnhandledExceptionFilter', 'SetUnhandledExceptionFilter', 'IsProcessorFeaturePresent',
|
||||
'SetErrorMode', 'GetTimeZoneInformation', 'GetDriveTypeW', 'GetDiskFreeSpaceA'
|
||||
},
|
||||
'Keylogging': {
|
||||
'GetAsyncKeyState', 'GetKeyState', 'GetKeyboardState', 'MapVirtualKeyA', 'MapVirtualKeyW',
|
||||
'ToAscii', 'ToAsciiEx', 'ToUnicode', 'ToUnicodeEx', 'GetKeyNameTextA', 'GetForegroundWindow',
|
||||
'KeybdEvent', 'SendInput', 'MapVirtualKeyExA', 'MapVirtualKeyExW', 'CallNextHookEx'
|
||||
},
|
||||
'Input': {
|
||||
'GetCursorPos', 'SetCursorPos', 'MouseEvent', 'GetDoubleClickTime',
|
||||
'GetCapture', 'SetCapture'
|
||||
},
|
||||
'ScreenCapture': {
|
||||
'BitBlt', 'StretchBlt', 'GetDC', 'GetWindowDC', 'CreateCompatibleDC', 'CreateCompatibleBitmap',
|
||||
'GdipSaveImageToFile', 'PrintWindow', 'GetDesktopWindow', 'CreateDCW', 'CreateDCA',
|
||||
'SelectObject', 'DeleteDC', 'DeleteObject', 'GetDeviceCaps', 'GetSystemMetrics'
|
||||
},
|
||||
'Graphics': {
|
||||
'Direct3DCreate9', 'D3D11CreateDevice'
|
||||
},
|
||||
'Audio': {
|
||||
'WaveInOpen', 'WaveInClose', 'WaveInStart', 'WaveInStop'
|
||||
},
|
||||
'Clipboard': {
|
||||
'OpenClipboard', 'CloseClipboard', 'GetClipboardData', 'SetClipboardData'
|
||||
},
|
||||
'Camera': {
|
||||
'CapCreateCaptureWindowA', 'CapCreateCaptureWindowW'
|
||||
},
|
||||
'Memory': {
|
||||
'HeapAlloc', 'HeapFree', 'HeapSize', 'HeapCreate', 'GetProcessHeap',
|
||||
'LocalAlloc', 'LocalFree', 'GlobalAlloc', 'GlobalFree', 'GlobalUnlock',
|
||||
'HeapDestroy', 'GlobalLock', 'SysFreeString', 'SysAllocStringLen'
|
||||
},
|
||||
'Resource': {
|
||||
'LoadResource', 'SizeofResource', 'LockResource', 'FreeResource', 'FindResourceA'
|
||||
},
|
||||
'WindowControl': {
|
||||
'ShowWindow', 'DestroyWindow', 'TranslateMessage', 'DispatchMessageA', 'GetWindow',
|
||||
'PeekMessageA', 'GetWindowLongA', 'CallWindowProcA', 'SetWindowLongA', 'GetWindowTextA',
|
||||
'ScreenToClient', 'GetActiveWindow', 'CreateWindowExA', 'DefWindowProcA', 'GetMessageA',
|
||||
'RegisterClassA', 'UnregisterClassA', 'MessageBoxA'
|
||||
},
|
||||
'COM': {
|
||||
'CoCreateInstance', 'CoUninitialize', 'CoInitialize'
|
||||
}
|
||||
}
|
||||
|
||||
TARGET_DLLS = {
|
||||
'kernel32.dll', 'user32.dll', 'gdi32.dll', 'advapi32.dll', 'shell32.dll', 'ntdll.dll', 'hal.dll',
|
||||
'ws2_32.dll', 'wsock32.dll', 'wininet.dll', 'winhttp.dll', 'urlmon.dll', 'crypt32.dll', 'bcrypt.dll',
|
||||
'psapi.dll', 'dbghelp.dll', 'imagehlp.dll', 'shlwapi.dll', 'ole32.dll', 'oleaut32.dll', 'comctl32.dll',
|
||||
'mscoree.dll', 'vbscript.dll', 'netapi32.dll', 'iphlpapi.dll', 'wtsapi32.dll', 'version.dll', 'winmm.dll'
|
||||
}
|
||||
|
||||
BASE_SCHEMA = [
|
||||
"FileEntropy", "SectionMeanEntropy", "IsExe", "IsDll", "IsDriver", "Is64Bit",
|
||||
"Machine", "Magic", "CheckSum", "ImageBase", "SizeOfImage", "SizeOfHeaders",
|
||||
"Characteristics", "DllCharacteristics", "Subsystem", "LoaderFlags",
|
||||
"MajorLinkerVersion", "MinorLinkerVersion", "SizeOfCode",
|
||||
"SizeOfInitializedData", "SizeOfUninitializedData", "AddressOfEntryPoint",
|
||||
"BaseOfCode", "SectionAlignment", "FileAlignment",
|
||||
"MajorOperatingSystemVersion", "MinorOperatingSystemVersion",
|
||||
"MajorImageVersion", "MinorImageVersion",
|
||||
"MajorSubsystemVersion", "MinorSubsystemVersion",
|
||||
"SizeOfStackReserve", "SizeOfStackCommit", "SizeOfHeapReserve", "SizeOfHeapCommit",
|
||||
"NumberOfSections", "NumberOfRvaAndSizes", "PointerToSymbolTable", "NumberOfSymbols",
|
||||
"SizeOfOptionalHeader",
|
||||
"TextSectionMaxEntropy", "TextSectionMeanEntropy", "TextSizeRatio",
|
||||
"DataSectionMaxEntropy", "DataSectionMeanEntropy", "DataSizeRatio",
|
||||
"RsrcSectionMaxEntropy", "RsrcSectionMeanEntropy", "RsrcSizeRatio",
|
||||
"SectionCount", "ExecutableSections",
|
||||
"WritableSections", "ReadableSections", "SectionException",
|
||||
"Char_00000020_Count", "Char_00000020_MeanEntropy",
|
||||
"Char_00000040_Count", "Char_00000040_MeanEntropy",
|
||||
"Char_00000080_Count", "Char_00000080_MeanEntropy",
|
||||
"Char_02000000_Count", "Char_02000000_MeanEntropy",
|
||||
"Char_20000000_Count", "Char_20000000_MeanEntropy",
|
||||
"Char_40000000_Count", "Char_40000000_MeanEntropy",
|
||||
"Char_80000000_Count", "Char_80000000_MeanEntropy",
|
||||
"IconCount", "ApiCount", "ExportCount", "DebugCount", "ExceptionCount",
|
||||
"ImportCount", "ImportFunctionCount",
|
||||
"FileDescriptionLength", "FileVersionLength", "ProductNameLength",
|
||||
"ProductVersionLength", "CompanyNameLength", "LegalCopyrightLength",
|
||||
"CommentsLength", "InternalNameLength", "LegalTrademarksLength",
|
||||
"SpecialBuildLength", "PrivateBuildLength",
|
||||
"TrustSigned", "IsDebug", "IsPatched", "IsPrivateBuild", "IsPreRelease",
|
||||
"IsSpecialBuild", "IsAdmin", "IsInstall", "HasTlsCallbacks",
|
||||
"HasInvalidTimestamp", "HasRelocationDirectory", "HasPacked", "FileTimeException"
|
||||
]
|
||||
|
||||
CAT_SCHEMA = [f"Cat_{k}" for k in sorted(API_CAT_MAPPING.keys())]
|
||||
DLL_SCHEMA = [f"Dll_{d.replace('.', '_')}" for d in sorted(TARGET_DLLS)]
|
||||
|
||||
_ALL_APIS = {api for apis in API_CAT_MAPPING.values() for api in apis}
|
||||
API_SCHEMA = [f"Api_{api}" for api in sorted(_ALL_APIS)]
|
||||
SCHEMA = BASE_SCHEMA + CAT_SCHEMA + DLL_SCHEMA + API_SCHEMA
|
||||
|
||||
_API_TO_CATS = {}
|
||||
for _cat, _apis in API_CAT_MAPPING.items():
|
||||
for _api in _apis:
|
||||
if _api not in _API_TO_CATS:
|
||||
_API_TO_CATS[_api] = []
|
||||
_API_TO_CATS[_api].append(_cat)
|
||||
|
||||
PACKERS = {
|
||||
'upx', 'aspack', 'asprotect', 'pecompact', 'upack', 'fsg', 'mew',
|
||||
'mpress', 'ezip', 'pklt', 'shrink', 'petite', 'telock',
|
||||
'themida', 'winlicense', 'tmd', 'vmp', 'enigma', 'obsidium',
|
||||
'pelock', 'exestealth', 'yoda', 'armadillo', 'zprotect',
|
||||
'sforce', 'starforce', 'qihoo', 'wisevec', 'megastop',
|
||||
}
|
||||
INSTALLER_SIGS = [b"Nullsoft.NSIS", b"Inno Setup", b"7-Zip.7zip", b"InstallShield"]
|
||||
|
||||
####################################################################################################
|
||||
|
||||
def __init__(self):
|
||||
self.model = None
|
||||
self.input_name = None
|
||||
@@ -387,168 +185,163 @@ class pe_scanner:
|
||||
self.model = onnxruntime.InferenceSession(model_path, providers=['CPUExecutionProvider'])
|
||||
self.input_name = self.model.get_inputs()[0].name
|
||||
feat_path = os.path.join(os.path.dirname(model_path), "features.json")
|
||||
|
||||
model_features = []
|
||||
if os.path.exists(feat_path):
|
||||
with open(feat_path, 'r') as f:
|
||||
model_features = json.load(f)
|
||||
else:
|
||||
model_features = [re.sub(r'[^A-Za-z0-9_]+', '', f) for f in self.SCHEMA]
|
||||
|
||||
local_map = {re.sub(r'[^A-Za-z0-9_]+', '', raw_name): raw_name for raw_name in self.SCHEMA}
|
||||
self.feature_order = [local_map.get(req_feat) for req_feat in model_features]
|
||||
with open(feat_path, 'r', encoding='utf-8') as f:
|
||||
self.feature_order = json.load(f)
|
||||
except Exception:
|
||||
pass
|
||||
|
||||
####################################################################################################
|
||||
|
||||
def _safe_float(self, val):
|
||||
try:
|
||||
f = float(val)
|
||||
if math.isinf(f) or math.isnan(f): return 0.0
|
||||
if math.isinf(f) or math.isnan(f):
|
||||
return 0.0
|
||||
return f
|
||||
except Exception:
|
||||
return 0.0
|
||||
|
||||
def _safe_div(self, n, d):
|
||||
return float(n) / d if d > 0 else 0.0
|
||||
|
||||
def _calc_entropy(self, data):
|
||||
if not data: return 0.0
|
||||
if not data:
|
||||
return 0.0
|
||||
arr = numpy.frombuffer(data, dtype=numpy.uint8)
|
||||
counts = numpy.bincount(arr, minlength=256)
|
||||
probs = counts[counts > 0] / len(arr)
|
||||
return float(-numpy.sum(probs * numpy.log2(probs)))
|
||||
|
||||
####################################################################################################
|
||||
|
||||
def extract_features(self, file_path):
|
||||
fts = {k: 0.0 for k in self.SCHEMA}
|
||||
pe = None
|
||||
try:
|
||||
fsize = os.path.getsize(file_path)
|
||||
if fsize == 0 or fsize > 512 * 1024 * 1024:
|
||||
if fsize == 0 or fsize > 536870912:
|
||||
return None
|
||||
|
||||
with open(file_path, "rb") as f:
|
||||
file_bytes = f.read()
|
||||
|
||||
base = {}
|
||||
dlls = set()
|
||||
apis = set()
|
||||
pe = pefile.PE(data=file_bytes, fast_load=True)
|
||||
fts['TrustSigned'] = 1 if self.signer.sign_verify(file_path) else 0
|
||||
|
||||
base['TrustSigned'] = 1.0 if self.signer.sign_verify(file_path) else 0.0
|
||||
base['FileEntropy'] = self._calc_entropy(file_bytes)
|
||||
base['FileSize'] = float(fsize)
|
||||
|
||||
fh = pe.FILE_HEADER
|
||||
fts['Machine'] = fh.Machine
|
||||
fts['NumberOfSections'] = fh.NumberOfSections
|
||||
fts['TimeDateStamp'] = fh.TimeDateStamp
|
||||
fts['PointerToSymbolTable'] = fh.PointerToSymbolTable
|
||||
fts['NumberOfSymbols'] = fh.NumberOfSymbols
|
||||
fts['SizeOfOptionalHeader'] = fh.SizeOfOptionalHeader
|
||||
fts['Characteristics'] = fh.Characteristics
|
||||
curr_ts = datetime.datetime.now(datetime.timezone.utc).timestamp()
|
||||
fts['HasInvalidTimestamp'] = 1 if (fh.TimeDateStamp < 631152000 or fh.TimeDateStamp > curr_ts + 2592000) else 0
|
||||
fts['FileTimeException'] = 1 if fh.TimeDateStamp == 0 else 0
|
||||
base['Machine'] = getattr(fh, 'Machine', 0)
|
||||
base['NumberOfSections'] = getattr(fh, 'NumberOfSections', 0)
|
||||
base['TimeDateStamp'] = getattr(fh, 'TimeDateStamp', 0)
|
||||
base['PointerToSymbolTable'] = getattr(fh, 'PointerToSymbolTable', 0)
|
||||
base['NumberOfSymbols'] = getattr(fh, 'NumberOfSymbols', 0)
|
||||
base['SizeOfOptionalHeader'] = getattr(fh, 'SizeOfOptionalHeader', 0)
|
||||
base['Characteristics'] = getattr(fh, 'Characteristics', 0)
|
||||
|
||||
curr_ts = datetime.datetime.utcnow().timestamp()
|
||||
base['HasInvalidTimestamp'] = 1.0 if (base['TimeDateStamp'] < 631152000 or base['TimeDateStamp'] > curr_ts + 2592000) else 0.0
|
||||
base['FileTimeException'] = 1.0 if base['TimeDateStamp'] == 0 else 0.0
|
||||
base['Is64Bit'] = 1.0 if base['Machine'] in (0x8664, 0xAA64, 0x0200) else 0.0
|
||||
base['IsExe'] = 1.0 if pe.is_exe() else 0.0
|
||||
base['IsDll'] = 1.0 if pe.is_dll() else 0.0
|
||||
|
||||
base['ExceptionCount'] = 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 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
|
||||
fields = [
|
||||
'Magic', 'MajorLinkerVersion', 'MinorLinkerVersion', 'SizeOfCode',
|
||||
'SizeOfInitializedData', 'SizeOfUninitializedData', 'AddressOfEntryPoint',
|
||||
'BaseOfCode', 'ImageBase', 'SectionAlignment', 'FileAlignment',
|
||||
'MajorOperatingSystemVersion', 'MinorOperatingSystemVersion',
|
||||
'MajorImageVersion', 'MinorImageVersion', 'MajorSubsystemVersion',
|
||||
'MinorSubsystemVersion', 'SizeOfImage', 'SizeOfHeaders', 'CheckSum',
|
||||
'Subsystem', 'DllCharacteristics', 'SizeOfStackReserve', 'SizeOfStackCommit',
|
||||
'SizeOfHeapReserve', 'SizeOfHeapCommit', 'LoaderFlags', 'NumberOfRvaAndSizes'
|
||||
]
|
||||
for f_field in fields:
|
||||
base[f_field] = getattr(op, f_field, 0)
|
||||
|
||||
base['IsDriver'] = 1.0 if base.get('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
|
||||
for i, directory in enumerate(op.DATA_DIRECTORY):
|
||||
base[f'DataDirectory_{i}_Size'] = directory.Size
|
||||
base[f'DataDirectory_{i}_VA'] = directory.VirtualAddress
|
||||
|
||||
exc_idx = pefile.DIRECTORY_ENTRY.get('IMAGE_DIRECTORY_ENTRY_EXCEPTION', 3)
|
||||
if len(op.DATA_DIRECTORY) > exc_idx and op.DATA_DIRECTORY[exc_idx].Size > 0:
|
||||
base['ExceptionCount'] = op.DATA_DIRECTORY[exc_idx].Size // 12 if base['Machine'] in (0x8664, 0xAA64) else 0.0
|
||||
|
||||
try:
|
||||
fts['FileEntropy'] = self._calc_entropy(file_bytes)
|
||||
except Exception:
|
||||
pass
|
||||
|
||||
sec_entropies = []
|
||||
text_entropies = []
|
||||
data_entropies = []
|
||||
rsrc_entropies = []
|
||||
char_flags = [0x00000020, 0x00000040, 0x00000080, 0x02000000, 0x20000000, 0x40000000, 0x80000000]
|
||||
for flag in char_flags:
|
||||
base[f'Char_{flag:08X}_Count'] = 0.0
|
||||
base[f'Char_{flag:08X}_MeanEntropy'] = 0.0
|
||||
|
||||
if hasattr(pe, 'sections'):
|
||||
fts['SectionCount'] = len(pe.sections)
|
||||
base['SectionCount'] = len(pe.sections)
|
||||
entropies = []
|
||||
raw_sizes = []
|
||||
v_sizes = []
|
||||
exec_sec = 0
|
||||
write_sec = 0
|
||||
read_sec = 0
|
||||
sec_exc = 0
|
||||
|
||||
for section in pe.sections:
|
||||
try:
|
||||
name = section.Name.decode('ascii', 'ignore').strip('\x00')
|
||||
except Exception:
|
||||
name = ""
|
||||
try:
|
||||
try:
|
||||
s_data = section.get_data()
|
||||
s_entropy = self._calc_entropy(s_data)
|
||||
except Exception:
|
||||
s_data = b""
|
||||
except Exception:
|
||||
s_entropy = 0.0
|
||||
|
||||
sec_entropies.append(s_entropy)
|
||||
if any(p in name.lower() for p in self.PACKERS): fts['HasPacked'] = 1
|
||||
if section.Characteristics & 0x20000000: fts['ExecutableSections'] += 1
|
||||
if section.Characteristics & 0x80000000: fts['WritableSections'] += 1
|
||||
if section.Characteristics & 0x40000000: fts['ReadableSections'] += 1
|
||||
if section.SizeOfRawData + section.PointerToRawData > fsize: fts['SectionException'] = 1
|
||||
entropies.append(s_entropy)
|
||||
raw_sizes.append(section.SizeOfRawData)
|
||||
v_sizes.append(section.Misc_VirtualSize)
|
||||
|
||||
if section.Characteristics & 0x20000000: exec_sec += 1
|
||||
if section.Characteristics & 0x80000000: write_sec += 1
|
||||
if section.Characteristics & 0x40000000: read_sec += 1
|
||||
if section.SizeOfRawData + section.PointerToRawData > fsize: sec_exc = 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
|
||||
base[f'Char_{flag:08X}_Count'] += 1.0
|
||||
base[f'Char_{flag:08X}_MeanEntropy'] += s_entropy
|
||||
|
||||
ratio = self._safe_div(section.SizeOfRawData, fsize)
|
||||
if name.startswith('.text'):
|
||||
text_entropies.append(s_entropy)
|
||||
fts['TextSizeRatio'] += ratio
|
||||
elif name.startswith('.data'):
|
||||
data_entropies.append(s_entropy)
|
||||
fts['DataSizeRatio'] += ratio
|
||||
elif name.startswith('.rsrc'):
|
||||
rsrc_entropies.append(s_entropy)
|
||||
fts['RsrcSizeRatio'] += ratio
|
||||
if b"requireAdministrator" in s_data: fts['IsAdmin'] = 1
|
||||
for sig in self.INSTALLER_SIGS:
|
||||
if sig in s_data:
|
||||
fts['IsInstall'] = 1
|
||||
break
|
||||
base['SectionMaxEntropy'] = max(entropies) if entropies else 0.0
|
||||
base['SectionMinEntropy'] = min(entropies) if entropies else 0.0
|
||||
base['SectionMeanEntropy'] = sum(entropies) / len(entropies) if entropies else 0.0
|
||||
base['SectionMaxRawSize'] = max(raw_sizes) if raw_sizes else 0.0
|
||||
base['SectionMinRawSize'] = min(raw_sizes) if raw_sizes else 0.0
|
||||
base['SectionMeanRawSize'] = sum(raw_sizes) / len(raw_sizes) if raw_sizes else 0.0
|
||||
base['SectionMaxVSize'] = max(v_sizes) if v_sizes else 0.0
|
||||
base['SectionMinVSize'] = min(v_sizes) if v_sizes else 0.0
|
||||
base['SectionMeanVSize'] = sum(v_sizes) / len(v_sizes) if v_sizes else 0.0
|
||||
base['ExecutableSections'] = float(exec_sec)
|
||||
base['WritableSections'] = float(write_sec)
|
||||
base['ReadableSections'] = float(read_sec)
|
||||
base['SectionException'] = float(sec_exc)
|
||||
|
||||
fts['SectionMeanEntropy'] = self._safe_div(sum(sec_entropies), len(sec_entropies))
|
||||
fts['TextSectionMaxEntropy'] = max(text_entropies) if text_entropies else 0.0
|
||||
fts['TextSectionMeanEntropy'] = self._safe_div(sum(text_entropies), len(text_entropies))
|
||||
fts['DataSectionMaxEntropy'] = max(data_entropies) if data_entropies else 0.0
|
||||
fts['DataSectionMeanEntropy'] = self._safe_div(sum(data_entropies), len(data_entropies))
|
||||
fts['RsrcSectionMaxEntropy'] = max(rsrc_entropies) if rsrc_entropies else 0.0
|
||||
fts['RsrcSectionMeanEntropy'] = self._safe_div(sum(rsrc_entropies), len(rsrc_entropies))
|
||||
for flag in char_flags:
|
||||
if base[f'Char_{flag:08X}_Count'] > 0:
|
||||
base[f'Char_{flag:08X}_MeanEntropy'] = base[f'Char_{flag:08X}_MeanEntropy'] / base[f'Char_{flag:08X}_Count']
|
||||
|
||||
for flag in char_flags:
|
||||
fts[f'Char_{flag:08X}_MeanEntropy'] = self._safe_div(fts[f'Char_{flag:08X}_MeanEntropy'], fts[f'Char_{flag:08X}_Count'])
|
||||
string_keys = ['FileDescription', 'FileVersion', 'ProductName', 'ProductVersion', 'CompanyName', 'LegalCopyright', 'Comments', 'InternalName', 'LegalTrademarks', 'SpecialBuild', 'PrivateBuild']
|
||||
for key in string_keys:
|
||||
base[f'{key}Length'] = 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)
|
||||
if k in string_keys:
|
||||
v = val.decode('utf-8', 'ignore') if isinstance(val, bytes) else str(val)
|
||||
base[f'{k}Length'] = float(len(v))
|
||||
except Exception:
|
||||
continue
|
||||
|
||||
try:
|
||||
pe.parse_data_directories(directories=[
|
||||
@@ -563,110 +356,90 @@ class pe_scanner:
|
||||
|
||||
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
|
||||
|
||||
base['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 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
|
||||
base['IsDebug'] = 1.0 if flags & 0x1 else 0.0
|
||||
base['IsPreRelease'] = 1.0 if flags & 0x2 else 0.0
|
||||
base['IsPatched'] = 1.0 if flags & 0x4 else 0.0
|
||||
base['IsPrivateBuild'] = 1.0 if flags & 0x8 else 0.0
|
||||
base['IsSpecialBuild'] = 1.0 if flags & 0x20 else 0.0
|
||||
|
||||
if hasattr(pe, 'DIRECTORY_ENTRY_IMPORT'):
|
||||
fts['ImportCount'] = len(pe.DIRECTORY_ENTRY_IMPORT)
|
||||
base['ImportCount'] = float(len(pe.DIRECTORY_ENTRY_IMPORT))
|
||||
func_count = 0
|
||||
for entry in pe.DIRECTORY_ENTRY_IMPORT:
|
||||
if getattr(entry, 'dll', None):
|
||||
try:
|
||||
dll_name = entry.dll.decode('ascii', 'ignore').lower()
|
||||
if dll_name in self.TARGET_DLLS:
|
||||
fts[f"Dll_{dll_name.replace('.', '_')}"] = 1.0
|
||||
except Exception:
|
||||
dlls.add(entry.dll.decode('ascii', 'ignore').lower())
|
||||
except Exception:
|
||||
pass
|
||||
|
||||
imports = getattr(entry, 'imports', [])
|
||||
for imp in imports:
|
||||
fts['ImportFunctionCount'] += 1
|
||||
if not getattr(imp, 'name', None): continue
|
||||
try:
|
||||
func_name = imp.name.decode('ascii', 'ignore')
|
||||
if func_name in self._ALL_APIS:
|
||||
fts[f'Api_{func_name}'] += 1
|
||||
fts['ApiCount'] += 1
|
||||
for cat in self._API_TO_CATS.get(func_name, []):
|
||||
fts[f'Cat_{cat}'] += 1
|
||||
except Exception:
|
||||
continue
|
||||
for imp in getattr(entry, 'imports', []):
|
||||
func_count += 1
|
||||
if getattr(imp, 'name', None):
|
||||
try:
|
||||
apis.add(imp.name.decode('ascii', 'ignore'))
|
||||
except Exception:
|
||||
pass
|
||||
base['ImportFunctionCount'] = float(func_count)
|
||||
else:
|
||||
base['ImportCount'] = 0.0
|
||||
base['ImportFunctionCount'] = 0.0
|
||||
|
||||
if hasattr(pe, 'DIRECTORY_ENTRY_EXPORT') and hasattr(pe.DIRECTORY_ENTRY_EXPORT, 'symbols'):
|
||||
fts['ExportCount'] = len(pe.DIRECTORY_ENTRY_EXPORT.symbols)
|
||||
|
||||
base['ExportCount'] = float(len(pe.DIRECTORY_ENTRY_EXPORT.symbols))
|
||||
else:
|
||||
base['ExportCount'] = 0.0
|
||||
|
||||
if hasattr(pe, 'DIRECTORY_ENTRY_RESOURCE'):
|
||||
icon_count = 0
|
||||
for entry in pe.DIRECTORY_ENTRY_RESOURCE.entries:
|
||||
if getattr(entry, 'id', None) == 3 and hasattr(entry, 'directory'):
|
||||
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)
|
||||
icon_count += len(getattr(entry.directory, 'entries', []))
|
||||
base['IconCount'] = float(icon_count)
|
||||
else:
|
||||
base['IconCount'] = 0.0
|
||||
|
||||
for k in fts:
|
||||
fts[k] = self._safe_float(fts[k])
|
||||
|
||||
return fts
|
||||
if hasattr(pe, 'DIRECTORY_ENTRY_DEBUG'):
|
||||
base['DebugCount'] = float(len(pe.DIRECTORY_ENTRY_DEBUG))
|
||||
else:
|
||||
base['DebugCount'] = 0.0
|
||||
|
||||
for k in base:
|
||||
base[k] = self._safe_float(base[k])
|
||||
|
||||
return {"Base": base, "DLLs": list(dlls), "APIs": list(apis)}
|
||||
|
||||
except Exception:
|
||||
return None
|
||||
finally:
|
||||
if pe:
|
||||
if pe:
|
||||
pe.close()
|
||||
|
||||
####################################################################################################
|
||||
|
||||
def pe_scan(self, file_path, enhanced_mode=False):
|
||||
if not self.model:
|
||||
if not self.model or not self.feature_order:
|
||||
return False, False
|
||||
try:
|
||||
data = self.extract_features(file_path)
|
||||
if not data:
|
||||
raw_data = self.extract_features(file_path)
|
||||
if not raw_data:
|
||||
return False, False
|
||||
|
||||
vec_data = []
|
||||
for key in self.feature_order:
|
||||
if key:
|
||||
vec_data.append(data.get(key, 0.0))
|
||||
vec = numpy.zeros((1, len(self.feature_order)), dtype=numpy.float32)
|
||||
base = raw_data.get('Base', {})
|
||||
dlls = set(raw_data.get('DLLs', []))
|
||||
apis = set(raw_data.get('APIs', []))
|
||||
|
||||
for i, feat in enumerate(self.feature_order):
|
||||
if feat.startswith('Dll_'):
|
||||
if feat[4:] in dlls:
|
||||
vec[0, i] = 1.0
|
||||
elif feat.startswith('Api_'):
|
||||
if feat[4:] in apis:
|
||||
vec[0, i] = 1.0
|
||||
else:
|
||||
vec_data.append(0.0)
|
||||
vec[0, i] = base.get(feat, 0.0)
|
||||
|
||||
vec = numpy.array(vec_data, dtype=numpy.float32).reshape(1, -1)
|
||||
outputs = self.model.run(None, {self.input_name: vec})
|
||||
result = outputs[1]
|
||||
prob = 0.0
|
||||
@@ -675,7 +448,6 @@ class pe_scanner:
|
||||
prob_dict = result[0]
|
||||
if hasattr(prob_dict, 'get'):
|
||||
prob = float(prob_dict.get(1, prob_dict.get('1', 0.0)))
|
||||
|
||||
elif isinstance(result, numpy.ndarray):
|
||||
if result.ndim == 2 and result.shape[1] > 1:
|
||||
prob = float(result[0][1])
|
||||
|
||||
+1
-1
@@ -2,7 +2,7 @@ import pyinstaller_versionfile
|
||||
|
||||
pyinstaller_versionfile.create_versionfile(
|
||||
output_file="versionfile.txt",
|
||||
version='3.5.2.0',
|
||||
version='3.5.4.0',
|
||||
company_name="PYAS Security",
|
||||
file_description="PYAS Security Antivirus",
|
||||
internal_name="PYAS",
|
||||
|
||||
@@ -2,7 +2,6 @@
|
||||
"Rule_Registry_BlockList": [
|
||||
"\\REGISTRY\\MACHINE\\BCD00000000",
|
||||
"\\REGISTRY\\MACHINE\\BCD00000000\\*",
|
||||
"*\\Image File Execution Options\\*",
|
||||
|
||||
"*\\Classes\\.exe",
|
||||
"*\\Classes\\.bat",
|
||||
@@ -47,7 +46,6 @@
|
||||
"*\\DisableTaskMgr",
|
||||
"*\\DisableRegistryTools",
|
||||
"*\\DisableChangePassword",
|
||||
"*\\Wallpaper",
|
||||
"*\\NoComponents",
|
||||
"*\\NoStartMenuMorePrograms",
|
||||
"*\\NoActiveDesktop",
|
||||
@@ -84,7 +82,6 @@
|
||||
"*\\SystemBiosVersion",
|
||||
"*\\NoTheme",
|
||||
"*\\SystemSetupInProgress",
|
||||
"*\\CmdLine",
|
||||
"*\\SetupType",
|
||||
"*\\AutoReboot",
|
||||
"*\\Userinit",
|
||||
@@ -107,24 +104,36 @@
|
||||
|
||||
"\\Device\\HarddiskVolume*\\ProgramData\\Microsoft\\Windows Defender\\*",
|
||||
"\\Device\\HarddiskVolume*\\ProgramData\\Microsoft\\Windows Defender Advanced Threat Protection\\*",
|
||||
"*\\Sources\\SetupHost.exe",
|
||||
"*\\Work\\*\\DismHost.exe",
|
||||
|
||||
"*\\Google\\Chrome\\Application\\chrome.exe",
|
||||
"*\\Microsoft\\Edge\\Application\\msedge.exe",
|
||||
"*\\Mozilla Firefox\\firefox.exe",
|
||||
"*\\Thorium\\Application\\thorium.exe",
|
||||
|
||||
"*\\Steam\\steam.exe",
|
||||
"*\\steam\\*\\steam.exe",
|
||||
"*\\Battle.net\\*\\Battle.net.exe",
|
||||
"*\\Epic Games\\Launcher\\Portal\\Binaries\\*\\EpicGamesLauncher.exe",
|
||||
|
||||
"*\\resources\\bin\\SDK\\DownloadSDKServer.exe",
|
||||
"*\\ThunderEv.exe",
|
||||
"*\\ThunderStart.exe",
|
||||
"*\\resources\\bin\\SDK\\DownloadSDKServer.exe",
|
||||
"*\\KuGou.exe",
|
||||
"*\\XLGame.exe",
|
||||
"*\\XLLiveUD.exe",
|
||||
|
||||
"*\\hrkill-*.exe",
|
||||
"*\\bin\\HipsDaemon.exe",
|
||||
"*\\360Safe.exe",
|
||||
"*\\bilibili\\bilibili.exe",
|
||||
|
||||
"*\\PC4399_WPFLauncher.exe",
|
||||
"*\\bin\\APlayer.exe",
|
||||
"*\\Kingsoft\\WPS Office\\ksolaunch.exe",
|
||||
"*\\Neverness To Everness\\NTELauncher.exe",
|
||||
"*\\Application\\AndrowsLauncher.exe",
|
||||
],
|
||||
"Rule_Process_ExploitableBlacklist": [
|
||||
"*\\cmd.exe",
|
||||
@@ -203,6 +212,7 @@
|
||||
"*\\ventoy\\Boot\\*",
|
||||
"*\\AdminRegionConfig.*",
|
||||
"*\\nt_qq\\nt_*\\*",
|
||||
"*\\cache\\*",
|
||||
],
|
||||
"Rule_File_RansomwareExtensions": [
|
||||
".zip", ".7z", ".rar", ".tar", ".gz",
|
||||
|
||||
@@ -26,6 +26,7 @@ pip install pandas
|
||||
pip install scikit-learn
|
||||
pip install lightgbm
|
||||
pip install onnxmltools
|
||||
pip install orjson
|
||||
```
|
||||
|
||||
## File Information
|
||||
@@ -127,6 +128,12 @@ graph TD
|
||||
BootProtect -->|IOCTL_DISK_FORMAT_TRACKS...| Disk
|
||||
```
|
||||
|
||||
## Machine Learning
|
||||
|
||||
The machine learning-based virus scanning engine training achieved extremely high accuracy.
|
||||
|
||||
<img width="1191" height="1323" alt="train" src="https://github.com/user-attachments/assets/2eea8426-1f82-44e3-9a38-f96f9257cf50" />
|
||||
|
||||
## Support System
|
||||
|
||||
Please ensure your computer supports Microsoft Visual C++ 2015-2022 Redistributable and Edge Webview2.
|
||||
|
||||
Reference in new issue
Block a user