From c088eeb5bc0f11a3cf916f2f75e49a88f14de72e Mon Sep 17 00:00:00 2001 From: 87owo <85057800+87owo@users.noreply.github.com> Date: Sat, 9 May 2026 22:47:09 +0800 Subject: [PATCH] Add files via upload --- Interface/static/js/main.js | 7 +- PYAS.py | 1 + PYAS_Engine.py | 562 +++++++++++------------------------- 3 files changed, 173 insertions(+), 397 deletions(-) diff --git a/Interface/static/js/main.js b/Interface/static/js/main.js index 947d9ec..16e402f 100644 --- a/Interface/static/js/main.js +++ b/Interface/static/js/main.js @@ -650,8 +650,10 @@ document.addEventListener('DOMContentLoaded', () => { finalizeAction(); }); } else if (action === 'ignore') { - removeListItems(paths); - finalizeAction(); + window.pywebview.api.remove_virus_result(paths).then(() => { + removeListItems(paths); + finalizeAction(); + }); } else if (action === 'quarantine' || action === 'whitelist') { const listKey = action === 'quarantine' ? 'quarantine' : 'white_list'; window.pywebview.api.manage_named_list(listKey, paths, 'add').then(() => { @@ -837,6 +839,7 @@ document.addEventListener('DOMContentLoaded', () => { renderDataGrid('#scan_window', window.virusResults, cols.virus, 'path', true); scanMethodSelect.classList.add('hidden'); stopBtn.classList.remove('hidden'); + stopBtn.disabled = false; changeScanSelectMode('scan'); if (progressTitle) { progressTitle.setAttribute('data-i18n', ''); diff --git a/PYAS.py b/PYAS.py index 7003fde..2077ffe 100644 --- a/PYAS.py +++ b/PYAS.py @@ -1424,6 +1424,7 @@ class WindowAPI: self.lock_file(norm_path, True) finally: with self.lock_virus: + self.scan_running = False self.scan_finished = True count = len(self.virus_results) elapsed = int(time.time() - self.scan_start) diff --git a/PYAS_Engine.py b/PYAS_Engine.py index b4deae4..ebb72df 100644 --- a/PYAS_Engine.py +++ b/PYAS_Engine.py @@ -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])