// Detect It Easy: detection rule file /* ██████╗ ███████╗███╗ ██╗███████╗██████╗ ██╗ ██████╗ ██╔════╝ ██╔════╝████╗ ██║██╔════╝██╔══██╗██║██╔════╝ ██║ ███╗█████╗ ██╔██╗ ██║█████╗ ██████╔╝██║██║ ██║ ██║██╔══╝ ██║╚██╗██║██╔══╝ ██╔══██╗██║██║ ╚██████╔╝███████╗██║ ╚████║███████╗██║ ██║██║╚██████╗ ╚═════╝ ╚══════╝╚═╝ ╚═══╝╚══════╝╚═╝ ╚═╝╚═╝ ╚═════╝ ██╗ ██╗ ██╗███████╗██╗ ██╗██████╗ ██╗███████╗████████╗██╗ ██████╗ ██╗ ██╔╝ ██║ ██║██╔════╝██║ ██║██╔══██╗██║██╔════╝╚══██╔══╝██║██╔════╝ ╚██╗ ██╔╝ ███████║█████╗ ██║ ██║██████╔╝██║███████╗ ██║ ██║██║ ╚██╗ ╚██╗ ██╔══██║██╔══╝ ██║ ██║██╔══██╗██║╚════██║ ██║ ██║██║ ██╔╝ ╚██╗ ██║ ██║███████╗╚██████╔╝██║ ██║██║███████║ ██║ ██║╚██████╗ ██╔╝ ╚═╝ ╚═╝ ╚═╝╚══════╝ ╚═════╝ ╚═╝ ╚═╝╚═╝╚══════╝ ╚═╝ ╚═╝ ╚═════╝ ╚═╝ █████╗ ███╗ ██╗ █████╗ ██╗ ██╗ ██╗███████╗██╗███████╗ ██╔══██╗████╗ ██║██╔══██╗██║ ╚██╗ ██╔╝██╔════╝██║██╔════╝ ███████║██╔██╗ ██║███████║██║ ╚████╔╝ ███████╗██║███████╗ ██╔══██║██║╚██╗██║██╔══██║██║ ╚██╔╝ ╚════██║██║╚════██║ ██║ ██║██║ ╚████║██║ ██║███████╗██║ ███████║██║███████║ ╚═╝ ╚═╝╚═╝ ╚═══╝╚═╝ ╚═╝╚══════╝╚═╝ ╚══════╝╚═╝╚══════╝ */ // ┌────────────────────────┤ ≡ ABOUT ≡ ├───────────────[–][▫][×]┐ // │ │ // │ This heuristic analysis helps detect the presence of │ // │ obfuscation, packers, licensing systems, debugging │ // │ information, file integrity issues, or even potential │ // │ malicious code using advanced and complex checks. It │ // │ includes pinpoint scanning, intelligent inspections, │ // │ signature-based analysis, native code emulation, │ // │ thorough file content evaluation, and much more! >:) │ // │ │ // │ Supported architectures: │ // │ 1. I386 (x86-32) 2. AMD64 (x86-64) │ // │ 3. ARM (A32) 4. THUMB (T32) │ // │ 5. ARMNT (ARMv7 Thumb-2) 6. ARM64 (AArch64) │ // │ │ // └─────────────────────────────────────────────────────────────┘ // ┌──────────────────────┤ ⌂ CONTACTS ⌂ ├───────────────────────┐ // │ │ // │ ┌ Author is DosX │ // │ └┬─ GitHub: https://github.com/DosX-dev │ // │ ├─ Website: https://dosx.su │ // │ └─ Telegram: @DosX_dev │ // │ │ // ├─────────────────────────────────────────────────────────────┤ // │ If I don't respond to email, message to Telegram │ // └─────────────────────────────────────────────────────────────┘ // ┌───────────────────────┤ ♥ DONATE ♥ ├────────────────────────┐ // │ │ // │ ┌ Did you like my work? Thank you! What about donation? │ // │ │ This will greatly help the development! │ // │ └─┐ │ // │ ├─ BTC: bc1qclzlujunh8zyyxaar8y8t0chwfardesq9uvphh │ // │ │ │ // │ ├─ SOL: 6693kpkspRU2fauVpMkVznnETGX7GdMsLFQED6QFfaWa │ // │ │ │ // │ ├─ TRX: TYezKdq5YkrSmmUy6d9e212KcHxQigtTqJ │ // │ │ │ // │ ├─ ETH: 0xd5f1e450a4A9325165ed6b5ed5E28Da882AB932a │ // │ │ ERC-20 ─┘ │ // │ │ │ // │ └─ USDT: TYezKdq5YkrSmmUy6d9e212KcHxQigtTqJ │ // │ TRC-20 ─┘ │ // │ │ // └─────────────────────────────────────────────────────────────┘ // ┌───────────────────────┤ © LICENSE © ├───────────────────────┐ // │ │ // │ ┌ Copyright (C) 2026 DosX. MIT License. │ // │ └─┐ │ // │ ├─ √ You CAN: │ // │ │ ├─ Use in personal and commercial projects │ // │ │ ├─ Modify and adapt the code │ // │ │ └─ Redistribute, including in modified form │ // │ │ │ // │ └─ × You CANNOT: │ // │ ├─ Remove or alter the copyright notice │ // │ ├─ Claim authorship of the original code │ // │ └─ Hold the author liable for any damages │ // │ │ // └─────────────────────────────────────────────────────────────┘ // ┌───────────────────────────────────────────────┐ // │ For the module to work correctly, official │ // │ Detect It Easy components are recommended │ // ├───────────────────────────────────────────────┘ // └─┐ // └── Please don't read the code out loud // unless you have exorcism skills "use strict"; const PE_Cached = {}, // PE-related cache-map logType = { warning: -2, // Service warnings about: -1, // Information about a module nothing: 0, // General information about the scanning process any: 1, // Native and .NET net: 2, // .NET only emulation: 3 // Instruction emulation }, heurLabel = "HEUR"; var lastOffsetDetected = "0x00"; function detect() { if (stubForWrongEnvironment()) return null; // 'PE' is undefined if (stubForLegacyEngines()) return null; // old DIE version return main(); } function main() { initializeCache(); if (PE.isHeuristicScan()) { if (!PE.isVerbose()) { log(logType.warning, "To get the full heuristic scan result use \"--verbose\""); } log(logType.about, "Generic Heuristic Analysis by DosX (@DosX_dev)"); log(logType.nothing, "Scanning has begun!"); if (PE_Cached.isDotNet) { // Detection obfuscation and anti-analysis techniques specific to .NET scanForObfuscations_NET(); scanForAntiAnalysis_NET(); } else { // Detection of obfuscation and anti-analysis techniques specific to Native scanForObfuscations_Native(); scanForAntiAnalysis_Native(); } // Detection of anomalies most commonly found in packers, cryptors, protectors... scanForPackersAndCryptors_NET_and_Native(); // Detection licensing information (detecting possible DRM systems or licensing strings) scanForLicensingSystems_NET_and_Native(); // A cursory checks of the file format integrity scanForBadFileFormat_NET_and_Native(); // Detection of debug information (headers, sections, strings...) scanForDebugData_NET_and_Native(); // Detection for suspicious or potentially malicious patterns scanForMaliciousCode_NET_and_Native(); if (PE.isVerbose()) { // Detection of programming language, compiler, linker scanForLanguagesAndCompilers_NET_and_Native(); // Detection of specific markers and pointers that are read by the operating system scanForInterestingMarkers_NET_and_Native(); } // Detection of an executable file extension type other than the standard .exe/.dll scanNameForAnomalies_FileSystem_NET_and_Native(); // >> Happy end << log(logType.nothing, "Scan completed."); } else { log(logType.warning, "Heuristic scan is disabled. Use '--heuristicscan' to enable"); } unloadCache(); } function stubForLegacyEngines() { if (typeof PE.getNetAssemblyName !== "function" || typeof PE.section[0].Offset !== "number") { stdout(">>> Update DIE Engine to 4.0 and higher for using Heuristic-analyzer by DosX <<<"); debugger; return true; } return false; } function stubForWrongEnvironment() { if (typeof PE === "undefined") { stdout(">>> Wrong environment! 'PE' is undefined. Check DIE-engine for correct installation <<<"); debugger; return true; } if (PE.isHeuristicScan() && PE.isVerbose() && !_getNumberOfResults("operation system")) { stdout(">>> Script is running outside the environment! Are you in debug mode? <<<"); debugger; } return false; } /** * Outputs a warning message to the appropriate output stream. * * Depending on the environment, this function will: * - Use `console.warn` if `console` is available. * - Use `_setResult` if `File` is available. * - Use `_error` if it is defined as a function. * - Throw an error if none of the above conditions are met. * * @param {string} stringToOut - The warning message to output. * @returns {void} No return value; */ function stdout(stringToOut) { if (typeof console === "object") { console.warn(stringToOut); } else if (typeof File === "object") { _setResult("~warning", stringToOut, String(), String()); } else { if (typeof _error === "function") { _error(stringToOut); } else { throw new Error(stringToOut); } } } /** * Initialize the PE-related cache by reading values from the PE API and storing them * into the global PE_Cached object. * * The function constructs a temporary map containing typed entries sourced from the PE * interface, then copies each entry into PE_Cached. * * Additionally compiles the obfuscation whitelist into case-sensitive and * case-insensitive regular expressions for repeated token checks. These cached * expressions are valid until {@link unloadCache} runs at the end of the scan. * The linear entry-point disassembly cache is also reset for the current image. * * Side effects: * - Mutates the global PE_Cached object by adding/updating cached values. * - Evaluates local and PE API functions stored in the cache map. * * @function initializeCache * @returns {void} No return value; updates PE_Cached as a side effect. */ function initializeCache() { const architecture = getPeArchitecture(), fileSize = PE.getSize(), overlaySize = PE.getOverlaySize(), dosStubOffset = PE.getDosStubOffset(), dosStubSize = PE.getDosStubSize(); var obfuscationWhitelistSources = [], obfuscationWhitelistInsensitiveSources = []; // The engine context may be reused for several files. Start every scan with an empty // linear-disassembly cache so instructions from a previous image cannot be returned. epAsmInstructions = []; epAsmInstructionAddresses = []; // Preserve individual anchors while combining patterns for one test per group. for (var i = 0; i < obfuscationWhitelistPatterns.length; i++) { obfuscationWhitelistSources.push("(?:" + obfuscationWhitelistPatterns[i].source + ")"); } for (var i = 0; i < obfuscationWhitelistInsensitivePatterns.length; i++) { obfuscationWhitelistInsensitiveSources.push("(?:" + obfuscationWhitelistInsensitivePatterns[i].source + ")"); } var cacheMap = { /* Computed values */ /* Number */ imageBodySize: function () { return fileSize - overlaySize - dosStubOffset - dosStubSize; }, /* Number */ fileSize: function () { return fileSize; }, /* Number */ imageSubsystem: function () { return PE.getImageOptionalHeader("Subsystem"); }, /* RegExp */ obfuscationWhitelist: function () { return new RegExp(obfuscationWhitelistSources.join("|")); }, /* RegExp */ obfuscationWhitelistInsensitive: function () { return new RegExp(obfuscationWhitelistInsensitiveSources.join("|"), "i"); }, /* Native functions */ /* Boolean */ is64bit: PE["is64"], /* Boolean */ isDotNet: PE["isNet"], /* Boolean */ isDynamicLinkLibrary: PE["isDll"], /* Boolean */ isDriver: PE["isDriver"], /* Boolean */ isNetGlobalCctorPresent: PE["isNetGlobalCctorPresent"], /* Boolean */ isRichSignaturePresent: PE["isRichSignaturePresent"], /* Number */ addressOfUnmanagedEntryPoint: PE["getAddressOfEntryPoint"], /* Number */ imageBase: PE["getImageBase"], /* Number */ dosStubOffset: function () { return dosStubOffset; }, /* Number */ dosStubSize: function () { return dosStubSize; }, /* Number */ overlayOffset: PE["getOverlayOffset"], /* Number */ overlaySize: function () { return overlaySize; }, /* Number */ numberOfSections: PE["getNumberOfSections"], /* Number */ numberOfRichIDs: PE["getNumberOfRichIDs"], /* Number */ numberOfUnmanagedResources: PE["getNumberOfResources"], /* Number */ numberOfUnmanagedImports: PE["getNumberOfImports"], /* Number */ numberOfUnmanagedExports: PE["getNumberOfExports"], /* Number */ indexOfEntryPointSection: PE["getEntryPointSection"], /* Number */ indexOfImportsSection: PE["getImportSection"], /* Number */ indexOfExportsSection: PE["getExportSection"], /* Number */ indexOfResourceSection: PE["getResourceSection"], /* Number */ indexOfTlsSection: PE["getTLSSection"], /* Number */ indexOfRelocsSection: PE["getRelocsSection"], /* String */ nameOfNetAssemblyName: PE["getNetAssemblyName"], /* String */ nameOfNetModuleName: PE["getNetModuleName"], /* Local functions */ /* String */ architecture: function () { return architecture; }, /* Boolean */ isI386: function () { return architecture === "I386"; }, /* Boolean */ isAMD64: function () { return architecture === "AMD64"; }, /* Boolean */ isARM: function () { return architecture === "ARM"; }, /* Boolean */ isThumb: function () { return architecture === "ARMNT" || architecture === "THUMB"; }, /* Boolean */ isARM64: function () { return architecture === "ARM64"; }, /* Boolean */ isArchX86: function () { return architecture === "I386" || architecture === "AMD64"; }, /* Boolean */ isCppClrLikeApp: isCppClrLikeApp, /* Boolean */ isVbNetStandardLibraryPresent: isVbNetStandardLibraryPresent, /* Boolean */ isJscriptNetStandardLibraryPresent: isJscriptNetStandardLibraryPresent, /* String */ firstEpAsmInstruction: getFirstEpAsmInstruction }; for (var key in cacheMap) { const functionBody = cacheMap[key]; if (functionBody && typeof functionBody === "function") { PE_Cached[key] = functionBody(); } else if (functionBody) { stdout(">>> PE API function '" + key + "' is not a function! <<<"); debugger; } else { stdout(">>> PE API function '" + key + "' is undefined! <<<"); debugger; } } } /** * Clears the PE and linear-disassembly caches after analysis so no values from * the previous image can be reused. * * @function unloadCache * @returns {void} No return value; clears PE_Cached properties as a side effect. */ function unloadCache() { for (var key in PE_Cached) { delete PE_Cached[key]; } epAsmInstructions = []; epAsmInstructionAddresses = []; } // #region "obfuscations (.NET)" function scanForObfuscations_NET() { log(logType.nothing, "Scanning for obfuscation..."); var options = String(); var isDetected = Boolean(); // Is managed entry point modified? Let's check it using default .NET EP names var isEntryPointModified = false; const vbNetEntries = [ "Main", "main", "MAIN", "MyApplication" ], defaultEntries = [ // like MSIL, C#, C++ NET etc "Main", "_Main", // Small Basic "main", // F# entry "main@", // F# entry "
$", // For programs with top-level operators (C#) "mainCRTStartup", // C++ CLR .NET (CLI) "wWinMainCRTStartup", // C++ CLR .NET (GUI) "_WinMainCRTStartup" ] if (!PE_Cached.isDynamicLinkLibrary) { if (PE_Cached.isVbNetStandardLibraryPresent) { if (isAllNetObjectsMissing(vbNetEntries)) { isEntryPointModified = true; } } else if (isAllNetObjectsMissing(defaultEntries)) { isEntryPointModified = true; } if (isEntryPointModified) { log(logType.net, "No \"Main\" method found"); } } if (isEntryPointModified) options = "Modified managed EP"; // .cctor can execute code before the Main method is called var isNetCctorPresent = false; if (PE_Cached.isNetGlobalCctorPresent && !PE_Cached.isCppClrLikeApp && !isUnpackagedWindowsAppSdkLinked()) { log(logType.net, "Global constructor detected!"); isNetCctorPresent = true; } if (isNetCctorPresent) options = addOption(options, "CLR constructor"); // If .NET meta headers are not present in the default .NET section, then the file is mutant var isStrangeEpPosition = false; const netMetaHeaders = [ "~", "Strings", "US", "GUID", "Blob" ]; // Specify the default .NET section index const defaultNetSection = 0; // Check conditions for a strange entry point position (not for CLR apps) if (!PE_Cached.isDynamicLinkLibrary && PE_Cached.numberOfSections > 1 && !PE_Cached.isCppClrLikeApp) { // Iterate through .NET metadata headers for (var s = 0; s < netMetaHeaders.length && !isStrangeEpPosition; s++) { const headerName = netMetaHeaders[s]; // Check if the signature is not present in the default .NET section if (!PE.isSignatureInSectionPresent(defaultNetSection, "00'#" + headerName + "'00")) { log(logType.net, "Managed entry point position appears unusual"); isStrangeEpPosition = true; } } } if (isStrangeEpPosition) options = addOption(options, "Strange EP position"); // .NET files has a specific pattern of the entry point var isNativeEntryPointModified = false; if (!PE_Cached.isDynamicLinkLibrary && !PE_Cached.isCppClrLikeApp) { // not for CLR apps if (PE_Cached.isI386) { // FF2500????00: jmp dword ptr [ ... ] const firstOpCode = getFirstEpAsmOpCode(); if (firstOpCode !== "JMP") { if (PE.VAToOffset(PE_Cached.addressOfUnmanagedEntryPoint) !== -1) { log(logType.emulation, "Very strange EP pattern: " + emulateAndGetEpAsmPattern(true, 4).split("|").join(" .. ")); } else { log(logType.net, "No native entry point"); } isNativeEntryPointModified = true; } } else if (PE_Cached.isAMD64 || PE_Cached.isARM64) { if (PE.VAToOffset(PE_Cached.addressOfUnmanagedEntryPoint) !== 0x00) { isNativeEntryPointModified = true; } } } if (isNativeEntryPointModified) options = addOption(options, "Modified native EP"); // Check if the specified DOS message is not found in the DOS stub var isDosMessageModified = false; if (PE.findSignature(PE_Cached.dosStubOffset, PE_Cached.dosStubSize, "'This program cannot be run in DOS mode.'") === -1) { log(logType.any, "DOS-stub modified!"); isDosMessageModified = true; } if (isDosMessageModified) options = addOption(options, "Modified DOS"); // PE file must contain at least two sections: one for code and one for resources. var isOneSectionFile = false; if (PE_Cached.numberOfSections === 1) { log(logType.any, "Only one section found!"); isOneSectionFile = true; } if (isOneSectionFile) options = addOption(options, "Only one section"); // Check for strange sections var isStrangeSectionsPresent = false; const badChars = '_-=+~!@#$%^&*()"№;%:?*():;,/\\|\'`<>.0123456789'; // Very very bad! if (PE_Cached.numberOfSections > (!PE_Cached.isCppClrLikeApp ? 6 : 10) || !PE.section[".text"]) { log(logType.any, "Unusual section layout detected"); isStrangeSectionsPresent = true; } else { // Iterate through each section for (var i = 0; i < PE_Cached.numberOfSections && !isStrangeSectionsPresent; i++) { const sectionName = PE.getSectionName(i); // Check if the first character is not "." and the length of name is less than 3 if (sectionName[0] !== "." && sectionName.length < 3) { log(logType.any, "Unusual section name detected: \"" + File.cleanString(sectionName) + "\""); isStrangeSectionsPresent = true; break; } // Iterate through characters after "." // Check if the character is in the badChars list for (var d = 0; d < badChars.length && !isStrangeSectionsPresent; d++) { if (sectionName.substring(1).indexOf(badChars[d]) !== -1) { log(logType.any, "Unusual section name detected: \"" + File.cleanString(sectionName) + "\""); isStrangeSectionsPresent = true; } } if (!isAsciiString(sectionName) && !isStrangeSectionsPresent) { log(logType.any, "Non-ASCII section name detected"); isStrangeSectionsPresent = true; } } } if (isStrangeSectionsPresent) options = addOption(options, "Strange sections"); // Check for strange resources var isStrangeUnmanagedResourcesPresent = false; for (var i = 0; i < PE_Cached.numberOfUnmanagedResources && !isStrangeUnmanagedResourcesPresent; i++) { const unmanagedResourceName = PE.getResourceNameByNumber(i); if (unmanagedResourceName && (!isAsciiString(unmanagedResourceName) || isNameObfuscated(unmanagedResourceName))) { log(logType.any, "Strange unmanaged resource name: \"" + File.cleanString(unmanagedResourceName) + "\""); isStrangeUnmanagedResourcesPresent = true; } } if (isStrangeUnmanagedResourcesPresent) options = addOption(options, "Strange unmanaged resources"); // Check for a lot of sections (more than 10 -> this is a lot for .NET) var tooManySectionsPresent = false; if (PE_Cached.numberOfSections > 10) { log(logType.any, "Unusually high section count detected"); tooManySectionsPresent = true; } if (tooManySectionsPresent) options = addOption(options, "Too many sections"); const opCodes = new NetOpCodes(); // A popular way to obfuscate numbers/booleans var isIntConfusionPresent = false; const intConfusionXorPattern = opCodes.ldc_i4 + opCodes.ldc_i4 + opCodes.xor + opCodes.ldc_i4; if (validateNetByteCode(intConfusionXorPattern)) { if (validateNetByteCode( // samples by: Inx Obfuscator intConfusionXorPattern + (opCodes.setStrict(opCodes.bne_un_s, "09") + opCodes.ldc_i4_2 + opCodes.stloc_0 + opCodes.sizeof + opCodes.add) ) || validateNetByteCode( // samples by: MindLated, NetShield intConfusionXorPattern + (opCodes.bne_un + opCodes.ldc_i4 + opCodes.stloc + opCodes.sizeof + opCodes.add) ) || validateNetByteCode( // samples by: VavilonProtect intConfusionXorPattern + (opCodes.bne_un + opCodes.ldc_i4_2 + opCodes.stloc_s + opCodes.sizeof + opCodes.add) ) ) { log(logType.net, "Int confusion detected! Offset: " + lastOffsetDetected); isIntConfusionPresent = true; } } if (isIntConfusionPresent) options = addOption(options, "Int confusion"); // Virtualization is a method of protection in which some code segments are rewritten into instructions inherent in the built-in virtual machine and executed by it var isVirtualizationPresent = false; if ( isAllNetObjectsPresent( [ "System.Reflection", // System.Reflection.dll "GetILGenerator", // MSIL: 'System.Reflection.Emit.DynamicMethod::GetILGenerator()' "BeginInvoke", "EndInvoke", "OpCode" // MSIL: 'System.Reflection.Emit.OpCode' ] ) && ( validateNetObject("Ldarg_0") || // MSIL: 'System.Reflection.Emit.OpCodes.Ldarg_0' validateNetObject("CreateDelegate") // MSIL: 'System.Delegate.CreateDelegate' ) && !isFrameworkComponent() ) { log(logType.net, "Virtualization-like behavior detected!"); isVirtualizationPresent = true; } if (isVirtualizationPresent) options = addOption(options, "Virtualization"); // Hiding calls using delegate tricks var callsEncrypt = false; if ( isAllNetObjectsPresent( [ "GetTypeFromHandle", // MSIL: 'System.Type::GetTypeFromHandle( ... )' "BinaryReader", // MSIL: 'System.IO.BinaryReader' "CreateDelegate", // MSIL: '[Delegate].CreateDelegate' "MakeByRefType", // MSIL: 'System.Type::MakeByRefType()' "DynamicMethod" // MSIL: 'System.Reflection.Emit.DynamicMethod' ] ) && !isFrameworkComponent() ) { log(logType.net, "Delegate-based call indirection may be present"); callsEncrypt = true; } if (callsEncrypt) options = addOption(options, "Calls encrypt"); // https://learn.microsoft.com/en-us/dotnet/api/system.runtime.compilerservices.suppressildasmattribute var isAntiIldasmPresent = false; if (validateNetObject("SuppressIldasmAttribute")) { log(logType.net, "IL disassembly suppression attribute detected"); isAntiIldasmPresent = true; } if (isAntiIldasmPresent) options = addOption(options, "Anti-ILDASM"); // Anti de4dot via inheritance var isAntiDe4dotPresent = false; if ( validateSignature("'Form'******00'Form'******00'Form'******00") || // samples by: NetShield validateNetObject("Form0") // samples by: MindLated ) { log(logType.net, "File may be protected by de4dot!"); isAntiDe4dotPresent = true; } if (isAntiDe4dotPresent) options = addOption(options, "Anti-de4dot"); // An obfuscation method in which calli is used instead of regular calls var isCalliInvokesPresent = false; if (validateNetByteCode( // samples by: MindLated opCodes.setStrict(opCodes.ldftn, "** ?? 00 0A") + opCodes.setStrict(opCodes.calli, "** 00 00 11") ) || validateNetByteCode( // samples by: ArmDot, DarksProtector opCodes.ldelem_i + opCodes.setStrict(opCodes.calli, "** 00 00 11") )) { log(logType.net, "Calli invokes detected! Offset: " + lastOffsetDetected); isCalliInvokesPresent = true; } if (isCalliInvokesPresent) options = addOption(options, "Calli invokes"); // An obfuscation method that uses ldftn to create pointers to methods, which are then used in the code var isLdftnPointersPresent = false; if (validateNetByteCode( opCodes.nop + opCodes.setStrict(opCodes.ldftn, "** 00 00 06") + opCodes.stelem_i ) || validateNetByteCode( opCodes.nop + opCodes.setStrict(opCodes.ldftn, "** 00 00 0A") + opCodes.stelem_i ) || validateNetByteCode( // samples by: Quantum (private) opCodes.setStrict(opCodes.ldftn, "** 00 00 0A") + opCodes.setStrict(opCodes.calli, "** 00 00 11") )) { log(logType.net, "Ldftn pointers method-obfuscation detected! Offset: " + lastOffsetDetected); isLdftnPointersPresent = true; } if (isLdftnPointersPresent) options = addOption(options, "Ldftn pointers"); // Turns the code into spaghetti by splitting it into blocks that it executes depending on the situation var isCtrlFlowPresent = false; if ( validateNetByteCode( // samples by: ConfuserEx opCodes.nop + opCodes.ldloc_0 + opCodes.ldc_i4 + opCodes.mul + opCodes.ldc_i4 + opCodes.xor + opCodes.br_s + opCodes.nop + opCodes.ldloc_0 + opCodes.ldc_i4 + opCodes.mul + opCodes.ldc_i4 + opCodes.xor + opCodes.br_s ) || validateNetByteCode( // samples by: ConfuserEx (neo mod) opCodes.ldc_i4 + opCodes.ldc_i4 + opCodes.xor + opCodes.dup + opCodes.stloc_0 + opCodes.ldc_i4_3 + opCodes.rem_un + opCodes.switch__nobody ) || validateNetByteCode( // samples by: .NET Reactor (v6.9.8) opCodes.setStrict(opCodes.ldc_i4, "00 00 00 00") + opCodes.br + opCodes.br + opCodes.ldloc ) || validateNetByteCode( // samples by: .NET Reactor opCodes.ldsfld + opCodes.brfalse + opCodes.pop + opCodes.setStrict(opCodes.ldc_i4, "01 00 00 00") + // MSIL: 'ldc.4 1' opCodes.br + opCodes.nop ) || validateNetByteCode( // samples by: .NET Reactor opCodes.setNullValue(opCodes.ldc_i4) + opCodes.ldsfld + opCodes.brtrue + opCodes.pop + opCodes.ldc_i4 + opCodes.br ) || validateNetByteCode( // samples by: .NET Reactor (legacy~~) opCodes.stloc + opCodes.ldloc + opCodes.joinNoBodyAndValue(opCodes.switch__nobody, "** ** ?? ?? ?? ?? ?? ?? ?? ?? ?? ?? ?? ?? ?? ?? ?? ?? ?? ?? ?? ?? 00 00", "switch__nobody") + opCodes.ldc_i4 + opCodes.br ) || validateNetByteCode( // Crypto Obfuscator opCodes.ldc_i4_3 + opCodes.joinNoBodyAndValue(opCodes.switch__nobody, "01 00 00 00 F6 FF FF FF") + opCodes.ldc_i4_1 + opCodes.brtrue_s + opCodes.ldtoken + opCodes.pop ) || validateNetByteCode( // samples by: MindLated, NetShield opCodes.setNullValue(opCodes.ldc_i4) + // MSIL: 'ldc.i4 0' opCodes.stloc + opCodes.br + opCodes.nop + opCodes.ldloc + opCodes.setStrict(opCodes.ldc_i4, "01 00 00 00") + // MSIL: 'ldc.i4 1' opCodes.ceq + opCodes.brfalse ) || validateNetByteCode( // samples by: Rose Obfuscator opCodes.setNullValue(opCodes.ldc_i4) + // MSIL: 'ldc.i4 0' opCodes.stloc + opCodes.br + opCodes.nop + opCodes.ldloc + opCodes.ldc_i4 + opCodes.ceq + opCodes.brfalse ) || validateNetByteCode( // samples by: Smart Assembly opCodes.ldc_i4 + opCodes.br + opCodes.ldloc_s + opCodes.ldc_i4_s + opCodes.ldc_i4_0 + opCodes.stelem_i1 + opCodes.ldc_i4 + opCodes.br ) || validateNetByteCode( // samples by: ConfuserEx (Beds mod) opCodes.ldc_i4 + opCodes.ldc_i4 + opCodes._unknown + opCodes.ldc_i4 + opCodes._unknown + opCodes.stloc_0 + opCodes.nop + opCodes.ldloc_0 + opCodes.ldc_i4 + opCodes.ldc_i4 + opCodes._unknown + opCodes.ldc_i4 + opCodes._unknown + opCodes.ceq + opCodes.brfalse_s ) || validateNetByteCode( // samples by: DotNetPatcher opCodes.setStrict(opCodes.stloc_s, "05") + opCodes.nop + opCodes.ldloc_s + opCodes._unknown + opCodes.ceq + opCodes.brfalse_s + opCodes._unknown + opCodes.setStrict(opCodes.stloc_s, "05") + opCodes.nop + opCodes.ldloc_s + opCodes._unknown + opCodes.ceq + opCodes.brfalse_s ) || validateNetByteCode( // samples by: VMProtect opCodes.ldloc_0 + opCodes.setStrict(opCodes.ldc_i4, "?? ** ** **") + opCodes._unknown + opCodes.stloc_0 + opCodes.ldloc_0 + opCodes.setStrict(opCodes.ldc_i4, "?? ** ** **") + opCodes.xor + opCodes.stloc_0 ) || validateNetByteCode( // samples by: VMProtect opCodes.setStrict(opCodes.ldc_i4, "?? ** ** **") + opCodes._unknown + opCodes.stloc_0 + opCodes.setStrict(opCodes.ldftn, "** ?? ?? ??") ) ) { log(logType.net, "Control flow obfuscation detected! Offset: " + lastOffsetDetected); isCtrlFlowPresent = true; } if (isCtrlFlowPresent) options = addOption(options, "Ctrl flow"); const ctorSignatureOffset = PE.findSignature(PE_Cached.dosStubSize, PE_Cached.imageBodySize, "''00**"); const afterCtorOffset = ctorSignatureOffset !== -1 ? ctorSignatureOffset + 8 : -1; // Indicates that the file uses short object names. Typically this indicates the presence of an obfuscation // There are two ways to detect short names - find one-letter objects or just use signature mask at Ctor offset :D var isShortNamesPresent = false; if (afterCtorOffset !== -1) { if (PE.compare("00**00**00", afterCtorOffset) || PE.compare("00****00****00****00", afterCtorOffset) || PE.compare("00******00******00******00", afterCtorOffset) || PE.compare("00********00********00********00", afterCtorOffset) || PE.compare("00****00****00", afterCtorOffset) || PE.compare("00****00**00", afterCtorOffset) || PE.compare("00**00****00", afterCtorOffset)) { log(logType.net, "Short names detected! (mask)"); isShortNamesPresent = true; } if (!isShortNamesPresent && PE.compare("00**00", afterCtorOffset)) { // the second way :D var shortNamesFound = 0; const chars = "QWERTYUIOPASDFGHJKLZXCVBNMqwertyuiopasdfghjklzxcvbnm"; for (var i = 0; i < chars.length && !isShortNamesPresent; i++) { if (PE.isNetObjectPresent(chars[i])) { log(logType.net, "Short name found: \"" + chars[i] + "\" (" + shortNamesFound + "/20)"); shortNamesFound++; } if (shortNamesFound === 20) { isShortNamesPresent = true; } } } } if (isShortNamesPresent) options = addOption(options, "Short names"); // Scan for strange patterns after .ctor (like "a1b2c3d4e5f6g7h8i9j0") var badNamings = false; var buffer = String(); // get next 0x12c bytes after .ctor for (var i = 0; afterCtorOffset !== -1 && i < 0x12c; i++) { var currentByte = PE.readByte(afterCtorOffset + i).toString(16); if (currentByte.length === 1) currentByte = '0' + currentByte; buffer += currentByte + ' '; } var bufferString = String(); // 0x00 to [0x20, 0x20, 0x20] const bufferArray = buffer.replaceAll('00 ', '20 20 20 ').split(' '); // buffer to a string for (var i = 0; i < bufferArray.length; i++) bufferString += String.fromCharCode(parseInt(bufferArray[i], 16)); const patternsToExplore = bufferString.split(" "); var strangePatternsCounter = 0; // https://web.archive.org/web/20190719134346/http://bartdesmet.net/blogs/bart/archive/2008/08/21/how-c-array-initializers-work.aspx var isPrivateImplementationDetailsPresent = validateNetObject(""); for (var i = 0; i < patternsToExplore.length && !badNamings; i++) { const currentStringPattern = patternsToExplore[i]; if (isPrivateImplementationDetailsPresent && currentStringPattern.length === 40) { break; } if (currentStringPattern.indexOf("<") === -1 && !/^(?:[0-9A-F]{64})$/.test(currentStringPattern) && // These names may be generated by the compiler isNameObfuscated(currentStringPattern)) { strangePatternsCounter++; } if (strangePatternsCounter > 2) { log(logType.net, "Obfuscated naming patterns detected"); badNamings = true; } } if (badNamings) options = addOption(options, "Bad namings"); // AntiTamper protects the file from modification var isAntiTamperPresent = false; if ( validateNetUnicodeString(" is tampered.") || // samples by: .NET Reactor validateNetUnicodeString("ping 127.0.0.1 > nul") || // samples by: ConfuserEx (Trinity, SkiDzEx like) validateNetUnicodeString("/C ping 1.1.1.1 -n 1 -w 3000 > Nul & Del \"") || // samples by: MindLated validateNetUnicodeString( // samples by: ConfuserEx opCodes.ldloc_s + opCodes.ldc_i4_0 + opCodes.ldloc_s + opCodes.ldc_i4_0 + opCodes.ldelem_u4 + opCodes.ldloc_s + opCodes.ldc_i4_0 + opCodes.ldelem_u4 + opCodes._unknown + opCodes.stelem_i4 + opCodes.ldloc_s + opCodes.ldc_i4_1 + opCodes.ldloc_s + opCodes.ldc_i4_1 + opCodes.ldelem_u4 + opCodes.ldloc_s + opCodes.ldc_i4_1 + opCodes.ldelem_u4 + opCodes._unknown + opCodes.stelem_i4 + opCodes.ldloc_s + opCodes.ldc_i4_2 + opCodes.ldloc_s + opCodes.ldc_i4_2 + opCodes.ldelem_u4 + opCodes.ldloc_s + opCodes.ldc_i4_2 + opCodes.ldelem_u4 + opCodes._unknown + opCodes.stelem_i4 + opCodes.ldloc_s + opCodes.ldc_i4_3 + opCodes.ldloc_s + opCodes.ldc_i4_3 + opCodes.ldelem_u4 + opCodes.ldloc_s + opCodes.ldc_i4_3 + opCodes.ldelem_u4 + opCodes._unknown + opCodes.stelem_i4 + opCodes.ldloc_s + opCodes.ldc_i4_4 + opCodes.ldloc_s + opCodes.ldc_i4_4 + opCodes.ldelem_u4 + opCodes.ldloc_s + opCodes.ldc_i4_4 + opCodes.ldelem_u4 + opCodes._unknown + opCodes.stelem_i4 ) || validateNetByteCode( // samples: ConfuserEx (Beds mod, private) opCodes.ldloc_s + opCodes._unknown + opCodes.shr_un + opCodes.ldloc_s + opCodes.ldc_i4_s + opCodes.shl + opCodes.or + opCodes.stloc_s + opCodes.ldloc_s + opCodes._unknown + opCodes.shr_un + opCodes.ldloc_s + opCodes.ldc_i4_s + opCodes.shl + opCodes.or + opCodes.stloc_s + opCodes.ldloc_s + opCodes._unknown + opCodes.shr_un + opCodes.ldloc_s + opCodes.ldc_i4_s + opCodes.shl + opCodes.or + opCodes.stloc_s ) ) { log(logType.net, "Anti-tamper detected!"); isAntiTamperPresent = true; } if (isAntiTamperPresent) options = addOption(options, "Anti-tamper"); // If in the assembly you can find the second object starting with “”, then this is a fakeee! var isModuleCtorMultiple = false; var currentCtorOffset = PE.findSignature(PE_Cached.dosStubSize, PE_Cached.imageBodySize, "00''00"); if (currentCtorOffset !== -1) { var secondCtorNameOffset = PE.findSignature(currentCtorOffset + 10, PE_Cached.imageBodySize, "''"); if (secondCtorNameOffset !== -1 && PE.readByte(secondCtorNameOffset + 8) !== 0x00) { log(logType.net, "Fake detected! Offset: 0x" + Number(secondCtorNameOffset).toString(16)); isModuleCtorMultiple = true; } } if (isModuleCtorMultiple) options = addOption(options, "Fake .cctor name"); var isBadCctor = false; if (currentCtorOffset === -1) { log(logType.net, "It seems that the .cctor is missing. Bad PE format!"); isBadCctor = true; } if (isBadCctor) options = addOption(options, "Bad .cctor format"); // Detects the use of unusual mathematical expressions that would be simplified by the compiler. // For example, an expression like "912874 + 39188124^834" var isMutationsPresent = false; const mathOpCodes = [ opCodes.add, opCodes.sub, opCodes.mul, opCodes.div, opCodes.xor, opCodes.shr, opCodes.shl, opCodes.or, opCodes.not, opCodes.and ]; const mathTemplates = [ // %s = math opcode opCodes.ldc_i4 + opCodes.ldc_i4 + "%s" + opCodes.stloc, // samples by: .NET Reactor opCodes.ldc_i4 + opCodes.ldc_i4 + "%s" + opCodes.ldsfld, // samples by: .NET Reactor opCodes.ldc_i4 + opCodes.ldc_i4 + "%s" + opCodes.ldc_i4 + opCodes.add, // samples by: ConfuserEx (Beds mod) opCodes.ldloc_1 + opCodes.ldc_i4 + opCodes.ldc_i4 + "%s" + opCodes.ldc_i4 + opCodes.ldc_i4, // samples by: SkiDzEX opCodes.ldloc + opCodes.ldc_i4 + opCodes.ldc_i4 + opCodes.ldc_i4 + "%s" + opCodes.stelem_i1, // samples by: .NET Reactor opCodes.ldc_i4 + opCodes.ldc_i4 + "%s" + opCodes.br_s // samples by: [Unknown protector, only samples] ]; for (var y = 0; y < mathTemplates.length && !isMutationsPresent; y++) { const template = mathTemplates[y]; for (var e = 0; e < mathOpCodes.length && !isMutationsPresent; e++) { if (e === 0 && !validateNetByteCode(template.replaceAll("%s", opCodes._unknown))) break; // No math mutations const pattern = template.replaceAll("%s", mathOpCodes[e]); if (validateNetByteCode(pattern)) { log(logType.net, "Math mutations detected! Offset: " + lastOffsetDetected); isMutationsPresent = true; } } } if (isMutationsPresent) options = addOption(options, "Math mutations"); // VB NET apps with resources only var isStringsEncryptionPresent = false; if (PE_Cached.isVbNetStandardLibraryPresent && !isFrameworkComponent()) { if (validateNetObject("Resources") && !validateGlobalUnicodeString(".Resources")) { log(logType.net, "It appears that the strings are hidden/encrypted and can be loaded dynamically."); isStringsEncryptionPresent = true; } } if (isStringsEncryptionPresent) options = addOption(options, "Strings encryption"); // A type of obfuscation of numbers in which they are inverted several times from positive to negative and vice versa... var isMathInversionsPresent = false; if (validateNetByteCode(opCodes.ldc_i4 + opCodes.not) && ( validateNetByteCode( // ~(-(~(-(~(-(~(-( num )))))))) opCodes.ldc_i4 + opCodes.not + opCodes.neg + opCodes.not + opCodes.neg + opCodes.not + opCodes.neg + opCodes.not + opCodes.neg ) || validateNetByteCode( // ~(~(-(-(~(~( num )))))) opCodes.ldc_i4 + opCodes.not + opCodes.not + opCodes.neg + opCodes.neg + opCodes.not + opCodes.not ) || validateNetByteCode( // ~(-(~(~(-(-( num )))))) opCodes.ldc_i4 + opCodes.not + opCodes.neg + opCodes.not + opCodes.not + opCodes.neg + opCodes.neg ) || validateNetByteCode( // ~(-(~(-(~(~( num )))))) opCodes.ldc_i4 + opCodes.not + opCodes.neg + opCodes.not + opCodes.neg + opCodes.not + opCodes.not ) || validateNetByteCode( // ~(-(~(-(~(-( num )))))) opCodes.ldc_i4 + opCodes.not + opCodes.neg + opCodes.not + opCodes.neg + opCodes.not + opCodes.neg ) )) { log(logType.net, "Math inversions detected, offset " + lastOffsetDetected); isMathInversionsPresent = true; } if (isMathInversionsPresent) options = addOption(options, "Math inversions"); // A technique that allows you to avoid code decompilation. dnSpy gives a parsing error when trying to open such a file var invalidOpCodes = false; if ( /* validateNetByteCode( // samples by: SugarGuard opCodes.setStrict(opCodes.box, "?? 00 00 01") + opCodes.ret ) || */ validateNetByteCode( // samples by: ConfuserEx (Beds mod) opCodes.setStrict(opCodes.calli, "FF FF FF FF") + opCodes.setStrict(opCodes.sizeof, "FF FF FF FF") ) ) { log(logType.net, "Invalid OpCodes detected, offset " + lastOffsetDetected); invalidOpCodes = true; } if (invalidOpCodes) options = addOption(options, "Invalid OpCodes"); // Some protections include their own dependencies in the form of runtime libraries var isProtectionRuntimePresent = false; var runtimeFound = String(); var protectionsRuntime = [ // Need more { lib: "haspdnert.dll", name: "SafeNet Sentinel LDK" }, { lib: "AgileDotNet.VMRuntime.dll", name: "Agile" }, { lib: "Xerin.Runtime.dll", name: "XerinFuscator" }, { lib: "CliSecureRd64.dll", name: "CliSecure" }, { lib: "Xerin.Runtime", name: "XerinFuscator" }, { lib: "CliSecureRd.dll", name: "CliSecure" }, { lib: "Protect32.dll", name: "ILProtector" }, { lib: "Protect64.dll", name: "ILProtector" }, { lib: "OneVM.Runtime.dll", name: "OneVM" }, { lib: "MRuntime3.dll", name: "Maxtocode" }, { lib: "NecroVM.Runtime", name: "NecroVM" }, { lib: "Attick.dll", name: "Maxtocode" }, { lib: "HVMRuntm.dll", name: "DNGuard" } ]; for (var i = 0; i < protectionsRuntime.length; i++) { const runtimeInfo = protectionsRuntime[i], runtimeLibraryName = runtimeInfo.lib, protectorName = runtimeInfo.name; if (validateNetObject(runtimeLibraryName) || // "runtime.dll" validateNetObject(runtimeLibraryName.substring(0, runtimeLibraryName.length - 4))) { // "runtime" log(logType.net, protectorName + " runtime detected!"); isProtectionRuntimePresent = true; runtimeFound = protectorName; break; } } // Clean up: release the dictionary protectionsRuntime = undefined; if (isProtectionRuntimePresent) options = addOption(options, runtimeFound + " runtime"); const obfuscatorsAttributes = [ "Xenocode.Client.Attributes.AssemblyAttributes.ProcessedByXenocode", // Xenocode "CryptoObfuscator.ProtectedWithCryptoObfuscatorAttribute", // Crypto Obfuscator "SecureTeam.Attributes.ObfuscatedByAgileDotNetAttribute", // Agile.NET "Xenocode.Client.Attributes.AssemblyAttributes", // Xenocode "SmartAssembly.Attributes.PoweredByAttribute", // Smart Assembly "ObfuscatedByAgileDotNetAttribute", // Agile.NET "NineRays.Obfuscator.Evaluation", // Spices.Net "ObfuscatedByCliSecureAttribute", // CliSecure "BabelObfuscatorAttribute", // Babel "GeneratedWithBatchToApp", // BatchToApp "AsStrongAsFuckAttribute", // AsStrongAsFuck "ProtectedByDotnetsafer", // DotNetSafer "Macrobject.Obfuscator", // Macrobject "DotfuscatorAttribute", // Dotfuscator "CodeWallTrialVersion", // CodeWall "ConfusedByAttribute", // ConfuserEx "ObfuscatedByGoliath", // Goliath "NETSpider.Attribute", // NETSpider "NineRays.Obfuscator", // Spices.Net "PoweredByAttribute", // Smart Assembly "RustemSoft.Skater", // Skater "Beds-Protector", // fake Beds Protector "BabelAttribute", // Babel "MRuntime3.dll", // Maxtocode "YanoAttribute", // Yano "EMyPID_8234_", // Eazfuscator.NET "ZYXDNGuarder", // DNGuard "Sixxpack", // Sixxpack "____KILL", // CodeVeil "SkiDzEX", // ConfuserEx based "Reactor", // Fake .NET Reactor ]; var isFakeSignaturesPresent = false; var isWatermarkPresent = false; var signaturesCounter = 0; var obfuscatorAttributeFound = String(); // Iterate through obfuscators attributes for (var t = 0; t < obfuscatorsAttributes.length; t++) { if (validateNetObject(obfuscatorsAttributes[t])) { obfuscatorAttributeFound = obfuscatorsAttributes[t]; signaturesCounter++; if (signaturesCounter > 1) { log(logType.net, "Multiple obfuscator attributes detected"); isFakeSignaturesPresent = true; break; } } } if (isFakeSignaturesPresent) { options = addOption(options, "Fake signatures"); } else { // "Watermark" is only possible in the absence of fake signatures if (signaturesCounter === 1) { log(logType.net, "Watermark (Attribute) found: \"" + obfuscatorAttributeFound + "\""); isWatermarkPresent = true; } if ((!isWatermarkPresent && ( validateSignature("'Obfuscated'") || validateSignature("'obfuscated'") || validateSignature("'ByAttribute'") || validateSignature("'ObfuscatorAttribute'") || validateNetObject("ObfuscationAttribute") )) && !isFrameworkComponent()) { // System.Reflection.ObfuscationAttribute log(logType.net, "Obfuscation watermark pattern detected"); isWatermarkPresent = true; } } if (isWatermarkPresent) options = addOption(options, "Watermark"); // The module name must always contain the assembly name and the output file extension (Assembly "App", Module "App.dll"). // If this standard is not followed, it means that the sample was modified after compilation. var isBuildInfoModified = false; var buildAssemblyName = PE_Cached.nameOfNetAssemblyName, buildModuleName = PE_Cached.nameOfNetModuleName; if (buildAssemblyName.indexOf("Interop.") === 0) { buildAssemblyName = buildAssemblyName.substring(8, buildAssemblyName.length); } if ((buildAssemblyName !== getFileNameWithoutExtension(buildModuleName) || buildAssemblyName === buildModuleName) && !(PE_Cached.isJscriptNetStandardLibraryPresent && buildModuleName === "JScript Module")) { log(logType.net, "Managed assembly and module names do not match"); isBuildInfoModified = true; } if (isBuildInfoModified) options = addOption(options, "Modified build info"); const protectorsLabelsToRemove = [ // Protectors with these names will be removed from results "SafeNet Sentinel LDK .NET", "Xenocode Postbuild", "Smart Assembly", "Dotfuscator", "Babel .NET", "Spices.Net", "Maxtocode", "FISH .NET", "CliSecure", "CodeWall", "CodeVeil", "Sixxpack", "DNGuard", "Goliath", "Agile", "Yano" ], packersLabelsToRemove = [ "Quest PowerGUI", "DataAnubis", "NsPack", "ASPack" ], protectionsLabelsToRemove = [ "Sentinel SuperPro dongle reference", "Unikey/Activator dongle reference", "Eutron SmartKey dongle reference", "SenseLock dongle reference", "Hardlock dongle reference", "WIBU Key dongle reference", "Wizzkey dongle reference", "SoftLok dongle reference", "NetHASP dongle reference" ]; // Волки делают АУФ 🐺☝️ if (isFakeSignaturesPresent) { for (var i = 0; i < protectorsLabelsToRemove.length; i++) { _removeResult("protector", protectorsLabelsToRemove[i]); } for (var i = 0; i < packersLabelsToRemove.length; i++) { _removeResult("cryptor", packersLabelsToRemove[i]); _removeResult("packer", packersLabelsToRemove[i]); } for (var i = 0; i < protectionsLabelsToRemove.length; i++) { _removeResult("protection", protectionsLabelsToRemove[i]); } } if (options.length !== 0) isDetected = true; if (isDetected) { _setResult("~protection", "Obfuscation", String(), PE.isVerbose() ? options : String()); } } // #endregion // #region "anti-analysis (.NET)" function scanForAntiAnalysis_NET() { log(logType.nothing, "Scanning for anti-analysis tricks..."); var options = String(); var isDetected = false; // Assumes the file can detect debugging protection var isAntiDebugPresent = false; const debuggerObject = "Debugger", // MSIL: 'System.Diagnostics.Debugger' from System.Diagnostics.dll isAttached = "get_IsAttached", // MSIL: '*.Debugger::get_IsAttached()' isLogging = "IsLogging" // MSIL: '*.Debugger::IsLogging()' if ( ( ( // .NET Functions validateNetObject(debuggerObject) || validateNetUnicodeString(debuggerObject) // Check for 'Debugger' ) && ( (validateNetObject(isAttached) || validateNetUnicodeString(isAttached)) || // Check for 'get_IsAttached' property (validateNetObject(isLogging) || validateNetUnicodeString(isLogging)) // Check for 'IsLogging' function ) || ( // Native (WinAPI) functions validateNetObject("CheckRemoteDebuggerPresent") || validateNetObject("IsDebuggerPresent") || validateNetObject("NtRemoveProcessDebug") || validateNetObject("NtQueryInformationProcess") ) ) && !isFrameworkComponent() ) { log(logType.net, "Debugger detection pattern detected"); isAntiDebugPresent = true; } if (isAntiDebugPresent) options = addOption(options, "Anti-debug"); // A type of protection in which, after launching an application, it erases the headers and/or PE signature behind itself var isAntiDumpPresent = false; if ( !isFrameworkComponent() && validateNetObject("VirtualProtect") && ( // from 'kernel32.dll', WinAPI // Need to check isAllNetObjectsPresent( // samples by: ConfuserEx, SkiDzEX [ "System.Runtime.InteropServices", // System.Runtime.InteropServices.dll "Marshal", // MSIL: '*.Marshal::GetHINSTANCE( ... )' "GetHINSTANCE", // MSIL: '*.Marshal::GetHINSTANCE( ... )' "IntPtr", // MSIL: 'System.IntPtr' "op_Explicit" // MSIL: 'System.IntPtr::op_Explicit' ] ) || isAllNetObjectsPresent( // samples by: Inx Obfuscator [ "System.Diagnostics", // System.Diagnostics "memcpy", // from 'msvcrt.dll', WinAPI "IntPtr", // MSIL: 'System.IntPtr' "get_MainModule", // MSIL: '*.Process::get_MainModule()' "get_BaseAddress" // MSIL: '*.ProcessModule::get_BaseAddress()' ] ) || isAllNetObjectsPresent( // samples by: MindLated [ "System.Runtime.InteropServices", // System.Runtime.InteropServices.dll "Marshal", // MSIL: '*.Marshal::GetHINSTANCE( ... )' "GetHINSTANCE", // MSIL: '*.Marshal::GetHINSTANCE( ... )' "IntPtr", // MSIL: 'System.IntPtr' "CopyBlock", "InitBlock" ] ) ) ) { log(logType.net, "Memory-based anti-dump pattern detected"); isAntiDumpPresent = true; } if (isAntiDumpPresent) options = addOption(options, "Anti-dump"); // https://medium.com/@tarunrd77/dnspy-static-analysis-of-a-net-malware-012806424acf const antiDnSpyTriggers = [ "dnspy", "dnSpy", "DNSPY" ]; var isAntiDnSpyPresent = false; // Iterate through anti-DnSpy triggers for (var l = 0; l < antiDnSpyTriggers.length && !isAntiDnSpyPresent; l++) { const dnSpyName = antiDnSpyTriggers[l]; // Check if the signature for anti-DnSpy trigger is valid using Unicode signature mask or the original signature if ( validateGlobalUnicodeString(dnSpyName) || validateSignature("'" + dnSpyName + "'") ) { log(logType.net, "dnSpy-related anti-analysis references detected"); isAntiDnSpyPresent = true; } } if (isAntiDnSpyPresent) options = addOption(options, "Anti-dnSpy"); // https://github.com/icsharpcode/ILSpy const antiIlSpyTriggers = [ "ilspy", "ilSpy", "ILSpy", "ILSPY" ]; var isAntiIlSpyPresent = false; for (var l = 0; l < antiIlSpyTriggers.length && !isAntiIlSpyPresent; l++) { const ilSpyName = antiIlSpyTriggers[l]; // Check if the signature for anti-ILSpy trigger is valid using Unicode signature mask or the original signature if ( validateGlobalUnicodeString(ilSpyName) || validateSignature("'" + ilSpyName + "'") ) { log(logType.net, "ILSpy-related anti-analysis references detected"); isAntiIlSpyPresent = true; } } if (isAntiIlSpyPresent) options = addOption(options, "Anti-ILSpy"); const isGetModuleHandleDetected = validateSignature("'GetModuleHandle'"); // from 'kernel32.dll' // https://en.wikipedia.org/wiki/Sandboxie const sbieVariants = [ "sbiedll.", "SbieDll.", "SBIEDLL." ]; var isAntiSbiePresent = false; if (isGetModuleHandleDetected) { // Iterate through Sandboxie variants for (var l = 0; l < sbieVariants.length && !isAntiSbiePresent; l++) { const sbieLib = sbieVariants[l]; // Check if the signature for Sandboxie variant is valid using Unicode signature mask or the original signature if ( PE.isNetObjectPresent("IsSandboxiePresent") || // https://github.com/AdvDebug/AntiCrack-DotNet/blob/f3cd8104e952580e0edf298101139d15befe008e/AntiCrack-DotNet/AntiVirtualization.cs#L53 validateGlobalUnicodeString(sbieLib) || validateSignature("'" + sbieLib + "'") ) { log(logType.any, "Sandboxie-related environment markers detected"); isAntiSbiePresent = true; } } } if (isAntiSbiePresent) options = addOption(options, "Anti-SandBoxie"); // https://help.comodo.com/topic-394-1-767-9229-the-sandbox.html const comodoVariants = [ "cmdvrt32.", "cmdvrt64.", "CMDVRT32.", "CMDVRT64." ]; var isAntiComodoPresent = false; if (isGetModuleHandleDetected) { // Iterate through Comodo Sandbox variants for (var l = 0; l < comodoVariants.length && !isAntiComodoPresent; l++) { const comodoLib = comodoVariants[l]; // Check if the signature for Comodo Sandbox variant is valid using Unicode signature mask or the original signature if ( PE.isNetObjectPresent("IsComodoSandboxPresent") || // https://github.com/AdvDebug/AntiCrack-DotNet/blob/f3cd8104e952580e0edf298101139d15befe008e/AntiCrack-DotNet/AntiVirtualization.cs#L64 validateGlobalUnicodeString(comodoLib) || validateSignature("'" + comodoLib + "'") ) { log(logType.any, "Comodo sandbox markers detected"); isAntiComodoPresent = true; } } } if (isAntiComodoPresent) options = addOption(options, "Anti-Comodo"); // https://blog.360totalsecurity.com/en/how-to-use-360-sandbox/ const qihoo360Variants = [ "SxIn.", "SXIn." ]; var isAntiQihoo360Present = false; if (isGetModuleHandleDetected) { // Iterate through Qihoo 360 Sandbox variants for (var l = 0; l < qihoo360Variants.length && !isAntiQihoo360Present; l++) { const qihoo360Lib = qihoo360Variants[l]; // Check if the signature for Qihoo 360 Sandbox variant is valid using Unicode signature mask or the original signature if ( PE.isNetObjectPresent("IsQihoo360SandboxPresent") || // https://github.com/AdvDebug/AntiCrack-DotNet/blob/f3cd8104e952580e0edf298101139d15befe008e/AntiCrack-DotNet/AntiVirtualization.cs#L75 validateGlobalUnicodeString(qihoo360Lib) || validateSignature("'" + qihoo360Lib + "'") ) { log(logType.any, "Qihoo 360 sandbox markers detected"); isAntiQihoo360Present = true; } } } if (isAntiQihoo360Present) options = addOption(options, "Anti-Qihoo 360"); // https://www.networkintelligence.ai/blogs/cuckoo-sandbox/ const cuckooVariants = [ "cuckoomon.", "CUCKOOMON." ]; var isAntiCuckooPresent = false; if (isGetModuleHandleDetected) { // Iterate through Cuckoo Sandbox variants for (var l = 0; l < cuckooVariants.length && !isAntiCuckooPresent; l++) { const cuckooLib = cuckooVariants[l]; // Check if the signature for Cuckoo Sandbox variant is valid using Unicode signature mask or the original signature if ( PE.isNetObjectPresent("IsCuckooSandboxPresent") || // https://github.com/AdvDebug/AntiCrack-DotNet/blob/f3cd8104e952580e0edf298101139d15befe008e/AntiCrack-DotNet/AntiVirtualization.cs#L86 validateGlobalUnicodeString(cuckooLib) || validateSignature("'" + cuckooLib + "'") ) { log(logType.any, "Cuckoo sandbox markers detected"); isAntiCuckooPresent = true; } } } if (isAntiCuckooPresent) options = addOption(options, "Anti-Cuckoo"); // https://www.winehq.org/ var isAntiWinePresent = false; var wineGetUnixFileName = "wine_get_unix_file_name"; if (isGetModuleHandleDetected && ( validateGlobalUnicodeString(wineGetUnixFileName) || validateSignature("'" + wineGetUnixFileName + "'") )) { log(logType.any, "Wine environment marker detected"); isAntiWinePresent = true; } if (isAntiWinePresent) options = addOption(options, "Anti-Wine"); var isAntiVmPresent = false; if (validateNetUnicodeString("VirtualBox Graphics Adapters") || validateNetUnicodeString("VMWARE svga 2") || validateNetUnicodeString("VMware SVGA II") || validateNetUnicodeString("VirtualBox") || validateNetUnicodeString("VMBusHID") || validateNetUnicodeString("vmware") || validateGlobalUnicodeString("WDAGUtilityAccount")) { log(logType.any, "Virtual-machine environment markers detected"); isAntiVmPresent = true; } if (isAntiVmPresent) options = addOption(options, "Anti-VM"); if (options.length !== 0) isDetected = true; if (isDetected) { _setResult("~protection", "Anti analysis", String(), PE.isVerbose() ? options : String()); } } // #endregion // #region "anti-analysis (Native)" function scanForAntiAnalysis_Native() { log(logType.nothing, "Scanning for anti-analysis tricks..."); var options = String(); var isDetected = Boolean(); // Direct PEB debug-field reads and debug-register accesses are two compact forms // of native anti-debugging. They share one verdict while retaining the source type. var isNativeAntiDebugPresent = false, nativeAntiDebugType = String(); if (!PE_Cached.isDynamicLinkLibrary && !PE_Cached.isDriver && PE_Cached.isArchX86 && (PE_Cached.imageSubsystem === 2 || PE_Cached.imageSubsystem === 3)) { const antiDebugNativeRegister = PE_Cached.isI386 ? "E(?:AX|BX|CX|DX|SI|DI|BP)" : "R(?:AX|BX|CX|DX|SI|DI|BP|8|9|1[0-5])", antiDebugGeneralPurposeRegister = "(?:E(?:AX|BX|CX|DX|SI|DI|BP)|R(?:AX|BX|CX|DX|SI|DI|BP|8D?|9D?|1[0-5]D?))", antiDebugRegister32BitPrefixPattern = /^E/, antiDebugRegister32BitSuffixPattern = /D$/, antiDebugRegister32BitPattern = /^(?:E(?:AX|BX|CX|DX|SI|DI|BP)|R(?:8|9|1[0-5])D)$/i, antiDebugPebSegment = PE_Cached.isI386 ? "FS" : "GS", antiDebugPebAddress = PE_Cached.isI386 ? "(?:0X0*30|48)" : "(?:0X0*60|96)", antiDebugPebFieldOffset = PE_Cached.isI386 ? "(?:0X0*(?:2|68)|2|104)" : "(?:0X0*(?:2|BC)|2|188)", antiDebugPebLoadPattern = new RegExp("^MOV (" + antiDebugNativeRegister + "), (?:(?:DWORD|QWORD) PTR )?" + antiDebugPebSegment + ":\\[" + antiDebugPebAddress + "\\]$", "i"), antiDebugPebFieldAccessPattern = new RegExp("\\[(" + antiDebugNativeRegister + ") ?\\+ ?" + antiDebugPebFieldOffset + "\\]", "i"), antiDebugRegisterCopyPattern = new RegExp("^MOV (" + antiDebugNativeRegister + "), (" + antiDebugNativeRegister + ")$", "i"), antiDebugRegisterWritePattern = new RegExp("^(?:MOV(?:ABS)?|MOVZX|MOVSX|MOVSXD|LEA|XOR|ADD|SUB|ADC|SBB|AND|OR|NOT|NEG|INC|DEC|POP) (" + antiDebugGeneralPurposeRegister + ")(?:,|$)", "i"), antiDebugRegisterAccessPattern = /\bDR[0-7]\b/i, antiDebugSelfMovePattern = new RegExp("^(?:MOV|XCHG) (" + antiDebugGeneralPurposeRegister + "), \\1$|^LEA (" + antiDebugGeneralPurposeRegister + "), \\[\\2\\]$", "i"), antiDebugValuePreservingInstructionPattern = new RegExp("^(?:ADD|SUB|OR|XOR|SHL|SHR|SAR|ROL|ROR) (" + antiDebugGeneralPurposeRegister + "), (?:0X0+|0+)$|^IMUL (" + antiDebugGeneralPurposeRegister + "), \\2, (?:0X0*1|1)$", "i"), antiDebugStopOpCodes = [ "CALL", "HLT", "INT", "INT1", "INT3", "INTO", "IRET", "IRETD", "IRETQ", "JMP", "JMPF", "LOOP", "LOOPE", "LOOPNE", "LOOPNZ", "LOOPZ", "RET", "RETF", "RETN", "SYSCALL", "SYSENTER", "SYSRET", "SYSEXIT", "UD2" ]; // A PEB pointer may be copied through several registers. Track all proven aliases // and invalidate only registers that are explicitly overwritten by polymorphic junk. var antiDebugPebRegisters = {}; // Walk the bounded EP stream shared by native heuristics. Stop as soon as one // complete anti-debug proof is found because the verdict retains only its type. for (var i = 0; i < 256 && !isNativeAntiDebugPresent; i++) { var currentEpInstruction = emulateAndGetAsmInstructionByIndex(i), currentEpOpCode = getAsmOpCode(currentEpInstruction); if (!currentEpOpCode) break; // Safety check // Classify the instruction once so the state transitions below remain explicit. var pebLoad = currentEpInstruction.match(antiDebugPebLoadPattern), pebDebugFieldAccess = currentEpInstruction.match(antiDebugPebFieldAccessPattern), registerCopy = currentEpInstruction.match(antiDebugRegisterCopyPattern), registerWrite = currentEpInstruction.match(antiDebugRegisterWritePattern), selfMove = currentEpInstruction.match(antiDebugSelfMovePattern), valuePreservingInstruction = currentEpInstruction.match(antiDebugValuePreservingInstructionPattern), isPebDebugFieldAccess = pebDebugFieldAccess && antiDebugPebRegisters[pebDebugFieldAccess[1].toUpperCase().replace(antiDebugRegister32BitPrefixPattern, "R").replace(antiDebugRegister32BitSuffixPattern, String())] && /^(?:MOV|MOVZX|MOVSX|CMP|TEST|AND)$/.test(currentEpOpCode); if (currentEpOpCode === "MOV" && antiDebugRegisterAccessPattern.test(currentEpInstruction)) { // Any explicit MOV involving DR0-DR7 is already a complete register-based proof. log(logType.emulation, "Debug-register access near EP: " + currentEpInstruction); nativeAntiDebugType = "REG"; isNativeAntiDebugPresent = true; } else if (isPebDebugFieldAccess) { // Accept a debug-field read only through a register proven to contain the PEB. log(logType.emulation, "PEB debug-field access near EP: " + currentEpInstruction); nativeAntiDebugType = "PEB"; isNativeAntiDebugPresent = true; } else if (pebLoad) { // Seed pointer provenance from the architecture-specific FS/GS PEB slot. antiDebugPebRegisters[pebLoad[1].toUpperCase().replace(antiDebugRegister32BitPrefixPattern, "R").replace(antiDebugRegister32BitSuffixPattern, String())] = true; } else if (registerCopy) { var registerCopyDestination = registerCopy[1].toUpperCase().replace(antiDebugRegister32BitPrefixPattern, "R").replace(antiDebugRegister32BitSuffixPattern, String()), registerCopySource = registerCopy[2].toUpperCase().replace(antiDebugRegister32BitPrefixPattern, "R").replace(antiDebugRegister32BitSuffixPattern, String()); // Copy or clear only the destination proof according to the source provenance. if (antiDebugPebRegisters[registerCopySource]) { antiDebugPebRegisters[registerCopyDestination] = true; } else { delete antiDebugPebRegisters[registerCopyDestination]; } } else if (selfMove) { var selfMoveRegister = (selfMove[1] || selfMove[2]).toUpperCase(), selfMoveRegisterKey = selfMoveRegister.replace(antiDebugRegister32BitPrefixPattern, "R").replace(antiDebugRegister32BitSuffixPattern, String()); // A PE32+ r32 self-move zero-extends its destination. It cannot retain // a 64-bit PEB pointer, but aliases held in other registers remain valid. if (PE_Cached.is64bit && antiDebugRegister32BitPattern.test(selfMoveRegister)) { delete antiDebugPebRegisters[selfMoveRegisterKey]; } } else if (valuePreservingInstruction) { var valuePreservingRegister = (valuePreservingInstruction[1] || valuePreservingInstruction[2]).toUpperCase(), valuePreservingRegisterKey = valuePreservingRegister.replace(antiDebugRegister32BitPrefixPattern, "R").replace(antiDebugRegister32BitSuffixPattern, String()); // Full-width neutral arithmetic preserves a pointer; an r32 form truncates it. if (PE_Cached.is64bit && antiDebugRegister32BitPattern.test(valuePreservingRegister)) { delete antiDebugPebRegisters[valuePreservingRegisterKey]; } } else if (currentEpOpCode === "NOP" || currentEpOpCode === "FNOP" || currentEpOpCode === "PAUSE" || /^(?:CMP|TEST)$/.test(currentEpOpCode) || /^J(?!MP)[A-Z]+$/.test(currentEpOpCode)) { // General-purpose registers survive these operations on the fall-through path. } else if (registerWrite) { var registerWriteKey = registerWrite[1].toUpperCase().replace(antiDebugRegister32BitPrefixPattern, "R").replace(antiDebugRegister32BitSuffixPattern, String()); delete antiDebugPebRegisters[registerWriteKey]; } else { // Unknown semantics may clobber any alias, so discard the whole proof set. antiDebugPebRegisters = {}; // CALL retains a physical fall-through path but may clobber registers, so // its proofs are discarded while the bounded scan may start a new match later. if (antiDebugStopOpCodes.indexOf(currentEpOpCode) !== -1 && currentEpOpCode !== "CALL") break; } } } if (isNativeAntiDebugPresent) options = addOption(options, "Anti-debug near EP (" + nativeAntiDebugType + ")"); if (options.length !== 0) isDetected = true; if (isDetected) { _setResult("~protection", "Anti analysis", String(), PE.isVerbose() ? options : String()); } } // #endregion /** * Determines if the current PE file is a C++/CLR .NET application. * * @returns {boolean} True if the PE file matches the C++/CLR .NET application heuristics, otherwise false. */ function isCppClrLikeApp() { return PE_Cached.isDotNet && PE_Cached.isRichSignaturePresent && PE.isLibraryPresent("KERNEL32.DLL") && PE_Cached.isNetGlobalCctorPresent; } /** * Checks if the PE file is linked with an unpackaged Windows App SDK. * * This function verifies the presence of specific .NET objects that indicate * the application uses the Windows App SDK in an unpackaged deployment scenario. * * @returns {boolean} True if both "AutoInitialize" and "InitializeWindowsAppSDK" * .NET objects are present in the PE file, false otherwise. */ function isUnpackagedWindowsAppSdkLinked() { return PE.isNetObjectPresent("AutoInitialize") && PE.isNetObjectPresent("InitializeWindowsAppSDK"); } /** * Microsoft Intermediate Language (MSIL) OpCodes class for .NET assembly analysis. * * This class provides a comprehensive collection of MSIL instruction opcodes with their * corresponding bytecode patterns for signature-based detection in .NET assemblies. * All opcode values are verified against the official Microsoft .NET Runtime source code * and ECMA-335 Common Language Infrastructure (CLI) specification. * * @class NetOpCodes * @description Represents MSIL opcodes as hexadecimal string patterns for bytecode matching. * * Features: * - Complete MSIL instruction set coverage (93 opcodes) * - Support for both single-byte (0x00-0xFF) and two-byte (0xFE00-0xFEFF) opcodes * - Wildcard pattern support using '?' for variable operands * - Prefix instruction handling (volatile, tail, readonly, etc.) * - Helper methods for strict value substitution and null value setting * * Pattern Format: * - Fixed bytes: "58" (add instruction) * - Variable operands: "28????????" (call with 4-byte method token) * - Short operands: "2B??" (br.s with 1-byte offset) * - Two-byte opcodes: "FE01" (ceq instruction) * * Helper Methods: * - setStrict(opcode, value): Sets specific operand values for opcodes * - setNullValue(opcode): Replaces wildcards with zero bytes * * @see {@link https://docs.microsoft.com/en-us/dotnet/api/system.reflection.emit.opcodes} Microsoft OpCodes Documentation * @see {@link https://www.ecma-international.org/publications-and-standards/standards/ecma-335/} ECMA-335 CLI Specification * @see {@link https://github.com/dotnet/runtime/blob/main/src/libraries/System.Private.CoreLib/src/System/Reflection/Emit/OpCodes.cs} .NET Runtime Source */ function NetOpCodes() { // ========== BASIC STACK OPERATIONS ========== this.dup = "25"; // MSIL: 'dup' - Duplicate top stack value this.nop = "00"; // MSIL: 'nop' - No operation this.pop = "26"; // MSIL: 'pop' - Remove top stack value // ========== ARITHMETIC OPERATIONS ========== this.add = "58"; // MSIL: 'add' - Add two values this.add_ovf = "D6"; // MSIL: 'add.ovf' - Add signed integer values with overflow check this.add_ovf_un = "D7"; // MSIL: 'add.ovf.un' - Add unsigned integer values with overflow check this.div = "5B"; // MSIL: 'div' - Divide two values this.div_un = "5C"; // MSIL: 'div.un' - Divide unsigned values this.mul = "5A"; // MSIL: 'mul' - Multiply two values this.mul_ovf = "D8"; // MSIL: 'mul.ovf' - Multiply signed integer values with overflow check this.mul_ovf_un = "D9"; // MSIL: 'mul.ovf.un' - Multiply unsigned integer values with overflow check this.neg = "65"; // MSIL: 'neg' - Negate value this.rem = "5D"; // MSIL: 'rem' - Remainder when dividing two values this.rem_un = "5E"; // MSIL: 'rem.un' - Remainder (unsigned) this.sub = "59"; // MSIL: 'sub' - Subtract two values this.sub_ovf = "DA"; // MSIL: 'sub.ovf' - Subtract native int with overflow check this.sub_ovf_un = "DB"; // MSIL: 'sub.ovf.un' - Subtract unsigned int with overflow check // ========== LOGICAL OPERATIONS ========== this.and = "5F"; // MSIL: 'and' - Bitwise AND this.not = "66"; // MSIL: 'not' - Bitwise NOT this.or = "60"; // MSIL: 'or' - Bitwise OR this.shl = "62"; // MSIL: 'shl' - Shift left this.shr = "63"; // MSIL: 'shr' - Shift right this.shr_un = "64"; // MSIL: 'shr.un' - Shift right (unsigned) this.xor = "61"; // MSIL: 'xor' - Bitwise XOR // ========== COMPARISON OPERATIONS ========== this.ceq = "FE01"; // MSIL: 'ceq' - Compare equal this.cgt = "FE02"; // MSIL: 'cgt' - Compare greater than this.cgt_un = "FE03"; // MSIL: 'cgt.un' - Compare greater than (unsigned) this.clt = "FE04"; // MSIL: 'clt' - Compare less than this.clt_un = "FE05"; // MSIL: 'clt.un' - Compare less than (unsigned) // ========== CONSTANT LOADING ========== this.ldc_i4_m1 = "15"; // MSIL: 'ldc.i4.m1' - Load constant -1 this.ldc_i4 = "20????????"; // MSIL: 'ldc.i4' - Load 32-bit integer constant this.ldc_i4_0 = "16"; // MSIL: 'ldc.i4.0' - Load constant 0 this.ldc_i4_1 = "17"; // MSIL: 'ldc.i4.1' - Load constant 1 this.ldc_i4_2 = "18"; // MSIL: 'ldc.i4.2' - Load constant 2 this.ldc_i4_3 = "19"; // MSIL: 'ldc.i4.3' - Load constant 3 this.ldc_i4_4 = "1A"; // MSIL: 'ldc.i4.4' - Load constant 4 this.ldc_i4_5 = "1B"; // MSIL: 'ldc.i4.5' - Load constant 5 this.ldc_i4_6 = "1C"; // MSIL: 'ldc.i4.6' - Load constant 6 this.ldc_i4_7 = "1D"; // MSIL: 'ldc.i4.7' - Load constant 7 this.ldc_i4_8 = "1E"; // MSIL: 'ldc.i4.8' - Load constant 8 this.ldc_i4_s = "1F??"; // MSIL: 'ldc.i4.s' - Load small integer constant this.ldc_i8 = "21????????????????"; // MSIL: 'ldc.i8' - Load 64-bit integer constant this.ldc_r4 = "22????????"; // MSIL: 'ldc.r4' - Load 32-bit float constant this.ldc_r8 = "23????????????????"; // MSIL: 'ldc.r8' - Load 64-bit float constant this.ldnull = "14"; // MSIL: 'ldnull' - Load null reference // ========== ARGUMENT OPERATIONS ========== this.ldarg = "FE09????"; // MSIL: 'ldarg' - Load argument this.ldarg_0 = "02"; // MSIL: 'ldarg.0' - Load argument 0 this.ldarg_1 = "03"; // MSIL: 'ldarg.1' - Load argument 1 this.ldarg_2 = "04"; // MSIL: 'ldarg.2' - Load argument 2 this.ldarg_3 = "05"; // MSIL: 'ldarg.3' - Load argument 3 this.ldarg_s = "0E??"; // MSIL: 'ldarg.s' - Load argument (short form) this.starg_s = "10??"; // MSIL: 'starg.s' - Store to argument (short form) this.ldarga = "FE0A????"; // MSIL: 'ldarga' - Load argument address this.ldarga_s = "0F??"; // MSIL: 'ldarga.s' - Load argument address (short form) this.starg = "FE0B????"; // MSIL: 'starg' - Store to argument this.ldloca = "FE0D????"; // MSIL: 'ldloca' - Load local variable address this.ldloca_s = "12??"; // MSIL: 'ldloca.s' - Load local variable address (short form) // ========== LOCAL VARIABLE OPERATIONS ========== this.ldloc = "FE0C????"; // MSIL: 'ldloc' - Load local variable this.ldloc_0 = "06"; // MSIL: 'ldloc.0' - Load local variable 0 this.ldloc_1 = "07"; // MSIL: 'ldloc.1' - Load local variable 1 this.ldloc_2 = "08"; // MSIL: 'ldloc.2' - Load local variable 2 this.ldloc_3 = "09"; // MSIL: 'ldloc.3' - Load local variable 3 this.ldloc_s = "11??"; // MSIL: 'ldloc.s' - Load local variable (short form) this.stloc = "FE0E????"; // MSIL: 'stloc' - Store to local variable this.stloc_0 = "0A"; // MSIL: 'stloc.0' - Store to local variable 0 this.stloc_1 = "0B"; // MSIL: 'stloc.1' - Store to local variable 1 this.stloc_2 = "0C"; // MSIL: 'stloc.2' - Store to local variable 2 this.stloc_3 = "0D"; // MSIL: 'stloc.3' - Store to local variable 3 this.stloc_s = "13??"; // MSIL: 'stloc.s' - Store to local variable (short form) // ========== FIELD OPERATIONS ========== this.ldfld = "7B????????"; // MSIL: 'ldfld' - Load instance field this.ldflda = "7C????????"; // MSIL: 'ldflda' - Load field address this.ldsfld = "7E????????"; // MSIL: 'ldsfld' - Load static field this.ldsflda = "7F????????"; // MSIL: 'ldsflda' - Load static field address this.stfld = "7D????????"; // MSIL: 'stfld' - Store to instance field this.stsfld = "80????????"; // MSIL: 'stsfld' - Store to static field // ========== ARRAY OPERATIONS ========== this.ldelem = "A3????????"; // MSIL: 'ldelem' - Load array element this.ldelem_i = "97"; // MSIL: 'ldelem.i' - Load array element (native int) this.ldelem_i1 = "90"; // MSIL: 'ldelem.i1' - Load array element (8-bit signed) this.ldelem_i2 = "92"; // MSIL: 'ldelem.i2' - Load array element (16-bit signed) this.ldelem_i4 = "94"; // MSIL: 'ldelem.i4' - Load array element (32-bit signed) this.ldelem_i8 = "96"; // MSIL: 'ldelem.i8' - Load array element (64-bit signed) this.ldelem_r4 = "98"; // MSIL: 'ldelem.r4' - Load array element (32-bit float) this.ldelem_r8 = "99"; // MSIL: 'ldelem.r8' - Load array element (64-bit float) this.ldelem_ref = "9A"; // MSIL: 'ldelem.ref' - Load array element (object reference) this.ldelem_u1 = "91"; // MSIL: 'ldelem.u1' - Load array element (unsigned 8-bit) this.ldelem_u2 = "93"; // MSIL: 'ldelem.u2' - Load array element (unsigned 16-bit) this.ldelem_u4 = "95"; // MSIL: 'ldelem.u4' - Load array element (unsigned 32-bit) this.ldelema = "8F????????"; // MSIL: 'ldelema' - Load array element address this.ldlen = "8E"; // MSIL: 'ldlen' - Load array length this.newarr = "8D????????"; // MSIL: 'newarr' - Create new array this.stelem = "A4????????"; // MSIL: 'stelem' - Store to array element this.stelem_i = "9B"; // MSIL: 'stelem.i' - Store to array element (native int) this.stelem_i1 = "9C"; // MSIL: 'stelem.i1' - Store to array element (8-bit) this.stelem_i2 = "9D"; // MSIL: 'stelem.i2' - Store to array element (16-bit) this.stelem_i4 = "9E"; // MSIL: 'stelem.i4' - Store to array element (32-bit) this.stelem_i8 = "9F"; // MSIL: 'stelem.i8' - Store to array element (64-bit) this.stelem_r4 = "A0"; // MSIL: 'stelem.r4' - Store to array element (32-bit float) this.stelem_r8 = "A1"; // MSIL: 'stelem.r8' - Store to array element (64-bit float) this.stelem_ref = "A2"; // MSIL: 'stelem.ref' - Store to array element (object reference) // ========== INDIRECT OPERATIONS ========== this.ldind_i = "4D"; // MSIL: 'ldind.i' - Load indirect native int this.ldind_i1 = "46"; // MSIL: 'ldind.i1' - Load indirect 8-bit value this.ldind_i2 = "48"; // MSIL: 'ldind.i2' - Load indirect 16-bit value this.ldind_i4 = "4A"; // MSIL: 'ldind.i4' - Load indirect 32-bit value this.ldind_i8 = "4C"; // MSIL: 'ldind.i8' - Load indirect 64-bit value this.ldind_r4 = "4E"; // MSIL: 'ldind.r4' - Load indirect 32-bit float this.ldind_r8 = "4F"; // MSIL: 'ldind.r8' - Load indirect 64-bit float this.ldind_ref = "50"; // MSIL: 'ldind.ref' - Load indirect object reference this.ldind_u1 = "47"; // MSIL: 'ldind.u1' - Load indirect unsigned 8-bit value this.ldind_u2 = "49"; // MSIL: 'ldind.u2' - Load indirect unsigned 16-bit value this.ldind_u4 = "4B"; // MSIL: 'ldind.u4' - Load indirect unsigned 32-bit value this.stind_i = "DF"; // MSIL: 'stind.i' - Store indirect native int this.stind_i1 = "52"; // MSIL: 'stind.i1' - Store indirect 8-bit value this.stind_i2 = "53"; // MSIL: 'stind.i2' - Store indirect 16-bit value this.stind_i4 = "54"; // MSIL: 'stind.i4' - Store indirect 32-bit value this.stind_i8 = "55"; // MSIL: 'stind.i8' - Store indirect 64-bit value this.stind_r4 = "56"; // MSIL: 'stind.r4' - Store indirect 32-bit float this.stind_r8 = "57"; // MSIL: 'stind.r8' - Store indirect 64-bit float this.stind_ref = "51"; // MSIL: 'stind.ref' - Store indirect object reference // ========== CONTROL FLOW ========== this.beq = "3B????????"; // MSIL: 'beq' - Branch if equal this.beq_s = "2E??"; // MSIL: 'beq.s' - Branch if equal (short form) this.bge = "3C????????"; // MSIL: 'bge' - Branch if greater or equal this.bge_s = "2F??"; // MSIL: 'bge.s' - Branch if greater or equal (short form) this.bge_un = "41????????"; // MSIL: 'bge.un' - Branch if greater or equal (unsigned/unordered) this.bge_un_s = "34??"; // MSIL: 'bge.un.s' - Branch if greater or equal (unsigned/unordered, short) this.bgt = "3D????????"; // MSIL: 'bgt' - Branch if greater than this.bgt_s = "30??"; // MSIL: 'bgt.s' - Branch if greater than (short form) this.bgt_un = "42????????"; // MSIL: 'bgt.un' - Branch if greater than (unsigned/unordered) this.bgt_un_s = "35??"; // MSIL: 'bgt.un.s' - Branch if greater than (unsigned/unordered, short) this.ble = "3E????????"; // MSIL: 'ble' - Branch if less or equal this.ble_s = "31??"; // MSIL: 'ble.s' - Branch if less or equal (short form) this.ble_un = "43????????"; // MSIL: 'ble.un' - Branch if less or equal (unsigned/unordered) this.ble_un_s = "36??"; // MSIL: 'ble.un.s' - Branch if less or equal (unsigned/unordered, short) this.blt = "3F????????"; // MSIL: 'blt' - Branch if less than this.blt_s = "32??"; // MSIL: 'blt.s' - Branch if less than (short form) this.blt_un = "44????????"; // MSIL: 'blt.un' - Branch if less than (unsigned/unordered) this.blt_un_s = "37??"; // MSIL: 'blt.un.s' - Branch if less than (unsigned/unordered, short) this.bne_un = "40????????"; // MSIL: 'bne.un' - Branch if not equal (unsigned) this.bne_un_s = "33??"; // MSIL: 'bne.un.s' - Branch if not equal (unsigned, short) this.br = "38????????"; // MSIL: 'br' - Unconditional branch this.br_s = "2B??"; // MSIL: 'br.s' - Unconditional branch (short form) this.break_ = "01"; // MSIL: 'break' - Breakpoint instruction this.brfalse = "39????????"; // MSIL: 'brfalse' - Branch if false/null/zero this.brfalse_s = "2C??"; // MSIL: 'brfalse.s' - Branch if false/null/zero (short) this.brtrue = "3A????????"; // MSIL: 'brtrue' - Branch if true/non-null/non-zero this.brtrue_s = "2D??"; // MSIL: 'brtrue.s' - Branch if true/non-null/non-zero (short) this.call = "28????????"; // MSIL: 'call' - Call method this.calli = "29????????"; // MSIL: 'calli' - Call indirect this.callvirt = "6F????????"; // MSIL: 'callvirt' - Call virtual method this.jmp = "27????????"; // MSIL: 'jmp' - Jump to method this.ldtoken = "D0????????"; // MSIL: 'ldtoken' - Load metadata token this.ldftn = "FE06????????"; // MSIL: 'ldftn' - Load function pointer this.ldvirtftn = "FE07????????"; // MSIL: 'ldvirtftn' - Load virtual function pointer this.leave = "DD????????"; // MSIL: 'leave' - Exit protected region this.leave_s = "DE??"; // MSIL: 'leave.s' - Exit protected region (short form) this.ret = "2A"; // MSIL: 'ret' - Return from method this.switch__nobody = "45"; // MSIL: 'switch' - Switch statement // ========== TYPE OPERATIONS ========== this.box = "8C????????"; // MSIL: 'box' - Convert value type to object reference this.castclass = "74????????"; // MSIL: 'castclass' - Cast object to class this.ckfinite = "C3"; // MSIL: 'ckfinite' - Check if value is finite number this.conv_i = "D3"; // MSIL: 'conv.i' - Convert to native int this.conv_i1 = "67"; // MSIL: 'conv.i1' - Convert to 8-bit integer this.conv_i2 = "68"; // MSIL: 'conv.i2' - Convert to 16-bit integer this.conv_i4 = "69"; // MSIL: 'conv.i4' - Convert to 32-bit integer this.conv_i8 = "6A"; // MSIL: 'conv.i8' - Convert to 64-bit integer this.conv_ovf_i = "D4"; // MSIL: 'conv.ovf.i' - Convert to native int with overflow check this.conv_ovf_i_un = "8A"; // MSIL: 'conv.ovf.i.un' - Convert unsigned to native int with overflow check this.conv_ovf_i1 = "B3"; // MSIL: 'conv.ovf.i1' - Convert to 8-bit integer with overflow check this.conv_ovf_i1_un = "82"; // MSIL: 'conv.ovf.i1.un' - Convert to unsigned 8-bit integer with overflow check this.conv_ovf_i2 = "B5"; // MSIL: 'conv.ovf.i2' - Convert to 16-bit integer with overflow check this.conv_ovf_i2_un = "83"; // MSIL: 'conv.ovf.i2.un' - Convert to unsigned 16-bit integer with overflow check this.conv_ovf_i4 = "B7"; // MSIL: 'conv.ovf.i4' - Convert to 32-bit integer with overflow check this.conv_ovf_i4_un = "84"; // MSIL: 'conv.ovf.i4.un' - Convert to unsigned 32-bit integer with overflow check this.conv_ovf_i8 = "B9"; // MSIL: 'conv.ovf.i8' - Convert to 64-bit integer with overflow check this.conv_ovf_i8_un = "85"; // MSIL: 'conv.ovf.i8.un' - Convert to unsigned 64-bit integer with overflow check this.conv_ovf_u = "D5"; // MSIL: 'conv.ovf.u' - Convert to native unsigned int with overflow check this.conv_ovf_u_un = "8B"; // MSIL: 'conv.ovf.u.un' - Convert unsigned to native unsigned int with overflow check this.conv_ovf_u1 = "B4"; // MSIL: 'conv.ovf.u1' - Convert to unsigned 8-bit integer with overflow check this.conv_ovf_u1_un = "86"; // MSIL: 'conv.ovf.u1.un' - Convert unsigned to unsigned 8-bit integer with overflow check this.conv_ovf_u2 = "B6"; // MSIL: 'conv.ovf.u2' - Convert to unsigned 16-bit integer with overflow check this.conv_ovf_u2_un = "87"; // MSIL: 'conv.ovf.u2.un' - Convert unsigned to unsigned 16-bit integer with overflow check this.conv_ovf_u4 = "B8"; // MSIL: 'conv.ovf.u4' - Convert to unsigned 32-bit integer with overflow check this.conv_ovf_u4_un = "88"; // MSIL: 'conv.ovf.u4.un' - Convert unsigned to unsigned 32-bit integer with overflow check this.conv_ovf_u8 = "BA"; // MSIL: 'conv.ovf.u8' - Convert to unsigned 64-bit integer with overflow check this.conv_ovf_u8_un = "89"; // MSIL: 'conv.ovf.u8.un' - Convert unsigned to unsigned 64-bit integer with overflow check this.conv_r_un = "76"; // MSIL: 'conv.r.un' - Convert unsigned integer to floating-point this.conv_r4 = "6B"; // MSIL: 'conv.r4' - Convert to 32-bit float this.conv_r8 = "6C"; // MSIL: 'conv.r8' - Convert to 64-bit float this.conv_u = "E0"; // MSIL: 'conv.u' - Convert to native unsigned int this.conv_u1 = "D2"; // MSIL: 'conv.u1' - Convert to unsigned 8-bit integer this.conv_u2 = "D1"; // MSIL: 'conv.u2' - Convert to unsigned 16-bit integer this.conv_u4 = "6D"; // MSIL: 'conv.u4' - Convert to unsigned 32-bit integer this.conv_u8 = "6E"; // MSIL: 'conv.u8' - Convert to unsigned 64-bit integer this.isinst = "75????????"; // MSIL: 'isinst' - Test if object is instance of class this.mkrefany = "C6????????"; // MSIL: 'mkrefany' - Push typed reference to value this.refanytype = "FE1D"; // MSIL: 'refanytype' - Push type token from typed reference this.refanyval = "C2????????"; // MSIL: 'refanyval' - Push address from typed reference this.sizeof = "FE1C????????"; // MSIL: 'sizeof' - Get size of value type this.unbox = "79????????"; // MSIL: 'unbox' - Convert object reference to value type this.unbox_any = "A5????????"; // MSIL: 'unbox.any' - Convert boxed type to value // ========== OBJECT OPERATIONS ========== this.cpblk = "FE17"; // MSIL: 'cpblk' - Copy block of memory this.cpobj = "70????????"; // MSIL: 'cpobj' - Copy value type this.initblk = "FE18"; // MSIL: 'initblk' - Initialize block of memory this.initobj = "FE15????????"; // MSIL: 'initobj' - Initialize value type this.ldobj = "71????????"; // MSIL: 'ldobj' - Load value type from address this.ldstr = "72????????"; // MSIL: 'ldstr' - Load string literal this.localloc = "FE0F"; // MSIL: 'localloc' - Allocate space from local memory pool this.newobj = "73????????"; // MSIL: 'newobj' - Create new object instance this.stobj = "81????????"; // MSIL: 'stobj' - Store value type to address // ========== EXCEPTION HANDLING ========== this.endfilter = "FE11"; // MSIL: 'endfilter' - End exception filter clause this.endfinally = "DC"; // MSIL: 'endfinally' - End finally clause this.rethrow = "FE1A"; // MSIL: 'rethrow' - Rethrow current exception this._throw = "7A"; // MSIL: 'throw' - Throw exception // ========== PREFIX INSTRUCTIONS ========== this.constrained = "FE16????????"; // MSIL: 'constrained' - Constrain type for virtual method call this.no_ = "FE19??"; // MSIL: 'no.' - Skip fault check (typecheck, rangecheck, nullcheck) this.readonly = "FE1E"; // MSIL: 'readonly' - Subsequent array address operation performs no type check this.unaligned = "FE12??"; // MSIL: 'unaligned.' - Subsequent pointer instruction might be unaligned (alignment: 1, 2, or 4) this.volatile = "FE13"; // MSIL: 'volatile' - Subsequent pointer reference is volatile // ========== UTILITY OPCODES ========== this._unknown = "**"; // MSIL: - - Unknown opcode pattern (any byte except 0x00) this._any = "??"; // MSIL: - - Any single byte pattern this.arglist = "FE00"; // MSIL: 'arglist' - Return argument list handle /** * Sets strict operand values for MSIL opcodes with variable operands. * * This method replaces wildcard patterns ('??') in opcode masks with specific * hexadecimal values, allowing for precise bytecode pattern matching. * * @method setStrict * @param {string} opCodeMask - The opcode pattern with wildcards (e.g., "20????????") * @param {string} value - The hexadecimal value to substitute (e.g., "01 00 00 00") * @returns {string} The opcode pattern with substituted values * * @example * // Set ldc.i4 instruction to load constant value 1 * var pattern = opCodes.setStrict(opCodes.ldc_i4, "01 00 00 00"); * // Result: "2001000000" (ldc.i4 1) * * @example * // Set specific method token for call instruction * var callPattern = opCodes.setStrict(opCodes.call, "10 00 00 06"); * // Result: "2810000006" (call method token 0x06000010) * * @throws {Error} If operand size doesn't match the expected size */ this.setStrict = function (opCodeMask, value) { // Remove spaces from opcode mask and value opCodeMask = removeWhitespaces(opCodeMask); value = removeWhitespaces(value); // Find the index of the special pattern "??" in the opcode mask var indexOfSpecialPattern = opCodeMask.indexOf("??"); // Check if the opcode mask has a body (contains the special pattern "??") var isOpCodeMaskHasBody = indexOfSpecialPattern !== -1; // -1 if not found // Extract the opcode in hexadecimal var opCodeInHex = isOpCodeMaskHasBody ? opCodeMask.substring(0x00, indexOfSpecialPattern) : opCodeMask; // Check if the opcode mask has a body and the length of the body matches the length of the value if (isOpCodeMaskHasBody && opCodeMask.substring(opCodeInHex.length).length !== value.length) { _error("The size of the input values does not match."); } // Combine the opcode in hexadecimal with the value return opCodeInHex + value; } /** * Sets all wildcard operands in an opcode pattern to zero bytes. * * This method replaces all wildcard patterns ('??') in the opcode mask with * zero bytes ('00'), useful for matching instructions with null/empty operands. * * @method setNullValue * @param {string} opCodeMask - The opcode pattern containing wildcards * @returns {string} The opcode pattern with all wildcards replaced by '00' * * @example * // Set ldc.i4 instruction to load zero value * var pattern = opCodes.setNullValue(opCodes.ldc_i4); * // Result: "2000000000" (ldc.i4 0) * * @example * // Create null branch instruction * var brPattern = opCodes.setNullValue(opCodes.br); * // Result: "3800000000" (br with zero offset) * * @throws {Error} If the opcode mask contains no wildcards to replace */ this.setNullValue = function (opCodeMask) { if (opCodeMask.indexOf("??") === -1) { _error("Instruction does not have a body to overwrite the value."); } return opCodeMask.replaceAll("??", "00"); } /** * Joins an opcode without wildcards with additional bytes. * * This method concatenates opcodes that don't have wildcards (like switch) * with specific byte sequences, useful for instructions with variable-length operands. * * @method joinNoBodyAndValue * @param {string} opCode - The opcode without wildcards (e.g., "45" for switch) * @param {string} value - The hexadecimal bytes to append (e.g., "02 00 00 00 XX XX XX XX") * @param {string} [opCodeName] - Optional: name of the opcode variable for validation * @returns {string} The combined opcode pattern * * @example * // Create switch instruction with 2 targets * var switchPattern = opCodes.joinNoBodyAndValue(opCodes.switch__nobody, "02 00 00 00 10 00 00 00 20 00 00 00", "switch__nobody"); * // Result: "45020000001000000020000000" (switch with 2 targets at offsets 0x10 and 0x20) * * @throws {Error} If the opcode contains wildcards (should use setStrict instead) * @throws {Error} If opCodeName is provided but doesn't contain '__nobody' */ this.joinNoBodyAndValue = function (opCode, value, opCodeName) { // Type validation if (!opCode || typeof opCode !== "string") { _error("Invalid opcode provided."); } if (!value || typeof value !== "string") { _error("Invalid value provided."); } // Check that opcode doesn't contain wildcards if (opCode.indexOf("??") !== -1) { _error("Opcode contains wildcards. Use setStrict() instead."); } // Optional validation: check if opcode name contains '__nobody' if (opCodeName && typeof opCodeName === "string") { if (opCodeName.indexOf("__nobody") === -1) { _error("joinNoBodyAndValue should only be used with '__nobody' opcodes (variable-length instructions)."); } } return opCode + removeWhitespaces(value); } } /** * Removes all whitespace characters from a string including spaces, tabs, newlines, and carriage returns. * * @param {string} inputString - The input string to remove whitespaces from * @returns {string} The string with all whitespace characters removed, or an empty string if input is invalid */ function removeWhitespaces(inputString) { // Type validation to prevent runtime errors if (!inputString || typeof inputString !== "string") { return String(); } // Regex removes all whitespace: spaces, tabs, newlines, carriage returns return inputString.replace(/\s+/g, String()); } /** * Sanitizes section names to prevent DiE output corruption from malformed PE files. * Removes control characters that could break console output or cause display issues. * * @param {string} sectionName - The section name to clean and sanitize. * @returns {string} - The sanitized section name safe for DiE output. */ function cleanSectionName(sectionName) { // Type validation to prevent runtime errors if (!sectionName || typeof sectionName !== "string") { return String(); } // Remove control characters (0x00-0x1F) and extended control chars (0x7F-0x9F) // This prevents newlines, tabs, null bytes from breaking DiE output format var cleaned = sectionName.replace(/[\x00-\x1F\x7F-\x9F]/g, String()); // Remove leading/trailing whitespace cleaned = cleaned.trim(); return cleaned; } /** * Checks if a given string consists only of ASCII printable characters (ASCII range 0x20 to 0x7E). * * @param {string} inputString - The string to validate. * @returns {boolean} Returns true if the string contains only ASCII printable characters, otherwise false. */ function isAsciiString(inputString) { // Type validation to prevent runtime errors if (!inputString || typeof inputString !== "string") { return false; } return /^[\x20-\x7E]+$/.test(inputString); } /** * Returns the file name without its extension. * * @param {string} fileName - The name of the file, possibly including an extension. * @returns {string} The file name without the extension. If no extension is found, returns the original file name. */ function getFileNameWithoutExtension(fileName) { // Type validation to prevent runtime errors if (!fileName || typeof fileName !== "string") { return String(); } // Remove the file extension by splitting on the last dot and returning the first part var lastDotIndex = fileName.lastIndexOf("."); if (lastDotIndex === -1) { return fileName; // No extension found, return original name } return fileName.substring(0, lastDotIndex); } const SECTION_FLAGS_DISCARDABLE = 0x02000000, // IMAGE_SCN_MEM_DISCARDABLE SECTION_FLAGS_EXECUTE = 0x20000000, // IMAGE_SCN_MEM_EXECUTE SECTION_FLAGS_READ = 0x40000000, // IMAGE_SCN_MEM_READ SECTION_FLAGS_WRITE = 0x80000000, // IMAGE_SCN_MEM_WRITE SECTION_FLAGS_CODE = 0x00000020, // IMAGE_SCN_CNT_CODE IMAGE_FILE_RELOCS_STRIPPED = 0x0001, IMAGE_DIRECTORY_ENTRY_SECURITY = 4, IMAGE_DIRECTORY_ENTRY_TLS = 9, IMAGE_NUMBEROF_DIRECTORY_ENTRIES = 16; const SECTION_FLAGS_RWX_MASK = SECTION_FLAGS_EXECUTE | SECTION_FLAGS_READ | SECTION_FLAGS_WRITE, SECTION_FLAGS_RWX_CODE_MASK = SECTION_FLAGS_RWX_MASK | SECTION_FLAGS_CODE; // #region "packers and cryptors (.NET and Native)" function scanForPackersAndCryptors_NET_and_Native() { // For .NET and Native apps log(logType.nothing, "Scanning for packers and cryptors..."); var options = String(); var isDetected = Boolean(), isCryptor = Boolean(); if (PE_Cached.isDotNet) { var isAssemblyInvokeFound = false; if (!isFrameworkComponent() && PE_Cached.nameOfNetModuleName !== "System.dll" && isAllNetObjectsPresent( // TODO: update [!!!] [ "System.Reflection", // System.Reflection.dll "get_EntryPoint", // MSIL: '*.Assembly::get_EntryPoint()' "Assembly", // MSIL: 'System.Reflection.Assembly' from System.Reflection.dll "Invoke", // MSIL: '*.MethodBase::Invoke(object, object[])' "Load" // MSIL: '*.Assembly::Load(uint8[])' ] )) { log(logType.net, "Dynamic assembly invocation pattern detected"); isAssemblyInvokeFound = true; options = "Assembly invoke"; } // Check if any class from System.Security.Cryptography namespace is used (non-full name) - for cryptors if (findAndMark("System.Security.Cryptography", false) !== String()) { // Specify cryptography classes to look for const cryptoClasses = [ "TripleDESCryptoServiceProvider", "RSACryptoServiceProvider", "DSACryptoServiceProvider", "DESCryptoServiceProvider", "AesCryptoServiceProvider", "Rfc2898DeriveBytes", "TripleDES", "Rijndael", "ECDsaCng", "AesAEAD", "Aes192Cbc", "Aes256Cbc", "Aes128Cbc", "AesManaged", "AesCng", "RC2CryptoServiceProvider", "RNGCryptoServiceProvider" ]; // Iterate through cryptography classes for (var i = 0; i < cryptoClasses.length; i++) { if (!isCryptor) { var cryptoClassSign = cryptoClasses[i], result = findAndMark( cryptoClassSign, true ); // Check if assembly invoke is found and the cryptography class is present if (isAssemblyInvokeFound && result.length) { log(logType.net, "Crypto class present: " + cryptoClassSign); isCryptor = true; // Add the cryptography class to options options = addOption(options, cryptoClassSign); } } } } // Check if any class from System.IO.Compression namespace is used (non-full name) if (findAndMark("System.IO.Compression", false).length) { // Specify compression classes to look for const compressionClasses = [ "DeflateStream", "GZipStream" ]; // Iterate through compression classes for (var i = 0; i < compressionClasses.length; i++) { var compressionClassSign = compressionClasses[i], result = findAndMark(compressionClassSign, true); // Check if assembly invoke is found and the compression class is present if (isAssemblyInvokeFound && result.length) { log(logType.net, "Compression class present: " + compressionClassSign); // If it's a cryptor, add the compression class to options if (isCryptor) options = addOption(options, compressionClassSign); // Break the loop if a match is found break; } } } // RunPE is a method for running payload from RAM, with minimal writing to disk. var isRunPePresent = false; if (validateNetObject("RunPE") || validateNetObject("PELoader") || // https://github.com/nettitude/RunPE/blob/main/RunPE/Internals/PELoader.cs validateNetObject("CMemoryExecute") || // https://github.com/wojciech-kulik/Sample-Projects/blob/master/Windows%20Desktop/WIN32_MemoryAppLoader/MemoryAppLoader/CMemoryExecute.cs (validateNetObject("GetProcAddress") && validateNetUnicodeString("WriteProcessMemory") && validateNetUnicodeString("VirtualAllocEx") && validateNetUnicodeString("ZwUnmapViewOfSection")) || ( (validateNetObject("WriteProcessMemory") || validateNetObject("NtWriteVirtualMemory")) && (validateNetObject("ZwUnmapViewOfSection") || validateNetObject("NtUnmapViewOfSection")) && (validateNetObject("CreateProcess") || validateNetObject("NtCreateProcess") || validateNetObject("CreateProcessA") || validateNetObject("CreateProcessW")) && validateNetObject("VirtualAllocEx")) ) { log(logType.net, "RunPE-like behavior detected!"); isRunPePresent = true; } if (isRunPePresent) options = addOption(options, "RunPE"); } // Check for packers and protections by .NET objects // Only user-defined metadata names (classes, methods, resources) that directly participate IN CODE EXECUTION are added. // Attributes and watermarks are not included. var isObjectLikePacker = false; // [{Name}, {Version}, {ObjectNames}] var dbCollectionOfObjectNamesDict = [ ["ConfuserEx", null, ["Decrypt", "LzmaDecoder", "BitTreeDecoder", "LenDecoder", "Resolve", "OutWindow"]], // https://github.com/yck1509/ConfuserEx ["CodeVeil (demo)", null, ["CodeWallTrialVersion", "CodeWallTrialVersion\u0001", "CodeWallTrialVersion\u0002"]], // https://marketplace.visualstudio.com/items?itemName=XHEOINC.DeployLXCodeVeil ["BatchToApp", null, ["RunApplication", "ReadResource", "Decompress", "embeddedBatchScript"]], // https://github.com/DosX-dev/BatchToApp [".NETZ", null, ["NetzStarter", "StartApp"]], // https://github.com/madebits/msnet-netz-compressor ["MPRESS", "2.XX", ["mpress", "lzmat"]], // https://www.autohotkey.com/mpress/mpress_web.htm ["Costura.Fody", null, ["Costura", "AssemblyLoader"]], // https://github.com/fody/costura ["Phoenix", "1.7-1.8", ["?0?", "?3?", "?5?"]], // https://ntcore.com/phoenix-protector/ ["Phoenix", "1.7-1.8", ["?0?", "?1?", "?7?"]], // . ["netcrypt", "1.1", ["stub", "program", "key", "IV", "GENERIC_ERROR"]], // https://github.com/friedkiwi/netcrypt ["NETPacker", "1.0", ["Stub", "Decrypt", "Decompress", "Stub.Properties.Resources.resources"]], // https://github.com/Mageland29/NETPacker ["DotNetPatcher", "4.X.X", ["SevenZipHelper", "pKey", "hash_table"]], // https://github.com/3DotDev/DotNetPatcher ["ReNET-pack", null, ["GetStreamAsByteArray", "Aced.Compression", "AcedInflator"]], ["NecroVM", null, ["VmLoad", "necro", "lz"]] ]; const objectNamesValidatingResult = validateNetObjectNames(dbCollectionOfObjectNamesDict); var versionByObjectNameDetected = String(); if (objectNamesValidatingResult !== null) { versionByObjectNameDetected = objectNamesValidatingResult[1]; log(logType.net, "Objects like " + objectNamesValidatingResult[0] + (versionByObjectNameDetected ? " (v" + versionByObjectNameDetected + ")" : String())); isObjectLikePacker = true; } // Clean up: release the dictionary dbCollectionOfObjectNamesDict = undefined; if (isObjectLikePacker) options = addOption(options, "Object names like " + objectNamesValidatingResult[0] + (versionByObjectNameDetected ? " (v" + versionByObjectNameDetected + ")" : String())); // Self-Extracting archives // TODO: Upgrade var isSfx = false; if (!PE_Cached.isDynamicLinkLibrary && PE.isOverlayPresent()) { const overlayPatterns = [ "'Rar!'", // samples by: WinRAR "'PK'03", // samples by: Zip SFX (by Intel) "';!@Install@!UTF-8!'", "'7z'BCAF271C", "efbbbf';!@Install@!UTF-8!'" // samples by: 7z ] for (var l = 0; l < overlayPatterns.length; l++) { if (PE.compareOverlay(overlayPatterns[l])) { log(logType.any, "SFX archive pattern detected in overlay"); isSfx = true; } } if (!isSfx && !PE_Cached.isDotNet) { const sfxEntries = [ // "E8$$$$$$$$558BEC83EC..A1........8365....8365....5357BF........3BC7BB........74..85C374..F7D0", // samples by: WinZip // "558BEC6A..68........68........64A1........50648925........83....5356578965..FF15", // samples by: Zip SFX // "E8$$$$$$$$558BEC83EC..8365....8365....A1........5657BF........BE........3BC7", // samples by: WinRAR Installer // "E8$$$$$$$$8BFF558BEC83EC..A1........8365....8365....5357BF........BB", // samples by: Zip SFX // "558BEC83C4..B8........E8........33C05568........64FF30648920E8", // samples by: Zip SFX // "4883EC..E8$$$$$$$$48895C24..55488BEC4883EC..488365....48BB................488B05........483BC375", // samples by: WinRAR Installer "83EC..5657FF15........8BF08D4424..50C74424..........FF15........8A068B3D........3C..75..56FFD7", // samples by: Zip SFX "E9$$$$$$$$558BEC81EC........830D..........5356576A..33DBBF........68........895D..881D", // samples by: Microsoft Cabinet "558BEC83EC..56FF15........8BF08A003C..75..84C074..803E..74..46803E..75..803E..75..46EB", // samples by: Zip SFX "6A..33C0505050FF15........50E8$$$$$$$$55B8........8BECE8........53B9........5657BE", // samples by: Zip SFX "6A..68........E8........66813D............75..A1........81B8................75..", // samples by: Microsoft Cabinet "558BEC83EC..565733FFFF15........8BF0897D..8D45..50FF15........8A063C..75..56FF15", // samples by: Zip SFX "51526A..2EFF15........506A..6A..2EFF15........50E8........502EFF15........5A59C3", // samples by: WinIMP "558BEC81EC........535657FF15........A3........FF15........A1........6625....3D", // samples by: Microsoft Cabinet "558BECB8........E8........5356BE........578D45..5633DB5053FF15........85C00F84", // samples by: Zip SFX "A1........C1E0..A3........575133C0BF........B9........3BCF76..2BCFFCF3AA595F", // samples by: WinRAR Installer "558BEC83C4..5356E8$$$$$$$$E8........6A..E8........8905........E8........8905", // samples by: Zip SFX "FF15........B1..380874..B1..4080....74..380874..4080....75..80....74..4033", // samples by: WinZip "53FF15........B3..38..74..80C3..4033D28A083ACA74..3ACB74..408A083ACA75", // samples by: WinZip "558BEC83C4..535657E8........E8........33C05568........64FF30648920E8", // samples by: WinRAR "53FF15........B3..38..74..80C3..8A48..4033D23ACA74..3ACB74..8A48..40", // samples by: WinZip "E8$$$$$$$$53BB........E8........85C074..33D28A1083FA..75..40EB", // samples by: WinRAR "FFFE2A002A002A006D0065007300730061006700650073002A002A002A00", // samples by: WinRAR Installer "E8$$$$$$$$558BEC83C4..B8........53", // samples by: WinRAR Installer "8A48014033D23ACA740A3ACB74068A4801" // samples by: WinZip ] if (PE.isSectionNamePresent("_winzip_")) { isSfx = true; } if (PE_Cached.isArchX86) { for (var k = 0; k < sfxEntries.length; k++) { if (PE.compareEP(sfxEntries[k])) { log(logType.any, "SFX-style entry point pattern detected"); isSfx = true; } } } } } if (isSfx) options = addOption(options, "SFX"); var entryLikePacker = false; const entries = [ "53565755488D35........488DBE", // samples by: UPX (x64) "6A6068........E8........BF94", // samples by: PELock (x32) "68........E8........C3C3", // samples by: KByS (x32) "B8........68........64", // samples by: Petite (x32) "60..................E8", // samples by: Anticrack Software (x32) "33C08BC068........68", // samples by: ExE Pack (x32) "74..E9........60E8", // samples by: PE-PACK "EB0668........C39C", // samples by: PECompact (x32) "93071F05....8ED0BC", // samples by: aPack (x32) "558BEC83C4E05333C0", // samples by: NTPacker (x32) "60BE........8DBE", // samples by: UPX (x32) "B8........6A..68", // samples by: Petite (x32) "BE........AD8BF8", // samples by: WinUPack (x32) "68........9C60E8", // samples by: XComp, XPACK (x32) "53558BE833DBEB60", // samples by: WWPack (x32) "E8000000005D81ED", // samples by: ASPack (x32), TTP Pack (x32) "BD........C745", // samples by: kkrunchy (x32) "57565351524150", // samples by: mpress (x64) "B8........5064", // samples by: PECompact (x32) "8CCBBA....03DA", // samples by: aPack (x32) "B8........669C", // samples by: Petite, Themida (x32) "E8........53", // samples by: Themida (x32), Safengine Shielden "8CC0FA8ED0BC", // samples by: PACKWIN (x32) "B8........60", // samples by: Petite, Themida (x32) "60FC0FB605", // samples by: NakedPacker (x32) "8B44240456", // samples by: ASDPack (x32) "1E068CC88E", // samples by: aPack (x32) "1E068CCBBA", // samples by: aPack (x32) "EB..9C60E8", // samples by: PECompact (x32) "9C60E8CA", // samples by: Petite (x??) "60FCBED4", // samples by: ANDPakk (x32) "60EB..5D", // samples by: ASPack (x32) "60EB..E8", // samples by: G!X Protector "64FF35", // samples by: Petite (x32) "6033C0", // samples by: yzPack (x32) "669C60", // samples by: Petite (x??) "EB..60", // samples by: kkryptor, dePACK (x32) "60E8", // samples by: mpress, Packman, Pack Master, Yodas Crypter, DxPack, ASPack, MSLRH, tElock (x32), WinUPack "6068", // samples by: BeRo, ExE Pack, AHPacker (x32) "60B8", // samples by: NakedPacker (x32) "9C60" // samples by: PEBundle (x32) ]; // Iterate through the entries to check against the entry point for (var e = 0; e < entries.length && !entryLikePacker; e++) { if (PE_Cached.isArchX86) { const entryToCheck = entries[e]; // If the entry point matches the current entry, set the flag to true and break the loop if (PE.compareEP(entryToCheck)) { log(logType.any, "Packer-like entry point pattern detected"); entryLikePacker = true; } } } if (entryLikePacker) options = addOption(options, "EntryPoint"); // Check for UPX-like structure: 3 sections with specific characteristics and specific imports // (common for UPX, but can be used by other packers as well) var isUpxLikeStructure = false; var isUpxLikeImports = false; if (PE_Cached.numberOfSections === 3 && PE.section[0].FileSize === 0 && PE.section[1].FileSize !== 0 && PE.section[2].FileSize !== 0 && (PE.section[0].Characteristics & SECTION_FLAGS_RWX_MASK) === SECTION_FLAGS_RWX_MASK && (PE.section[1].Characteristics & SECTION_FLAGS_RWX_MASK) === SECTION_FLAGS_RWX_MASK && (PE.section[2].Characteristics & SECTION_FLAGS_RWX_MASK) !== SECTION_FLAGS_RWX_MASK && ( (PE.section[2].Characteristics & SECTION_FLAGS_READ) === SECTION_FLAGS_READ || (PE.section[2].Characteristics & (SECTION_FLAGS_READ | SECTION_FLAGS_WRITE)) === (SECTION_FLAGS_READ | SECTION_FLAGS_WRITE) ) ) { log(logType.any, "UPX-like section layout detected"); isUpxLikeStructure = true; if (PE.isLibraryPresentExp(/^kernel32(\.dll)?$/i) && PE.isFunctionPresent("GetProcAddress") && ( PE.isFunctionPresent("VirtualProtect") || PE.isFunctionPresent("LoadLibraryA") ) && ( PE.isFunctionPresent("ExitProcess") || // Original PE.isFunctionPresent("CopyContext") // https://github.com/DosX-dev/UPX-Patcher ) ) { isUpxLikeImports = true; } } if (isUpxLikeStructure) options = addOption(options, "Structure" + (isUpxLikeImports ? " and IAT " : " ") + "like UPX"); // Check if the first instruction at entry point starts with a stack operation var isStartsWithStackOperation = false, firstEpAsmOpCode; // Get the opcode of the first instruction at entry point and check for specific stack operations if (PE_Cached.isArchX86) { firstEpAsmOpCode = getFirstEpAsmOpCode(); switch (firstEpAsmOpCode) { case "PUSHAL": case "PUSHA": case "PUSHF": case "POPA": log(logType.emulation, "\"" + firstEpAsmOpCode + "\" at EP"); isStartsWithStackOperation = true; } } else if (PE_Cached.isARM || PE_Cached.isThumb) { firstEpAsmOpCode = getFirstEpAsmOpCode(); switch (firstEpAsmOpCode) { case "POP": case "STMDB": case "LDMIA": case "STMFD": case "LDMFD": log(logType.emulation, "\"" + firstEpAsmOpCode + "\" at EP"); isStartsWithStackOperation = true; } } else if (PE_Cached.isARM64) { firstEpAsmOpCode = getFirstEpAsmOpCode(); if (firstEpAsmOpCode === "LDP") { log(logType.emulation, "\"" + firstEpAsmOpCode + "\" at EP"); isStartsWithStackOperation = true; } } if (isStartsWithStackOperation) options = addOption(options, firstEpAsmOpCode[0] + firstEpAsmOpCode.toLowerCase().substring(1) + " at EP"); // Check if the entry point is in the last section var isLastSectionEP = false; if (!PE_Cached.isDynamicLinkLibrary) { if (PE_Cached.numberOfSections > 1 && PE.isEntryPointCorrect()) { // Get addresses of the last section and entry point var lastSectionAddress = -1, entryPointAddress = PE_Cached.addressOfUnmanagedEntryPoint; // Get last section with non -1 address for (var i = 1; lastSectionAddress === -1 && i <= PE_Cached.numberOfSections; i++) { lastSectionAddress = PE.OffsetToVA(PE.getSectionFileOffset(PE_Cached.numberOfSections - i)); } // Check if a valid last-section address was found and the entry point is at or after it if (lastSectionAddress !== -1 && entryPointAddress >= lastSectionAddress) { log(logType.any, "EP address (0x" + entryPointAddress.toString(16) + ") more than last section address (0x" + lastSectionAddress.toString(16) + ")"); isLastSectionEP = true; } } } if (isLastSectionEP) options = addOption(options, "Last section EP"); // Check for strange calls if entry point is in the last section var isStrangeCallOrJmpPresent = false, firstOpCode; if (PE_Cached.isArchX86) { firstOpCode = getAsmOpCode(PE_Cached.firstEpAsmInstruction); if (!PE_Cached.isDynamicLinkLibrary) { if (isLastSectionEP && !PE.compareEP("E8 00 00 00 00") && (firstOpCode === "CALL" || firstOpCode === "JMP")) { log(logType.emulation, "Strange " + firstOpCode.toLowerCase() + " at EP to address: " + PE_Cached.firstEpAsmInstruction.split(" ")[1].toLowerCase()); isStrangeCallOrJmpPresent = true; } } } else if (PE_Cached.isARM || PE_Cached.isThumb) { // ARM/Thumb: Check for strange branch instructions firstOpCode = getAsmOpCode(PE_Cached.firstEpAsmInstruction); if (!PE_Cached.isDynamicLinkLibrary) { // ARM uses B (branch) and BL (branch with link) for jumps and calls if (isLastSectionEP && (firstOpCode === "B" || firstOpCode === "BL" || firstOpCode === "BX" || firstOpCode === "BLX")) { log(logType.emulation, "Strange " + firstOpCode.toLowerCase() + " at EP to address: " + PE_Cached.firstEpAsmInstruction.split(" ")[1].toLowerCase()); isStrangeCallOrJmpPresent = true; } } } else if (PE_Cached.isARM64) { firstOpCode = getAsmOpCode(PE_Cached.firstEpAsmInstruction); if (!PE_Cached.isDynamicLinkLibrary && isLastSectionEP && (firstOpCode === "B" || firstOpCode === "BL" || firstOpCode === "BR" || firstOpCode === "BLR")) { log(logType.emulation, "Strange " + firstOpCode.toLowerCase() + " at EP to address: " + PE_Cached.firstEpAsmInstruction.split(" ")[1].toLowerCase()); isStrangeCallOrJmpPresent = true; } } if (isStrangeCallOrJmpPresent) options = addOption(options, "Strange " + firstOpCode.toLowerCase() + " at EP"); // Check for packers and protections by imports (hashes) var isImportsLikePacker = false; // [{Name}, {Version}, {ImportLibraryIndex}, {Hash/Hashes}, {Type (0 = packer, 1 = cryptor, 2 = protector, 3 = protection)}] // If {ImportLibraryIndex} is -1, it means the hash has no index var dbCollectionOfHashesDict = [ // packers ["UPX", "0.59-0.93", 0, 0xd4fdcab1, 0], // https://github.com/upx/upx ["UPX", "0.94-1.93", 0, 0x1d51299a, 0], // . ["UPX", "1.94-2.03", 0, [0xb3318086, 0x3778aab9], 0], // . ["UPX", "2.90-3.XX", 0, 0xf375ee03, 0], // . ["UPX", "2.90-3.XX", -1, 0xf737d853, 0], // . ["UPX", "3.91+", -1, [0x82a048fc, 0x554a1748], 0], // . ["PESpin", null, 2, 0xd4fdcab1, 0], ["NsPack", null, -1, 0xf375ee03, 0], // https://nspack.apponic.com/ ["NsPack", "3.X", 0, 0x86111e49, 0], // . (for .NET) ["ASPack", "1.XX-2.XX", 0, 0x1272f45b, 0], // https://www.aspack.com/ ["ASPack", null, 0, 0xee6ea174, 0], ["MKFPACK", null, 0, 0x42b3e7f9, 0], ["MPRESS", null, 0, 0x174efb84, 0], // https://www.autohotkey.com/mpress/mpress_web.htm ["Packman", "0.0.0.1", 0, 0x174efb84, 0], // https://packmanpacker.sourceforge.net/ ["Packman", "1.0", 0, 0x69076a83, 0], // . ["PECompact", "0.90-0.91", -1, 0xbea416d1, 0], // https://bitsum.com/portfolio/pecompact/ ["PECompact", "0.92-0.94", -1, 0x93312c2e, 0], // . ["PECompact", "0.97-0.971b", -1, 0xe6aa8495, 0], // . ["PECompact", "1.10b7-1.34", -1, 0xe4c11305, 0], // . ["PECompact", "1.30-1.40", 0, 0x9b3305ed, 0], // . ["PECompact", "1.40-1.84", 0, 0xcc5b2a3c, 0], // . ["PECompact", "2.40-3.XX", 0, 0x2652ce4f, 0], // . ["PECompact", "2.40-3.XX", -1, 0xdb8fbb75, 0], // . ["tElock", "1.0", -1, 0x051946f7, 0], // https://www.softpedia.com/get/Programming/Packers-Crypters-Protectors/Telock.shtml ["JDPack", "1.01", 0, 0xd9380ce5, 0], ["JDPack", "2.00", 0, 0xc002db0e, 0], ["CRINKLER", null, 0, 0x0b0e1fbf, 0], ["XComp", "0.97-0.98", -1, 0xea1e66e4, 0], ["XPack", null, -1, [0x2ac44dd2, 0x6c170ab3], 0], ["kkrunchy", null, 0, 0x29188619, 0], // https://www.farbrausch.de/~fg/kkrunchy/ ["ANDPakk", "0.18", -1, 0x29188619, 0], ["RLPack", "1.1-1.2", 0, 0x5bd1d713, 0], ["RLPack", "1.16+", 0, 0x6889715b, 0], ["Petite", "2.1", -1, [0x69a2b86d, 0x69a2b86d, 0xaf362d6a], 0], // https://www.un4seen.com/petite/ ["Petite", "2.2", -1, 0x61e53682, 0], // . ["Petite", "2.X", 0, 0x6d73bcd2, 0], // . ["Petite", "2.X", -1, 0x61088397, 0], // . ["bambam", "0.1-0.4", 0, 0xb5b153cd, 0], ["py2exe", null, 2, 0xe5907ffa, 0], // https://pypi.org/project/py2exe/ ["py2exe", null, -1, 0xc544ca52, 0], ["MEW", null, 0, 0x87214e52, 0], ["nPack", "1.1+", 0, 0xba8bf4be, 0], ["CExe", "1.0a", 0, 0xdcf764d2, 0], ["PE-PACK", "1.0", 0, 0xa4d96677, 0], ["BoxedApp", null, 0, 0x6206c0c2, 0], // https://www.softanics.com/boxedapp ["VPacker", "0.02.X", 0, 0xebc3bc90, 0], ["PKLITE32", "1.1", 0, [0x621f19fb, 0xee04c332], 0], ["PKLITE32", "1.1", 1, 0x184539e7, 0], ["PeX", "0.99", 0, 0x5fbf5fd8, 0], ["XComp", null, 0, 0x4bc52e77, 0], ["Fatpack", null, 0, 0x74244911, 0], ["NTPacker", "1.0", 0, 0x1a721d2d, 0], ["NakedPacker", "1.0", 0, 0x64910c88, 0], ["ezip", "1.X", 1, 0x174efb84, 0], ["Rusty", null, 10, 0xa2260c1d, 0], // https://github.com/Azr43lKn1ght/Rusty-PE-Packer ["SimpleDpack", null, 0, [0x6facb4b4, 0x47bc97fd], 0], // https://github.com/YuriSizuku/win-SimpleDpack ["RPacked", "1.0", 2, 0x8f8ca638, 0], // https://github.com/BadRyuner/RPacked // cryptors ["Huan", null, 0, [0x66bc5f20, 0x4b3d7cea], 1], // https://github.com/frkngksl/Huan ["Yoda's Crypter", "1.X", -1, 0x1303a51b, 1], ["EXECryptor", "1.4-1.5", -1, 0xd1de53b5, 1], ["EXECryptor", "2.X", -1, 0xf51b2817, 1], ["Amber", null, -1, 0x97c72051, 1], // https://github.com/EgeBalci/amber ["Lumy", null, -1, 0x821669d5, 1], // protectors ["ASProtect", "1.XX-2.XX", 0, 0x1272f45b, 2], // https://www.asprotect.ru/ ["Shrinker", "3.2", 0, [0xb2a64858, 0x158af2d0, 0x49e8aa1f], 2], ["Shrinker", "3.5", 0, [0xe9ea0851, 0x3344b95d, 0x586088f3], 2], ["Enigma", "1.00-3.60", 0, 0xc002db0e, 2], // https://enigmaprotector.com/ ["Enigma", "2.XX", 0, 0xdd92de10, 2], // . ["Enigma", "3.70+", 0, 0xd04c7a50, 2], // . ["PCGUARD", "5.04-5.05", 0, [0x5a169c7a, 0x0b0b2965], 2], ["Vbs To Exe", null, 1, [0x2bc5f1f9, 0x009a2869, 0x5a7ce7d6, 0xe513a67d, 0x4d65f922], 2], ["eXPressor", "1.2", -1, [0x66b35c6e, 0x32f4466c], 2], // https://www.cgsoftlabs.ro/express.html ["eXPressor", "1.3", -1, [0x921d0280, 0xf51eba68, 0xbc84ce09], 2], // . ["eXPressor", "1.4.5.X", 0, [0x427816ab, 0x3c705cae, 0x4d02e093, 0x958a9ea2], 2], // . ["eXPressor", "1.5.0.X", -1, [0x7ababb5a, 0x95ca15e4, 0xbd41da20], 2], // . ["eXPressor", "1.6", -1, 0xca58fa0c, 2], // . ["eXPressor", "1.6", 0, 0x30bc0039, 2], // . ["eXPressor", "1.6.1", 0, 0xa5792a26, 2], // . ["eXPressor", "1.6.1", -1, 0x48ffd359, 2], // . ["VMProtect", "1.70", -1, 0x1ff3103f, 2], // https://vmpsoft.com/ ["VMProtect", "2.0.3-2.13", -1, 0x9d12b153, 2], // . ["VMProtect", "3.0.0", -1, 0x1e5500c1, 2], // . ["VMProtect", "3.0.9", -1, 0xc5fb6a4b, 2], // . ["VMProtect", "3.2.0-3.5.0", -1, 0x5caa99c7, 2], // . ["VMProtect", "3.6.0+", -1, [0x66e03954, 0xdae9f570], 2], // . ["YodasProtector", "1.0b", -1, 0x1303a51b, 2], ["ASM Guard", "2.XX+", -1, 0xf1e0d63b, 2], // https://github.com/DosX-dev/ASM-Guard ["Themida", "2.XX-3.XX", 0, 0x3ffccc8a, 2], // https://www.oreans.com/Themida.php ["Themida", null, 0, 0xad97c503, 2], // . [".NET Reactor", null, 0, 0x96be8e26, 2], // https://www.eziriz.com/dotnet_reactor.htm [".NET Reactor", null, 1, 0xb4cda32f, 2], // . ["Bat To Exe Converter", null, 0, 0x72a2ca64, 2], ["Vbs To Exe Converter", null, 0, 0x182aac68, 2], ["DNGuard", null, 0, 0x38432571, 2], // https://www.dnguard.net/ ["DNGuard", "4.9+", 0, 0x99f8b58e, 2], ["obfus.h", null, -1, 0x02f4771f, 2], // https://github.com/DosX-dev/obfus.h ; default, antidebug v1 ["Crunch", "1.0-2.0", 0, 0x615665dd, 2], ["Private EXE Protector", null, 0, 0xd89af68d, 2], // https://www.privacy-tutorials.com/private-exe-protector/ ["CodeVirtualizer", null, 4, 0x4fbeea23, 2], // https://www.oreans.com/CodeVirtualizer.php ["PELock", null, 0, [0x6eb8f6f1, 0xe571f715], 2], ["PELock", "2.X", 0, 0xe1689d7c, 2], ["CrypToCrack", "0.9.X", 0, 0x8b28bb4e, 2], ["DBPE", null, 0, [0x301ad755, 0x33406ae3], 2], ["Virbox", null, 0, 0xe6eb008d, 2], // https://lm-global.virbox.com/detail/virboxProtector.html ["Quick Batch File Compiler", null, -1, 0x347060fa, 2], // https://www.abyssmedia.com/quickbfc/ ["CodeVeil", null, 0, 0xd2c88a9c, 2], ["VCasm-Protector", "1.1-1.2", 0, 0xcfed7c1e, 2], ["Excalibur", "1.0X", 0, 0x7f364e1f, 2] ]; const importValidatingResult = validateImportHashes(dbCollectionOfHashesDict); var versionByImportsDetected; if (importValidatingResult !== null) { versionByImportsDetected = importValidatingResult[1]; log(logType.any, "Imports hash like " + importValidatingResult[0] + (versionByImportsDetected ? " (version " + versionByImportsDetected + ")" : String())); isImportsLikePacker = true; } // Clean up: release the dictionary dbCollectionOfHashesDict = undefined; if (isImportsLikePacker) options = addOption(options, "Imports like " + importValidatingResult[0] + (versionByImportsDetected ? " (v" + importValidatingResult[1] + ")" : String())); // Check for packers and protections by section names var isSectionNameLikePacker = false; // [{Name}, {Version}, {SectionNameSignature}] var dbCollectionOfSectionNamesDict = [ ["Ramnit (infected)", null, ".rmnet"], ["Parite (infected)", null, /\.[a-z]{3}[\u0006\u0007\u0009]$/], ["Win9X.Dupator (infected)", null, "DUPATOR!"], ["Mars Stealer (payload)", null, "LLCPPC"], ["Themida", "2.XX", " "], // https://www.oreans.com/Themida.php ["Themida", "3.XX", [".loadcon", ".themida", ".winlice"]], // . ["Themida", null, [".stub01", "WinLicen", ".v-lizer", "Themida "]], // . ["DBPE", null, " "], ["PEBundle", null, /^(?:PEB|peb)undle$/], // https://bitsum.com/pebundle.htm ["DZA Patcher", null, "yoda"], ["UPX", null, /^UPX[0-3]$/], // https://github.com/upx/upx ["VMProtect", null, /^\.vmp[0-3]$/], // https://vmpsoft.com/ [".NET Reactor", "2.XX", ".reacto"], // https://www.eziriz.com/dotnet_reactor.htm ["ACProtect", null, ".perplex"], // https://acprotect-standard.soft112.com/ ["ANDpakk", "2.0", "ANDpakk2"], ["ASM Guard", "2.XX", [".asmg", "ASMGUARD"]], // https://github.com/DosX-dev/ASM-Guard ["ASPack", "1.08-2.XX", ".adata"], // https://www.aspack.com/ ["ASPack", "2.XX", ".aspack"], // . ["ASPack", null, /^(?:\.)?ASPack$/], // . ["Alienyze", null, ".alien"], // https://alienyze.com/ ["BoxedApp", null, ".bxpck"], // https://www.boxedapp.com/ ["CodeVirtualizer", null, ".vlizer"], // https://www.oreans.com/CodeVirtualizer.php ["Enigma", null, /^\.enigma[12]$/], // https://enigmaprotector.com/ ["Eronana", null, ".packer"], // https://github.com/Eronana/packer ["MPRESS", "2.XX", /^\.MPRESS[12]$/], // https://www.autohotkey.com/mpress/mpress_web.htm ["NsPack", null, [/^(?:\.)?nsp[01]$/, /^PE[Pp][01]$/, ".Packer!"]], // https://nspack.apponic.com/ ["PE Diminisher", null, ".teraphy"], // https://web.archive.org/web/20060111104142/http://www.exetools.com/files/compressors/win/ped.zip ["PE-SHiELD", null, ["PESHiELD", "ANAKiN98"]], // https://webscene.ir/tools/show/PE-SHIELD-0.25 ["PECompact", null, [/^PEC2(MO)?$/, /^pec(1)?$/]], // https://bitsum.com/portfolio/pecompact/ ["PELock", null, ["PELOCKnt", ".pelock"]], // https://www.pelock.com/ 💩 ["Petite", null, /^(?:\.)?petite$/], // https://www.un4seen.com/petite/ ["SecuROM", null, [/^\.cms_[dt]$/, ".securom", ".dsstext"]], // https://en.wikipedia.org/wiki/SecuROM ["StarForce", "3.X", [".sforce3", ".brick"]], // https://www.star-force.com/ ["Wise Installer", null, ".wise"], // https://wpkg.org/WISE_installer ["Gentee Installer", null, ".gentee"], // https://www.gentee.com/download/ ["Nullsoft Installer", null, ".ndata"], // https://sourceforge.net/projects/nsis/ ["WiX Installer", null, ".wixburn"], // https://github.com/wixtoolset ["BeRoEXEPacker", null, ["packerBY", "bero^fr "]], // https://blog.rosseaux.net/page/875fbe6549aa072b5ee0ac9cefff4827/BeRoEXEPacker ["Warbird", null, ["?g_Encry", "PROTDATA", /^PAGE(?:VRFY|wx[1-6])$/]], // https://security-explorations.com/microsoft-warbird-pmp.html ["YodasCrypter", "1.X", "yC"], // https://sourceforge.net/projects/yodap/files/Yoda%20Crypter/1.3/yC1.3.zip/download ["eXPressor", null, /^\.ex_(?:cod|rsc)$/], // https://www.cgsoftlabs.ro/express.html ["kkrunchy", null, "kkrunchy"], // https://www.farbrausch.de/~fg/kkrunchy/ ["tElock", null, "UPX!"], // https://www.softpedia.com/get/Programming/Packers-Crypters-Protectors/Telock.shtml ["Private EXE Protector", null, [".-PEP-", ".TRIAL!", ".const"]], // https://github.com/NIKJOO/PEP ["AtomPePacker", null, ".ATOM"], // https://web.archive.org/web/20221012050538/https://github.com/ORCx41/AtomPePacker ["ExeStealth", null, "ExeS"], // https://web.archive.org/web/20250124130104/https://www.webtoolmaster.com/exestealth.htm ["RLPack", null, [".RLPack", ".packed", ".Shield"]], ["RLPack", null, "Obsidium"], // (Fake Obsidium section name) ["BattlEye", null, /^\.be[0-2]$/], // https://www.battleye.com/ (VMProtect-based) ["YodasProtector", "1.0b", [".y0da", ".yP"]], // https://yodap.sourceforge.net/ ["obfus.h", null, ".obfh"], // https://github.com/DosX-dev/obfus.h ["Windows PE Packer by Chenzs108", null, ".shell"], // https://github.com/czs108/Windows-PE-Packer ["SoftSentry", null, [/^_(?:stext|rdata|data|idata|rsrc|reloc)$/, ".prdata"]], // https://forum.exetools.com/showthread.php?t=1181 ["PE Lock Phantasm", "1.X", ["DINGBOY", ".Sign"]], // https://www.arteam.accessroot.com/download39d1.html ["XComp", null, /^\.XComp(?:0)?$/], // http://www.soft-lab.de/JoKo/ ["XVolkolak (unpacked)", null, ".xvlk"], // (unpacker & reconstructor) https://github.com/horsicq/XVolkolak ["Scylla (dumped)", null, ["unpacked", ".SCY", ".newimp"]], // (dumper & reconstructor) https://github.com/ntquery/scylla ["ImpRec (dumped)", null, ".mackt"], // (dumper & reconstructor) https://forum.tuts4you.com/files/file/2367-import-reconstructor-imprec/ ["OllyDump (dumped)", null, /^\.[Nn]ewIID$/], // (dumper & reconstructor) https://www.openrce.org/downloads/details/108/ollydump ["Process Dump (dumped)", null, /^pd_rec[0-9]$/], // (dumper & reconstructor) https://github.com/glmcdona/process-dump ["LordPE (edited)", null, ".NewSec"], // (dumper & reconstructor & editor) https://www.softpedia.com/get/Programming/File-Editors/LordPE.shtml ["DeDe (dumped)", null, ".dafixer"], // (dumper & reconstructor, Delphi) https://www.softpedia.com/get/Programming/Debuggers-Decompilers-Dissasemblers/DeDe.shtml ["ClamAV (dumped)", null, /^\.clam0[1-9]$/], // (dumper & recostructor, Antivirus) https://www.clamav.net/ ["NetShield", null, "!Sugar"], // https://github.com/AdvDebug/NetShield_Protector ["Fatpack", null, ".fpack "], // https://github.com/Fatmike-GH/Fatpack ["Safengine Shielden", null, ".sedat"], // https://safengine.com/downloads/get-demo ["Inquartos Obfuscator", null, ".inq"], // http://www.vbnet.ru/forum/show.aspx?id=175877 ["WinUPack", null, [".ByDwing", ".Upack"]], // https://www.softpedia.com/get/PORTABLE-SOFTWARE/Compression-Tools/Windows-Portable-Applications-Portable-WinUpack.shtml ["Xenocode Postbuild", null, ".xcpad"], // https://download.cnet.com/xenocode-postbuild/3000-10250_4-10506240.html ["Denuvo", null, [/^\.[ex](?:code|text)$/, /^(?:\.)?v(?:3[5-9]|40)$/, ".xtls", ".arch", ".ooa"]], // https://en.wikipedia.org/wiki/Denuvo ["SteamStub", null, ".bind"], // https://gameindustry.eu/blog/steamstub-drm/ ["Huan", null, ".huan"], // https://github.com/frkngksl/Huan ["inject2pe", null, ".inj"], // https://github.com/0xballistics/inject2pe ["Helios", null, [".helios", ".htext"]], // https://t.me/helios_robot (protection online service) ["XerinFuscator", null, ".Xerin"], // https://xerinfuscator.com/ ["XerinFuscator", "8.X.X", ".xerin"], // . ["Eclipse-Protector", "1.X.X", ".eclipse"], // https://github.com/Av3lure1337/Eclipse-Protector ["PE Packer by levanvn", null, "packed"], // https://github.com/levanvn/PE_Packer ["SimpleDPack", null, ".dpack"], // https://github.com/YuriSizuku/win-SimpleDpack ["xor_packer", null, ".new"], // https://github.com/eternaleclipse/xor_packer ["Packed-Infected-PE", null, ".infect"], // https://github.com/huykingsofm/Packed-Infected-PE ["SafeDisc", null, /^stxt(?:774|371)$/], // https://ru.wikipedia.org/wiki/SafeDisc ["Origami", null, ".origami"], // https://github.com/dr4k0nia/Origami ["notcpuid", null, ".ptext"], // https://github.com/notcpuid/pe-packer ["WinZip SFX", null, "_winzip_"], // https://www.winzip.com/en/product/self-extractor/ ["RDG Tejon Crypter", "1.3", "RDGMax"], // http://crypter.unblog.fr/rdg-tejon-crypter-v13/ ["EsProtect", "1.3-3.9", [/^Esp[01]$/, /^\.Esp[! ]$/]], // https://www.greenxf.com/soft/29156.html ["troglodyte9", null, ".pkr"], // https://github.com/troglodyte9/packer ["RPacked", "1.0", ".kek"], // https://github.com/BadRyuner/RPacked ["xObf", null, ".xObf"], // https://github.com/d35ha/xObf ["Code Entanglement Model", null, ".vmdata"], ["vQPU Protection Tech", null, [".vQPU", ".vqmeta"]], ["Rockey4nd Enveloper", "2.XX", "RY4SHL"], ["Rockey4nd Feitian Shell Protect Center", "1.X", ".ftsafe"], ["Alloy", null, ".alloy32"], ["Crinkler", null, "lz32.dll"], ["Crunch", "2.0", "BitArts"], ["CrypToCrack", null, ".ccp3p"], ["DxPack", "1.0", ["coderpub", ".reloc1"]], ["ExE Pack", "1.0-1.2", "!EPack"], ["FishPE Shield", null, ".FishPE"], ["FishPE", null, ".FISHEP"], ["G!X Protector", null, ".g!x"], ["JDPack", null, ".jdpack"], ["Krypton", null, ["krypton", "YADO", "_!_!_!_"]], ["MEW", null, "MEW"], ["MaskPE", null, ".MaskPE"], ["Morphnah", "1.0.X", ".nah"], ["NakedPacker", null, [/^\.naked[12]$/, /^\.Kaos(?:2 |12)$/]], ["NativeCryptor", null, /^(?:NATIVES|CONFIG|(?:F)?CRYPT)~$/], ["NeoLite", null, ".neolite"], ["PE-PACK", "1.0", "PEPACK!!"], ["RLP", null, ".rlp"], ["ORiEN", "2.XX", ".loader"], ["SC Pack", null, ".scpack"], ["SVK Protector", null, ["SVKP", /\.svkp(?: )?/]], ["SafeNet", null, /^\.AKS[1-3]$/], ["Shrinker", null, ".shrink1"], ["Simple Pack", null, ".spack"], ["Software Compress", null, "SoftComp"], ["StarForce", "4.X-5.X", ".ps4"], ["VCL", null, ".vcl"], ["VirtualizeProtect", null, "VProtect"], ["WWPack", null, ".WWPACK"], ["WWPack32", null, ".WWP32"], ["dePack", "1.3.5", ".depack"], ["hmimys", "1.3", "hmimys"], ["nPack", null, ".nPack"], ["yzPack", null, ".yzpack2"], ["NeoLite", null, ".neolit"], ["bambam", null, ".bedrock"], ["WibuKey", null, ".wibu"], ["Wibu CodeMeter", null, /^__wibu0[01]$/], ["AverCryptor", "1.0-1.02", [".avc", ".Polyene"]], ["TSULoader", null, ".tsustub"], ["RPCrypt", null, /^\.R(Cryptor|PCrypt)$/], ["ExE Pack", null, ["!EPack", ".!ep"]], ["AZProtect 0001", null, "AZPR0001"], ["ExeShield", null, ".shield"], ["XPack", null, /^\.XPack(?:0)?$/], ["KByS", null, ".shoooo"], ["HyperTech Crackproof", null, "peC"], ["Byfron", null, /^\.byfron(?:1)?$/], ["DYAMAR", "1.3.X", /^\.dyamar[CD]$/], ["Alcatraz", null, ".0Dev"], ["Squishy", null, "logicoma"], ["DragonArmor", "0.0.4.1", /^(?:\.)?DAStub$/], ["EXECrypt", "1.0", "CRPT"], ["N-Code", "0.2", [".pepsi", "n-coded", ".vrs"]], ["NoodleCrypt", "2.X", [".Ncryo ", ".De-vir "]], ["TheArk", null, ["30cm", ".tw"]], ["Virbox", null, /^\.v(?:data[1-9]|irbox[1-9]?)$/], ["ElecKey", null, ".sstb"], ["PKLITE32", "1.1", ".pklstb"], [".BJFnt", "1.X", ".BJFnt"], ["Goliath", null, ".GOLIATH"], ["PE Encrypt", null, [".ice", ".lea"]], ["PECRYPT32", null, ".ficken"], ["PolyCrypt", "2.8", /^sec[0-9]$/], ["RCryptor", null, /^(?:\.)?RCryptor$/], ["SDProtector", null, ".data "], ["Shielden", null, ".sedata"], ["SimbiOZ", null, ".ximera"], ["LARP", "2.X", "SnD "], ["DalKrypt", "1.X", ".DalKiT"], ["Fish PE", "1.2-1.4", ".PEDATA"], ["NoobyProtect (Safengine)", null, "SE"], ["KeySec", null, ".ksec"], ["Lumy", null, ".lumy"], ["TomatoX", null, ".tomato"], ["PwdProtect", null, ".pwdprot"], ["ID Application", null, /^\.Prt(?:1)?$/], ["PETetris", null, "PETETRIS"], ["VCasm-Protector", null, "vcasm"], ["Pandora Obfuscator", null, ".pandora"], ["SafeGuard", null, /^\.safe[0-9]$/], ["SexCrypt", null, ".sex"], ["PEBizo", null, ".pebizo"], ["BlackMirror", null, /^\.bl(?:a)?ck$/], ["FUD Cryptor", null, ".fud"], ["Anskya NTPacker", null, "Anskya"], ["Bad UPX Patcher", null, /^BAD[0-1]$/], ["Feoktist Packer", null, "Feokt."], ["Snaker", null, ".snaker"], ["NEzZ Patcher", null, [".HACK", ".NEzZ"]], ["Zprotect", "1.X", ["PackedBy", ".Shadowz"]], ["Guy UPX Mod", null, ["Hello", "Guy !"]], ["AiR Cryptor", null, "TEAM AiR"], ["Stone's PE Encrypter", "1.13-2.0", /^\.Stone(?:r)?$/], ["RedWolf", null, [".!rc!", ".redwolf"]], ["SIS Cryptor", null, "SISCrypt"], ["ProCrypt", null, "ProCrypt"], ["YaWiGonaDown Team's Cryptor", null, ".WeGonaD"], ["BinPakker", null, ".pk00"] ]; const sectionNamesValidatingResult = validateSectionNames(dbCollectionOfSectionNamesDict.concat( (function () { var dbCollectionOfFakeSectionNamesDict = [ ["Themida", [/^themida$/i, ".Themida"]], ["Enigma", /^(?:\.)?enigma$/i], ["UPX", [/^\.upx/i, /^upx/]], ["VMProtect", /^(?:\.)?vmp$/i], ["ACProtect", /^perplex/i], ["PE-SHiELD", "peshield"], ["Alienyze", /^alien$/i], ["MPRESS", /^mpress/i], ["Denuvo", /denuvo|irdeto/i] ]; return dbCollectionOfFakeSectionNamesDict.map(function (entry) { return ["fake " + entry[0], null, entry[1]]; }); })() ).concat( [ ["a packer", null, [/p(?:a)?ck|sh(?:e)?ll|exe|^\.pe/i, /^PE/]], ["a cryptor", null, [/crypt|crpt|stub|enc|inj/i, /stb/]], ["a protector", null, [/prot|safe|lock|sec|virt|obf|guard|trial|sh(?:ie)?ld/i, /DRM|drm/]] ] ).concat([ [null, null, [ /^_guard_[cd]$/i, // Unity /^prot$/i // Chromium ]]] )); var versionBySectionDetected = String(); if (sectionNamesValidatingResult !== null) { versionBySectionDetected = sectionNamesValidatingResult[1]; log(logType.any, "Sections like " + sectionNamesValidatingResult[0] + (versionBySectionDetected ? " (v" + versionBySectionDetected + ")" : String())); isSectionNameLikePacker = true; } // Clean up: release the dictionary dbCollectionOfSectionNamesDict = undefined; if (isSectionNameLikePacker) options = addOption(options, "Sections like " + sectionNamesValidatingResult[0] + (versionBySectionDetected ? " (v" + sectionNamesValidatingResult[1] + ")" : String())); // Check for packers and protections by resource names var isResourceNameLikePacker = false; // [{Name}, {Version}, {ResourceNameSignature}] var dbCollectionOfResourceNamesDict = [ ["Bat To Exe Converter", null, /^(?=[0-9A-F]{40}$)(?=.*[0-9])(?=.*[A-F])[0-9A-F]{40}$/], ["ASM Guard", null, /^(?:ASMG\.DLL$|ASM_GUARD)/], ["Fatpack", null, "FPACK"], ["Quick Batch File Compiler", null, "PLATFORMTARGETS"], ["ScriptCryptor", null, "AOPT"], ["ExeStealth", null, "TN"], [".NET Reactor", null, "__"], ["SoftEntry", null, "SENTRYABORTDLG"], ["UPX Protector", null, "SCAREBYTE"], ["Vbs To Exe", null, "B"], ["QQProtect", null, "QQPROTECT"] ]; const resourceNamesValidatingResult = validateResourceNames(dbCollectionOfResourceNamesDict); var versionByResourceDetected = String(); if (resourceNamesValidatingResult !== null) { versionByResourceDetected = resourceNamesValidatingResult[1]; log(logType.any, "Resources like " + resourceNamesValidatingResult[0] + (versionByResourceDetected ? " (v" + versionByResourceDetected + ")" : String())); isResourceNameLikePacker = true; } // Clean up: release the dictionary dbCollectionOfResourceNamesDict = undefined; if (isResourceNameLikePacker) options = addOption(options, "Resources like " + resourceNamesValidatingResult[0] + (versionByResourceDetected ? " (v" + versionByResourceDetected + ")" : String())); // Check if there is a collision in sections var isCollisionInSectionsPresent = false; // Get section name collision between "0" and "1" const sectionNameCollision = PE.getSectionNameCollision("0", "1"); // Check if there is a collision if (sectionNameCollision.length) { log(logType.any, "Section names collision: \"" + File.cleanString(sectionNameCollision) + "\""); isCollisionInSectionsPresent = true; } if (isCollisionInSectionsPresent) options = addOption(options, "Sections collision (\"" + cleanSectionName(sectionNameCollision) + "\")"); // Check if there are repeating section names var isSectionNamesRepeatingPresent = false; var sectionNamesDict = {}; for (var i = 0; i < PE_Cached.numberOfSections && !isSectionNamesRepeatingPresent; i++) { var sectionName = PE.getSectionName(i); sectionNamesDict[sectionName] = (sectionNamesDict[sectionName] || 0) + 1; if (sectionNamesDict[sectionName] > 1) { log(logType.any, "Section names repeating: \"" + File.cleanString(sectionName) + "\""); isSectionNamesRepeatingPresent = true; } } // Clean up: release the dictionary sectionNamesDict = undefined; if (isSectionNamesRepeatingPresent) options = addOption(options, "Section names repeating"); // Check for compressed sections using entropy var isCompressedSectionPresent = false; var compressedSectionIndex = -1; var compressedSectionName = String(); for (var t = 0; t < PE_Cached.numberOfSections && !isCompressedSectionPresent; t++) { compressedSectionIndex = t; if (PE.section[t].FileOffset === 0 || PE.section[t].FileSize < 0x1000) continue; var sectionEntropy = PE.calculateEntropy(PE.getSectionFileOffset(compressedSectionIndex), PE.getSectionFileSize(compressedSectionIndex)); if (sectionEntropy > 7.4) { compressedSectionName = cleanSectionName(PE.getSectionName(compressedSectionIndex)); log(logType.any, "Section #" + compressedSectionIndex + " (\"" + compressedSectionName + "\") entropy: " + sectionEntropy); isCompressedSectionPresent = true; } } if (isCompressedSectionPresent) options = addOption(options, "Section #" + compressedSectionIndex + " (\"" + compressedSectionName + "\") compressed"); var standardSectionsWithUnusualValuesDict = [ ".bss", "BSS", ".tls", "hydrated", // .NET Native ".imrsiv" // UWP ]; // Check for sections with strange characteristics var isSectionHasWrongOffsetOrSize = false; var sectionWithWrongOffsetOrSizeIndex = -1; var wrongOffset = false, wrongSize = false; var sectionWithWrongOffsetOrSizeLabel = String(), wrongOffsetOrSizeDetails = String(), wrongOffsetOrSizeLogDetails = String(); for (var t = 0; t < PE_Cached.numberOfSections && !isSectionHasWrongOffsetOrSize; t++) { sectionWithWrongOffsetOrSizeIndex = t; if (standardSectionsWithUnusualValuesDict.indexOf(PE.getSectionName(sectionWithWrongOffsetOrSizeIndex)) === -1) { const currentSection = PE.section[sectionWithWrongOffsetOrSizeIndex], imagePhysicalEndOffset = PE_Cached.fileSize - PE_Cached.overlaySize; if (currentSection.FileOffset === 0x00 || currentSection.FileOffset >= imagePhysicalEndOffset) { wrongOffset = true; } // Validate the complete physical interval. Testing offset and size independently // misses truncated sections whose individually valid values extend past the image. if (currentSection.FileSize === 0x00 || currentSection.FileSize > PE_Cached.imageBodySize || currentSection.FileOffset + currentSection.FileSize > imagePhysicalEndOffset) { wrongSize = true; } isSectionHasWrongOffsetOrSize = wrongOffset || wrongSize; if (isSectionHasWrongOffsetOrSize) { sectionWithWrongOffsetOrSizeLabel = "Section #" + sectionWithWrongOffsetOrSizeIndex + " (\"" + cleanSectionName(PE.getSectionName(sectionWithWrongOffsetOrSizeIndex)) + "\")"; wrongOffsetOrSizeDetails = (wrongOffset ? "offset" : String()) + (wrongOffset && wrongSize ? "/" : String()) + (wrongSize ? "size" : String()); wrongOffsetOrSizeLogDetails = (wrongOffset ? "offset (0x" + currentSection.FileOffset.toString(16) + ")" : String()) + (wrongOffset && wrongSize ? ", " : String()) + (wrongSize ? "size (0x" + currentSection.FileSize.toString(16) + ")" : String()); log(logType.any, sectionWithWrongOffsetOrSizeLabel + " has wrong " + wrongOffsetOrSizeLogDetails); } } } if (isSectionHasWrongOffsetOrSize) options = addOption(options, sectionWithWrongOffsetOrSizeLabel + " has wrong " + wrongOffsetOrSizeDetails); // Standard zero-filled sections are allowed above, but they must not be executable var isExecutableZeroFilledSectionPresent = false; var executableZeroFilledSectionIndex = -1; for (var t = 0; t < PE_Cached.numberOfSections && !isExecutableZeroFilledSectionPresent; t++) { const currentSection = PE.section[t]; if (standardSectionsWithUnusualValuesDict.indexOf(PE.getSectionName(t)) !== -1 && currentSection.FileSize === 0x00 && (currentSection.Characteristics & SECTION_FLAGS_EXECUTE)) { log(logType.any, "Executable zero-filled section detected: #" + t + " (\"" + cleanSectionName(PE.getSectionName(t)) + "\")"); isExecutableZeroFilledSectionPresent = true; executableZeroFilledSectionIndex = t; } } if (isExecutableZeroFilledSectionPresent) options = addOption(options, "Section #" + executableZeroFilledSectionIndex + " (\"" + cleanSectionName(PE.getSectionName(executableZeroFilledSectionIndex)) + "\") executable zero-fill"); // Check for sections with identical offsets var isSectionHasDuplicateOffset = false; var currentSectionIndex = -1; var firstSectionIndex = 0, firstSectionName = String(), secondSectionIndex = 0, secondSectionName = String(); var sectionOffsetsDict = {}; for (var i = 0; i < PE_Cached.numberOfSections && !isSectionHasDuplicateOffset; i++) { currentSectionIndex = i; if (standardSectionsWithUnusualValuesDict.indexOf(PE.getSectionName(currentSectionIndex)) === -1) { var currentSectionOffset = PE.section[currentSectionIndex].FileOffset; if (currentSectionOffset !== 0) { if (typeof sectionOffsetsDict[currentSectionOffset] === "undefined") { sectionOffsetsDict[currentSectionOffset] = [currentSectionIndex]; } else { log(logType.any, "Section #" + sectionOffsetsDict[currentSectionOffset][0] + " (\"" + cleanSectionName(PE.getSectionName(sectionOffsetsDict[currentSectionOffset][0])) + "\") and #" + currentSectionIndex + " (\"" + cleanSectionName(PE.getSectionName(currentSectionIndex)) + "\") have one offset: 0x" + currentSectionOffset.toString(16)); firstSectionIndex = sectionOffsetsDict[currentSectionOffset][0]; secondSectionIndex = currentSectionIndex; firstSectionName = cleanSectionName(PE.getSectionName(firstSectionIndex)); secondSectionName = cleanSectionName(PE.getSectionName(secondSectionIndex)); isSectionHasDuplicateOffset = true; } } } } // Clean up: release the dictionary sectionOffsetsDict = undefined; if (isSectionHasDuplicateOffset) options = addOption(options, "Section #" + firstSectionIndex + " (\"" + firstSectionName + "\") and #" + secondSectionIndex + " (\"" + secondSectionName + "\") have one offset"); // Check for partially overlapping physical section ranges. Equal starting offsets are // reported by the preceding rule, while this rule covers every remaining intersection. var isSectionDataOverlappingPresent = false; for (var i = 0; i < PE_Cached.numberOfSections && !isSectionDataOverlappingPresent; i++) { const firstSectionFileOffset = PE.getSectionFileOffset(i), firstSectionFileSize = PE.getSectionFileSize(i), firstSectionFileEnd = firstSectionFileOffset + firstSectionFileSize; if (firstSectionFileOffset === 0 || firstSectionFileSize === 0) continue; for (var d = i + 1; d < PE_Cached.numberOfSections && !isSectionDataOverlappingPresent; d++) { const secondSectionFileOffset = PE.getSectionFileOffset(d), secondSectionFileSize = PE.getSectionFileSize(d), secondSectionFileEnd = secondSectionFileOffset + secondSectionFileSize; if (secondSectionFileOffset === 0 || secondSectionFileSize === 0 || firstSectionFileOffset === secondSectionFileOffset) continue; if (firstSectionFileOffset < secondSectionFileEnd && secondSectionFileOffset < firstSectionFileEnd) { log(logType.any, "Section #" + i + " (\"" + cleanSectionName(PE.getSectionName(i)) + "\") and #" + d + " (\"" + cleanSectionName(PE.getSectionName(d)) + "\") data overlap"); isSectionDataOverlappingPresent = true; } } } if (isSectionDataOverlappingPresent) options = addOption(options, "Overlapping section data"); // Check for 'MZ' signature in the overlay var isPeAtOverlayDetected = false; if (PE.isOverlayPresent() && PE_Cached.overlaySize >= 100 && PE.compareOverlay("'MZ'")) { log(logType.any, "PE signature at overlay detected"); isPeAtOverlayDetected = true; } if (isPeAtOverlayDetected) options = addOption(options, "PE at overlay"); // Check for a strange overlay in the PE file var hasStrangeOverlay = false; // Conditions to check for a strange overlay if (!isPeAtOverlayDetected && !isSfx && !PE.isSigned() && PE.isOverlayPresent()) { var overlayEntropy = PE.calculateEntropy(PE_Cached.overlayOffset, PE_Cached.overlaySize); if (PE_Cached.overlaySize > 150 && overlayEntropy > 7 || PE_Cached.overlaySize > PE_Cached.imageBodySize) { log(logType.any, "Overlay size: " + PE_Cached.overlaySize + " bytes; Entropy: " + overlayEntropy); hasStrangeOverlay = true; } } if (hasStrangeOverlay) options = addOption(options, "Strange overlay"); // Checks if a PE file is embedded in the resources var isPeInResourcesPresent = false; for (var i = 0; i < PE_Cached.numberOfUnmanagedResources && !isPeInResourcesPresent; i++) { const resourceOffset = PE.getResourceOffsetByNumber(i); if (resourceOffset !== -1 && PE.compare("'MZ'", resourceOffset)) { log(logType.any, "PE signature in resources detected"); isPeInResourcesPresent = true; } } if (isPeInResourcesPresent) options = addOption(options, "PE in resources"); // Flag to indicate high entropy var isHighEntropy = false; // Checks for high entropy (ignore overlay) if (!(PE_Cached.isDynamicLinkLibrary && (PE.section[".rdata"] || PE.isSectionNamePresent(".rsrc"))) && // .dll with resources PE.calculateEntropy(PE_Cached.dosStubSize, PE_Cached.imageBodySize) > 7.3) { log(logType.any, "High overall image entropy detected"); isHighEntropy = true; } if (isHighEntropy) options = addOption(options, "High entropy"); // Check for recurring import names var recurringImports = false; var importNamesDict = {}; if (!_isResultPresent("linker", "Turbo Linker")) { // Ignore Delphi for (var i = 0; i < PE_Cached.numberOfUnmanagedImports && !recurringImports; i++) { var name = PE.getImportLibraryName(i); importNamesDict[name] = (importNamesDict[name] || 0) + 1; if (importNamesDict[name] >= 3) { log(logType.any, "Recurring import name: \"" + File.cleanString(name) + "\""); recurringImports = true; } } } // Clean up: release the dictionary importNamesDict = undefined; if (recurringImports) options = addOption(options, "Recurring import names"); var isPackerOrProtectionNotDetected = !_getNumberOfResults("packer") && !_getNumberOfResults("cryptor") && !_getNumberOfResults("protector") && !_getNumberOfResults("protection"); if (isPackerOrProtectionNotDetected) { if (isImportsLikePacker) { var typeOfDetectedItem = String(); switch (importValidatingResult[4]) { case 0: typeOfDetectedItem = "packer"; break; case 1: typeOfDetectedItem = "cryptor"; break; case 2: typeOfDetectedItem = "protector"; break; case 3: typeOfDetectedItem = "protection"; break; } if (typeOfDetectedItem) { var version = String(), importAndSectionValidationVerdictsDiffer = false; if (importValidatingResult[1]) { version = importValidatingResult[1]; } else if (isSectionNameLikePacker && !/^(?:a [pc]|fake )$/.test(sectionNamesValidatingResult[0])) { if (importValidatingResult[0] === sectionNamesValidatingResult[0]) { // is same result if (sectionNamesValidatingResult[1]) { version = sectionNamesValidatingResult[1]; } } else { // Inconsistency of verdicts importAndSectionValidationVerdictsDiffer = true; } } _setResult("~" + typeOfDetectedItem, importValidatingResult[0] + (importAndSectionValidationVerdictsDiffer ? "-like" : String()), version, "Suspicion only"); } } else if (isUpxLikeStructure && isUpxLikeImports) { _setResult("~packer", "UPX-like", String(), "Suspicion only"); } } if (options.length !== 0) isDetected = true; if (isDetected) { var detectedType = isCryptor ? "cryptor" : "packer"; _setResult("~" + detectedType, "Generic", String(), PE.isVerbose() ? options : String()); } } // #endregion // #region "licensing systems (.NET and Native)" function scanForLicensingSystems_NET_and_Native() { // For .NET and Native apps log(logType.nothing, "Scanning for licensing systems..."); var options = String(); var isDetected = Boolean(); if (PE_Cached.isDotNet) { var isLicenseCheckingPresent = false; if (validateNetObject("CheckLicense") || validateNetObject("set_License") || validateNetObject("Licensing")) { log(logType.net, "Managed image contains licensing-related methods"); isLicenseCheckingPresent = true; } if (isLicenseCheckingPresent) options = "DotNET methods"; // https://learn.microsoft.com/en-us/dotnet/api/system.componentmodel.licenseproviderattribute var isLicenseProviderPresent = false; if (validateNetObject("LicenseProviderAttribute")) { log(logType.net, "Managed image uses the standard .NET license provider attribute"); isLicenseProviderPresent = true; } if (isLicenseProviderPresent) options = addOption(options, "Provider attribute"); // https://learn.microsoft.com/ru-ru/dotnet/api/system.componentmodel.licensemanager var isLicenseManagerPresent = false; if (validateNetObject("LicenseManager")) { log(logType.net, "Managed image uses the standard .NET license manager"); isLicenseManagerPresent = true; } if (isLicenseManagerPresent) options = addOption(options, "License manager"); } // https://en.wikipedia.org/wiki/Denuvo var isDenuvoLibraryPresent = false; if (PE.isLibraryPresentExp(/^(?:Core[\\/]Activation(?:64)?\.dll)$/i) || ( PE_Cached.numberOfSections > 10 && PE_Cached.numberOfUnmanagedImports > 10 && ( PE.isSectionNamePresentExp(/^\.[ex](?:code|text)$/) || PE.isExportFunctionPresentExp(/^GetDenuvo(?:TimeTicketRequest|TicketLocation)$/) ) )) { log(logType.any, "Denuvo-like licensing layout detected"); isDenuvoLibraryPresent = true; } if (isDenuvoLibraryPresent) options = addOption(options, "Denuvo DRM"); // https://gameindustry.eu/blog/steamstub-drm/ var isSteamStubPresent = false; if (PE_Cached.isArchX86 && PE.compareEP("E800000000") && PE.section[PE_Cached.numberOfSections - 1].Name === ".bind") { log(logType.any, "SteamStub-like DRM layout detected"); isSteamStubPresent = true; } if (isSteamStubPresent) options = addOption(options, "SteamStub DRM"); // Check for licensing strings var isInterestingStringsFound = false; // Generic strings are a fallback for images without a stronger licensing marker. // Besides avoiding redundant details, this prevents many full-body searches in // large protected images after the licensing system has already been identified. if (options.length === 0) { const licensingStringContextLength = 256; // Entry: { marker, strings[, prefixVariants] }; variant: { prefix, strings }. // Each marker requires an expensive full-area engine search, so share suffixes. // strings holds lowercase local context and must not repeat marker; null makes // marker sufficient. prefixVariants classifies characters immediately before // marker and adds variant-only context without anchor self-validation. const licensingStringsDictionary = [ { marker: /* [T/Se] */ "rial ", strings: ["enter", "key", "invalid", "wrong", "expired", "required", "activation", "activate", "license", "full version", "purchase", "unlock", "registration", "free", "version", "period", "mode", "ended", "subscription", "upgrade", "demo"], prefixVariants: [{ prefix: "t", strings: ["serial"] }, { prefix: "se", strings: ["trial"] }] }, { marker: /* [A] */ "ctivat", strings: ["wrong", "license", "serial", "product", "subscription", "trial", "demo", "purchase", "your copy", "key", "invalid", "expired", "required", "full version", "unlock", "registration"] }, { marker: /* [L] */ "icen", strings: ["expire", "key", "wrong", "valid", "invalid", "required", "not found", "missing", "revoked", "activation", "activate", "validation", "verification", "serial", "trial", "demo", "subscription", "purchase", "full version", "unlock", "registration"] }, { marker: /* [F] */ "ull version", strings: ["unlock", "purchase", "upgrade", "activate", "require", "license", "serial", "trial", "demo", "subscription", "key", "registration"] }, { marker: /* [P] */ "urchase a ", strings: ["license", "subscription", "full version", "unlock", "serial", "activation", "trial", "demo", "upgrade"] } ]; function isLicensingStringGroupPresent(areaOffset, areaSize, dictionaryEntry, isUnicode) { var currentMarker = dictionaryEntry.marker, validatingStrings = dictionaryEntry.strings, markerPattern = isUnicode ? convertStringToUnicodeSignature(currentMarker) : currentMarker, characterSize = isUnicode ? 2 : 1, currentOffset = areaOffset, remainingSize = areaSize; // A null dictionary entry means the marker itself is the complete // original pattern, so no candidate extraction is necessary. if (validatingStrings === null) { return (isUnicode ? PE.findSignature(areaOffset, areaSize, markerPattern) : PE.findString(areaOffset, areaSize, markerPattern)) !== -1; } while (remainingSize >= currentMarker.length * characterSize) { var markerOffset = isUnicode ? PE.findSignature(currentOffset, remainingSize, markerPattern) : PE.findString(currentOffset, remainingSize, currentMarker); if (markerOffset === -1) break; var advanced = markerOffset - currentOffset + characterSize; currentOffset += advanced; remainingSize -= advanced; // Read the short prefix in one operation, then decode it locally. // This lets a shared anchor validate phrases which begin before it // without issuing a separate engine comparison for every phrase. var prefixSize = Math.min(licensingStringContextLength * characterSize, markerOffset - areaOffset); prefixSize -= prefixSize % characterSize; var prefixBytes = PE.readBytes(markerOffset - prefixSize, prefixSize), candidateString = String(); for (var i = 0; i < prefixBytes.length; i += characterSize) { var characterCode = prefixBytes[i]; if (isUnicode) characterCode |= prefixBytes[i + 1] << 8; candidateString += String.fromCharCode(characterCode); } // Ignore adjacent null-terminated strings so their unrelated words // cannot validate the marker found in the current string. var stringBoundary = candidateString.lastIndexOf("\0"); if (stringBoundary !== -1) candidateString = candidateString.substring(stringBoundary + 1); // Prefix variants let several complete words share one engine anchor. // Reject unrelated suffix matches before requesting the remaining string. var markerVariantStrings = null; if (dictionaryEntry.prefixVariants !== undefined) { for (var i = 0; i < dictionaryEntry.prefixVariants.length; i++) { var currentPrefixVariant = dictionaryEntry.prefixVariants[i], prefixOffset = candidateString.length - currentPrefixVariant.prefix.length; if (prefixOffset >= 0 && candidateString.substring(prefixOffset).toLowerCase() === currentPrefixVariant.prefix) { markerVariantStrings = currentPrefixVariant.strings; break; } } if (markerVariantStrings === null) continue; } // The remaining characters are obtained in one engine call. Keep // the read inside the current scan area to preserve its boundaries. var remainingCharacterCount = Math.min(licensingStringContextLength, Math.floor((areaOffset + areaSize - markerOffset) / characterSize)); candidateString += isUnicode ? File.read_unicodeString(markerOffset, remainingCharacterCount) : PE.getString(markerOffset, remainingCharacterCount); candidateString = candidateString.toLowerCase(); for (var i = 0; i < validatingStrings.length; i++) { if (candidateString.indexOf(validatingStrings[i]) !== -1) return true; } if (markerVariantStrings !== null) { for (var i = 0; i < markerVariantStrings.length; i++) { if (candidateString.indexOf(markerVariantStrings[i]) !== -1) return true; } } } return false; } function isLicensingStringPresentInArea(areaOffset, areaSize) { for (var i = 0; i < licensingStringsDictionary.length; i++) { const currentDictionaryEntry = licensingStringsDictionary[i]; if ((!PE_Cached.isDotNet && isLicensingStringGroupPresent(areaOffset, areaSize, currentDictionaryEntry, false)) || isLicensingStringGroupPresent(areaOffset, areaSize, currentDictionaryEntry, true)) return true; } return false; } for (var i = 0; i < PE_Cached.numberOfSections && !isInterestingStringsFound; i++) { var sectionOffset = PE.getSectionFileOffset(i), sectionSize = PE.getSectionFileSize(i), sectionName = PE.getSectionName(i); // Skip only canonical areas whose role excludes licensing messages. // Resources are not excluded by role because user-facing text commonly lives there. if (PE_Cached.numberOfSections > 1) { if (sectionOffset === 0 || sectionSize < 0x2000) continue; var isLicensingPrimaryNativeCodeSection = !PE_Cached.isDotNet && i === PE_Cached.indexOfEntryPointSection && sectionName === ".text" && i === 0; var isLicensingDedicatedImportsSection = i === PE_Cached.indexOfImportsSection && sectionName === ".idata" && i !== PE_Cached.indexOfResourceSection && i !== PE_Cached.indexOfEntryPointSection && i !== PE_Cached.indexOfTlsSection && i !== PE_Cached.indexOfRelocsSection; var isLicensingDedicatedRelocsSection = i === PE_Cached.indexOfRelocsSection && sectionName === ".reloc" && i !== PE_Cached.indexOfResourceSection && i !== PE_Cached.indexOfEntryPointSection && i !== PE_Cached.indexOfTlsSection && i !== PE_Cached.indexOfImportsSection; if (isLicensingPrimaryNativeCodeSection || isLicensingDedicatedImportsSection || isLicensingDedicatedRelocsSection) continue; } if (sectionOffset > 0 && sectionOffset < PE_Cached.fileSize) { sectionSize = Math.min(sectionSize, PE_Cached.fileSize - sectionOffset); if (isLicensingStringPresentInArea(sectionOffset, sectionSize)) { log(logType.any, "Licensing-related string context detected"); isInterestingStringsFound = true; } } } } if (isInterestingStringsFound) options = "Strings"; if (options.length !== 0) isDetected = true; if (isDetected) { _setResult("~licensing", "Contains", String(), PE.isVerbose() ? options : String()); } } // #endregion // #region "corrupted data (.NET and Native)" function scanForBadFileFormat_NET_and_Native() { log(logType.nothing, "Scanning for corrupted data..."); var options = String(); var isDetected = Boolean(); // EP check var isEntryPointIncorrect = false; var isTlsVirtualEntryPoint = false; const isEntryPointCorrect = PE.isEntryPointCorrect(); if (PE.isTLSPresent()) { const corruptedDataEntryPointRva = PE_Cached.addressOfUnmanagedEntryPoint - PE_Cached.imageBase; if (corruptedDataEntryPointRva > 0) { for (var i = 0; i < PE_Cached.numberOfSections && !isTlsVirtualEntryPoint; i++) { var corruptedDataSectionRva = PE.getSectionVirtualAddress(i), corruptedDataSectionFileSize = PE.getSectionFileSize(i), corruptedDataSectionMappedSize = Math.max(PE.getSectionVirtualSize(i), corruptedDataSectionFileSize), corruptedDataEntryPointOffset = corruptedDataEntryPointRva - corruptedDataSectionRva; // The loader maps this range as zero-filled memory even though no bytes exist // in the file. A TLS callback may populate it before normal EP execution. if (corruptedDataEntryPointOffset >= corruptedDataSectionFileSize && corruptedDataEntryPointOffset >= 0 && corruptedDataEntryPointOffset < corruptedDataSectionMappedSize && (PE.section[i].Characteristics & SECTION_FLAGS_EXECUTE)) { isTlsVirtualEntryPoint = true; } } } } // A DB pseudo-instruction normally means that the EP is damaged or points to data. // It is expected when a TLS callback owns a deliberately virtual-only startup area. if (!isTlsVirtualEntryPoint && (!isEntryPointCorrect || /^DB /.test(PE_Cached.firstEpAsmInstruction))) { log(logType.any, "Entry point appears invalid or points to data"); isEntryPointIncorrect = true; } if (isEntryPointIncorrect) options = addOption(options, "EntryPoint"); // Section alignment check var isSectionAlignmentIncorrect = false; if (!PE.isSectionAlignmentCorrect()) { log(logType.any, "Section alignment appears invalid"); isSectionAlignmentIncorrect = true; } if (isSectionAlignmentIncorrect) options = addOption(options, "Section alignment"); // File alignment check var isFileAlignmentIncorrect = false; if (!PE.isFileAlignmentCorrect()) { log(logType.any, "File alignment appears invalid"); isFileAlignmentIncorrect = true; } if (isFileAlignmentIncorrect) options = addOption(options, "File alignment"); // Check for correct header var isHeaderIncorrect = false; if (!PE.isHeaderCorrect()) { log(logType.any, "PE header validation failed"); isHeaderIncorrect = true; } if (isHeaderIncorrect) options = addOption(options, "Header"); // Validate the section table as one virtual and physical map. Individual fields may // remain in range while two sections overlap, appear out of RVA order, begin inside // the headers, or extend beyond the memory reserved by SizeOfImage. var isInvalidSectionLayoutPresent = false; var invalidSectionLayoutReason = String(); const sectionLayoutSizeOfHeaders = PE.getImageOptionalHeader("SizeOfHeaders"), sectionLayoutSizeOfImage = PE.getImageOptionalHeader("SizeOfImage"); var previousMappedSectionRva = -1; for (var i = 0; i < PE_Cached.numberOfSections && !isInvalidSectionLayoutPresent; i++) { const currentSectionRva = PE.getSectionVirtualAddress(i), currentSectionRawOffset = PE.getSectionFileOffset(i), currentSectionRawSize = PE.getSectionFileSize(i), currentSectionVirtualSize = PE.getSectionVirtualSize(i), currentSectionMappedSize = currentSectionVirtualSize !== 0 ? currentSectionVirtualSize : currentSectionRawSize, currentSectionVirtualEnd = currentSectionRva + currentSectionMappedSize; // A zero raw offset is already reported by the dedicated offset/size rule. if (currentSectionRawSize !== 0 && currentSectionRawOffset !== 0 && currentSectionRawOffset < sectionLayoutSizeOfHeaders) { invalidSectionLayoutReason = "section #" + i + " raw data begins inside headers"; isInvalidSectionLayoutPresent = true; } else if (currentSectionMappedSize !== 0 && sectionLayoutSizeOfImage !== 0 && currentSectionVirtualEnd > sectionLayoutSizeOfImage) { invalidSectionLayoutReason = "section #" + i + " exceeds SizeOfImage"; isInvalidSectionLayoutPresent = true; } else if (currentSectionMappedSize !== 0 && previousMappedSectionRva !== -1 && currentSectionRva < previousMappedSectionRva) { invalidSectionLayoutReason = "section #" + i + " is out of RVA order"; isInvalidSectionLayoutPresent = true; } if (currentSectionMappedSize !== 0) previousMappedSectionRva = currentSectionRva; // Compare declared mapped intervals without alignment rounding. Adjacent sections // remain valid; any positive intersection makes the loader view ambiguous. for (var d = i + 1; d < PE_Cached.numberOfSections && !isInvalidSectionLayoutPresent; d++) { const comparedSectionRva = PE.getSectionVirtualAddress(d), comparedSectionVirtualSize = PE.getSectionVirtualSize(d), comparedSectionMappedSize = comparedSectionVirtualSize !== 0 ? comparedSectionVirtualSize : PE.getSectionFileSize(d), comparedSectionVirtualEnd = comparedSectionRva + comparedSectionMappedSize; if (currentSectionMappedSize !== 0 && comparedSectionMappedSize !== 0 && currentSectionRva < comparedSectionVirtualEnd && comparedSectionRva < currentSectionVirtualEnd) { invalidSectionLayoutReason = "sections #" + i + " and #" + d + " overlap in memory"; isInvalidSectionLayoutPresent = true; } } } if (isInvalidSectionLayoutPresent) { log(logType.any, "Invalid section layout: " + invalidSectionLayoutReason); } if (isInvalidSectionLayoutPresent) options = addOption(options, "Invalid section layout"); // Check for correct section headers var isRelocsTableIncorrect = false; if (!PE.isRelocsTableCorrect()) { log(logType.any, "Relocation table appears malformed"); isRelocsTableIncorrect = true; } if (isRelocsTableIncorrect) options = addOption(options, "Relocs"); // Check IAT (Import Address Table) // Can be destroyed when the PE dumped from memory var isImportTableIncorrect = false; if (!PE.isImportTableCorrect()) { log(logType.any, "Import table appears malformed"); isImportTableIncorrect = true; } if (isImportTableIncorrect) options = addOption(options, "IAT"); // Check EAT (only for .dll) var isExportTableIncorrect = false; if (PE_Cached.isDynamicLinkLibrary && !PE.isExportTableCorrect()) { log(logType.any, "Export table appears malformed"); isExportTableIncorrect = true; } if (isExportTableIncorrect) options = addOption(options, "EAT"); // Check for resources table var isResourcesTableIncorrect = false; if (!PE.isResourcesTableCorrect()) { log(logType.any, "Resource table appears malformed"); isResourcesTableIncorrect = true; } if (isResourcesTableIncorrect) options = addOption(options, "Resources"); // Validate every declared PE data-directory range. The certificate table is the only // standard entry whose first field is a file offset; all remaining entries contain RVAs. var isUnmappedDataDirectoryPresent = false; var unmappedDataDirectoryName = String(); const dataDirectoryOptionalHeaderOffset = getOptHeaderOffset(), dataDirectoryNumberOffset = dataDirectoryOptionalHeaderOffset + (PE_Cached.is64bit ? 0x6C : 0x5C), dataDirectoryTableOffset = dataDirectoryOptionalHeaderOffset + (PE_Cached.is64bit ? 0x70 : 0x60), dataDirectoryOptionalHeaderSize = PE.readWord(dataDirectoryOptionalHeaderOffset - 4), dataDirectoryOptionalHeaderEnd = Math.min( dataDirectoryOptionalHeaderOffset + dataDirectoryOptionalHeaderSize, PE_Cached.fileSize ); var dataDirectoryNames = [ "EAT", "Imports", "Resources", "Exceptions", "Security", "Relocs", "Debug", "Architecture", "GlobalPtr", "TLS", "Load config", "Bound imports", "IAT", "Delay imports", "CLR", "Reserved" ]; if (dataDirectoryNumberOffset + 4 <= dataDirectoryOptionalHeaderEnd && dataDirectoryTableOffset < dataDirectoryOptionalHeaderEnd) { const dataDirectoryCount = Math.min( PE.read_uint32(dataDirectoryNumberOffset), IMAGE_NUMBEROF_DIRECTORY_ENTRIES, Math.floor((dataDirectoryOptionalHeaderEnd - dataDirectoryTableOffset) / 8) ); for (var i = 0; i < dataDirectoryCount && !isUnmappedDataDirectoryPresent; i++) { var currentDataDirectoryOffset = dataDirectoryTableOffset + i * 8, currentDataDirectoryRva = PE.read_uint32(currentDataDirectoryOffset), currentDataDirectorySize = PE.read_uint32(currentDataDirectoryOffset + 4); // A partially empty entry is tolerated here because old linkers occasionally // preserve a stale RVA. Only a complete declared range is used as strong proof. if (currentDataDirectoryRva === 0 || currentDataDirectorySize === 0) continue; if (i === IMAGE_DIRECTORY_ENTRY_SECURITY) { // WIN_CERTIFICATE is addressed by physical file offset and may live in overlay. if (currentDataDirectoryRva >= PE_Cached.fileSize || currentDataDirectoryRva + currentDataDirectorySize > PE_Cached.fileSize) { isUnmappedDataDirectoryPresent = true; } } else { var currentDataDirectoryEndRva = currentDataDirectoryRva + currentDataDirectorySize - 1; if (currentDataDirectoryRva >= sectionLayoutSizeOfImage || currentDataDirectoryEndRva >= sectionLayoutSizeOfImage || PE.VAToOffset(PE_Cached.imageBase + currentDataDirectoryRva) === -1 || PE.VAToOffset(PE_Cached.imageBase + currentDataDirectoryEndRva) === -1) { isUnmappedDataDirectoryPresent = true; } } if (isUnmappedDataDirectoryPresent) { unmappedDataDirectoryName = dataDirectoryNames[i]; log(logType.any, unmappedDataDirectoryName + " data directory is not fully file-backed"); } } } // Clean up: release the data-directory name collection dataDirectoryNames = undefined; if (isUnmappedDataDirectoryPresent) options = addOption(options, "Unmapped data directory (" + unmappedDataDirectoryName + ")"); // Resolve the TLS callback array and require every declared callback to point to // physically backed executable code. Merely having a TLS directory is not suspicious. var isTlsCallbackOutsideCodePresent = false; var tlsCallbackOutsideCodeAddress = -1; if (PE.isTLSPresent() && dataDirectoryTableOffset + (IMAGE_DIRECTORY_ENTRY_TLS + 1) * 8 <= dataDirectoryOptionalHeaderEnd) { const tlsDirectoryEntryOffset = dataDirectoryTableOffset + IMAGE_DIRECTORY_ENTRY_TLS * 8, tlsDirectoryRva = PE.read_uint32(tlsDirectoryEntryOffset), tlsDirectorySize = PE.read_uint32(tlsDirectoryEntryOffset + 4), tlsDirectoryMinimumSize = PE_Cached.is64bit ? 0x28 : 0x18, tlsDirectoryOffset = tlsDirectoryRva !== 0 ? PE.VAToOffset(PE_Cached.imageBase + tlsDirectoryRva) : -1; if (tlsDirectorySize >= tlsDirectoryMinimumSize && tlsDirectoryOffset !== -1 && tlsDirectoryOffset + tlsDirectoryMinimumSize <= PE_Cached.fileSize) { var tlsCallbacksAddress; if (PE_Cached.is64bit) { tlsCallbacksAddress = PE.read_uint32(tlsDirectoryOffset + 0x18) + PE.read_uint32(tlsDirectoryOffset + 0x1C) * 0x100000000; } else { tlsCallbacksAddress = PE.read_uint32(tlsDirectoryOffset + 0x0C); } if (tlsCallbacksAddress !== 0) { var tlsCallbacksOffset = PE.VAToOffset(tlsCallbacksAddress), tlsCallbackPointerSize = PE_Cached.is64bit ? 8 : 4; if (tlsCallbacksOffset === -1) { isTlsCallbackOutsideCodePresent = true; } else { // The callback array is null-terminated. The bound prevents a damaged // image from turning a structural validation into an unbounded scan. for (var i = 0; i < 64 && !isTlsCallbackOutsideCodePresent; i++) { var tlsCallbackPointerOffset = tlsCallbacksOffset + i * tlsCallbackPointerSize; if (tlsCallbackPointerOffset + tlsCallbackPointerSize > PE_Cached.fileSize) { isTlsCallbackOutsideCodePresent = true; break; } var tlsCallbackAddress; if (PE_Cached.is64bit) { tlsCallbackAddress = PE.read_uint32(tlsCallbackPointerOffset) + PE.read_uint32(tlsCallbackPointerOffset + 4) * 0x100000000; } else { tlsCallbackAddress = PE.read_uint32(tlsCallbackPointerOffset); } if (tlsCallbackAddress === 0) break; var tlsCallbackRva = tlsCallbackAddress - PE_Cached.imageBase, isTlsCallbackExecutable = false; for (var d = 0; d < PE_Cached.numberOfSections && !isTlsCallbackExecutable; d++) { var tlsCallbackSectionRva = PE.getSectionVirtualAddress(d), tlsCallbackSectionRawSize = PE.getSectionFileSize(d); if (tlsCallbackRva >= tlsCallbackSectionRva && tlsCallbackRva < tlsCallbackSectionRva + tlsCallbackSectionRawSize && (PE.section[d].Characteristics & SECTION_FLAGS_EXECUTE) && PE.VAToOffset(tlsCallbackAddress) !== -1) { isTlsCallbackExecutable = true; } } if (!isTlsCallbackExecutable) { tlsCallbackOutsideCodeAddress = tlsCallbackAddress; isTlsCallbackOutsideCodePresent = true; } } } } } } if (isTlsCallbackOutsideCodePresent) { log(logType.any, tlsCallbackOutsideCodeAddress !== -1 ? "TLS callback outside executable code: 0x" + tlsCallbackOutsideCodeAddress.toString(16) : "TLS callback array is not file-backed"); } if (isTlsCallbackOutsideCodePresent) options = addOption(options, "TLS callback outside code"); // Check for corrupted DotNET data var isNetImportsCorrupted = false; if (!PE_Cached.isDotNet && isNetMetaDataPresent() && PE_Cached.numberOfUnmanagedImports === 0) { log(logType.net, "Managed metadata has no usable import table"); isNetImportsCorrupted = true; } if (isNetImportsCorrupted) options = addOption(options, "DotNET headers/meta"); // Check for corrupted DotNET CLR version var isNetClrVersionCorrupted = false; if (PE_Cached.isDotNet && !isAsciiString(PE.getNETVersion())) { log(logType.net, "CLR version string appears malformed"); isNetClrVersionCorrupted = true; } if (isNetClrVersionCorrupted) options = addOption(options, "DotNET CLR version"); // Check for stripped Windows Authenticode signature var isWindowsAuthenticodeStripped = false; if (PE_Cached.overlaySize < 0x4B0) { var securityDirOffset = getOptHeaderOffset() + (PE_Cached.is64bit ? 0x90 : 0x80); if (PE.read_int32(securityDirOffset) !== 0 && PE.read_int32(securityDirOffset + 4) !== 0) { log(logType.any, "Authenticode directory appears to reference missing data"); isWindowsAuthenticodeStripped = true; } } if (isWindowsAuthenticodeStripped) options = addOption(options, "Authenticode tampered"); if (options.length !== 0) isDetected = true; if (isDetected) { _setResult("~corrupted data", "Generic", String(), PE.isVerbose() ? options : String()); } } // #endregion // #region "debug data (.NET and Native)" function scanForDebugData_NET_and_Native() { // For .NET and Native apps log(logType.nothing, "Scanning for debug data..."); var options = String(); var isDetected = Boolean(); // Check for sections with debug data by name var isDebugSectionPresent = false; var debugSectionIndex = -1; var debugSectionName = String(); for (var i = 0; i < PE_Cached.numberOfSections && !isDebugSectionPresent; i++) { debugSectionIndex = i; const currentSectionName = PE.getSectionName(i); if (/(?:debug|^\.stab(?:str)?$)/i.test(currentSectionName)) { // Detects TCC/GCC and modern LLVM debug sections debugSectionName = cleanSectionName(currentSectionName); log(logType.any, "Debug section detected: \"" + debugSectionName + "\""); isDebugSectionPresent = true; } } if (isDebugSectionPresent) options = "Section #" + debugSectionIndex + " (\"" + debugSectionName + "\")"; // .NET Native debug header, EAT function var isNetNativeDebugDataPresent = false; if (!PE_Cached.isDotNet && PE.isExportFunctionPresent("DotNetRuntimeDebugHeader")) { log(logType.net, ".NET Native debug symbols detected"); isNetNativeDebugDataPresent = true; } if (isNetNativeDebugDataPresent) options = addOption(options, "DotNET runtime header"); // Check for -Wl,--export-all-symbols linker option var isSymbolsExported = false; if (!PE_Cached.isDynamicLinkLibrary && PE.isExportFunctionPresent("main")) { log(logType.any, "The \"--export-all-symbols\" linker option was probably used"); isSymbolsExported = true; } if (isSymbolsExported) options = addOption(options, "Symbols exported"); // Check for absolute PDB path var isPdbFullPathPresent = false; var numberOfDebugDataRecords = PE.getNumberOfDebugDataRecords(), pdbExtensionPatternOffset = -1, pdbDebugDataOffset = 0, pdbDebugDataSize = 0; // A PDB path is part of a Debug Directory record. Restricting the search to // these records avoids both unrelated `.pdb` strings and a full image scan. for (var i = 0; i < numberOfDebugDataRecords && pdbExtensionPatternOffset === -1; i++) { var currentDebugDataOffset = PE.getDebugDataOffset(i), currentDebugDataSize = PE.getDebugDataSize(i); if (PE.getDebugDataType(i) === "CODEVIEW" && currentDebugDataOffset > 0 && currentDebugDataSize > 6 && currentDebugDataOffset < PE_Cached.fileSize) { currentDebugDataSize = Math.min(currentDebugDataSize, PE_Cached.fileSize - currentDebugDataOffset); pdbExtensionPatternOffset = PE.findSignature(currentDebugDataOffset, currentDebugDataSize, "%% %% %% '.pdb' 00"); if (pdbExtensionPatternOffset !== -1) { pdbDebugDataOffset = currentDebugDataOffset; pdbDebugDataSize = currentDebugDataSize; } } } var pdbPathBeginOffset = pdbDebugDataOffset, pdbPath = String(); if (pdbExtensionPatternOffset !== -1) { for (var i = pdbExtensionPatternOffset; i > pdbDebugDataOffset && pdbPathBeginOffset === pdbDebugDataOffset; i--) { if (PE.readByte(i) === 0x00) { pdbPathBeginOffset = i + 1; } } var pdbPathMaxSize = Math.min(256, pdbDebugDataOffset + pdbDebugDataSize - pdbPathBeginOffset); pdbPath = File.cleanString(PE.getString(pdbPathBeginOffset, pdbPathMaxSize)); if (pdbPath.length > 255) { const pdbPathSeparator = " . . . . ", pdbPathFragmentLength = Math.floor((255 - pdbPathSeparator.length) / 2); pdbPath = pdbPath.substring(0, pdbPathFragmentLength) + pdbPathSeparator + pdbPath.substring(pdbPath.length - pdbPathFragmentLength); } pdbPath = pdbPath.replace(/[\n\r]/g, String()); if (pdbPath.match(/^[a-zA-Z]:[\\/]/) !== null) { log(logType.any, "PDB absolute path: \"" + pdbPath + "\""); isPdbFullPathPresent = true; } } if (isPdbFullPathPresent) options = addOption(options, "Absolute PDB path"); // Check for portable (relative path) or embedded PDB var isPortableOrEmbeddedPdbDetected = false; var pdbTextType = String(); if (pdbPath && pdbPath[0] !== '.' && !isPdbFullPathPresent) { pdbTextType = pdbDebugDataOffset > 0 && PE.compare("00 52 53 44 53", pdbDebugDataOffset - 1) ? "Embedded" : "Portable"; log(logType.any, pdbTextType + " PDB name: \"" + pdbPath + "\""); isPortableOrEmbeddedPdbDetected = true; } if (isPortableOrEmbeddedPdbDetected) options = addOption(options, pdbTextType + " PDB (release)"); // Check for Costura.Fody embedded PDB. It is a .NET tool (library) that embeds DLL builds into the main assembly. var isCosturaEmbeddedDebugData = false; if (PE_Cached.isDotNet && validateNetObject("costura.costura.pdb.compressed")) { log(logType.any, "Costura embedded PDB detected"); isCosturaEmbeddedDebugData = true; } if (isCosturaEmbeddedDebugData) options = addOption(options, "Costura.Fody embedded PDB"); if (options.length !== 0) isDetected = true; if (isDetected) { _setResult("~debug data", "Contains", String(), PE.isVerbose() ? options : String()); } } // #endregion /** * Checks if the .NET Visual Basic standard library is present. * * @returns {boolean} True if the Microsoft.VisualBasic library is found; otherwise false. */ function isVbNetStandardLibraryPresent() { return PE.isNetObjectPresent("Microsoft.VisualBasic"); } /** * Checks if the .NET JScript standard library is present. * * @returns {boolean} True if the Microsoft.JScript library is found; otherwise false. */ function isJscriptNetStandardLibraryPresent() { return PE.isNetObjectPresent("Microsoft.JScript"); } /** * Determines whether the current PE is a framework-style .NET component. * * A component is identified as a System-prefixed .NET DLL whose module name * matches its assembly name and which has no global CLR constructor. * * @returns {boolean} True for a matching System.* framework DLL; otherwise false. */ function isFrameworkComponent() { if (!PE_Cached.isDotNet || !PE_Cached.isDynamicLinkLibrary || PE_Cached.isNetGlobalCctorPresent) { return false; } const buildAssemblyName = PE_Cached.nameOfNetAssemblyName, buildModuleName = PE_Cached.nameOfNetModuleName; if (buildAssemblyName.length >= 6 && buildAssemblyName.substring(0, 6) === "System" && buildModuleName === (buildAssemblyName + ".dll")) { return true; } return false; } /** * Searches the PE body for a DIE signature pattern. * * Searches from the end of the DOS stub to the end of the PE body, excluding * the overlay. On success it logs the pattern and updates `lastOffsetDetected`. * * @param {string} pattern - The pattern to search for within the file. * @returns {boolean} True if the pattern is found, false otherwise. * @example * if (validateSignature("'UPX0'")) { * // A matching signature was found in the PE body. * } */ function validateSignature(pattern) { const offsetFound = PE.findSignature(PE_Cached.dosStubSize, PE_Cached.imageBodySize, pattern), resultBool = offsetFound !== -1; if (resultBool) { lastOffsetDetected = "0x" + Number(offsetFound).toString(16); log(logType.any, "Pattern found: " + pattern); } return resultBool; } /** * Searches PE sections for the specified byte-code signature. * * For multi-section images, empty sections and standalone `.rsrc`/`.reloc` * sections are skipped. A section is still searched when it also contains the * entry point or an important PE directory. * * @param {string} byteCode - The byte code signature to locate. * @returns {boolean} True if the byte code is found in any section, otherwise false. * @sideEffects Updates `lastOffsetDetected` and writes a log entry on success. */ function validateNetByteCode(byteCode) { for (var i = 0; i < PE_Cached.numberOfSections; i++) { const sectionName = PE.getSectionName(i), sectionOffset = PE.getSectionFileOffset(i), sectionSize = PE.getSectionFileSize(i); if (PE_Cached.numberOfSections > 1) { if (sectionOffset === 0 || sectionSize === 0) { continue; } var isDedicatedResourceSection = i === PE_Cached.indexOfResourceSection && sectionName === ".rsrc" && i !== PE_Cached.indexOfRelocsSection && i !== PE_Cached.indexOfEntryPointSection && i !== PE_Cached.indexOfTlsSection && i !== PE_Cached.indexOfImportsSection; var isDedicatedRelocsSection = i === PE_Cached.indexOfRelocsSection && sectionName === ".reloc" && i !== PE_Cached.indexOfResourceSection && i !== PE_Cached.indexOfEntryPointSection && i !== PE_Cached.indexOfTlsSection && i !== PE_Cached.indexOfImportsSection; if (isDedicatedResourceSection || isDedicatedRelocsSection) { continue; } } var offsetFound = PE.findSignature(sectionOffset, sectionSize, byteCode); if (offsetFound !== -1) { lastOffsetDetected = "0x" + Number(offsetFound).toString(16); log(logType.net, "ByteCode detected: " + byteCode); return true; } } return false; } /** * Checks whether a .NET metadata object is present in the current PE. * * @param {string} object - The metadata object name to search for. * @returns {boolean} True if the object is present; otherwise false. * @sideEffects Writes a .NET log entry when the object is found. */ function validateNetObject(object) { const result = PE.isNetObjectPresent(object); if (result) log(logType.net, "Object present: " + object); return result; } /** * Checks whether a .NET user string is present in the current PE metadata. * * @param {string} ustring - The Unicode string to validate. * @returns {boolean} True if the string is present; otherwise false. * @sideEffects Writes a .NET log entry when the string is found. */ function validateNetUnicodeString(ustring) { const result = PE.isNetUStringPresent(ustring); if (result) log(logType.net, "String present: \"" + ustring + "\""); return result; } /** * Validates if a given Unicode string exists within the PE file. * * This function converts the input to a UTF-16LE signature and searches the PE * body from the end of the DOS stub to the end of the file body, excluding the * overlay. It is independent of the .NET metadata string heap. * * @param {string} ustring - The Unicode string to search for within the PE file. * @returns {boolean} True if the UTF-16LE sequence is found; otherwise false. * @sideEffects Writes a general log entry when the string is found. */ function validateGlobalUnicodeString(ustring) { const result = PE.findSignature(PE_Cached.dosStubSize, PE_Cached.imageBodySize, convertStringToUnicodeSignature(ustring)) !== -1; if (result) log(logType.any, "Unicode string found: \"" + ustring + "\""); return result; } /** * Generates a precise UTF-16LE hex signature mask for a given string. * Translates characters into their true byte representation (Little-Endian) * instead of naively injecting null bytes. Safely handles non-ASCII characters. * * Example (ASCII): "test" -> "7400650073007400" * Example (Cyrillic): "Тест" -> "2204350441044204" * * @param {string} ustring - The input string to translate into a signature. * @returns {string} The formatted UTF-16LE hex signature mask. * @example * convertStringToUnicodeSignature("Test"); // "5400650073007400" */ function convertStringToUnicodeSignature(ustring) { var hexMask = []; for (var i = 0; i < ustring.length; i++) { var code = ustring.charCodeAt(i); // Extract Little-Endian bytes (low byte first, then high byte) var low = (code & 0xFF).toString(16).toUpperCase(), high = ((code >> 8) & 0xFF).toString(16).toUpperCase(); low = low.length === 1 ? "0" + low : low; high = high.length === 1 ? "0" + high : high; hexMask.push(low + high); } return hexMask.join(String()); } /** * Checks that none of the provided .NET metadata objects are present. * * @param {Array} objects - An array of .NET object names to check. * @returns {boolean} - Returns true if all .NET objects are missing, otherwise false. * @see isAllNetObjectsPresent */ function isAllNetObjectsMissing(objects) { // Iterate through the array of .NET objects for (var i = 0; i < objects.length; i++) { // Get the current object name const objectName = objects[i]; // If the current .NET object is present, return false if (PE.isNetObjectPresent(objectName)) { return false; } } // If all .NET objects are missing, return true return true; } /** * Checks that every provided .NET metadata object is present. * * @param {Array} objects - An array of .NET object names to check. * @returns {boolean} - Returns true if all .NET objects are present, otherwise false. * @see isAllNetObjectsMissing */ function isAllNetObjectsPresent(objects) { // Iterate through the array of .NET objects for (var i = 0; i < objects.length; i++) { // Get the current object name const objectName = objects[i]; // If the current .NET object is not present, return false if (!PE.isNetObjectPresent(objectName)) { return false; } } // If all .NET objects are present, return true return true; } /** * Searches for a null-prefixed string signature in the first PE section. * * @param {string} sign - The signature to search for. * @param {boolean} isFullName - A flag indicating whether to append '00' to the signature. * @returns {string} - The found signature if present, otherwise an empty string. * @example * findAndMark("UPX0", true); // "UPX0" when the full null-terminated name exists */ function findAndMark(sign, isFullName) { if (PE.isSignatureInSectionPresent(0, ("00'" + sign + "'") + // 00'string (isFullName ? "00" : String()))) { // ... '00 return sign; } return String(); } /** * Determines if a given string name is obfuscated. * * The function checks for obfuscation in two cases: * 1. If the name contains spaces, it splits the name into tokens and checks each token for obfuscation. * 2. If the name does not contain spaces, it checks each chunk of the string (of size 20) for obfuscation. * * Logs a message if an obfuscated segment or chunk is found. * * @param {string} name - The string to analyze for obfuscation. * @returns {boolean} True if the name or any of its segments/chunks are obfuscated, false otherwise. * @example * isNameObfuscated("a1b2c3d4e5f6"); // true * isNameObfuscated("GetWindowText"); // false */ function isNameObfuscated(name) { if (!name || name.length === 0) return false; if (PE_Cached.obfuscationWhitelist.test(name) || PE_Cached.obfuscationWhitelistInsensitive.test(name)) return false; if (name.indexOf(' ') !== -1) { const tokens = name.split(/\s+/); for (var i = 0; i < tokens.length; i++) { var currentToken = tokens[i]; // If it starts with an abbreviation and the string is separated by a space, then it is not obfuscated (samples: The Last Of Us: II Remastered resource files) if (i === 0 && currentToken.match(/^[A-Z]{3,}/)) { continue; } if (isTokenObfuscated(currentToken)) { log(logType.any, "Obfuscated string segment: \"" + File.cleanString(currentToken) + "\" in \"" + File.cleanString(name) + "\""); return true; } } return false; } // Split the name into chunks const CHUNK_SIZE = 20; for (var start = 0; start < name.length; start += CHUNK_SIZE) { const chunk = name.substring(start, start + CHUNK_SIZE); if (isTokenObfuscated(chunk)) { log(logType.any, "Obfuscated string chunk: \"" + File.cleanString(chunk) + "\" in \"" + File.cleanString(name) + "\""); return true; } } return false; } const obfuscationWhitelistPatterns = [ // ── NUMBERS · DATES · TIMESTAMPS ───────────────────────────────────────── // Year / date: 1999, 2020 - 2024, 31.12.2020, 2020-12-31 /^(?:\d{4}(?:\s*[-–\/.]\s*\d{1,4}(?:\s*[-\/.]\s*\d{1,4})?)?|\d{1,2}\s*[-\/.]\s*\d{1,2}\s*[-\/.]\s*\d{2,4})[,.]?$/, // Pure digit stamps (build, changelist, Unix-ms): 35398403, 20240101120000, 1720000000000000000 /^\d{6,19}$/, // IP address: 192.168.1.1 /^\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3}$/, // ── VERSION STRINGS ────────────────────────────────────────────────────── // Version tag prefix (no end anchor): v1.0-release, v21.9.1-beta /^[Vv]\d{1,2}\.\d{1,4}/, // SemVer with pre-release/metadata: 1.0.0-rc1, 2.0.0-alpha.1, 3.5.0+build.1 /^\d+\.\d+(?:\.\d+)*[-+][a-zA-Z0-9][a-zA-Z0-9._-]{0,29}$/, // OID (3+ components): 1.2.840, 1.2.840.113549.1.1.11 /^\d+(?:\.\d+){2,}$/, // Assembly/module with embedded version: MyLib.2.5, Component.1.0 /^[a-zA-Z][a-zA-Z0-9]{1,30}\.\d+\.\d+$/, // ── IDENTIFIER NAMING CONVENTIONS ───────────────────────────────────────── // SCREAMING_SNAKE_CASE: IDR_MSCORLIB_4030319, MY_CONST_VALUE /^[A-Z][A-Z0-9]*_[A-Z0-9_]+$/, // PascalCase (optional 1-3 digit suffix): GetWindowText, BthMtpContextHandler, CreateFile202 /^(?:[A-Z][a-z]{2,}){2,}\d{0,3}$/, // camelCase (optional 1-3 digit suffix): createFile, readBuffer, sendMessage202 /^[a-z]{1,12}(?:[A-Z][a-z]{2,})+\d{0,3}$/, // snake_case: create_file, get_process_id /^[a-z]{2,}(?:_[a-z]{2,})+$/, // Hungarian notation: lpszFileName, dwProcessId, pbBuffer /^(?:lp(?:sz)?|dw|hw|sz|cb|pb|ul|ui|hg)[A-Z][a-z]{3,}/, // ── PLATFORM & WINDOWS SPECIFICS ────────────────────────────────────────── // Hardware chip/model: AX88179_178A, Acx01000, CUDART64_132_51 /^[A-Za-z]{1,10}\d{3,}(?:[_-][A-Za-z0-9]{1,16})*$/, // All-caps abbreviation + 1-5 digits: DX11, Win32, BCB401, MSVC2022 /^[A-Z]{2,}[0-9]{1,5}$/, // ── PRODUCT, PACKAGE & API IDENTIFIERS ──────────────────────────────────── // Word (6+ letters) + year/date suffix: release20240101, AppBuild20231205 /^[a-zA-Z]{6,}(?:19|20)\d{2,8}$/, ], obfuscationWhitelistInsensitivePatterns = [ // ── NUMBERS · DATES · TIMESTAMPS ───────────────────────────────────────── // Copyright: (C) 1995, 2019, 2020, 2021SQUARE (four years, optional whitespace) /^(?:(?:\(\s*C\s*\)|©)\s*)?[12]\d{3}(?:\s*,\s*[12]\d{3}){3}(?:\s*[A-Z][A-Z0-9&._-]{1,24})?$/i, // Copyright/build years: © 2019-2026, Copyright (C) 2019; 2020; 2021 Company, All Rights Reserved /^(?:copyright\s*)?(?:(?:\(\s*c\s*\)|©)\s*)?[12]\d{3}(?:\s*(?:,|;|[-–—]|to)\s*[12]\d{3}){1,5}(?:\s+(?:all\s+rights\s+reserved[.!]?|[a-z][a-z&.,'®™()_-]*)){0,8}$/i, // Copyright/build year: ©2026, (C) 2026, Copyright 2026 Company™, All Rights Reserved. /^(?:(?:copyright\s*)?(?:\(\s*c\s*\)|©)\s*|copyright\s+)[12]\d{3}(?:\s*(?:,|;|[-–—]|to)\s*[12]\d{3}){0,5}(?:\s+(?:all\s+rights\s+reserved[.!]?|[a-z][a-z&.,'®™()_-]*)){0,8}$/i, // Reversed legal notice: All Rights Reserved © 2026 Company /^all\s+rights\s+reserved[.!]?\s*(?:\(\s*c\s*\)|©)\s*[12]\d{3}(?:\s*(?:,|;|[-–—]|to)\s*[12]\d{3}){0,5}(?:\s+[a-z][a-z&.,'®™()_-]*){0,8}$/i, // Version Info output labels: Dev-output: ©2026, Build Output - Copyright 2026 Company /^(?:(?:dev(?:elopment)?|build)[-_ ]?output\s*(?::|-|–|—)?\s*)(?:(?:copyright\s*)?(?:\(\s*c\s*\)|©)\s*|copyright\s+)[12]\d{3}(?:\s*(?:,|;|[-–—]|to)\s*[12]\d{3}){0,5}(?:\s+(?:all\s+rights\s+reserved[.!]?|[a-z][a-z&.,'®™()_-]*)){0,8}$/i, // ISO 8601 compact datetime: 20250527T143022, 20250527T143022Z, 20250527t143022.123z /^\d{8}T\d{6}(?:\.\d{1,3})?Z?$/i, // ISO 8601 / RFC 3339 datetime: 2025-05-27T14:30:22Z, 2025-05-27T14:30:22.123+03:00 /^\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}(?:\.\d{1,9})?(?:Z|[+-]\d{2}:\d{2})$/i, // File size: 123KB, 45.6 MB, 1.5GB /^\d+(?:\.\d+)?\s*(?:bytes?|[kmgt]ib?|[kmgt]b)$/i, // Hex literal: 0x1234ABCD /^0x[0-9a-f]+$/i, // ── VERSION STRINGS ────────────────────────────────────────────────────── // Version number: v1.2.3, v.1.2.3, 1.0.0.0, 10.15.7, 1.2.3.4.5 /^(?:v(?:\.)?)?\d+(?:\.\d+){1,5}$/i, // VERSIONINFO text fields: FileVersion: 1.2.3.4, Product Version=10.0.19045.1 /^(?:file|product|assembly|informational)[ _-]?version\s*[:=]?\s*v?\d+(?:\.\d+){1,5}$/i, // Library version (underscore/dot): lz4_1_9_4, zlib_1_2_13, openssl_3_0_9 /^[a-z]{2,12}\d{0,3}(?:[_.]\d{1,5}){1,}$/i, // Runtime/tool + version (no delimiter): node20.11.1, python3.12.10, dotnet9.0.202 /^[a-z]{2,}\d{1,3}\.\d+(?:\.\d+)*$/i, // .NET TFM: net8.0, netstandard2.1, net8.0-windows10.0.19041, net48 /^net(?:coreapp|standard)?\d+(?:\.\d+)?(?:-[a-z0-9.]+)*$/i, // ── GUIDS · HASHES · CRYPTO ─────────────────────────────────────────────── // UUID: 550e8400-e29b-41d4-a716-446655440000 /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i, // UUID with braces: {550e8400-e29b-41d4-a716-446655440000} /^\{[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}\}$/i, // Partial GUID fragments: 69fe178f-26e7-43a9-a, c4d66f00-b6f0-4439-a /^[0-9a-f]{4,8}(?:-[0-9a-f]{1,8}){2,}$/i, // Build/version hash: 2b7-0fc9cc7187f5_Hyp, 620-2cfb01a9318d_Hyp /^[0-9a-f]{2,8}-[0-9a-f]{8,}(?:[_-][a-zA-Z]+)?$/i, // ECC named curves: secp256k1, secp384r1, prime256v1 /^(?:secp|sect|prime|brainpoolp|c2[tp]nb)\d{2,4}[a-z]\d{1,2}(?:v\d)?$/i, // .NET PublicKeyToken: PublicKeyToken=b77a5c561934e089 /^PublicKeyToken=[0-9a-f]{16}$/i, // .NET assembly identity: Name, Version=1.2.3.4, Culture=neutral, PublicKeyToken=null /^[a-z][a-z0-9._-]{0,127},\s*version=\d+(?:\.\d+){1,3},\s*culture=(?:neutral|[a-z]{2}(?:-[a-z]{2})?),\s*publickeytoken=(?:null|[0-9a-f]{16})$/i, // Explicit cryptographic digest: SHA-256: 64hex, SHA1=40hex, MD5 32hex /^(?:md5\s*[:=]\s*[0-9a-f]{32}|sha(?:-?1)?\s*[:=]\s*[0-9a-f]{40}|sha-?256\s*[:=]\s*[0-9a-f]{64}|sha-?512\s*[:=]\s*[0-9a-f]{128})$/i, // ── PLATFORM & WINDOWS SPECIFICS ────────────────────────────────────────── // DirectX module names: D3D10_1, D3D11On12, D3D9on12 /^d3d\d{1,2}/i, // Kernel debugger transport: kd_02_1137, kdnet_uart16550 /^kd(?:_|net_)/i, // Windows driver/service suffixes: kbldfltr, WSDScDrv, hnswfpdriver, driver.sys /(?:driver|drv|fltr|clx|form|srv|svc|port|bus|hub|hid)(?:\.[a-z0-9]{1,5})?$/i, // Charset/codepage names: c_is2022, cp1251, utf32le /^(?:c_|cp|iso|utf|ucs|gb|koi|win|mac|ascii|latin|ansi)\w*\d/i, // Windows kernel/networking + build/CI keywords /(?:ndis|acpi|storport|classpnp|fltmgr|hidclass|usbhub|usbstor|kbdclass|mouclass|ksuser|nsiproxy|tcpip|netbt|wfplwf|pacer|krnl|sql|mgr|tmp|vds|word|runtime|buildbot|builder|jenkins|gitlab|runner|beta|alpha|client|relclient|steam|win(?:32|64|2)|x64|x86|cuda|iscsi|scsi|sstp|uart|nightly|canary|stable|preview|insider|hotfix|snapshot|release|update|patch|master|develop)/i, // ── PRODUCT, PACKAGE & API IDENTIFIERS ──────────────────────────────────── // OOP/API noun suffixes: GetWindowHandler, CreateContextManager, OnEventCallback /(?:handler|manager|factory|provider|helper|callback|context|interface|service|listener|controller|observer|processor|validator|converter|iterator|descriptor|dispatcher|allocator|builder|writer|reader|parser|encoder|decoder)$/i, // Product installer/config suffixes: ProductInstaller, AppSetup2024, Product-Setup /(?:installer|launcher|setup|updater|patcher|bootstrapper|uninstaller|configuration|settings|preferences|options|properties)(?:[-_.]?\d{1,4})?$/i, // Delimiter-separated identifiers: dotnet-sdk-9.0.202-w, libssl-3.0.9-dev, api-2.1 /^[a-z]{3,}(?:[-._](?:[a-z]+|\d+))*$/i, ]; /** * Determines whether a given token is considered obfuscated based on advanced heuristics. * * The function checks for: * - Tokens shorter than 7 characters are automatically considered clean * - Common legitimate patterns (dates, versions, UUIDs, etc.) * - Hexadecimal strings (8+ chars) like 'd3adc0de' * - Regular expression patterns indicating mixed alphanumeric sequences * - Statistical character analysis with entropy detection * - Repetitive pattern detection * * Whitelist matches are checked before every heuristic and always return false. * A positive result is a suspicious-name signal, not proof of malware. * * @param {string} token - The string token to analyze * @returns {boolean} True if token matches obfuscation patterns, false otherwise * @example * isTokenObfuscated("a1b2c3d4e5"); // true * isTokenObfuscated("550e8400-e29b-41d4-a716-446655440000"); // false */ function isTokenObfuscated(token) { if (!token || token.length < 7) return false; // === WHITELIST: Common legitimate patterns === if (PE_Cached.obfuscationWhitelist.test(token) || PE_Cached.obfuscationWhitelistInsensitive.test(token)) return false; // === OBFUSCATION DETECTION === var stats = { digits: 0, lower: 0, upper: 0, special: 0, transitions: 0 }; var charTypes = []; for (var i = 0; i < token.length; i++) { var char = token[i]; var prevType = charTypes[charTypes.length - 1]; var currType; if (char >= '0' && char <= '9') { stats.digits++; currType = 'digit'; } else if (char >= 'a' && char <= 'z') { stats.lower++; currType = 'lower'; } else if (char >= 'A' && char <= 'Z') { stats.upper++; currType = 'upper'; } else { stats.special++; currType = 'special'; } charTypes.push(currType); if (prevType && prevType !== currType) { stats.transitions++; } } const totalChars = token.length, alphaChars = stats.lower + stats.upper; // Detect excessive character type transitions (obfuscation indicator) if ((stats.transitions / totalChars) > 0.6 && stats.digits >= 4) return true; // Detect repetitive patterns (e.g., "a1b2c3d4e5") if (token.match(/^(?:[a-z]\d){4,}$/i)) return true; // Pattern: letter-digit-letter with 4+ total digits if (token.match(/^(?=\d|[a-z])(?=.*[a-z]\d[a-z])(?=(?:.*\d){4,})/i)) return true; // Detect pure hex strings (8+ chars) - likely hashes if (token.match(/^[0-9a-f]{8,}$/i)) { // Exception: common words that are hex-valid (like "decade", "faded") if (!token.match(/^(?:dead|beef|cafe|babe|fade|deed|feed|face|decade|deface)$/i)) { return true; } } // Repeating syllables/segments (e.g., "jnfasjknjnjnjn" - "nj" repeats 3+ times) // Detects 2-3 char segments repeating 3+ times consecutively if (token.match(/(.{2,3})\1{2,}/)) return true; // Mixed letters and digit clusters (e.g., "ac8a8sic8i19o9oi", "j9813jd1039i098") // Pattern: letter(s) + digit(s), repeated 4+ times with varying lengths if (token.match(/(?:[a-z]+\d+){4,}/i) && stats.digits >= 4) return true; // Chaotic alternating: single letter + multiple digits (e.g., "j9813jd1039i098") // Pattern: letter + 3+ digits, repeated 2+ times if (token.match(/(?:[a-z]\d{3,}){2,}/i)) return true; // Random consonant clusters without vowels (e.g., "jnfsjknjn", "bcdfgh") // 8+ consonants in a row (excluding common patterns and abbreviation-style names with doubled consonants like "spprgrss.dll") // Doubled consonants (pp, ss, rr, etc.) indicate vowel-stripped abbreviations, not obfuscation if (token.match(/[bcdfghjklmnpqrstvwxz]{8,}/i) && !token.match(/str|chr|thr|sch|scr|spr/i) && !token.match(/([bcdfghjklmnpqrstvwxz])\1/i)) return true; // Single consonant repeated excessively (e.g., "jnfasjknjnjnjn") // Same consonant appears 7+ times in strings shorter than 15 chars // (threshold 7 avoids FPs from "luainstall.dll" l=6, "rassstp.sys" s=5, "msiscsi.sys" s=5) if (token.length < 15) { var consonantCounts = {}; for (var i = 0; i < token.length; i++) { var ch = token[i].toLowerCase(); if (/[bcdfghjklmnpqrstvwxz]/.test(ch)) { if ((consonantCounts[ch] = (consonantCounts[ch] || 0) + 1) >= 7) return true; } } } // Keyboard walk patterns — horizontal rows, 5+ chars (forward and backward). // No legitimate abbreviation or identifier contains these sequences. if (token.match(/qwert|werty|ertyu|rtyui|tyuio|yuiop|asdfg|sdfgh|dfghj|fghjk|ghjkl|zxcvb|xcvbn|cvbnm|poiuy|oiuyt|iuyte|uyter|ytrew|trewq|lkjhg|kjhgf|jhgfd|hgfds|gfdsa|mnbvc|nbvcx|bvcxz/i)) return true; // Keyboard walk patterns — vertical columns, 6+ chars (e.g., "qazwsx", "wsxedc"). // Single 3-char columns (qaz, wsx) may appear in code; 6+ chars are unambiguous. if (token.match(/qazwsx|wsxedc|edcrfv|rfvtgb|tgbyhn|yhnujm|zaqxsw|xswcde|cdevfr|vfrbgt|bgtnhy|nhymju/i)) return true; // Number row sequences — 5+ consecutive digits in ascending or descending order. if (token.match(/01234|12345|23456|34567|45678|56789|98765|87654|76543|65432|54321/)) return true; // Excessive digit-letter boundaries (e.g., "a1b2c3d4e5f6") // Count transitions specifically between letters and digits (8+ transitions) var digitLetterTransitions = 0; for (var i = 1; i < token.length; i++) { var curr = token[i], prev = token[i - 1], currIsDigit = (curr >= '0' && curr <= '9'), prevIsDigit = (prev >= '0' && prev <= '9'), currIsLetter = ((curr >= 'a' && curr <= 'z') || (curr >= 'A' && curr <= 'Z')), prevIsLetter = ((prev >= 'a' && prev <= 'z') || (prev >= 'A' && prev <= 'Z')); if ((currIsDigit && prevIsLetter) || (currIsLetter && prevIsDigit)) { digitLetterTransitions++; } } if (digitLetterTransitions >= 8) return true; // Product filenames may combine a short name, release year and build number. // Keep this below deterministic obfuscation checks so a valid-looking suffix // cannot hide a keyboard walk or another unambiguous anomalous pattern. if (token.match(/^[a-z]{2,}[a-z0-9_-]{0,30}(?:19|20)\d{2}(?:[._-]\d{1,5}){0,3}(?:\.(?:exe|dll|sys|msi|msu|msp|cpl|scr|ocx|drv))?$/i)) return false; // Statistical thresholds for obfuscation return ( (stats.digits >= 7) || // Too many digits (stats.digits >= 4 && stats.digits / totalChars > 0.4) || // High digit density (raised from 0.35) (stats.digits >= 5 && stats.lower >= 3 && stats.upper >= 3) || // Balanced mix (suspicious) (stats.digits >= 6 && stats.lower >= 5) || // Many digits + lowercase (alphaChars >= 5 && stats.digits >= 5 && stats.upper === 0 && stats.lower === alphaChars) // No uppercase variation ); } const IMAGE_DLLCHARACTERISTICS_HIGH_ENTROPY_VA = 0x0020, IMAGE_DLLCHARACTERISTICS_DYNAMIC_BASE = 0x0040, IMAGE_DLLCHARACTERISTICS_FORCE_INTEGRITY = 0x0080, IMAGE_DLLCHARACTERISTICS_NX_COMPAT = 0x0100, IMAGE_DLLCHARACTERISTICS_NO_ISOLATION = 0x0200, IMAGE_DLLCHARACTERISTICS_NO_SEH = 0x0400, IMAGE_DLLCHARACTERISTICS_NO_BIND = 0x0800, IMAGE_DLLCHARACTERISTICS_APPCONTAINER = 0x1000, IMAGE_DLLCHARACTERISTICS_WDM_DRIVER = 0x2000, IMAGE_DLLCHARACTERISTICS_GUARD_CF = 0x4000, IMAGE_DLLCHARACTERISTICS_TERMINAL_SERVER_AWARE = 0x8000; // #region "obfuscations (Native)" function scanForObfuscations_Native() { log(logType.nothing, "Scanning for obfuscation..."); var options = String(); var isDetected = Boolean(); // PE file must contain at least two sections: one for code and one for resources. var isOneSectionFile = false; if (PE_Cached.numberOfSections === 1) { log(logType.any, "Only one section found!"); isOneSectionFile = true; } if (isOneSectionFile) options = addOption(options, "Only one section"); // Check for section names containing forbidden characters var isStrangeSectionsPresent = false; // Define forbidden characters const badSectionChars = '-=+~!@#$%^&*()"№;%:?*():;,/\\|\'`<> '; // Iterate through sections and characters to check for forbidden characters for (var i = 0; i < PE_Cached.numberOfSections && !isStrangeSectionsPresent; i++) { var sectionName = PE.getSectionName(i); if (sectionName.length < 3 || (/^\.\d.+/.test(sectionName) && sectionName !== ".00cfg") || sectionName[0] === " ") { isStrangeSectionsPresent = true; } var isIdioticMinGwSectionsPresent = false; if (_isResultPresent("linker", "GNU linker ld (GNU Binutils)")) { if (PE.isSectionNamePresent(".build-id")) { isIdioticMinGwSectionsPresent = true; } else { for (var d = 1; d < 10 && !isIdioticMinGwSectionsPresent; d++) { // sections like "/5", "/2" etc if (sectionName.indexOf("/" + d) !== -1) { isIdioticMinGwSectionsPresent = true; } } } } if (isIdioticMinGwSectionsPresent) { isStrangeSectionsPresent = false; break; } for (var d = 0; d < badSectionChars.length && !isStrangeSectionsPresent; d++) { // If forbidden character is found, set flag and break if (sectionName.indexOf(badSectionChars[d]) !== -1) { isStrangeSectionsPresent = true; } } if (!isAsciiString(sectionName) && !isStrangeSectionsPresent) { isStrangeSectionsPresent = true; } } if (isStrangeSectionsPresent) { log(logType.any, "Unusual section names detected"); options = addOption(options, "Strange sections"); } // Check for DOS header in the PE file var isDosMissing = false, isCustomDosPresent = false; // If DOS stub size is 0, set flag for missing DOS if (PE_Cached.dosStubSize === 0) { log(logType.any, "DOS stub is missing"); isDosMissing = true; } else { // Define messages to check for custom DOS const messages = [ "This program cannot be run in DOS mode.", // most popular (standard) "This program requires Microsoft Windows", // MASM / FASM / older assemblers "This program must be run under Windows", // twinBASIC "This program must be run under Win32", // Borland Delphi / C++ Builder (Turbo Linker) "This program must be run under Win64", // . "This program requires Win32", // Legacy Win32 SDK linkers "This is a Windows NT character-mode executable", // Watcom C/C++ "This program was launched in DOS but it requires Windows.", // EuroASM "Win32 only.", // Pellec C ]; isCustomDosPresent = true; // Iterate through messages to check for custom DOS for (var d = 0; d < messages.length && isCustomDosPresent; d++) { if (PE.isSignaturePresent(PE_Cached.dosStubOffset, PE_Cached.dosStubSize, "'" + messages[d] + "'")) { isCustomDosPresent = false; } } if (isCustomDosPresent) { log(logType.any, "Nonstandard DOS stub detected"); } } // Add appropriate option based on DOS presence if (isDosMissing) options = addOption(options, "Missing DOS"); else if (isCustomDosPresent) options = addOption(options, "Custom DOS"); // Checks if the import table is mutated (for example, instead of "kernel32.dll" it is written ".\./kernel32.dll") var isImportTableMutated = false; if (PE.isLibraryPresentExp(/^\.[\\/]\.[\\/]/)) { log(logType.any, "Import library path mutation detected"); isImportTableMutated = true; } if (isImportTableMutated) options = addOption(options, "IAT mutation"); // It works if the file contains an import without an extension (for example, instead of "kernel32.dll" it is written "kernel32"). // Compilers don't do that var isContainsNoExtensionLibrary = false; for (var i = 0; i < PE_Cached.numberOfUnmanagedImports && !isContainsNoExtensionLibrary; i++) { const libraryName = PE.getImportLibraryName(i).toLowerCase(); if (libraryName.length > 4) { if (libraryName[libraryName.length - 4] !== ".") { log(logType.any, "Import library without a filename extension detected: \"" + File.cleanString(libraryName) + "\""); isContainsNoExtensionLibrary = true; } } else { /* if (libraryName.indexOf(".") === -1) */ log(logType.any, "Import library without a filename extension detected: \"" + File.cleanString(libraryName) + "\""); isContainsNoExtensionLibrary = true; } } if (isContainsNoExtensionLibrary) options = addOption(options, "No extension import"); // .exe files in imports are a separate type of sophistication. But this happens. var exeInImports = false; for (var i = 0; i < PE_Cached.numberOfUnmanagedImports && !exeInImports; i++) { const libraryName = PE.getImportLibraryName(i).toLowerCase(); if (libraryName.length > 4) { if (libraryName !== "ntoskrnl.exe" && libraryName.substring(libraryName.length - 4) === ".exe") { log(logType.any, "Executable image referenced as an import: \"" + File.cleanString(libraryName) + "\""); exeInImports = true; } } } if (exeInImports) options = addOption(options, "EXE in imports"); // Checks if application resources can be read or if they are compressed/encrypted var isUnreadableResourcesPresent = false; for (var i = 0; i < PE_Cached.numberOfUnmanagedResources && !isUnreadableResourcesPresent; i++) { if (PE.getResourceOffsetByNumber(i) === -1) { log(logType.any, "One or more resources are not file-backed"); isUnreadableResourcesPresent = true; } } if (isUnreadableResourcesPresent) options = addOption(options, "Unreadable resources"); // Checks is executable application has been compiled or converted to a DLL // Like https://github.com/hasherezade/exe_to_dll var exeAsDll = false; if (PE_Cached.isDynamicLinkLibrary && PE.isExportFunctionPresentExp(/^(?:Start|main|_start|(?:w)?WinMain|EntryPoint)$/)) { log(logType.any, "DLL exports an application-style entry point"); exeAsDll = true; } if (exeAsDll) options = addOption(options, "EXE as DLL"); // The .text section should always come first var isTextSectionNotFirst = false; if (PE_Cached.numberOfSections > 0 && (PE.section[0].Name !== ".text" && PE.section[0].Name !== ".textbss" && PE.section[0].Name !== ".code") && (PE.isSectionNamePresent(".text") || PE.isSectionNamePresent(".textbss"))) { log(logType.any, "Primary code section is not first"); isTextSectionNotFirst = true; } if (isTextSectionNotFirst) options = addOption(options, "Text section is not first"); // If IAT (Import Address Table) is missing or empty var isIatMissing = false, isEmptyIatPresent = false; if (PE_Cached.indexOfImportsSection === -1 && !PE_Cached.isDynamicLinkLibrary) { // DLLs can be resource-only log(logType.any, "Import table is missing"); isIatMissing = true; } else if (PE_Cached.indexOfImportsSection !== -1 && !PE_Cached.numberOfUnmanagedImports) { log(logType.any, "Import table is present but empty"); isEmptyIatPresent = true; } if (isIatMissing) options = addOption(options, "No IAT"); else if (isEmptyIatPresent) options = addOption(options, "Empty IAT"); // If both IAT and EAT are missing (for DLLs only) var isDllIatAndEatMissing = false; if (PE_Cached.isDynamicLinkLibrary && PE_Cached.addressOfUnmanagedEntryPoint && (PE_Cached.indexOfImportsSection === -1 && PE_Cached.indexOfExportsSection === -1)) { // DLLs without imports/exports and with EP (not a resource-only DLL) log(logType.any, "DLL import and export tables are missing"); isDllIatAndEatMissing = true; } if (isDllIatAndEatMissing) options = addOption(options, "No IAT and EAT"); // If EAT (Export Address Table) is missing or empty (for DLLs only) var isDllEatMissing = false, isDllEmptyEatPresent = false; if (!isDllIatAndEatMissing && PE_Cached.isDynamicLinkLibrary) { if (PE_Cached.indexOfExportsSection === -1 && PE_Cached.addressOfUnmanagedEntryPoint) { // DLL without exports and with EP (not a resource-only DLL) log(logType.any, "DLL export table is missing"); isDllEatMissing = true; } else if (PE_Cached.indexOfExportsSection !== -1 && PE_Cached.numberOfUnmanagedExports === 0) { // DLL with empty exports log(logType.any, "DLL export table is present but empty"); isDllEmptyEatPresent = true; } } if (isDllEatMissing) options = addOption(options, "No EAT"); else if (isDllEmptyEatPresent) options = addOption(options, "Empty EAT"); // If the file contains a phantom EAT (Export Address Table) - when the PE header indicates the presence of exports, but there are actually no export entries in the file. // This is a common trick to break some PE parsers and analyzers that rely on the presence of an EAT for certain functionalities. var isPhantomEatPresent = false; if (PE_Cached.numberOfUnmanagedExports === 0 && PE_Cached.indexOfExportsSection !== -1) { log(logType.any, "Export directory has no export entries"); isPhantomEatPresent = true; } if (isPhantomEatPresent) options = addOption(options, "Phantom EAT"); // Check if the entry point starts with NOP or a NOP-like instruction var isStartsWithNop = false; if (PE_Cached.isArchX86) { /* * NOP-equivalent instructions for x86/x64: * * Standard NOP instructions: * - 0x90 -> NOP (standard 1-byte NOP) * - 0x66 0x90 -> XCHG AX, AX (2-byte NOP) * - 0x0F 0x1F 0x00 -> NOP DWORD PTR [EAX/RAX] (3-byte NOP) * - 0x0F 0x1F 0x40 0x00 -> NOP DWORD PTR [EAX/RAX+0x00] (4-byte NOP) * - 0x0F 0x1F 0x44 0x00 0x00 -> NOP DWORD PTR [EAX/RAX+EAX/RAX*1+0x00] (5-byte NOP) * - 0x66 0x0F 0x1F 0x44 0x00 0x00 -> NOP WORD PTR [EAX/RAX+EAX/RAX*1+0x00] (6-byte NOP) * - 0x0F 0x1F 0x80 0x00 0x00 0x00 0x00 -> NOP DWORD PTR [EAX/RAX+0x00000000] (7-byte NOP) * - 0x0F 0x1F 0x84 0x00 0x00 0x00 0x00 0x00 -> NOP DWORD PTR [EAX/RAX+EAX/RAX*1+0x00000000] (8-byte NOP) * - 0x66 0x0F 0x1F 0x84 0x00 0x00 0x00 0x00 0x00 -> NOP WORD PTR [EAX/RAX+EAX/RAX*1+0x00000000] (9-byte NOP) * * Functional equivalents of NOP: * - 0x89 0xF6 -> MOV ESI, ESI (x86) * - 0x48 0x89 0xF6 -> MOV RSI, RSI (x64) * - 0x8D 0x76 0x00 -> LEA ESI, [ESI+0x00] (x86) * - 0x48 0x8D 0x76 0x00 -> LEA RSI, [RSI+0x00] (x64) * - 0x8D 0x74 0x26 0x00 -> LEA ESI, [ESI+EIZ+0x00] (x86) * - 0x48 0x8D 0x74 0x26 0x00 -> LEA RSI, [RSI+RIZ+0x00] (x64) * - 0x87 0xDB -> XCHG EBX, EBX (x86) * - 0x48 0x87 0xDB -> XCHG RBX, RBX (x64) * - 0x87 0xC9 -> XCHG ECX, ECX (x86) * - 0x48 0x87 0xC9 -> XCHG RCX, RCX (x64) * - 0x87 0xD2 -> XCHG EDX, EDX (x86) * - 0x48 0x87 0xD2 -> XCHG RDX, RDX (x64) * - 0x87 0xFF -> XCHG EDI, EDI (x86) * - 0x48 0x87 0xFF -> XCHG RDI, RDI (x64) * - 0x87 0xC0 -> XCHG EAX, EAX (x86) * - 0x48 0x87 0xC0 -> XCHG RAX, RAX (x64) * - 0x8D 0x00 -> LEA EAX, [EAX] (x86) * - 0x48 0x8D 0x00 -> LEA RAX, [RAX] (x64) */ // Universal NOP equivalents (work on both architectures) const nopEquivalentsUniversal = [ "XCHG AX, AX", "XCHG BX, BX", "XCHG CX, CX", "XCHG DX, DX", "XCHG SI, SI", "XCHG DI, DI", "XCHG BP, BP" ]; // Collections of NOP-equivalent instructions based on architecture const nopEquivalents32bit = [ // x86 "MOV ESI, ESI", "XCHG EAX, EAX", "XCHG EBX, EBX", "XCHG ECX, ECX", "XCHG EDX, EDX", "XCHG EDI, EDI", "LEA EAX, [EAX]", "LEA ESI, [ESI]", "LEA ESI, CS:[ESI]" ]; const nopEquivalents64bit = [ // x64 "MOV RSI, RSI", "XCHG RAX, RAX", "XCHG RBX, RBX", "XCHG RCX, RCX", "XCHG RDX, RDX", "XCHG RDI, RDI", "LEA RAX, [RAX]", "LEA RSI, [RSI]", "LEA RSI, CS:[RSI]" ]; // Combine universal and architecture-specific instructions const nopEquivalents = nopEquivalentsUniversal.concat( PE_Cached.is64bit ? nopEquivalents64bit : nopEquivalents32bit ); // First check for standard NOP opcode which covers all NOP variants with different operands if (getFirstEpAsmOpCode() === "NOP" || getFirstEpAsmOpCode() === "FNOP") { isStartsWithNop = true; } else { // Then check for all functional NOP equivalents that have different opcodes for (var i = 0; i < nopEquivalents.length && !isStartsWithNop; i++) { if (PE_Cached.firstEpAsmInstruction === nopEquivalents[i]) { isStartsWithNop = true; } } } } else if (PE_Cached.isARM) { /* * NOP-equivalent instructions for ARM/A32: * Standard NOP instructions: * - 0x00 0xF0 0x20 0xE3 -> NOP (ARM32 explicit) * * Functional equivalents of NOP: * - 0x00 0x00 0xA0 0xE1 -> MOV R0, R0 (ARM32) * - 0x01 0x10 0xA0 0xE1 -> MOV R1, R1 (ARM32) * - 0x02 0x20 0xA0 0xE1 -> MOV R2, R2 (ARM32) * - 0x03 0x30 0xA0 0xE1 -> MOV R3, R3 (ARM32) * - 0x04 0x40 0xA0 0xE1 -> MOV R4, R4 (ARM32) * - 0x05 0x50 0xA0 0xE1 -> MOV R5, R5 (ARM32) * - 0x06 0x60 0xA0 0xE1 -> MOV R6, R6 (ARM32) * - 0x07 0x70 0xA0 0xE1 -> MOV R7, R7 (ARM32) * - 0x08 0x80 0xA0 0xE1 -> MOV R8, R8 (ARM32) * - 0x09 0x90 0xA0 0xE1 -> MOV R9, R9 (ARM32) * - 0x0A 0xA0 0xA0 0xE1 -> MOV R10, R10 (ARM32) * - 0x0B 0xB0 0xA0 0xE1 -> MOV R11, R11 (ARM32) * - 0x0C 0xC0 0xA0 0xE1 -> MOV R12, R12 (ARM32) */ // MOV Rn, Rn is a functional NOP for every A32 general-purpose register. isStartsWithNop = getFirstEpAsmOpCode() === "NOP" || /^MOV R(\d+), R\1$/.test(PE_Cached.firstEpAsmInstruction); } else if (PE_Cached.isThumb) { // Thumb/Thumb-2 also supports MOV Rn, Rn as a functional NOP. isStartsWithNop = getFirstEpAsmOpCode() === "NOP" || /^MOV R(\d+), R\1$/.test(PE_Cached.firstEpAsmInstruction); } else if (PE_Cached.isARM64) { /* * NOP-equivalent instructions for ARM64/AArch64: * - 0x1F 0x20 0x03 0xD5 -> NOP * - 0xE0 0x03 0x00 0xAA -> MOV X0, X0 * - 0xE1 0x03 0x01 0xAA -> MOV X1, X1 * - 0xE2 0x03 0x02 0xAA -> MOV X2, X2 * - 0xE3 0x03 0x03 0xAA -> MOV X3, X3 * - 0xE0 0x03 0x00 0x2A -> MOV W0, W0 * - 0xE1 0x03 0x01 0x2A -> MOV W1, W1 * - 0xE2 0x03 0x02 0x2A -> MOV W2, W2 * - 0xE3 0x03 0x03 0x2A -> MOV W3, W3 */ // MOV Xn, Xn and MOV Wn, Wn are functional NOPs for all integer registers. isStartsWithNop = getFirstEpAsmOpCode() === "NOP" || /^MOV ([XW])(\d+), \1\2$/.test(PE_Cached.firstEpAsmInstruction); } if (isStartsWithNop) { log(logType.emulation, "Instruction with no effect at EP: " + PE_Cached.firstEpAsmInstruction); options = addOption(options, "Nop at EP"); } // Check for NOP instructions mixed with real instructions near the entry point var isNopPaddingPresent = false; if (!isStartsWithNop && (PE_Cached.isArchX86 || PE_Cached.isARM || PE_Cached.isThumb || PE_Cached.isARM64)) { var internalNopCount = 0, pendingNopCount = 0, maxInternalNopRun = 0, isRealInstructionSeen = false, // Keeping both x86 and ARM branch mnemonics here lets the counter remain // architecture-neutral while respecting every terminal control transfer. nopPaddingStopOpCodes = [ "B", "BL", "BLR", "BR", "BRK", "BX", "CALL", "CBNZ", "CBZ", "ERET", "HLT", "HVC", "INT", "INT1", "INT3", "INTO", "IRET", "IRETD", "IRETQ", "JMP", "JMPF", "LOOP", "LOOPE", "LOOPNE", "LOOPNZ", "LOOPZ", "RET", "RETF", "RETN", "SMC", "SVC", "SYSCALL", "SYSENTER", "SYSRET", "SYSEXIT", "TBNZ", "TBZ", "UD2" ]; // Inspect at most the first 64 instructions and do not cross a basic-block boundary. // A pending run is counted only after another real instruction confirms it is internal. for (var i = 0; i < 64 && !isNopPaddingPresent; i++) { var currentInstruction = emulateAndGetAsmInstructionByIndex(i), currentOpCode = getAsmOpCode(currentInstruction); if (!currentOpCode) break; if (currentOpCode === "NOP" || currentOpCode === "FNOP") { if (isRealInstructionSeen) pendingNopCount++; } else { if (pendingNopCount) { internalNopCount += pendingNopCount; if (pendingNopCount > maxInternalNopRun) maxInternalNopRun = pendingNopCount; pendingNopCount = 0; // Two adjacent internal NOPs or three interspersed NOPs are unlikely compiler output. if (maxInternalNopRun >= 2 || internalNopCount >= 3) { log(logType.emulation, "Internal NOP padding near EP: " + internalNopCount + " instruction(s)"); isNopPaddingPresent = true; } } isRealInstructionSeen = true; if (nopPaddingStopOpCodes.indexOf(currentOpCode) !== -1 || /^J[A-Z]+$/.test(currentOpCode) || /^B(?:\.)?(?:EQ|NE|CS|HS|CC|LO|MI|PL|VS|VC|HI|LS|GE|LT|GT|LE)$/.test(currentOpCode)) { break; } } } } if (isNopPaddingPresent) options = addOption(options, "Nop padding near EP"); // Check for "Stack-push address near EP" (CALL $+5) var isAddrToStackTrickPresent = false; if (PE_Cached.isArchX86) { // Check first 30 instructions for "Stack-push address near EP" // This trick uses "CALL $+5" (E8 00 00 00 00) to push next instruction address on stack if (!PE_Cached.isDynamicLinkLibrary) { for (var i = 0; i < 32 && !isAddrToStackTrickPresent; i++) { var currentInstruction = emulateAndGetAsmInstructionByIndex(i), addrToStackOpCode = getAsmOpCode(currentInstruction); if (!currentInstruction) break; // Safety check // Check if instruction is CALL if (addrToStackOpCode === "CALL") { // Get address and offset for this instruction var disasmAddress = PE_Cached.addressOfUnmanagedEntryPoint; for (var j = 0; j < i; j++) { disasmAddress = PE.getDisasmNextAddress(disasmAddress); } var currentOffset = PE.VAToOffset(disasmAddress); // Check if machine code matches E8 00 00 00 00 pattern if (currentOffset !== -1 && PE.compare("E8 00 00 00 00", currentOffset)) { log(logType.emulation, "Stack-push address near EP found at offset: 0x" + currentOffset.toString(16)); isAddrToStackTrickPresent = true; } } // Do not inspect bytes which cannot execute after a hard control-flow transfer. if (/^(?:HLT|INT(?:1|3|O)?|IRET(?:D|Q)?|JMPF?|LOOP(?:E|NE|NZ|Z)?|RET(?:F|N)?|SYS(?:CALL|ENTER|RET|EXIT)|UD2)$/.test(addrToStackOpCode)) break; } } } if (isAddrToStackTrickPresent) options = addOption(options, "Stack-push address near EP"); // The following patterns model x86 register semantics and are consumed only by rules // guarded with isArchX86. EAX/R8D aliases are later canonicalized to RAX/R8 so that // writes through different widths invalidate the same physical register provenance. const epGeneralPurposeRegister = "(?:E(?:AX|BX|CX|DX|SI|DI|BP)|R(?:AX|BX|CX|DX|SI|DI|BP|8D?|9D?|1[0-5]D?))", epFlagsGeneralPurposeRegister = "(?:" + epGeneralPurposeRegister + "|(?:AX|BX|CX|DX|SI|DI|BP)|R(?:8|9|1[0-5])W)", // MOV EDI, EDI is excluded only in I386 because it is the conventional hotpatch // prologue. In PE32+, every 32-bit self-move may intentionally zero the upper half. epRedundantSelfMoveRegister = PE_Cached.isI386 ? "(?:E(?:AX|BX|CX|DX|SI|BP)|(?:AX|BX|CX|DX|SI|DI|BP))" : "(?:(?:AX|BX|CX|DX|SI|DI|BP)|R(?:(?:AX|BX|CX|DX|SI|DI|BP)|(?:8|9|1[0-5])W?))", epRegister32BitPrefixPattern = /^E/, epRegister32BitSuffixPattern = /D$/, epRegister32BitPattern = /^(?:E(?:AX|BX|CX|DX|SI|DI|BP)|R(?:8|9|1[0-5])D)$/i, epRegisterCopyPattern = new RegExp("^MOV (" + epGeneralPurposeRegister + "), (" + epGeneralPurposeRegister + ")$", "i"), epRedundantSelfMovePattern = new RegExp("^MOV (" + epRedundantSelfMoveRegister + "), \\1$", "i"), // Capture 1 belongs to MOV/XCHG and capture 2 to LEA. Consumers deliberately use // match[1] || match[2] to obtain the affected register from either alternative. epSimplePolymorphicInstructionPattern = new RegExp("^(?:MOV|XCHG) (" + epFlagsGeneralPurposeRegister + "), \\1$|^LEA (" + epGeneralPurposeRegister + "), \\[\\2\\]$", "i"), // These operations preserve the destination value while permitting an obfuscator // to vary both instruction spelling and EFLAGS side effects. epValuePreservingInstructionPattern = new RegExp("^(?:ADD|SUB|OR|XOR|SHL|SHR|SAR|ROL|ROR) (" + epFlagsGeneralPurposeRegister + "), (?:0X0+|0+)$|^IMUL (" + epGeneralPurposeRegister + "), \\2, (?:0X0*1|1)$", "i"), epConditionalBranchPattern = /^(J(?!MP)[A-Z]+) (0X[0-9A-F]+|\d+)$/i, epDirectJumpPattern = /^JMP (0X[0-9A-F]+|\d+)$/i, epStackRoundTripPushPattern = new RegExp("^PUSH (" + epFlagsGeneralPurposeRegister + ")$", "i"), epStackRoundTripPopPattern = new RegExp("^POP (" + epFlagsGeneralPurposeRegister + ")$", "i"), epReversibleBinaryPattern = new RegExp("^(XOR|ADD|SUB) (" + epFlagsGeneralPurposeRegister + "), (0X[0-9A-F]+|\\d+)$", "i"), epReversibleUnaryPattern = new RegExp("^(NOT|NEG|BSWAP) (" + epFlagsGeneralPurposeRegister + ")$", "i"), epLiteralMovePattern = new RegExp("^MOV(?:ABS)? (" + epGeneralPurposeRegister + "), (0X[0-9A-F]+|\\d+)$", "i"), epLiteralComparePattern = new RegExp("^CMP (" + epGeneralPurposeRegister + "), (0X[0-9A-F]+|\\d+)$", "i"), epFlagsNeutralRegisterWritePattern = new RegExp("^(?:MOV(?:ABS)?|MOVZX|MOVSX|MOVSXD|LEA) (" + epGeneralPurposeRegister + ")(?:,|$)", "i"), epTrackedRegisterWritePattern = new RegExp("^(?:XOR|ADD|SUB|ADC|SBB|AND|OR|NOT|NEG|INC|DEC|POP) (" + epGeneralPurposeRegister + ")(?:,|$)", "i"), epNeutralInstructionLimit = 4, epMaximumSafeInteger = 0x1FFFFFFFFFFFFF, epComplementaryBranchPairs = [ "JA:JBE", "JAE:JB", "JE:JNE", "JG:JLE", "JGE:JL", "JO:JNO", "JP:JNP", "JS:JNS" ], epCanonicalBranchOpCodes = { "JC": "JB", "JNA": "JBE", "JNAE": "JB", "JNB": "JAE", "JNBE": "JA", "JNC": "JAE", "JNG": "JLE", "JNGE": "JL", "JNL": "JGE", "JNLE": "JG", "JNZ": "JNE", "JPE": "JP", "JPO": "JNP", "JZ": "JE" }, epZeroFlagsAlwaysTakenBranches = [ "JAE", "JNB", "JNC", "JBE", "JNA", "JE", "JZ", "JGE", "JNL", "JLE", "JNG", "JNO", "JNS", "JP", "JPE" ], epZeroFlagsNeverTakenBranches = [ "JA", "JNBE", "JB", "JNAE", "JC", "JG", "JNLE", "JL", "JNGE", "JNE", "JNZ", "JO", "JNP", "JPO", "JS" ], epScanStopOpCodes = [ "CALL", "HLT", "INT", "INT1", "INT3", "INTO", "IRET", "IRETD", "IRETQ", "JMP", "JMPF", "LOOP", "LOOPE", "LOOPNE", "LOOPNZ", "LOOPZ", "RET", "RETF", "RETN", "SYSCALL", "SYSENTER", "SYSRET", "SYSEXIT", "UD2" ]; // Check for a non-standard entry-point transfer through RET. Normal PE startup code // does not return to a caller, while protected stubs use RET to conceal a direct jump. // Supported variants: direct VA, a VA propagated through registers, and a x86 stack pivot. var isSyntheticEpTransferPresent = false; if (!PE_Cached.isDynamicLinkLibrary && PE_Cached.isArchX86) { // Track only statically proven image addresses. Unknown instructions invalidate this // lightweight state, preventing a generic PUSH register; RET sequence from matching. var syntheticEpTargetAddress = String(), syntheticEpTargetType = String(), syntheticEpNeutralInstructionCount = 0, // RET consumes one native pointer. A partial DWORD store in PE32+ cannot prove // the resulting 64-bit return address and must therefore never become a target. syntheticEpStackValueSize = PE_Cached.isI386 ? "DWORD" : "QWORD", syntheticEpStackPointer = PE_Cached.isI386 ? "ESP" : "RSP", syntheticEpStackTransferRegister = PE_Cached.isI386 ? "E(?:AX|BX|CX|DX|SI|DI|BP)" : "R(?:AX|BX|CX|DX|SI|DI|BP|8|9|1[0-5])", // This is a plain object: its dynamically added properties are configurable, // so delete object[key] is valid in strict mode. syntheticEpKnownAddresses = {}, // Compile invariant operand forms once; the state loop consumes the same // architecture-specific grammar for every decoded instruction. syntheticEpRegisterLiteralAddressPattern = new RegExp("^MOV(?:ABS)? (" + epGeneralPurposeRegister + "), (0X[0-9A-F]+|\\d+)$", "i"), syntheticEpPushedRegisterPattern = new RegExp("^PUSH (" + epGeneralPurposeRegister + ")$", "i"), syntheticEpStoredRegisterPattern = new RegExp("^MOV " + syntheticEpStackValueSize + " PTR \\[" + syntheticEpStackPointer + "\\], (" + syntheticEpStackTransferRegister + ")$", "i"), syntheticEpPoppedRegisterPattern = new RegExp("^POP (" + epGeneralPurposeRegister + ")$", "i"), syntheticEpExchangedRegisterPattern = new RegExp("^XCHG " + syntheticEpStackValueSize + " PTR \\[" + syntheticEpStackPointer + "\\], (" + syntheticEpStackTransferRegister + ")$", "i"), syntheticEpStoredAddressPattern = new RegExp("^MOV " + syntheticEpStackValueSize + " PTR \\[" + syntheticEpStackPointer + "\\], (0X[0-9A-F]+|\\d+)$", "i"); // Two related states are tracked independently: known register VAs and the value // currently proven to occupy the top stack slot. RET matches only the latter. // Inspect the whole usual entry stub while using cached linear disassembly. for (var i = 0; i < 128 && !isSyntheticEpTransferPresent; i++) { var currentEpInstruction = emulateAndGetAsmInstructionByIndex(i), currentEpOpCode = getAsmOpCode(currentEpInstruction); if (!currentEpOpCode) break; // Safety check // Extract only register forms whose value can be followed without emulation. var registerLiteralAddress = currentEpInstruction.match(syntheticEpRegisterLiteralAddressPattern), registerCopy = currentEpInstruction.match(epRegisterCopyPattern), pushedRegister = currentEpInstruction.match(syntheticEpPushedRegisterPattern), storedRegister = currentEpInstruction.match(syntheticEpStoredRegisterPattern), poppedRegister = currentEpInstruction.match(syntheticEpPoppedRegisterPattern), exchangedRegister = currentEpInstruction.match(syntheticEpExchangedRegisterPattern), pushedAddress = currentEpInstruction.match(/^PUSH (0X[0-9A-F]+|\d+)$/i), storedAddress = currentEpInstruction.match(syntheticEpStoredAddressPattern), stackPivotAddress = PE_Cached.isI386 ? currentEpInstruction.match(/^MOV ESP, (0X[0-9A-F]+|\d+)$/i) : null, targetAddress = pushedAddress || storedAddress, targetAddressValue = targetAddress ? parseInt(targetAddress[1], 0) : -1, registerWrite = currentEpInstruction.match(epFlagsNeutralRegisterWritePattern) || currentEpInstruction.match(epTrackedRegisterWritePattern), conditionalBranch = currentEpInstruction.match(epConditionalBranchPattern), simplePolymorphicInstruction = currentEpInstruction.match(epSimplePolymorphicInstructionPattern), simplePolymorphicRegister = simplePolymorphicInstruction ? (simplePolymorphicInstruction[1] || simplePolymorphicInstruction[2]) : String(), valuePreservingInstruction = currentEpInstruction.match(epValuePreservingInstructionPattern), isNeutralInstruction = currentEpOpCode === "NOP" || currentEpOpCode === "FNOP" || currentEpOpCode === "PAUSE" || currentEpOpCode === "CLC" || currentEpOpCode === "STC" || currentEpOpCode === "CMC" || (simplePolymorphicInstruction && !(PE_Cached.is64bit && epRegister32BitPattern.test(simplePolymorphicRegister))); if (stackPivotAddress) { // A pivot is accepted only when its new stack contains an image VA. var stackOffset = PE.VAToOffset(parseInt(stackPivotAddress[1], 0)), returnAddress = stackOffset !== -1 ? PE.read_int32(stackOffset) >>> 0 : -1; syntheticEpTargetAddress = String(); syntheticEpTargetType = String(); if (returnAddress !== -1 && PE.VAToOffset(returnAddress) !== -1) { syntheticEpTargetAddress = returnAddress; syntheticEpTargetType = "stack pivot"; syntheticEpNeutralInstructionCount = 0; } } else if (targetAddress && (PE_Cached.isI386 || targetAddressValue <= 0x7FFFFFFF) && PE.VAToOffset(targetAddressValue) !== -1) { // A direct PUSH or a native-width [SP] write proves the next RET target. // AMD64 encodes both forms with a sign-extended imm32, therefore literals // above INT32_MAX cannot denote the unsigned address printed in the operand. syntheticEpTargetAddress = targetAddressValue.toString(); syntheticEpTargetType = pushedAddress ? "push" : "stack write"; syntheticEpNeutralInstructionCount = 0; } else if (registerLiteralAddress && PE.VAToOffset(parseInt(registerLiteralAddress[2], 0)) !== -1) { // MOV reg, establishes a proven target for a later PUSH/MOV/XCHG. syntheticEpKnownAddresses[registerLiteralAddress[1].toUpperCase().replace(epRegister32BitPrefixPattern, "R").replace(epRegister32BitSuffixPattern, String())] = registerLiteralAddress[2]; syntheticEpNeutralInstructionCount = 0; } else if (registerCopy && syntheticEpKnownAddresses[registerCopy[2].toUpperCase().replace(epRegister32BitPrefixPattern, "R").replace(epRegister32BitSuffixPattern, String())]) { // Preserve provenance through a plain register-to-register copy. A 32-bit // destination zero-extends the truncated value, so validate that VA again. const registerCopyDestination = registerCopy[1].toUpperCase().replace(epRegister32BitPrefixPattern, "R").replace(epRegister32BitSuffixPattern, String()); var registerCopySourceAddress = syntheticEpKnownAddresses[registerCopy[2].toUpperCase().replace(epRegister32BitPrefixPattern, "R").replace(epRegister32BitSuffixPattern, String())]; if (epRegister32BitPattern.test(registerCopy[1])) { registerCopySourceAddress = (parseInt(registerCopySourceAddress, 0) >>> 0).toString(); } if (PE.VAToOffset(parseInt(registerCopySourceAddress, 0)) !== -1) { syntheticEpKnownAddresses[registerCopyDestination] = registerCopySourceAddress; } else { delete syntheticEpKnownAddresses[registerCopyDestination]; } syntheticEpNeutralInstructionCount = 0; } else if (poppedRegister && syntheticEpTargetAddress) { // PUSH ; POP reg is a common polymorphic spelling of MOV reg, . // Moving the proof into a register also clears the top-of-stack proof, // because POP has consumed that slot. syntheticEpKnownAddresses[poppedRegister[1].toUpperCase().replace(epRegister32BitPrefixPattern, "R").replace(epRegister32BitSuffixPattern, String())] = syntheticEpTargetAddress; syntheticEpTargetAddress = String(); syntheticEpTargetType = String(); syntheticEpNeutralInstructionCount = 0; } else if (pushedRegister && syntheticEpKnownAddresses[pushedRegister[1].toUpperCase().replace(epRegister32BitPrefixPattern, "R").replace(epRegister32BitSuffixPattern, String())]) { syntheticEpTargetAddress = syntheticEpKnownAddresses[pushedRegister[1].toUpperCase().replace(epRegister32BitPrefixPattern, "R").replace(epRegister32BitSuffixPattern, String())]; syntheticEpTargetType = "register push"; syntheticEpNeutralInstructionCount = 0; } else if (storedRegister && syntheticEpKnownAddresses[storedRegister[1].toUpperCase().replace(epRegister32BitPrefixPattern, "R").replace(epRegister32BitSuffixPattern, String())]) { syntheticEpTargetAddress = syntheticEpKnownAddresses[storedRegister[1].toUpperCase().replace(epRegister32BitPrefixPattern, "R").replace(epRegister32BitSuffixPattern, String())]; syntheticEpTargetType = "register stack write"; syntheticEpNeutralInstructionCount = 0; } else if (exchangedRegister && syntheticEpKnownAddresses[exchangedRegister[1].toUpperCase().replace(epRegister32BitPrefixPattern, "R").replace(epRegister32BitSuffixPattern, String())]) { // XCHG [ESP/RSP], reg replaces the return address without a direct MOV. syntheticEpTargetAddress = syntheticEpKnownAddresses[exchangedRegister[1].toUpperCase().replace(epRegister32BitPrefixPattern, "R").replace(epRegister32BitSuffixPattern, String())]; syntheticEpTargetType = "register stack exchange"; syntheticEpNeutralInstructionCount = 0; } else if (/^RET(?:N)?$/.test(currentEpOpCode) && syntheticEpTargetAddress) { // RET at a process entry point is meaningful here only as a concealed transfer. log(logType.emulation, "Synthetic " + syntheticEpTargetType + "/ret transfer near EP to 0x" + parseInt(syntheticEpTargetAddress, 0).toString(16)); isSyntheticEpTransferPresent = true; } else if (isNeutralInstruction && syntheticEpNeutralInstructionCount < epNeutralInstructionLimit) { // Permit limited stack-preserving junk commonly inserted by protectors. syntheticEpNeutralInstructionCount++; } else if (simplePolymorphicInstruction) { // A PE32+ r32 self-operation zero-extends only its physical register. // Preserve unrelated address proofs and any proven top-of-stack target. var simplePolymorphicRegisterKey = (simplePolymorphicInstruction[1] || simplePolymorphicInstruction[2]).toUpperCase().replace(epRegister32BitPrefixPattern, "R").replace(epRegister32BitSuffixPattern, String()); delete syntheticEpKnownAddresses[simplePolymorphicRegisterKey]; } else if (valuePreservingInstruction) { var valuePreservingRegister = (valuePreservingInstruction[1] || valuePreservingInstruction[2]).toUpperCase(), valuePreservingRegisterKey = valuePreservingRegister.replace(epRegister32BitPrefixPattern, "R").replace(epRegister32BitSuffixPattern, String()), valuePreservingAddress = syntheticEpKnownAddresses[valuePreservingRegisterKey]; if (valuePreservingAddress !== undefined && PE_Cached.is64bit && epRegister32BitPattern.test(valuePreservingRegister)) { valuePreservingAddress = (parseInt(valuePreservingAddress, 0) >>> 0).toString(); if (PE.VAToOffset(parseInt(valuePreservingAddress, 0)) !== -1) { syntheticEpKnownAddresses[valuePreservingRegisterKey] = valuePreservingAddress; } else { delete syntheticEpKnownAddresses[valuePreservingRegisterKey]; } } } else if (registerWrite) { // An unrelated register write cannot invalidate other proven addresses. // Remove only the overwritten register instead of discarding all provenance. delete syntheticEpKnownAddresses[registerWrite[1].toUpperCase().replace(epRegister32BitPrefixPattern, "R").replace(epRegister32BitSuffixPattern, String())]; } else if (conditionalBranch || /^(?:CMP|TEST)$/.test(currentEpOpCode)) { // Register and stack provenance survives on the physical fall-through path. } else if (epScanStopOpCodes.indexOf(currentEpOpCode) !== -1) { break; } else { // Any other instruction can alter stack/register state, so discard provenance. syntheticEpTargetAddress = String(); syntheticEpTargetType = String(); syntheticEpNeutralInstructionCount = 0; syntheticEpKnownAddresses = {}; } } } if (isSyntheticEpTransferPresent) options = addOption(options, "Synthetic EP transfer"); // Check for an opaque conditional branch near the entry point. XOR/SUB/CMP reg, reg // and AND reg, 0 always produce CF=OF=SF=0 and ZF=PF=1, making the following Jcc constant. var isOpaqueEpBranchPresent = false; if (!PE_Cached.isDynamicLinkLibrary && PE_Cached.isArchX86) { const opaqueEpAllOnesFlagsAlwaysTakenBranches = [ "JA", "JNBE", "JAE", "JNB", "JNC", "JNE", "JNZ", "JL", "JNGE", "JLE", "JNG", "JNO", "JS", "JP", "JPE" ], opaqueEpAllOnesFlagsNeverTakenBranches = [ "JB", "JNAE", "JC", "JBE", "JNA", "JE", "JZ", "JG", "JNLE", "JGE", "JNL", "JO", "JNS", "JNP", "JPO" ], opaqueEpCarryClearAlwaysTakenBranches = ["JAE", "JNB", "JNC"], opaqueEpCarryClearNeverTakenBranches = ["JB", "JNAE", "JC"], opaqueEpCarrySetAlwaysTakenBranches = ["JB", "JNAE", "JC"], opaqueEpCarrySetNeverTakenBranches = ["JAE", "JNB", "JNC"], // Match -1 only at the operand's complete width. A shorter mask still fixes // CF/OF but leaves SF dependent on the original value and cannot use this profile. opaqueEpAllOnesPattern = /^(?:OR ((?:AX|BX|CX|DX|SI|DI|BP|R(?:8|9|1[0-5])W)), (?:-1|0X0*FFFF|65535)|OR ((?:E(?:AX|BX|CX|DX|SI|DI|BP)|R(?:8|9|1[0-5])D)), (?:-1|0X0*FFFFFFFF|4294967295)|OR (R(?:AX|BX|CX|DX|SI|DI|BP|8|9|1[0-5])), (?:-1|0X0*(?:FFFFFFFF|FFFFFFFFFFFFFFFF)|4294967295|18446744073709551615))$/i, // These instruction forms are invariant across the entire fall-through scan. opaqueEpZeroFlagsPattern = new RegExp("^(?:(?:XOR|SUB|CMP) (" + epFlagsGeneralPurposeRegister + "), \\1|AND (" + epFlagsGeneralPurposeRegister + "), (?:0X0+|0+)|TEST (" + epFlagsGeneralPurposeRegister + "), (?:0X0+|0+))$", "i"), opaqueEpZeroLiteralPattern = new RegExp("^MOV (" + epGeneralPurposeRegister + "), (?:0X0+|0+)$", "i"), opaqueEpZeroTestPattern = new RegExp("^(?:TEST|OR) (" + epGeneralPurposeRegister + "), \\1$", "i"), opaqueEpRegisterWritePattern = new RegExp("^(?:MOV|MOVZX|MOVSX|LEA|XOR|ADD|SUB|ADC|SBB|AND|OR|NOT|NEG|INC|DEC|POP) (" + epGeneralPurposeRegister + ")(?:,|$)", "i"), opaqueEpSelfMovePattern = new RegExp("^XCHG (" + epGeneralPurposeRegister + "), \\1$", "i"); // Flags-preserving junk is allowed to make the check resilient to light polymorphism. var opaqueEpOriginInstruction = String(), opaqueEpAlwaysTakenBranches = [], opaqueEpNeverTakenBranches = [], opaqueEpNeutralInstructionCount = 0, // Plain-object entries are configurable, therefore strict-mode delete is safe. opaqueEpKnownZeroRegisters = {}; // The state machine keeps two facts: registers proven to contain zero, and the // most recent instruction that established deterministic EFLAGS. A MOV may extend // the first fact without replacing the second because MOV itself preserves flags. // Scan one physical fall-through path. The first control-flow boundary terminates // the proof so flags from one basic block cannot be paired with another block's Jcc. for (var i = 0; i < 256 && !isOpaqueEpBranchPresent; i++) { var currentEpInstruction = emulateAndGetAsmInstructionByIndex(i), currentEpOpCode = getAsmOpCode(currentEpInstruction); if (!currentEpOpCode) break; // Safety check // Split each instruction into flag producers, value-provenance operations, // candidate consumers, and harmless padding before updating either fact set. var zeroFlagsInstruction = currentEpInstruction.match(opaqueEpZeroFlagsPattern), allOnesFlagsInstruction = currentEpInstruction.match(opaqueEpAllOnesPattern), carryFlagsInstruction = currentEpInstruction.match(/^(CLC|STC)$/), zeroLiteralRegister = currentEpInstruction.match(opaqueEpZeroLiteralPattern), zeroTestRegister = currentEpInstruction.match(opaqueEpZeroTestPattern), conditionalBranch = currentEpInstruction.match(epConditionalBranchPattern), registerCopy = currentEpInstruction.match(epRegisterCopyPattern), flagsNeutralRegisterWrite = currentEpInstruction.match(epFlagsNeutralRegisterWritePattern), registerWrite = currentEpInstruction.match(opaqueEpRegisterWritePattern), isNeutralInstruction = currentEpOpCode === "NOP" || currentEpOpCode === "FNOP" || currentEpOpCode === "PAUSE" || opaqueEpSelfMovePattern.test(currentEpInstruction); if (zeroFlagsInstruction) { // A zero result fixes CF, OF, SF, ZF, and PF regardless of the input value. opaqueEpOriginInstruction = currentEpInstruction; opaqueEpAlwaysTakenBranches = epZeroFlagsAlwaysTakenBranches; opaqueEpNeverTakenBranches = epZeroFlagsNeverTakenBranches; opaqueEpNeutralInstructionCount = 0; if (currentEpOpCode !== "CMP" && currentEpOpCode !== "TEST") { // XOR/SUB reg,reg and AND reg,0 also prove the destination value is zero; // CMP reg,reg and TEST reg,0 prove only flags because they do not write. var zeroFlagsRegister = (zeroFlagsInstruction[1] || zeroFlagsInstruction[2]).toUpperCase().replace(epRegister32BitPrefixPattern, "R").replace(epRegister32BitSuffixPattern, String()); opaqueEpKnownZeroRegisters[zeroFlagsRegister] = true; } } else if (allOnesFlagsInstruction) { // OR reg,-1 fixes the same five flags for an all-one result and destroys // any earlier proof that the affected physical register contains zero. var allOnesFlagsRegister = (allOnesFlagsInstruction[1] || allOnesFlagsInstruction[2] || allOnesFlagsInstruction[3]).toUpperCase(); if (/^(?:AX|BX|CX|DX|SI|DI|BP)$/.test(allOnesFlagsRegister)) { allOnesFlagsRegister = "R" + allOnesFlagsRegister; } else { allOnesFlagsRegister = allOnesFlagsRegister.replace(epRegister32BitPrefixPattern, "R").replace(/(?:D|W)$/, String()); } delete opaqueEpKnownZeroRegisters[allOnesFlagsRegister]; opaqueEpOriginInstruction = currentEpInstruction; opaqueEpAlwaysTakenBranches = opaqueEpAllOnesFlagsAlwaysTakenBranches; opaqueEpNeverTakenBranches = opaqueEpAllOnesFlagsNeverTakenBranches; opaqueEpNeutralInstructionCount = 0; } else if (carryFlagsInstruction) { // CLC and STC prove only CF, so accept exclusively carry-dependent branches. opaqueEpOriginInstruction = currentEpInstruction; opaqueEpAlwaysTakenBranches = currentEpOpCode === "CLC" ? opaqueEpCarryClearAlwaysTakenBranches : opaqueEpCarrySetAlwaysTakenBranches; opaqueEpNeverTakenBranches = currentEpOpCode === "CLC" ? opaqueEpCarryClearNeverTakenBranches : opaqueEpCarrySetNeverTakenBranches; opaqueEpNeutralInstructionCount = 0; } else if (zeroLiteralRegister) { // MOV reg, 0 preserves prior flags but enables a later TEST/OR reg, reg. opaqueEpKnownZeroRegisters[zeroLiteralRegister[1].toUpperCase().replace(epRegister32BitPrefixPattern, "R").replace(epRegister32BitSuffixPattern, String())] = true; // A value-propagation instruction still consumes the bounded padding budget // of an older EFLAGS proof; expire that proof without losing known zeroes. if (opaqueEpOriginInstruction && ++opaqueEpNeutralInstructionCount > epNeutralInstructionLimit) { opaqueEpOriginInstruction = String(); opaqueEpNeutralInstructionCount = 0; } } else if (registerCopy) { // Preserve zero provenance only through an exact register-to-register copy. if (opaqueEpKnownZeroRegisters[registerCopy[2].toUpperCase().replace(epRegister32BitPrefixPattern, "R").replace(epRegister32BitSuffixPattern, String())]) { opaqueEpKnownZeroRegisters[registerCopy[1].toUpperCase().replace(epRegister32BitPrefixPattern, "R").replace(epRegister32BitSuffixPattern, String())] = true; } else { // The source is unknown, so only the overwritten destination loses proof. delete opaqueEpKnownZeroRegisters[registerCopy[1].toUpperCase().replace(epRegister32BitPrefixPattern, "R").replace(epRegister32BitSuffixPattern, String())]; } if (opaqueEpOriginInstruction && ++opaqueEpNeutralInstructionCount > epNeutralInstructionLimit) { opaqueEpOriginInstruction = String(); opaqueEpNeutralInstructionCount = 0; } } else if (zeroTestRegister && opaqueEpKnownZeroRegisters[zeroTestRegister[1].toUpperCase().replace(epRegister32BitPrefixPattern, "R").replace(epRegister32BitSuffixPattern, String())]) { // TEST/OR of a proven zero register re-establishes deterministic EFLAGS. opaqueEpOriginInstruction = currentEpInstruction; opaqueEpAlwaysTakenBranches = epZeroFlagsAlwaysTakenBranches; opaqueEpNeverTakenBranches = epZeroFlagsNeverTakenBranches; opaqueEpNeutralInstructionCount = 0; } else if (conditionalBranch && opaqueEpOriginInstruction && PE.VAToOffset(parseInt(conditionalBranch[2], 0)) !== -1 && (opaqueEpAlwaysTakenBranches.indexOf(conditionalBranch[1].toUpperCase()) !== -1 || opaqueEpNeverTakenBranches.indexOf(conditionalBranch[1].toUpperCase()) !== -1)) { var opaqueEpBranchType = opaqueEpAlwaysTakenBranches.indexOf(conditionalBranch[1].toUpperCase()) !== -1 ? "always taken" : "never taken"; log(logType.emulation, "Opaque " + opaqueEpBranchType + " branch near EP: " + opaqueEpOriginInstruction + "; " + currentEpInstruction); isOpaqueEpBranchPresent = true; } else if (conditionalBranch) { // Any other Jcc ends this linear path. Continuing after it could combine // EFLAGS provenance with bytes that are not executed on the same path. break; } else if (flagsNeutralRegisterWrite) { // A flags-neutral write invalidates only its destination, not candidate EFLAGS. // This retains a constant branch even when polymorphic junk changes another register. delete opaqueEpKnownZeroRegisters[flagsNeutralRegisterWrite[1].toUpperCase().replace(epRegister32BitPrefixPattern, "R").replace(epRegister32BitSuffixPattern, String())]; if (opaqueEpOriginInstruction && ++opaqueEpNeutralInstructionCount > epNeutralInstructionLimit) { opaqueEpOriginInstruction = String(); opaqueEpNeutralInstructionCount = 0; } } else if (isNeutralInstruction) { // Pure padding preserves both facts, but only within the configured distance. if (opaqueEpOriginInstruction && ++opaqueEpNeutralInstructionCount > epNeutralInstructionLimit) { opaqueEpOriginInstruction = String(); opaqueEpNeutralInstructionCount = 0; } } else if (registerWrite) { // A flags-changing write destroys both the candidate and this register's value. // Other known-zero registers remain valid until they are individually overwritten. delete opaqueEpKnownZeroRegisters[registerWrite[1].toUpperCase().replace(epRegister32BitPrefixPattern, "R").replace(epRegister32BitSuffixPattern, String())]; opaqueEpOriginInstruction = String(); opaqueEpNeutralInstructionCount = 0; } else if (epScanStopOpCodes.indexOf(currentEpOpCode) !== -1) { // Do not linearly join instructions from a subsequent basic block. break; } else { // Any other instruction may alter EFLAGS or the intended control-flow sequence. opaqueEpOriginInstruction = String(); opaqueEpNeutralInstructionCount = 0; opaqueEpKnownZeroRegisters = {}; } } } if (isOpaqueEpBranchPresent) options = addOption(options, "Opaque EP branch"); // Check for redundant register self-moves used as junk instructions near the entry point. var isRedundantEpRegisterSelfMovePresent = false; if (!PE_Cached.isDynamicLinkLibrary && PE_Cached.isArchX86) { for (var i = 0; i < 32 && !isRedundantEpRegisterSelfMovePresent; i++) { var currentEpInstruction = emulateAndGetAsmInstructionByIndex(i), currentEpOpCode = getAsmOpCode(currentEpInstruction); if (!currentEpOpCode) break; // Safety check if (epRedundantSelfMovePattern.test(currentEpInstruction)) { log(logType.emulation, "Redundant register self-move near EP: " + currentEpInstruction); isRedundantEpRegisterSelfMovePresent = true; } else if (epScanStopOpCodes.indexOf(currentEpOpCode) !== -1 || /^J[A-Z]*$/.test(currentEpOpCode)) { break; } } } if (isRedundantEpRegisterSelfMovePresent) options = addOption(options, "Reg self-move near EP"); // Check for conditional branches whose taken and fall-through paths are equivalent. var isDegenerateEpConditionalBranchPresent = false; if (!PE_Cached.isDynamicLinkLibrary && PE_Cached.isArchX86) { var degenerateEpBranchOpCode = String(), degenerateEpBranchTarget = -1, degenerateEpNeutralInstructionCount = 0, degenerateEpCurrentAddress = PE_Cached.addressOfUnmanagedEntryPoint; for (var i = 0; i < 64 && !isDegenerateEpConditionalBranchPresent; i++) { var currentEpInstruction = emulateAndGetAsmInstructionByIndex(i), currentEpOpCode = getAsmOpCode(currentEpInstruction), currentEpInstructionLength = PE.getDisasmLength(degenerateEpCurrentAddress), fallThroughEpInstructionAddress = degenerateEpCurrentAddress + currentEpInstructionLength, nextEpInstructionAddress = PE.getDisasmNextAddress(degenerateEpCurrentAddress); if (!currentEpOpCode || currentEpInstructionLength <= 0 || nextEpInstructionAddress === -1) break; // Safety check var conditionalBranch = currentEpInstruction.match(epConditionalBranchPattern), directJump = currentEpInstruction.match(epDirectJumpPattern), simplePolymorphicInstruction = currentEpInstruction.match(epSimplePolymorphicInstructionPattern), simplePolymorphicRegister = simplePolymorphicInstruction ? (simplePolymorphicInstruction[1] || simplePolymorphicInstruction[2]) : String(), isSimplePolymorphicInstruction = currentEpOpCode === "NOP" || currentEpOpCode === "FNOP" || currentEpOpCode === "PAUSE" || (simplePolymorphicInstruction && !(PE_Cached.is64bit && epRegister32BitPattern.test(simplePolymorphicRegister))); // One pending Jcc is retained to recognize three equivalent shapes: a branch // to its own fall-through, complementary Jcc instructions with one target, // or a later direct JMP to the pending target. Only bounded neutral junk may // occur between the two encodings, so unrelated control flow is not joined. if (conditionalBranch && currentEpOpCode !== "JMP") { var currentBranchTarget = parseInt(conditionalBranch[2], 0), currentCanonicalBranchOpCode = epCanonicalBranchOpCodes[currentEpOpCode] || currentEpOpCode, previousCanonicalBranchOpCode = epCanonicalBranchOpCodes[degenerateEpBranchOpCode] || degenerateEpBranchOpCode, branchPair = previousCanonicalBranchOpCode + ":" + currentCanonicalBranchOpCode, reverseBranchPair = currentCanonicalBranchOpCode + ":" + previousCanonicalBranchOpCode; if (currentBranchTarget === fallThroughEpInstructionAddress || (currentBranchTarget === degenerateEpBranchTarget && PE.VAToOffset(currentBranchTarget) !== -1 && (epComplementaryBranchPairs.indexOf(branchPair) !== -1 || epComplementaryBranchPairs.indexOf(reverseBranchPair) !== -1))) { log(logType.emulation, "Degenerate conditional branch near EP: " + currentEpInstruction); isDegenerateEpConditionalBranchPresent = true; } else { degenerateEpBranchOpCode = currentEpOpCode; degenerateEpBranchTarget = currentBranchTarget; degenerateEpNeutralInstructionCount = 0; } } else if (directJump) { if (degenerateEpBranchOpCode && degenerateEpBranchTarget === parseInt(directJump[1], 0) && PE.VAToOffset(degenerateEpBranchTarget) !== -1) { log(logType.emulation, "Degenerate conditional branch near EP: " + currentEpInstruction); isDegenerateEpConditionalBranchPresent = true; } break; } else if (isSimplePolymorphicInstruction && degenerateEpBranchOpCode && degenerateEpNeutralInstructionCount < epNeutralInstructionLimit) { // Preserve the pending branch only across instructions that cannot change // its target or introduce a meaningful side effect for this structural check. degenerateEpNeutralInstructionCount++; } else { degenerateEpBranchOpCode = String(); degenerateEpBranchTarget = -1; degenerateEpNeutralInstructionCount = 0; if (epScanStopOpCodes.indexOf(currentEpOpCode) !== -1 || /^J[A-Z]+$/.test(currentEpOpCode)) break; } degenerateEpCurrentAddress = nextEpInstructionAddress; } } if (isDegenerateEpConditionalBranchPresent) options = addOption(options, "Degenerate Jcc near EP"); // Check for a register pushed and restored immediately through simple junk instructions. var isRedundantEpStackRoundTripPresent = false; if (!PE_Cached.isDynamicLinkLibrary && PE_Cached.isArchX86) { var redundantEpPushedRegister = String(), redundantEpStackNeutralInstructionCount = 0; for (var i = 0; i < 64 && !isRedundantEpStackRoundTripPresent; i++) { var currentEpInstruction = emulateAndGetAsmInstructionByIndex(i), currentEpOpCode = getAsmOpCode(currentEpInstruction); if (!currentEpOpCode) break; // Safety check var pushedRegister = currentEpInstruction.match(epStackRoundTripPushPattern), poppedRegister = currentEpInstruction.match(epStackRoundTripPopPattern), isSimplePolymorphicInstruction = currentEpOpCode === "NOP" || currentEpOpCode === "FNOP" || currentEpOpCode === "PAUSE" || epSimplePolymorphicInstructionPattern.test(currentEpInstruction); if (pushedRegister) { // Keep the exact operand spelling: PUSH AX/POP AX is balanced, while mixing // register widths changes both the stack delta and the restored value. redundantEpPushedRegister = pushedRegister[1].toUpperCase(); redundantEpStackNeutralInstructionCount = 0; } else if (poppedRegister && redundantEpPushedRegister === poppedRegister[1].toUpperCase()) { log(logType.emulation, "Redundant stack round-trip near EP: PUSH/POP " + redundantEpPushedRegister); isRedundantEpStackRoundTripPresent = true; } else if (isSimplePolymorphicInstruction && redundantEpPushedRegister && redundantEpStackNeutralInstructionCount < epNeutralInstructionLimit) { redundantEpStackNeutralInstructionCount++; } else { // Any non-neutral instruction makes the PUSH potentially meaningful, so a // later POP must start from a new candidate instead of reusing stale state. redundantEpPushedRegister = String(); redundantEpStackNeutralInstructionCount = 0; if (epScanStopOpCodes.indexOf(currentEpOpCode) !== -1 || /^J[A-Z]+$/.test(currentEpOpCode)) break; } } } if (isRedundantEpStackRoundTripPresent) options = addOption(options, "Redundant Push/Pop near EP"); // Check for nearby inverse operations which restore the original register value. var isReversibleEpJunkPairPresent = false; if (!PE_Cached.isDynamicLinkLibrary && PE_Cached.isArchX86) { var reversibleEpOpCode = String(), reversibleEpRegister = String(), reversibleEpLiteral = String(), reversibleEpNeutralInstructionCount = 0; for (var i = 0; i < 64 && !isReversibleEpJunkPairPresent; i++) { var currentEpInstruction = emulateAndGetAsmInstructionByIndex(i), currentEpOpCode = getAsmOpCode(currentEpInstruction); if (!currentEpOpCode) break; // Safety check var reversibleBinaryInstruction = currentEpInstruction.match(epReversibleBinaryPattern), reversibleUnaryInstruction = currentEpInstruction.match(epReversibleUnaryPattern), simplePolymorphicInstruction = currentEpInstruction.match(epSimplePolymorphicInstructionPattern), simplePolymorphicRegister = simplePolymorphicInstruction ? (simplePolymorphicInstruction[1] || simplePolymorphicInstruction[2]) : String(), isSafeReversiblePadding = currentEpOpCode === "NOP" || currentEpOpCode === "FNOP" || currentEpOpCode === "PAUSE" || (simplePolymorphicInstruction && !(PE_Cached.is64bit && epRegister32BitPattern.test(simplePolymorphicRegister))); // A candidate is valid only when the second operation has the same width, // register and literal. Safely representable literals are parsed so equivalent // hexadecimal and decimal spellings pair; larger values retain their exact text. if (reversibleBinaryInstruction) { var currentReversibleOpCode = reversibleBinaryInstruction[1].toUpperCase(), currentReversibleRegister = reversibleBinaryInstruction[2].toUpperCase(), currentReversibleLiteralValue = parseInt(reversibleBinaryInstruction[3], 0), // Preserve the exact spelling above Number's safe range. This may skip // an equivalent decimal/hex pair, but cannot merge two different imm64s. currentReversibleLiteral = currentReversibleLiteralValue <= epMaximumSafeInteger ? currentReversibleLiteralValue.toString() : reversibleBinaryInstruction[3].toUpperCase(), // A PE32+ r32 write clears the upper half, so the full register state // is not restored even when the low 32-bit operation is reversed. isInverseBinaryPair = !(PE_Cached.is64bit && epRegister32BitPattern.test(currentReversibleRegister)) && reversibleEpRegister === currentReversibleRegister && reversibleEpLiteral === currentReversibleLiteral && ((reversibleEpOpCode === "XOR" && currentReversibleOpCode === "XOR") || (reversibleEpOpCode === "ADD" && currentReversibleOpCode === "SUB") || (reversibleEpOpCode === "SUB" && currentReversibleOpCode === "ADD")); if (isInverseBinaryPair) { log(logType.emulation, "Reversible junk pair near EP: " + reversibleEpOpCode + "/" + currentReversibleOpCode + " " + currentReversibleRegister); isReversibleEpJunkPairPresent = true; } else { reversibleEpOpCode = currentReversibleOpCode; reversibleEpRegister = currentReversibleRegister; reversibleEpLiteral = currentReversibleLiteral; reversibleEpNeutralInstructionCount = 0; } } else if (reversibleUnaryInstruction) { var currentReversibleOpCode = reversibleUnaryInstruction[1].toUpperCase(), currentReversibleRegister = reversibleUnaryInstruction[2].toUpperCase(); if (!(PE_Cached.is64bit && epRegister32BitPattern.test(currentReversibleRegister)) && reversibleEpOpCode === currentReversibleOpCode && reversibleEpRegister === currentReversibleRegister) { log(logType.emulation, "Reversible junk pair near EP: " + currentReversibleOpCode + " " + currentReversibleRegister); isReversibleEpJunkPairPresent = true; } else { reversibleEpOpCode = currentReversibleOpCode; reversibleEpRegister = currentReversibleRegister; reversibleEpLiteral = String(); reversibleEpNeutralInstructionCount = 0; } } else if (isSafeReversiblePadding && reversibleEpOpCode && reversibleEpNeutralInstructionCount < epNeutralInstructionLimit) { // Full-width self-moves on any register and architectural NOP/PAUSE // instructions are globally neutral. PE32+ r32 self-moves are excluded // because their implicit zero-extension changes the physical register. reversibleEpNeutralInstructionCount++; } else { reversibleEpOpCode = String(); reversibleEpRegister = String(); reversibleEpLiteral = String(); reversibleEpNeutralInstructionCount = 0; if (epScanStopOpCodes.indexOf(currentEpOpCode) !== -1 || /^J[A-Z]+$/.test(currentEpOpCode)) break; } } } if (isReversibleEpJunkPairPresent) options = addOption(options, "Reversible pair near EP"); // Check for comparisons against a statically proven identical register literal. var isConstantOpaqueEpComparisonPresent = false; if (!PE_Cached.isDynamicLinkLibrary && PE_Cached.isArchX86) { var constantEpKnownRegisterValues = {}, constantEpComparisonOrigin = String(), constantEpNeutralInstructionCount = 0; // Values use canonical physical-register keys, but truncation is applied whenever // a 32-bit destination is written. This mirrors x86-64 zero-extension and also lets // PE32 reuse the same propagation logic without treating AX as a full-register write. for (var i = 0; i < 128 && !isConstantOpaqueEpComparisonPresent; i++) { var currentEpInstruction = emulateAndGetAsmInstructionByIndex(i), currentEpOpCode = getAsmOpCode(currentEpInstruction); if (!currentEpOpCode) break; // Safety check var literalMove = currentEpInstruction.match(epLiteralMovePattern), registerCopy = currentEpInstruction.match(epRegisterCopyPattern), literalCompare = currentEpInstruction.match(epLiteralComparePattern), conditionalBranch = currentEpInstruction.match(epConditionalBranchPattern), flagsNeutralRegisterWrite = currentEpInstruction.match(epFlagsNeutralRegisterWritePattern), registerWrite = currentEpInstruction.match(epTrackedRegisterWritePattern), simplePolymorphicInstruction = currentEpInstruction.match(epSimplePolymorphicInstructionPattern), valuePreservingInstruction = currentEpInstruction.match(epValuePreservingInstructionPattern), isSimplePolymorphicInstruction = currentEpOpCode === "NOP" || currentEpOpCode === "FNOP" || currentEpOpCode === "PAUSE" || simplePolymorphicInstruction; if (literalCompare) { var comparedRegister = literalCompare[1].toUpperCase().replace(epRegister32BitPrefixPattern, "R").replace(epRegister32BitSuffixPattern, String()), comparedLiteral = parseInt(literalCompare[2], 0), knownComparedLiteral = constantEpKnownRegisterValues[comparedRegister]; if (knownComparedLiteral !== undefined && epRegister32BitPattern.test(literalCompare[1])) knownComparedLiteral = knownComparedLiteral >>> 0; // Restrict the proof to unambiguous non-negative signed 32-bit literals. // This avoids JavaScript precision and x86-64 sign-extension ambiguities. if (comparedLiteral <= 0x7FFFFFFF && knownComparedLiteral === comparedLiteral) { constantEpComparisonOrigin = currentEpInstruction; constantEpNeutralInstructionCount = 0; } else { constantEpComparisonOrigin = String(); constantEpNeutralInstructionCount = 0; } } else if (conditionalBranch) { if (constantEpComparisonOrigin && PE.VAToOffset(parseInt(conditionalBranch[2], 0)) !== -1 && (epZeroFlagsAlwaysTakenBranches.indexOf(conditionalBranch[1].toUpperCase()) !== -1 || epZeroFlagsNeverTakenBranches.indexOf(conditionalBranch[1].toUpperCase()) !== -1)) { log(logType.emulation, "Constant opaque comparison near EP: " + constantEpComparisonOrigin + "; " + currentEpInstruction); isConstantOpaqueEpComparisonPresent = true; } break; } else if (literalMove || registerCopy || flagsNeutralRegisterWrite || isSimplePolymorphicInstruction || valuePreservingInstruction) { // Propagate only operations whose destination value is statically known. // Other flags-neutral writes invalidate their destination without discarding // unrelated register facts accumulated earlier in the linear block. if (literalMove) { var literalMoveRegister = literalMove[1].toUpperCase().replace(epRegister32BitPrefixPattern, "R").replace(epRegister32BitSuffixPattern, String()), literalMoveValue = parseInt(literalMove[2], 0); if (literalMoveValue <= epMaximumSafeInteger) { if (epRegister32BitPattern.test(literalMove[1])) literalMoveValue = literalMoveValue >>> 0; constantEpKnownRegisterValues[literalMoveRegister] = literalMoveValue; } else { // JavaScript Number cannot preserve this literal exactly, so retaining // it could turn a later 32-bit truncation into a false known value. delete constantEpKnownRegisterValues[literalMoveRegister]; } } else if (registerCopy) { var constantEpRegisterCopyDestination = registerCopy[1].toUpperCase().replace(epRegister32BitPrefixPattern, "R").replace(epRegister32BitSuffixPattern, String()), constantEpRegisterCopySource = registerCopy[2].toUpperCase().replace(epRegister32BitPrefixPattern, "R").replace(epRegister32BitSuffixPattern, String()); if (constantEpKnownRegisterValues[constantEpRegisterCopySource] !== undefined) { var registerCopyValue = constantEpKnownRegisterValues[constantEpRegisterCopySource]; if (epRegister32BitPattern.test(registerCopy[1])) registerCopyValue = registerCopyValue >>> 0; constantEpKnownRegisterValues[constantEpRegisterCopyDestination] = registerCopyValue; } else { delete constantEpKnownRegisterValues[constantEpRegisterCopyDestination]; } } else if (simplePolymorphicInstruction) { var simplePolymorphicRegister = (simplePolymorphicInstruction[1] || simplePolymorphicInstruction[2]).toUpperCase(), simplePolymorphicRegisterKey = simplePolymorphicRegister.replace(epRegister32BitPrefixPattern, "R").replace(epRegister32BitSuffixPattern, String()); if (constantEpKnownRegisterValues[simplePolymorphicRegisterKey] !== undefined && epRegister32BitPattern.test(simplePolymorphicRegister)) { constantEpKnownRegisterValues[simplePolymorphicRegisterKey] = constantEpKnownRegisterValues[simplePolymorphicRegisterKey] >>> 0; } } else if (valuePreservingInstruction) { var valuePreservingRegister = (valuePreservingInstruction[1] || valuePreservingInstruction[2]).toUpperCase(), valuePreservingRegisterKey = valuePreservingRegister.replace(epRegister32BitPrefixPattern, "R").replace(epRegister32BitSuffixPattern, String()); if (constantEpKnownRegisterValues[valuePreservingRegisterKey] !== undefined && epRegister32BitPattern.test(valuePreservingRegister)) { constantEpKnownRegisterValues[valuePreservingRegisterKey] = constantEpKnownRegisterValues[valuePreservingRegisterKey] >>> 0; } } else if (flagsNeutralRegisterWrite && !isSimplePolymorphicInstruction) { delete constantEpKnownRegisterValues[flagsNeutralRegisterWrite[1].toUpperCase().replace(epRegister32BitPrefixPattern, "R").replace(epRegister32BitSuffixPattern, String())]; } if (valuePreservingInstruction) { // Value-preserving arithmetic may still replace EFLAGS. Keep register // provenance but require a fresh CMP before accepting a later Jcc. constantEpComparisonOrigin = String(); constantEpNeutralInstructionCount = 0; } else if (constantEpComparisonOrigin && ++constantEpNeutralInstructionCount > epNeutralInstructionLimit) { constantEpComparisonOrigin = String(); constantEpNeutralInstructionCount = 0; } } else if (registerWrite) { delete constantEpKnownRegisterValues[registerWrite[1].toUpperCase().replace(epRegister32BitPrefixPattern, "R").replace(epRegister32BitSuffixPattern, String())]; constantEpComparisonOrigin = String(); constantEpNeutralInstructionCount = 0; } else if (epScanStopOpCodes.indexOf(currentEpOpCode) !== -1) { break; } else { constantEpKnownRegisterValues = {}; constantEpComparisonOrigin = String(); constantEpNeutralInstructionCount = 0; } } } if (isConstantOpaqueEpComparisonPresent) options = addOption(options, "Const Cmp/Jcc near EP"); // Check for manual module enumeration through PEB->Ldr loader lists near the entry point. var isPebModuleWalkPresent = false; if (!PE_Cached.isDynamicLinkLibrary && !PE_Cached.isDriver && PE_Cached.isArchX86 && (PE_Cached.imageSubsystem === 2 || PE_Cached.imageSubsystem === 3)) { const pebModuleWalkNativeRegister = PE_Cached.isI386 ? "E(?:AX|BX|CX|DX|SI|DI|BP)" : "R(?:AX|BX|CX|DX|SI|DI|BP|8|9|1[0-5])", pebModuleWalkSegment = PE_Cached.isI386 ? "FS" : "GS", pebModuleWalkPebAddress = PE_Cached.isI386 ? "(?:0X0*30|48)" : "(?:0X0*60|96)", pebModuleWalkLdrOffset = PE_Cached.isI386 ? "(?:0X0*C|12)" : "(?:0X0*18|24)", pebModuleWalkListOffset = PE_Cached.isI386 ? "(?:0X0*(?:C|14|1C)|12|20|28)" : "(?:0X0*(?:10|20|30)|16|32|48)", pebModuleWalkPebLoadPattern = new RegExp("^MOV (" + pebModuleWalkNativeRegister + "), (?:(?:DWORD|QWORD) PTR )?" + pebModuleWalkSegment + ":\\[" + pebModuleWalkPebAddress + "\\]$", "i"), pebModuleWalkLdrLoadPattern = new RegExp("^MOV (" + pebModuleWalkNativeRegister + "), (?:(?:DWORD|QWORD) PTR )?\\[(" + pebModuleWalkNativeRegister + ") ?\\+ ?" + pebModuleWalkLdrOffset + "\\]$", "i"), pebModuleWalkListLoadPattern = new RegExp("^MOV (" + pebModuleWalkNativeRegister + "), (?:(?:DWORD|QWORD) PTR )?\\[(" + pebModuleWalkNativeRegister + ") ?\\+ ?" + pebModuleWalkListOffset + "\\]$", "i"); // State 1 proves a PEB pointer and state 2 proves its PEB_LDR_DATA pointer. // Copies preserve the proof, while writes invalidate only their physical register. // Unknown instructions discard all proofs but may begin a new chain later. var pebModuleWalkRegisterStates = {}; // Search the bounded EP stream for the ordered PEB -> Ldr -> module-list chain. // Per-register states allow the chain to survive register substitution and copies. for (var i = 0; i < 256 && !isPebModuleWalkPresent; i++) { var currentEpInstruction = emulateAndGetAsmInstructionByIndex(i), currentEpOpCode = getAsmOpCode(currentEpInstruction); if (!currentEpOpCode) break; // Safety check // Decode all state-producing and state-destroying forms before advancing the chain. var pebPointerLoad = currentEpInstruction.match(pebModuleWalkPebLoadPattern), ldrPointerLoad = currentEpInstruction.match(pebModuleWalkLdrLoadPattern), moduleListLoad = currentEpInstruction.match(pebModuleWalkListLoadPattern), registerCopy = currentEpInstruction.match(epRegisterCopyPattern), simplePolymorphicInstruction = currentEpInstruction.match(epSimplePolymorphicInstructionPattern), valuePreservingInstruction = currentEpInstruction.match(epValuePreservingInstructionPattern), registerWrite = currentEpInstruction.match(epFlagsNeutralRegisterWritePattern) || currentEpInstruction.match(epTrackedRegisterWritePattern), conditionalBranch = currentEpInstruction.match(epConditionalBranchPattern); if (pebPointerLoad) { var pebRegister = pebPointerLoad[1].toUpperCase().replace(epRegister32BitPrefixPattern, "R").replace(epRegister32BitSuffixPattern, String()); // State 1 marks an architecture-width pointer read directly from FS/GS. pebModuleWalkRegisterStates[pebRegister] = 1; } else if (moduleListLoad && pebModuleWalkRegisterStates[moduleListLoad[2].toUpperCase().replace(epRegister32BitPrefixPattern, "R").replace(epRegister32BitSuffixPattern, String())] === 2) { // Reading a loader list through a proven Ldr pointer completes the proof. log(logType.emulation, "PEB loader module-list walk near EP: " + currentEpInstruction); isPebModuleWalkPresent = true; } else if (ldrPointerLoad) { var ldrDestinationRegister = ldrPointerLoad[1].toUpperCase().replace(epRegister32BitPrefixPattern, "R").replace(epRegister32BitSuffixPattern, String()), ldrSourceRegister = ldrPointerLoad[2].toUpperCase().replace(epRegister32BitPrefixPattern, "R").replace(epRegister32BitSuffixPattern, String()); // Promote only a destination loaded through a proven PEB pointer to state 2. if (pebModuleWalkRegisterStates[ldrSourceRegister] === 1) { pebModuleWalkRegisterStates[ldrDestinationRegister] = 2; } else { delete pebModuleWalkRegisterStates[ldrDestinationRegister]; } } else if (moduleListLoad) { // An unproven list-shaped load overwrites its destination with unknown data. delete pebModuleWalkRegisterStates[moduleListLoad[1].toUpperCase().replace(epRegister32BitPrefixPattern, "R").replace(epRegister32BitSuffixPattern, String())]; } else if (registerCopy) { var pebModuleWalkCopyDestination = registerCopy[1].toUpperCase().replace(epRegister32BitPrefixPattern, "R").replace(epRegister32BitSuffixPattern, String()), pebModuleWalkCopySource = registerCopy[2].toUpperCase().replace(epRegister32BitPrefixPattern, "R").replace(epRegister32BitSuffixPattern, String()); // A PE32+ r32 destination destroys a 64-bit PEB/Ldr pointer by zero-extension. if (PE_Cached.is64bit && epRegister32BitPattern.test(registerCopy[1])) { delete pebModuleWalkRegisterStates[pebModuleWalkCopyDestination]; } else if (pebModuleWalkRegisterStates[pebModuleWalkCopySource] !== undefined) { pebModuleWalkRegisterStates[pebModuleWalkCopyDestination] = pebModuleWalkRegisterStates[pebModuleWalkCopySource]; } else { delete pebModuleWalkRegisterStates[pebModuleWalkCopyDestination]; } } else if (simplePolymorphicInstruction || valuePreservingInstruction) { var pebModuleWalkPreservingRegister = (simplePolymorphicInstruction ? (simplePolymorphicInstruction[1] || simplePolymorphicInstruction[2]) : (valuePreservingInstruction[1] || valuePreservingInstruction[2])).toUpperCase(), pebModuleWalkPreservingRegisterKey = pebModuleWalkPreservingRegister.replace(epRegister32BitPrefixPattern, "R").replace(epRegister32BitSuffixPattern, String()); if (PE_Cached.is64bit && epRegister32BitPattern.test(pebModuleWalkPreservingRegister)) { delete pebModuleWalkRegisterStates[pebModuleWalkPreservingRegisterKey]; } } else if (currentEpOpCode === "NOP" || currentEpOpCode === "FNOP" || currentEpOpCode === "PAUSE" || currentEpOpCode === "CLC" || currentEpOpCode === "STC" || currentEpOpCode === "CMC" || conditionalBranch || /^(?:CMP|TEST)$/.test(currentEpOpCode)) { // These instructions preserve every proven pointer on the fall-through path. } else if (registerWrite) { // A recognized write invalidates only the overwritten physical register. delete pebModuleWalkRegisterStates[registerWrite[1].toUpperCase().replace(epRegister32BitPrefixPattern, "R").replace(epRegister32BitSuffixPattern, String())]; } else { // Unmodelled semantics may alter every tracked pointer; restart the chain. pebModuleWalkRegisterStates = {}; if (epScanStopOpCodes.indexOf(currentEpOpCode) !== -1 && currentEpOpCode !== "CALL") break; } } } if (isPebModuleWalkPresent) options = addOption(options, "PEB module walk near EP"); // Check for the x87 environment trick used by 32-bit shellcode to recover its EIP. var isFpuEpRecoveryPresent = false; if (!PE_Cached.isDynamicLinkLibrary && !PE_Cached.isDriver && PE_Cached.isI386 && (PE_Cached.imageSubsystem === 2 || PE_Cached.imageSubsystem === 3)) { var isFpuEnvironmentStored = false, fpuEpNeutralInstructionCount = 0, fpuEpCurrentAddress = PE_Cached.addressOfUnmanagedEntryPoint; // In the 28-byte x87 environment, the saved instruction pointer begins at // offset 0xC. Writing the environment at ESP-0xC therefore places EIP at // the current stack top, where a following POP can recover it. // Advance by physical instruction length because raw x87 encodings may be // rendered inconsistently by the disassembler used by different DiE builds. for (var i = 0; i < 256 && !isFpuEpRecoveryPresent; i++) { var currentEpInstruction = PE.getDisasmString(fpuEpCurrentAddress), currentEpOpCode = getAsmOpCode(currentEpInstruction), currentEpInstructionLength = PE.getDisasmLength(fpuEpCurrentAddress), currentEpOffset = PE.VAToOffset(fpuEpCurrentAddress), // Match the two exact encodings of FNSTENV [ESP-0xC] without depending on // textual x87 decoding, then advance over the complete instruction atomically. storesRawFpuEnvironmentAtStack = currentEpOffset !== -1 && (PE.compare("D9 74 24 F4", currentEpOffset) || PE.compare("D9 B4 24 F4 FF FF FF", currentEpOffset)), isRawFpuNop = currentEpOffset !== -1 && PE.compare("D9 D0", currentEpOffset); if (!currentEpOpCode || currentEpInstructionLength <= 0) break; // Safety check if (storesRawFpuEnvironmentAtStack) { // Override the decoded length with the exact short or long raw encoding. currentEpInstructionLength = PE.compare("D9 74 24 F4", currentEpOffset) ? 4 : 7; } else if (isRawFpuNop) { // Treat FNOP bytes atomically even when their textual decoding differs. currentEpInstructionLength = 2; } var storesFpuEnvironmentAtStack = /^(?:FSTENV|FNSTENV) (?:(?:BYTE|WORD|DWORD|QWORD|TBYTE|FWORD) PTR )?\[ESP ?- ?(?:0X0*C|12)\]$/i.test(currentEpInstruction), popsNativeRegister = /^POP E(?:AX|BX|CX|DX|SI|DI|BP)$/i.test(currentEpInstruction), registerWrite = currentEpInstruction.match(epFlagsNeutralRegisterWritePattern) || currentEpInstruction.match(epTrackedRegisterWritePattern), isNeutralInstruction = currentEpOpCode === "NOP" || currentEpOpCode === "FNOP" || isRawFpuNop || currentEpOpCode === "PAUSE" || epSimplePolymorphicInstructionPattern.test(currentEpInstruction) || (registerWrite && currentEpOpCode !== "POP") || /^(?:CMP|TEST)$/.test(currentEpOpCode); if (storesFpuEnvironmentAtStack || storesRawFpuEnvironmentAtStack) { // Remember that the current stack top now contains the saved x87 EIP field. isFpuEnvironmentStored = true; fpuEpNeutralInstructionCount = 0; } else if (popsNativeRegister && isFpuEnvironmentStored) { // A native-width POP consumes the saved EIP and completes the recovery idiom. log(logType.emulation, "FPU EIP recovery near EP: " + currentEpInstruction); isFpuEpRecoveryPresent = true; } else if (isNeutralInstruction && isFpuEnvironmentStored && fpuEpNeutralInstructionCount < epNeutralInstructionLimit) { // Register-only junk cannot move the stack slot containing the saved EIP. fpuEpNeutralInstructionCount++; } else { // A stack-affecting or unknown instruction invalidates the saved-slot proof. isFpuEnvironmentStored = false; fpuEpNeutralInstructionCount = 0; if (epScanStopOpCodes.indexOf(currentEpOpCode) !== -1 && currentEpOpCode !== "CALL") break; } // Continue the physical fall-through sweep using the validated instruction size. fpuEpCurrentAddress += currentEpInstructionLength; } } if (isFpuEpRecoveryPresent) options = addOption(options, "FPU EIP recovery"); // Check for instructions that cannot normally execute in a user-mode process. var isPrivilegedEpOpCodePresent = false; var privilegedEpOpCodeDetected = String(); if (!PE_Cached.isDynamicLinkLibrary && !PE_Cached.isDriver && PE_Cached.isArchX86 && (PE_Cached.imageSubsystem === 2 || PE_Cached.imageSubsystem === 3)) { const privilegedEpOpCodes = [ "CLAC", "CLGI", "CLI", "CLTS", "HLT", "IN", "INSB", "INSD", "INSW", "INVD", "INVLPG", "INVLPGA", "LGDT", "LIDT", "LLDT", "LMSW", "LTR", "OUT", "OUTSB", "OUTSD", "OUTSW", "RDMSR", "RSM", "SKINIT", "STAC", "STGI", "STI", "SWAPGS", "VMCALL", "VMCLEAR", "VMLAUNCH", "VMLOAD", "VMPTRLD", "VMPTRST", "VMREAD", "VMRESUME", "VMRUN", "VMSAVE", "VMWRITE", "VMXOFF", "VMXON", "WBINVD", "WRMSR", "XSETBV" ]; // Sweep only the bounded user-mode startup path; the first privileged operation // is sufficient because its concrete opcode is preserved in the verdict. for (var i = 0; i < 256 && !isPrivilegedEpOpCodePresent; i++) { var currentEpInstruction = emulateAndGetAsmInstructionByIndex(i), currentEpOpCode = getAsmOpCode(currentEpInstruction); if (!currentEpOpCode) break; // Safety check // MOV requires operand inspection because ordinary register moves are legal, // while any access to CR0-CR8 is privileged in a user-mode image. var isControlRegisterAccess = currentEpOpCode === "MOV" && /\bCR[0-8]\b/i.test(currentEpInstruction); if (privilegedEpOpCodes.indexOf(currentEpOpCode) !== -1 || isControlRegisterAccess) { log(logType.emulation, "Privileged instruction near user-mode EP: " + currentEpInstruction); privilegedEpOpCodeDetected = currentEpOpCode; isPrivilegedEpOpCodePresent = true; } else if (epScanStopOpCodes.indexOf(currentEpOpCode) !== -1 && currentEpOpCode !== "CALL") { // Do not decode bytes beyond a terminal transfer as the same startup path. break; } } } if (isPrivilegedEpOpCodePresent) options = addOption(options, "Privileged op " + privilegedEpOpCodeDetected[0] + privilegedEpOpCodeDetected.substring(1).toLowerCase() + " near EP"); // Check for descriptor-table and machine-status probes traditionally used by anti-VM code. var isSystemEpProbePresent = false; if (!PE_Cached.isDynamicLinkLibrary && !PE_Cached.isDriver && PE_Cached.isArchX86 && (PE_Cached.imageSubsystem === 2 || PE_Cached.imageSubsystem === 3)) { const systemEpProbeOpCodes = ["SGDT", "SIDT", "SLDT", "SMSW", "STR"]; // These non-privileged probes are distinctive only while they remain close to the // process entry path, so stop the bounded sweep at a terminal transfer. for (var i = 0; i < 256 && !isSystemEpProbePresent; i++) { var currentEpInstruction = emulateAndGetAsmInstructionByIndex(i), currentEpOpCode = getAsmOpCode(currentEpInstruction); if (!currentEpOpCode) break; // Safety check if (systemEpProbeOpCodes.indexOf(currentEpOpCode) !== -1) { // One descriptor-table or machine-status read is a complete probe proof. log(logType.emulation, "System-state probe near EP: " + currentEpInstruction); isSystemEpProbePresent = true; } else if (epScanStopOpCodes.indexOf(currentEpOpCode) !== -1 && currentEpOpCode !== "CALL") { // Calls may return to the fall-through path; other stop opcodes may not. break; } } } if (isSystemEpProbePresent) options = addOption(options, "System probe near EP"); // Check for a direct transition from a regular user-mode entry stub into the kernel. var isDirectEpSystemCallPresent = false; if (!PE_Cached.isDynamicLinkLibrary && !PE_Cached.isDriver && PE_Cached.isArchX86 && (PE_Cached.imageSubsystem === 2 || PE_Cached.imageSubsystem === 3)) { // Scan the bounded startup stream for kernel-entry instructions emitted directly // by the image rather than reached through an imported operating-system wrapper. for (var i = 0; i < 256 && !isDirectEpSystemCallPresent; i++) { var currentEpInstruction = emulateAndGetAsmInstructionByIndex(i), currentEpOpCode = getAsmOpCode(currentEpInstruction); if (!currentEpOpCode) break; // Safety check if (currentEpOpCode === "SYSCALL" || currentEpOpCode === "SYSENTER" || /^INT (?:0X0*2E|46)$/i.test(currentEpInstruction)) { // Cover native x64/x86 fast calls and the legacy Windows INT 2E gateway. log(logType.emulation, "Direct system call near EP: " + currentEpInstruction); isDirectEpSystemCallPresent = true; } else if (epScanStopOpCodes.indexOf(currentEpOpCode) !== -1 && currentEpOpCode !== "CALL") { // A terminal transfer ends the startup stream before unrelated bytes. break; } } } if (isDirectEpSystemCallPresent) options = addOption(options, "Direct syscall near EP"); // Check whether a direct branch enters the middle of another decoded instruction. var isOverlappingEpCodePresent = false; if (!PE_Cached.isDynamicLinkLibrary && !PE_Cached.isDriver && PE_Cached.isArchX86 && (PE_Cached.imageSubsystem === 2 || PE_Cached.imageSubsystem === 3)) { var overlappingEpInstructionStarts = {}, overlappingEpBranchTargets = [], overlappingEpCurrentAddress = PE_Cached.addressOfUnmanagedEntryPoint, overlappingEpScannedEndAddress = overlappingEpCurrentAddress; // This rule deliberately performs a physical linear sweep instead of following // getDisasmNextAddress(). Every decoded byte belongs to exactly one instruction in // the fall-through stream; a direct target inside one of those byte ranges proves // an alternate overlapping decode. Targets outside the bounded sweep are ignored. // Phase one records every canonical instruction boundary and every valid direct // control-flow target encountered in the same physical byte stream. for (var i = 0; i < 256; i++) { var currentEpInstruction = PE.getDisasmString(overlappingEpCurrentAddress), currentEpOpCode = getAsmOpCode(currentEpInstruction), currentEpInstructionLength = PE.getDisasmLength(overlappingEpCurrentAddress); if (!currentEpOpCode || currentEpInstructionLength <= 0) break; // Safety check // Mark the start before advancing so a later target can be compared exactly. overlappingEpInstructionStarts[overlappingEpCurrentAddress.toString()] = true; overlappingEpScannedEndAddress = overlappingEpCurrentAddress + currentEpInstructionLength; var directControlFlow = currentEpInstruction.match(/^(?:CALL|J[A-Z]+|LOOP(?:E|NE|NZ|Z)?) (0X[0-9A-F]+|\d+)$/i); if (directControlFlow) { var directControlFlowTarget = parseInt(directControlFlow[1], 0); // Ignore external and unmapped targets because their byte boundaries are unknown. if (PE.VAToOffset(directControlFlowTarget) !== -1) { overlappingEpBranchTargets.push(directControlFlowTarget); } } // Calls and conditional jumps have a valid fall-through path. Terminal and // unconditional transfers end the linear byte stream before embedded data. if (epScanStopOpCodes.indexOf(currentEpOpCode) !== -1 && currentEpOpCode !== "CALL") break; // Move physically to the byte following the instruction, never to its branch target. overlappingEpCurrentAddress = overlappingEpScannedEndAddress; } // Phase two reports only targets inside the scanned interval which do not coincide // with any recorded instruction start; such a target necessarily overlaps bytes. for (var i = 0; i < overlappingEpBranchTargets.length && !isOverlappingEpCodePresent; i++) { var overlappingEpBranchTarget = overlappingEpBranchTargets[i]; if (overlappingEpBranchTarget >= PE_Cached.addressOfUnmanagedEntryPoint && overlappingEpBranchTarget < overlappingEpScannedEndAddress && !overlappingEpInstructionStarts[overlappingEpBranchTarget.toString()]) { log(logType.emulation, "Overlapping branch target near EP: 0x" + overlappingEpBranchTarget.toString(16)); isOverlappingEpCodePresent = true; } } } if (isOverlappingEpCodePresent) options = addOption(options, "Overlapping code near EP"); // Check for an image address materialized in a register and used as an indirect transfer. var isIndirectEpTransferPresent = false; if (!PE_Cached.isDynamicLinkLibrary && !PE_Cached.isDriver && PE_Cached.isArchX86 && (PE_Cached.imageSubsystem === 2 || PE_Cached.imageSubsystem === 3)) { const indirectEpTransferPattern = new RegExp("^(JMP|CALL) (" + epGeneralPurposeRegister + ")$", "i"), indirectEpRegisterWritePattern = new RegExp("^(?:MOV(?:ABS)?|MOVZX|MOVSX|MOVSXD|LEA|XOR|ADD|SUB|ADC|SBB|AND|OR|NOT|NEG|INC|DEC|POP) (" + epGeneralPurposeRegister + ")(?:,|$)", "i"); // Multiple proven addresses are retained because polymorphic junk commonly writes // unrelated registers between materialization and transfer. Every write invalidates // only its physical destination; calls and unknown instructions discard all proofs. var indirectEpKnownAddresses = {}; // Follow address provenance through the bounded EP stream until an indirect Call/Jmp // consumes it or a control-flow boundary makes the linear proof ambiguous. for (var i = 0; i < 256 && !isIndirectEpTransferPresent; i++) { var currentEpInstruction = emulateAndGetAsmInstructionByIndex(i), currentEpOpCode = getAsmOpCode(currentEpInstruction); if (!currentEpOpCode) break; // Safety check // Classify producers, copies, consumers and invalidators before mutating provenance. var registerLiteralAddress = currentEpInstruction.match(epLiteralMovePattern), registerCopy = currentEpInstruction.match(epRegisterCopyPattern), indirectTransfer = currentEpInstruction.match(indirectEpTransferPattern), registerWrite = currentEpInstruction.match(indirectEpRegisterWritePattern), conditionalBranch = currentEpInstruction.match(epConditionalBranchPattern), simplePolymorphicInstruction = currentEpInstruction.match(epSimplePolymorphicInstructionPattern), simplePolymorphicRegister = simplePolymorphicInstruction ? (simplePolymorphicInstruction[1] || simplePolymorphicInstruction[2]) : String(), valuePreservingInstruction = currentEpInstruction.match(epValuePreservingInstructionPattern), isNeutralInstruction = currentEpOpCode === "NOP" || currentEpOpCode === "FNOP" || currentEpOpCode === "PAUSE" || (simplePolymorphicInstruction && !(PE_Cached.is64bit && epRegister32BitPattern.test(simplePolymorphicRegister))); if (registerLiteralAddress) { var indirectEpTargetRegister = registerLiteralAddress[1].toUpperCase().replace(epRegister32BitPrefixPattern, "R").replace(epRegister32BitSuffixPattern, String()), indirectEpTargetAddress = parseInt(registerLiteralAddress[2], 0); // Reject imprecise JavaScript integers before applying architectural truncation. if (indirectEpTargetAddress <= epMaximumSafeInteger) { if (epRegister32BitPattern.test(registerLiteralAddress[1])) indirectEpTargetAddress = indirectEpTargetAddress >>> 0; // Retain only literals which resolve back into the current PE image. if (PE.VAToOffset(indirectEpTargetAddress) !== -1) { indirectEpKnownAddresses[indirectEpTargetRegister] = indirectEpTargetAddress; } else { delete indirectEpKnownAddresses[indirectEpTargetRegister]; } } else { delete indirectEpKnownAddresses[indirectEpTargetRegister]; } } else if (registerCopy) { // Follow one exact copy of the materialized VA. A PE32+ r32 destination // truncates and zero-extends the address, so validate the transformed VA. var indirectEpTargetRegister = registerCopy[1].toUpperCase().replace(epRegister32BitPrefixPattern, "R").replace(epRegister32BitSuffixPattern, String()), indirectEpSourceRegister = registerCopy[2].toUpperCase().replace(epRegister32BitPrefixPattern, "R").replace(epRegister32BitSuffixPattern, String()), indirectEpTargetAddress = indirectEpKnownAddresses[indirectEpSourceRegister]; if (indirectEpTargetAddress !== undefined) { if (epRegister32BitPattern.test(registerCopy[1])) indirectEpTargetAddress = indirectEpTargetAddress >>> 0; if (PE.VAToOffset(indirectEpTargetAddress) !== -1) { indirectEpKnownAddresses[indirectEpTargetRegister] = indirectEpTargetAddress; } else { delete indirectEpKnownAddresses[indirectEpTargetRegister]; } } else { delete indirectEpKnownAddresses[indirectEpTargetRegister]; } } else if (indirectTransfer) { var indirectTransferRegister = indirectTransfer[2].toUpperCase().replace(epRegister32BitPrefixPattern, "R").replace(epRegister32BitSuffixPattern, String()); var indirectEpTargetAddress = indirectEpKnownAddresses[indirectTransferRegister]; // A mapped target consumed through its proven register completes the proof. if (indirectEpTargetAddress !== undefined && PE.VAToOffset(indirectEpTargetAddress) !== -1) { log(logType.emulation, "Indirect " + indirectTransfer[1].toUpperCase() + " transfer near EP to 0x" + indirectEpTargetAddress.toString(16)); isIndirectEpTransferPresent = true; } // Any indirect transfer ends the physical path, even when its target is unknown. break; } else if (isNeutralInstruction || conditionalBranch || /^(?:CMP|TEST)$/.test(currentEpOpCode)) { // These instructions preserve every general-purpose register on the // physical fall-through path, so all proven image addresses remain valid. } else if (simplePolymorphicInstruction) { // Only PE32+ r32 self-operations reach this branch. They zero-extend one // physical register but cannot invalidate addresses proven in other registers. delete indirectEpKnownAddresses[simplePolymorphicRegister.toUpperCase().replace(epRegister32BitPrefixPattern, "R").replace(epRegister32BitSuffixPattern, String())]; } else if (valuePreservingInstruction) { var valuePreservingRegister = (valuePreservingInstruction[1] || valuePreservingInstruction[2]).toUpperCase(), valuePreservingRegisterKey = valuePreservingRegister.replace(epRegister32BitPrefixPattern, "R").replace(epRegister32BitSuffixPattern, String()), valuePreservingAddress = indirectEpKnownAddresses[valuePreservingRegisterKey]; if (valuePreservingAddress !== undefined && PE_Cached.is64bit && epRegister32BitPattern.test(valuePreservingRegister)) { // Revalidate the zero-extended low DWORD before retaining its provenance. valuePreservingAddress = valuePreservingAddress >>> 0; if (PE.VAToOffset(valuePreservingAddress) !== -1) { indirectEpKnownAddresses[valuePreservingRegisterKey] = valuePreservingAddress; } else { delete indirectEpKnownAddresses[valuePreservingRegisterKey]; } } } else if (registerWrite) { // A modeled write invalidates only the destination and preserves other proofs. delete indirectEpKnownAddresses[registerWrite[1].toUpperCase().replace(epRegister32BitPrefixPattern, "R").replace(epRegister32BitSuffixPattern, String())]; } else { // Unknown semantics may consume or transform any address, so restart tracking. indirectEpKnownAddresses = {}; if (epScanStopOpCodes.indexOf(currentEpOpCode) !== -1 && currentEpOpCode !== "CALL") break; } } } if (isIndirectEpTransferPresent) options = addOption(options, "Indirect EP transfer"); // Check for native trap-flag inspection or deliberate single-step activation near EP. var isEpTrapFlagCheckPresent = false; if (!PE_Cached.isDynamicLinkLibrary && !PE_Cached.isDriver && PE_Cached.isArchX86 && (PE_Cached.imageSubsystem === 2 || PE_Cached.imageSubsystem === 3)) { const trapFlagNativeRegister = PE_Cached.isI386 ? "E(?:AX|BX|CX|DX|SI|DI|BP)" : "R(?:AX|BX|CX|DX|SI|DI|BP|8|9|1[0-5])", trapFlagStackPointer = PE_Cached.isI386 ? "ESP" : "RSP", trapFlagPushPattern = PE_Cached.isI386 ? /^(?:PUSHF|PUSHFD)$/ : /^(?:PUSHF|PUSHFQ)$/, trapFlagPopPattern = PE_Cached.isI386 ? /^(?:POPF|POPFD)$/ : /^(?:POPF|POPFQ)$/, trapFlagPopRegisterPattern = new RegExp("^POP (" + trapFlagNativeRegister + ")$", "i"), trapFlagRegisterTestPattern = new RegExp("^(?:TEST|AND) (" + epGeneralPurposeRegister + "), (?:0X0*100|256)$|^BT (" + epGeneralPurposeRegister + "), (?:0X0*8|8)$", "i"), trapFlagStackSetPattern = new RegExp("^(?:OR|XOR) (?:(?:DWORD|QWORD) PTR )?\\[" + trapFlagStackPointer + "\\], (?:0X0*100|256)$", "i"), trapFlagBitTestBranchOpCodes = ["JAE", "JB", "JC", "JNAE", "JNB", "JNC"], trapFlagValueTestBranchOpCodes = ["JE", "JNE", "JNZ", "JZ"]; // State 1 tracks flags saved on the stack; state 2 follows the matching // native-width POP; state 3 waits for a condition compatible with the // TEST/AND or BT flags; state 4 tracks direct TF modification on the stack. var trapFlagState = 0, trapFlagRegister = String(), trapFlagTestOpCode = String(), trapFlagNeutralInstructionCount = 0; // Walk the bounded startup path and accept only one complete flags-save sequence. for (var i = 0; i < 256 && !isEpTrapFlagCheckPresent; i++) { var currentEpInstruction = emulateAndGetAsmInstructionByIndex(i), currentEpOpCode = getAsmOpCode(currentEpInstruction); if (!currentEpOpCode) break; // Safety check // Determine which instruction advances, preserves, or invalidates the active state. var poppedFlagsRegister = currentEpInstruction.match(trapFlagPopRegisterPattern), testedFlagsRegister = currentEpInstruction.match(trapFlagRegisterTestPattern), conditionalBranch = currentEpInstruction.match(epConditionalBranchPattern), simplePolymorphicInstruction = currentEpInstruction.match(epSimplePolymorphicInstructionPattern), valuePreservingInstruction = currentEpInstruction.match(epValuePreservingInstructionPattern), flagsNeutralRegisterWrite = currentEpInstruction.match(epFlagsNeutralRegisterWritePattern), registerWrite = flagsNeutralRegisterWrite || currentEpInstruction.match(epTrackedRegisterWritePattern), registerWriteKey = registerWrite ? registerWrite[1].toUpperCase().replace(epRegister32BitPrefixPattern, "R").replace(epRegister32BitSuffixPattern, String()) : String(), testedFlagsRegisterKey = testedFlagsRegister ? (testedFlagsRegister[1] || testedFlagsRegister[2]).toUpperCase().replace(epRegister32BitPrefixPattern, "R").replace(epRegister32BitSuffixPattern, String()) : String(), isRegisterOnlyInstruction = registerWrite && currentEpOpCode !== "POP"; var isTrapFlagStatePreservingInstruction = ((trapFlagState === 1 || trapFlagState === 4) && (isRegisterOnlyInstruction || /^(?:CMP|TEST)$/.test(currentEpOpCode))) || (trapFlagState === 2 && ((isRegisterOnlyInstruction && registerWriteKey !== trapFlagRegister) || valuePreservingInstruction)) || (trapFlagState === 3 && flagsNeutralRegisterWrite); var isStatePreservingInstruction = currentEpOpCode === "NOP" || currentEpOpCode === "FNOP" || currentEpOpCode === "PAUSE" || simplePolymorphicInstruction || isTrapFlagStatePreservingInstruction; var isTrapFlagTestedRegister = trapFlagState === 2 && testedFlagsRegister && trapFlagRegister === testedFlagsRegisterKey; var isTrapFlagBranchCompatible = trapFlagState === 3 && conditionalBranch && ( (trapFlagTestOpCode === "BT" && trapFlagBitTestBranchOpCodes.indexOf(currentEpOpCode) !== -1) || (trapFlagTestOpCode !== "BT" && trapFlagValueTestBranchOpCodes.indexOf(currentEpOpCode) !== -1) ); if (trapFlagPushPattern.test(currentEpOpCode)) { // State 1: EFLAGS/RFLAGS has been copied to the current stack top. trapFlagState = 1; trapFlagRegister = String(); trapFlagTestOpCode = String(); trapFlagNeutralInstructionCount = 0; } else if (trapFlagState === 1 && trapFlagStackSetPattern.test(currentEpInstruction)) { // State 4: the saved stack value has had the Trap Flag bit toggled on. trapFlagState = 4; trapFlagNeutralInstructionCount = 0; } else if (trapFlagState === 4 && trapFlagPopPattern.test(currentEpOpCode)) { // Restoring the modified flags deliberately enables single-step execution. log(logType.emulation, "Trap flag enabled near EP: " + currentEpInstruction); isEpTrapFlagCheckPresent = true; } else if (trapFlagState === 1 && poppedFlagsRegister) { // State 2: preserve the complete flags value in a native-width register. trapFlagRegister = poppedFlagsRegister[1].toUpperCase().replace(epRegister32BitPrefixPattern, "R").replace(epRegister32BitSuffixPattern, String()); trapFlagState = 2; trapFlagNeutralInstructionCount = 0; } else if (isTrapFlagTestedRegister) { // State 3: the same saved value is tested specifically for bit 8 / 0x100. trapFlagState = 3; trapFlagTestOpCode = currentEpOpCode; trapFlagNeutralInstructionCount = 0; } else if (isTrapFlagBranchCompatible) { // Accept only branch families which consume the flags produced by BT or TEST/AND. log(logType.emulation, "Trap flag checked near EP: " + currentEpInstruction); isEpTrapFlagCheckPresent = true; } else if (isStatePreservingInstruction && trapFlagState && trapFlagNeutralInstructionCount < epNeutralInstructionLimit) { // Before the explicit TEST/BT, flag-changing junk on unrelated registers // is harmless. Afterwards only instructions that preserve EFLAGS are safe. trapFlagNeutralInstructionCount++; } else { // Any incompatible instruction breaks the sequence but permits a later restart. trapFlagState = 0; trapFlagRegister = String(); trapFlagTestOpCode = String(); trapFlagNeutralInstructionCount = 0; if (epScanStopOpCodes.indexOf(currentEpOpCode) !== -1 && currentEpOpCode !== "CALL") break; } } } if (isEpTrapFlagCheckPresent) options = addOption(options, "Trap flag check near EP"); // Check for dense instruction-level polymorphism in the reachable entry-point code. // A single bounded CFG walk keeps instruction collection independent from individual // verdicts. Direct calls and conditional branches retain both possible continuations; // unconditional jumps retain only their target, and terminal instructions end the path. var hasWritableExecutableSection = false; if (PE_Cached.isDynamicLinkLibrary) { // DLL startup is normally compiler-generated. Admit it to this expensive pass only // when writable executable memory provides a concrete self-modification prerequisite. for (var i = 0; i < PE_Cached.numberOfSections && !hasWritableExecutableSection; i++) { if ((PE.section[i].Characteristics & (SECTION_FLAGS_EXECUTE | SECTION_FLAGS_WRITE)) === (SECTION_FLAGS_EXECUTE | SECTION_FLAGS_WRITE)) { hasWritableExecutableSection = true; } } } // Collect orthogonal behavioral facts during one CFG walk. Separate counters let the // verdict rules below combine only the evidence relevant to their own FP threshold. const epPolymorphismBranchRequiredCount = 10, epPolymorphismCallRequiredCount = 2, epPolymorphismPushRequiredCount = 11, epPolymorphismStackImbalanceRequiredCount = 4, epPolymorphismTransformRequiredCount = 7, epPolymorphismTransformKindRequiredCount = 5, epPolymorphismDensityRequiredCount = 30, epInvalidBranchSourceMaximumDistance = 0x100, epRedundantPrefixSourceMaximumDistance = 0x1000, epRedundantPrefixRequiredInstructionCount = 2, epRedundantPrefixImmediateCount = 4; var epNoncanonicalShiftCount = 0, epStackMutationCount = 0, epComplexStackMutationCount = 0, epRareBitOpCount = 0, epPolymorphismBranchCount = 0, epPolymorphismBackwardControlFlowCount = 0, epPolymorphismCallCount = 0, epPolymorphismPushCount = 0, epPolymorphismPopCount = 0, epPolymorphismTransformCount = 0, epPolymorphismTransformKindCount = 0, epPolymorphismDominantTransformCount = 0, epBitstreamAdcCount = 0, epBitstreamMovsbCount = 0, epRedundantPrefixInstructionCount = 0, epMaximumRedundantPrefixCount = 0, epCallPopIpRecoveryAddress = -1, epSelfModifyingCallStubAddress = -1, epInvalidBranchSourceAddress = -1, epInvalidBranchTargetAddress = -1, epFlagsTransferPresent = false, epRolPresent = false, epRorPresent = false, epAdcPresent = false, epBitstreamIndirectTransferPresent = false, epConflictingPrefixPresent = false; var epInvalidBranchTargetOpCode = String(); // Count generic self-modifying call stubs per section; one loose match is insufficient. var epRepeatedSelfModifyingCallStubCountBySection = {}; if ((!PE_Cached.isDynamicLinkLibrary || hasWritableExecutableSection) && !PE_Cached.isDriver && PE_Cached.isArchX86 && (PE_Cached.imageSubsystem === 2 || PE_Cached.imageSubsystem === 3)) { // Global and local limits bound the reachable graph and every secondary proof. // Patterns describe semantics rather than concrete registers or byte sequences. const epPolymorphismInstructionLimit = 256, epPolymorphismImageSize = PE.getImageOptionalHeader("SizeOfImage"), epCallPopIpRecoveryLookaheadLimit = 16, epSelfModifyingCallStubLookaheadLimit = 16, epSelfModifyingCallStubNeutralInstructionLimit = 2, epRepeatedSelfModifyingCallStubLookaheadLimit = 24, epRepeatedSelfModifyingCallStubRequiredCount = 3, epRepeatedSelfModifyingCallStubMaximumWriteDistance = 0x100, epShiftRotatePattern = /^(?:SAL|SHL|SHR|SAR|ROL|ROR|RCL|RCR) (.+), (0X[0-9A-F]+|\d+)$/i, epStackMutationPattern = /^(ADD|SUB|ADC|SBB|AND|OR|XOR|INC|DEC|NEG|NOT|SAL|SHL|SHR|SAR|ROL|ROR|RCL|RCR|XADD|XCHG) (?:(?:BYTE|WORD|DWORD|QWORD) PTR )?(?:[A-Z]+:)?\[[^\]]*\b(?:ESP|RSP)\b[^\]]*\](?:,|$)/i, epPopGeneralPurposeRegisterPattern = new RegExp("^POP (" + epGeneralPurposeRegister + ")$", "i"), epBitstreamGeneralPurposeRegister = "(?:[ABCD][HL]|BPL|SIL|DIL|R(?:8|9|1[0-5])B|" + epFlagsGeneralPurposeRegister + ")", epBitstreamAdcPattern = new RegExp("^ADC (" + epBitstreamGeneralPurposeRegister + "), \\1$", "i"), epByteRegisterPattern = /^(?:[ABCD][HL]|SPL|BPL|SIL|DIL|R(?:8|9|1[0-5])B)$/i, epWordRegisterPattern = /^(?:AX|BX|CX|DX|SP|BP|SI|DI|R(?:8|9|1[0-5])W)$/i, epDwordRegisterPattern = /^(?:E(?:AX|BX|CX|DX|SP|BP|SI|DI)|R(?:8|9|1[0-5])D)$/i, epQwordRegisterPattern = /^(?:R(?:AX|BX|CX|DX|SP|BP|SI|DI|8|9|1[0-5]))$/i, epSelfModifyingCallStubNativePointer = PE_Cached.isI386 ? "DWORD" : "QWORD", epSelfModifyingCallStubNativeAccumulator = PE_Cached.isI386 ? "EAX" : "RAX", epSelfModifyingCallStubReturnStorePattern = new RegExp("^POP " + epSelfModifyingCallStubNativePointer + " PTR (?:[A-Z]+:)?\\[[^\\]]+\\]$", "i"), epSelfModifyingCallStubAccumulatorPushPattern = new RegExp("^PUSH " + epSelfModifyingCallStubNativeAccumulator + "$", "i"), epSelfModifyingCallStubKeyLoadPattern = /^MOV EAX, (?:(?:DWORD PTR )?(?:[A-Z]+:)?\[[^\]]+\]|0X[0-9A-F]+|\d+)$/i, epSelfModifyingCallStubDecodePattern = /^(?:LEA EAX, \[EAX (?:\+|-) (?:0X[0-9A-F]+|\d+)\]|(?:ADD|SUB|XOR) EAX, (?:0X[0-9A-F]+|\d+))$/i, epSelfModifyingCallStubWritePattern = /^XCHG DWORD PTR (?:[A-Z]+:)?\[(?:RIP\s*([+-])\s*(0X[0-9A-F]+|\d+)|(0X[0-9A-F]+|\d+))\], EAX$/i, epSelfModifyingCallStubAccumulatorPopPattern = new RegExp("^POP " + epSelfModifyingCallStubNativeAccumulator + "$", "i"), epRepeatedSelfModifyingCallStubNativeRegister = PE_Cached.isI386 ? "E(?:AX|BX|CX|DX|SI|DI|BP)" : "R(?:AX|BX|CX|DX|SI|DI|BP|8|9|1[0-5])", epRepeatedSelfModifyingCallStubStackPointer = PE_Cached.isI386 ? "ESP" : "RSP", epRepeatedSelfModifyingCallStubReturnReadPattern = new RegExp("^(?:POP (?:" + epSelfModifyingCallStubNativePointer + " PTR (?:[A-Z]+:)?\\[[^\\]]+\\]|" + epRepeatedSelfModifyingCallStubNativeRegister + ")|(?:MOV|LEA) " + epRepeatedSelfModifyingCallStubNativeRegister + ", (?:" + epSelfModifyingCallStubNativePointer + " PTR )?(?:[A-Z]+:)?\\[" + epRepeatedSelfModifyingCallStubStackPointer + "(?:\\s*[+-]\\s*(?:0X[0-9A-F]+|\\d+))?\\])$", "i"), epRepeatedSelfModifyingCallStubTransformPattern = new RegExp("^(?:ADD|ADC|SUB|SBB|XOR|AND|OR|NOT|NEG|ROL|ROR|RCL|RCR|LEA|MOVZX|MOVSX|MOVSXD|BSWAP) (" + epGeneralPurposeRegister + ")(?:,|$)", "i"), epRepeatedSelfModifyingCallStubWritePattern = new RegExp("^(?:MOV|XCHG|CMPXCHG) (?:BYTE|WORD|DWORD|QWORD) PTR (?:[A-Z]+:)?\\[(?:RIP\\s*([+-])\\s*(0X[0-9A-F]+|\\d+)|(0X[0-9A-F]+|\\d+))\\], (" + epGeneralPurposeRegister + ")$", "i"), epComplexStackMutationOpCodes = [ "NEG", "NOT", "SAL", "SHL", "SHR", "SAR", "ROL", "ROR", "RCL", "RCR", "XADD", "XCHG" ], epRareBitOpCodes = [ "ADC", "SBB", "ROL", "ROR", "BT", "BTC", "BTR", "BTS", "BSWAP", "XADD" ], // These uncommon transformations are safe only as one component of the compound // flow profile. Requiring repetition avoids treating ordinary low-level code as junk. epPolymorphismTransformOpCodes = [ "AAA", "AAD", "AAM", "AAS", "ADC", "BSF", "BSR", "BSWAP", "BT", "BTC", "BTR", "BTS", "CMC", "DAA", "DAS", "LAHF", "RCL", "RCR", "ROL", "ROR", "SAHF", "SAL", "SHLD", "SHRD", "STC", "XADD", "XCHG" ]; // The FIFO starts at EP. Visited addresses prevent loops, while the two call maps // retain only facts needed later by Call/Pop and self-modifying-stub validation. var epPolymorphismPendingAddresses = [PE_Cached.addressOfUnmanagedEntryPoint], epPolymorphismPendingAddressIndex = 0, epPolymorphismVisitedAddresses = {}, epPolymorphismVisitedInstructionCount = 0, epPolymorphismTransformOpCodesFound = {}, epRareBitOpCodesFound = {}, epDecodedInstructionLengthByAddress = {}, epDirectCallTargets = {}, epPopRegisterByAddress = {}, epInvalidBranchCandidates = [], epRedundantPrefixCandidates = []; // A statically proven synthetic Ret is an unconditional branch in disguise. Continue // from its image target so the shared CFG sees the real decoder behind the EP facade. if (isSyntheticEpTransferPresent && syntheticEpTargetAddress) { epPolymorphismPendingAddresses.push(parseInt(syntheticEpTargetAddress, 0)); } // Consume one reachable address at a time until the graph or global budget is exhausted. while (epPolymorphismPendingAddressIndex < epPolymorphismPendingAddresses.length && epPolymorphismVisitedInstructionCount < epPolymorphismInstructionLimit) { var epPolymorphismCurrentAddress = epPolymorphismPendingAddresses[epPolymorphismPendingAddressIndex++], epPolymorphismAddressKey = epPolymorphismCurrentAddress.toString(), epPolymorphismCurrentRva = epPolymorphismCurrentAddress - PE_Cached.imageBase, epPolymorphismSectionIndex = -1; // Skip loops and addresses which have no file-backed VA mapping. if (epPolymorphismVisitedAddresses[epPolymorphismAddressKey] || PE.VAToOffset(epPolymorphismCurrentAddress) === -1) continue; // VAToOffset alone does not prove that the address contains executable physical // bytes. Resolve the section explicitly to avoid decoding data or virtual padding. for (var d = 0; d < PE_Cached.numberOfSections && epPolymorphismSectionIndex === -1; d++) { var epPolymorphismSectionRva = PE.getSectionVirtualAddress(d), epPolymorphismSectionRawSize = PE.getSectionFileSize(d); if (epPolymorphismCurrentRva >= epPolymorphismSectionRva && epPolymorphismCurrentRva < epPolymorphismSectionRva + epPolymorphismSectionRawSize && (PE.section[d].Characteristics & SECTION_FLAGS_EXECUTE)) { epPolymorphismSectionIndex = d; } } if (epPolymorphismSectionIndex === -1) continue; // Mark an address only after its executable physical section has been proven. epPolymorphismVisitedAddresses[epPolymorphismAddressKey] = true; epPolymorphismVisitedInstructionCount++; var epPolymorphismInstruction = PE.getDisasmString(epPolymorphismCurrentAddress), epPolymorphismOpCode = getAsmOpCode(epPolymorphismInstruction), epPolymorphismInstructionLength = PE.getDisasmLength(epPolymorphismCurrentAddress); // Inspect the encoded instruction prefix groups at the already proven physical // boundary. A compiler may need one prefix from each group, but repeated members // are ignored or overridden by the CPU and therefore expose generated padding. var epPolymorphismInstructionOffset = PE.VAToOffset(epPolymorphismCurrentAddress), epPolymorphismSectionFileEnd = PE.getSectionFileOffset(epPolymorphismSectionIndex) + PE.getSectionFileSize(epPolymorphismSectionIndex), epCurrentPrefixCount = 0, epCurrentPrefixGroupOneCount = 0, epCurrentPrefixGroupThreeCount = 0, epCurrentPrefixGroupFourCount = 0, epCurrentRexPrefixCount = 0, epCurrentGroupOnePrefix = -1, epCurrentConflictingPrefixPresent = false, epCurrentRedundantPrefixPresent = false; var epCurrentGroupTwoPrefixesFound = {}; // Some disassemblers normalize repeated equal prefixes out of the reported // instruction length. Read the architectural prefix limit directly instead. for (var p = 0; p < 14 && epPolymorphismInstructionOffset + p < epPolymorphismSectionFileEnd; p++) { var epCurrentPrefixByte = PE.readByte(epPolymorphismInstructionOffset + p); if (epCurrentPrefixByte === 0xF0 || epCurrentPrefixByte === 0xF2 || epCurrentPrefixByte === 0xF3) { if (epCurrentGroupOnePrefix !== -1 && epCurrentGroupOnePrefix !== epCurrentPrefixByte) { epCurrentConflictingPrefixPresent = true; } epCurrentGroupOnePrefix = epCurrentPrefixByte; epCurrentPrefixGroupOneCount++; epCurrentRedundantPrefixPresent = epCurrentRedundantPrefixPresent || epCurrentPrefixGroupOneCount > 1; } else if (epCurrentPrefixByte === 0x2E || epCurrentPrefixByte === 0x36 || epCurrentPrefixByte === 0x3E || epCurrentPrefixByte === 0x26 || epCurrentPrefixByte === 0x64 || epCurrentPrefixByte === 0x65) { var epCurrentGroupTwoPrefixKey = epCurrentPrefixByte.toString(); // Different segment overrides may be printable inline data after a Call. // Repeating the same override remains unambiguous redundant encoding. if (epCurrentGroupTwoPrefixesFound[epCurrentGroupTwoPrefixKey]) { epCurrentRedundantPrefixPresent = true; } else { epCurrentGroupTwoPrefixesFound[epCurrentGroupTwoPrefixKey] = true; } } else if (epCurrentPrefixByte === 0x66) { epCurrentPrefixGroupThreeCount++; epCurrentRedundantPrefixPresent = epCurrentRedundantPrefixPresent || epCurrentPrefixGroupThreeCount > 1; } else if (epCurrentPrefixByte === 0x67) { epCurrentPrefixGroupFourCount++; epCurrentRedundantPrefixPresent = epCurrentRedundantPrefixPresent || epCurrentPrefixGroupFourCount > 1; } else if (PE_Cached.isAMD64 && epCurrentPrefixByte >= 0x40 && epCurrentPrefixByte <= 0x4F) { epCurrentRexPrefixCount++; epCurrentRedundantPrefixPresent = epCurrentRedundantPrefixPresent || epCurrentRexPrefixCount > 1; } else { break; } epCurrentPrefixCount++; } // Save raw candidates until every decoded boundary is known. Linkers deliberately // encode long Nop instructions with repeated 66 prefixes for alignment. if (epCurrentRedundantPrefixPresent && epPolymorphismOpCode !== "NOP" && Math.abs(epPolymorphismCurrentAddress - PE_Cached.addressOfUnmanagedEntryPoint) <= epRedundantPrefixSourceMaximumDistance) { epRedundantPrefixCandidates.push({ Address: epPolymorphismCurrentAddress, PrefixCount: epCurrentPrefixCount, IsConflicting: epCurrentConflictingPrefixPresent }); } // Prefix evidence remains useful even when the disassembler rejects an otherwise // valid instruction because of deliberately excessive encoding. Semantic analysis // below still requires a decoded opcode and a positive instruction length. if (!epPolymorphismOpCode || epPolymorphismInstructionLength <= 0) continue; epDecodedInstructionLengthByAddress[epPolymorphismAddressKey] = epPolymorphismInstructionLength; // Count explicit stack traffic. Calls are kept separate because their implicit // return-address push has normal compiler semantics and must not inflate Push. if (epPolymorphismOpCode === "PUSH") epPolymorphismPushCount++; if (epPolymorphismOpCode === "POP") epPolymorphismPopCount++; // Repeated uncommon transforms distinguish generated decoder/dispatcher code from // a compiler startup routine. Their diversity separates polymorphism from a fixed // decompressor which repeats only a few instruction families. if (epPolymorphismTransformOpCodes.indexOf(epPolymorphismOpCode) !== -1) { epPolymorphismTransformCount++; if (!epPolymorphismTransformOpCodesFound[epPolymorphismOpCode]) { epPolymorphismTransformOpCodesFound[epPolymorphismOpCode] = 0; epPolymorphismTransformKindCount++; } epPolymorphismTransformOpCodesFound[epPolymorphismOpCode]++; if (epPolymorphismTransformOpCodesFound[epPolymorphismOpCode] > epPolymorphismDominantTransformCount) { epPolymorphismDominantTransformCount = epPolymorphismTransformOpCodesFound[epPolymorphismOpCode]; } } // Count every reachable call spelling, including indirect dispatcher calls. if (epPolymorphismOpCode === "CALL") epPolymorphismCallCount++; // Conditional, unconditional and loop branches all contribute to CFG fragmentation. if (/^(?:J[A-Z]+|LOOP(?:E|NE|NZ|Z)?)$/.test(epPolymorphismOpCode)) { epPolymorphismBranchCount++; } // Keep both sides of Call/Pop pairs because a branch may make the target appear // before its call in the worklist. A match is accepted only after both addresses // have independently passed the executable, physically-backed section check. var epPopGeneralPurposeRegister = epPolymorphismInstruction.match(epPopGeneralPurposeRegisterPattern); if (epPopGeneralPurposeRegister) { // Record the exact native register used to recover a possible return address. epPopRegisterByAddress[epPolymorphismAddressKey] = epPopGeneralPurposeRegister[1].toUpperCase(); } // Count the two independent primitives required by the bitstream-unpacker rule. if (epBitstreamAdcPattern.test(epPolymorphismInstruction)) epBitstreamAdcCount++; if (/^REP MOVSB(?: |$)/i.test(epPolymorphismInstruction)) epBitstreamMovsbCount++; var epShiftRotate = epPolymorphismInstruction.match(epShiftRotatePattern); if (epShiftRotate) { var epShiftDestination = epShiftRotate[1], epShiftOperandWidth = 0; // Resolve operand width from either an explicit memory size or register class. if (/^BYTE PTR /i.test(epShiftDestination) || epByteRegisterPattern.test(epShiftDestination)) { epShiftOperandWidth = 8; } else if (/^WORD PTR /i.test(epShiftDestination) || epWordRegisterPattern.test(epShiftDestination)) { epShiftOperandWidth = 16; } else if (/^DWORD PTR /i.test(epShiftDestination) || epDwordRegisterPattern.test(epShiftDestination)) { epShiftOperandWidth = 32; } else if (/^QWORD PTR /i.test(epShiftDestination) || epQwordRegisterPattern.test(epShiftDestination)) { epShiftOperandWidth = 64; } // x86 masks immediate shift/rotate counts. Encoding a count at least as wide // as the operand is therefore a deliberately noncanonical spelling which // ordinary compilers normalize to the equivalent smaller immediate. if (epShiftOperandWidth && parseInt(epShiftRotate[2], 0) >= epShiftOperandWidth) { epNoncanonicalShiftCount++; } } var epStackMutation = epPolymorphismInstruction.match(epStackMutationPattern); if (epStackMutation) { // Count all arithmetic writes through SP and separately count uncommon transforms. epStackMutationCount++; if (epComplexStackMutationOpCodes.indexOf(epStackMutation[1].toUpperCase()) !== -1) { epComplexStackMutationCount++; } } // Flag-transfer and bit-operation facts jointly describe a dense bit mixer. if (/^(?:PUSHF|PUSHFD|PUSHFQ|POPF|POPFD|POPFQ|LAHF|SAHF)$/.test(epPolymorphismOpCode)) { epFlagsTransferPresent = true; } epRolPresent = epRolPresent || epPolymorphismOpCode === "ROL"; epRorPresent = epRorPresent || epPolymorphismOpCode === "ROR"; epAdcPresent = epAdcPresent || epPolymorphismOpCode === "ADC"; if (epRareBitOpCodes.indexOf(epPolymorphismOpCode) !== -1 && !epRareBitOpCodesFound[epPolymorphismOpCode]) { epRareBitOpCodesFound[epPolymorphismOpCode] = true; epRareBitOpCount++; } var epPolymorphismNextAddress = epPolymorphismCurrentAddress + epPolymorphismInstructionLength, epPolymorphismDirectControlFlow = epPolymorphismInstruction.match(/^(CALL|J[A-Z]+|LOOP(?:E|NE|NZ|Z)?) (0X[0-9A-F]+|\d+)$/i); // An unresolved Call/Jmp supplies the final dispatcher fact for bitstream unpacking. if (!epPolymorphismDirectControlFlow && (epPolymorphismOpCode === "CALL" || epPolymorphismOpCode === "JMP" || epPolymorphismOpCode === "JMPF")) { epBitstreamIndirectTransferPresent = true; } if (epPolymorphismDirectControlFlow) { var epPolymorphismTargetAddress = parseInt(epPolymorphismDirectControlFlow[2], 0); // A direct branch into unmapped, virtual-only or non-executable bytes is not // compiler control flow. Calls are excluded because this rule should not make // assumptions about unusual external or loader-provided call destinations. if (epPolymorphismOpCode !== "CALL") { var epPolymorphismTargetRva = epPolymorphismTargetAddress - PE_Cached.imageBase, isEpPolymorphismTargetExecutable = false; for (var d = 0; d < PE_Cached.numberOfSections && !isEpPolymorphismTargetExecutable; d++) { var epPolymorphismTargetSectionRva = PE.getSectionVirtualAddress(d), epPolymorphismTargetSectionRawSize = PE.getSectionFileSize(d); if (epPolymorphismTargetRva >= epPolymorphismTargetSectionRva && epPolymorphismTargetRva < epPolymorphismTargetSectionRva + epPolymorphismTargetSectionRawSize && (PE.section[d].Characteristics & SECTION_FLAGS_EXECUTE) && PE.VAToOffset(epPolymorphismTargetAddress) !== -1) { isEpPolymorphismTargetExecutable = true; } } const isEpPolymorphismTargetWithinImage = epPolymorphismTargetRva >= 0 && epPolymorphismTargetRva < epPolymorphismImageSize, isEpPolymorphismBranchNearEntryPoint = Math.abs( epPolymorphismCurrentAddress - PE_Cached.addressOfUnmanagedEntryPoint ) <= epInvalidBranchSourceMaximumDistance; // An internal invalid target is loader-visible proof at any reachable depth. // A target beyond the image additionally requires a spatially near startup // branch, filtering distant false instruction boundaries in ordinary code. if (!isEpPolymorphismTargetExecutable && (isEpPolymorphismTargetWithinImage || isEpPolymorphismBranchNearEntryPoint)) { epInvalidBranchCandidates.push({ SourceAddress: epPolymorphismCurrentAddress, TargetAddress: epPolymorphismTargetAddress, OpCode: epPolymorphismOpCode }); } } // Direct backward calls and branches both form dispatcher return edges. if (epPolymorphismTargetAddress < epPolymorphismCurrentAddress) { epPolymorphismBackwardControlFlowCount++; } if (epPolymorphismOpCode === "CALL") { var epPolymorphismTargetKey = epPolymorphismTargetAddress.toString(); // Preserve every reachable direct call target for secondary validation. epDirectCallTargets[epPolymorphismTargetKey] = true; } if (epPolymorphismOpCode === "JMP" || epPolymorphismOpCode === "JMPF") { // An unconditional jump has no reachable physical fall-through. epPolymorphismPendingAddresses.push(epPolymorphismTargetAddress); } else { // Calls and conditional branches retain both target and fall-through paths. epPolymorphismPendingAddresses.push(epPolymorphismTargetAddress); epPolymorphismPendingAddresses.push(epPolymorphismNextAddress); } } else if (epPolymorphismOpCode === "CALL") { // An unresolved indirect call may return, so retain only its fall-through. epPolymorphismPendingAddresses.push(epPolymorphismNextAddress); } else if (epScanStopOpCodes.indexOf(epPolymorphismOpCode) === -1) { epPolymorphismPendingAddresses.push(epPolymorphismNextAddress); } } // Resolve bad-target candidates after traversal so queue order cannot promote an // alternative address located inside a later decoded instruction boundary. for (var i = 0; i < epInvalidBranchCandidates.length && epInvalidBranchTargetAddress === -1; i++) { var epInvalidBranchCandidate = epInvalidBranchCandidates[i], isEpInvalidBranchCandidateOverlapping = false; for (var p = 1; p < 15 && !isEpInvalidBranchCandidateOverlapping; p++) { var epInvalidBranchPreviousAddress = epInvalidBranchCandidate.SourceAddress - p, epInvalidBranchPreviousInstructionLength = epDecodedInstructionLengthByAddress[epInvalidBranchPreviousAddress.toString()]; if (epInvalidBranchPreviousInstructionLength > p) { isEpInvalidBranchCandidateOverlapping = true; } } if (isEpInvalidBranchCandidateOverlapping) continue; epInvalidBranchSourceAddress = epInvalidBranchCandidate.SourceAddress; epInvalidBranchTargetAddress = epInvalidBranchCandidate.TargetAddress; epInvalidBranchTargetOpCode = epInvalidBranchCandidate.OpCode; } // Resolve prefix candidates only after every reachable instruction boundary is known. // A candidate inside another decoded instruction comes from an alternative x86 decode // path and cannot independently prove redundant prefixes. for (var i = 0; i < epRedundantPrefixCandidates.length; i++) { var epRedundantPrefixCandidate = epRedundantPrefixCandidates[i], isEpRedundantPrefixCandidateOverlapping = false; for (var p = 1; p < 15 && !isEpRedundantPrefixCandidateOverlapping; p++) { var epRedundantPrefixPreviousAddress = epRedundantPrefixCandidate.Address - p, epRedundantPrefixPreviousInstructionLength = epDecodedInstructionLengthByAddress[epRedundantPrefixPreviousAddress.toString()]; if (epRedundantPrefixPreviousInstructionLength > p) { isEpRedundantPrefixCandidateOverlapping = true; } } if (isEpRedundantPrefixCandidateOverlapping) continue; epRedundantPrefixInstructionCount++; epConflictingPrefixPresent = epConflictingPrefixPresent || epRedundantPrefixCandidate.IsConflicting; if (epRedundantPrefixCandidate.PrefixCount > epMaximumRedundantPrefixCount) { epMaximumRedundantPrefixCount = epRedundantPrefixCandidate.PrefixCount; } } // Reuse the reachable direct-call set for two independent behavioral proofs. for (var epDirectCallTargetKey in epDirectCallTargets) { // Object keys are decimal address strings; restore the numeric VA once per target. var epDirectCallTargetAddress = parseInt(epDirectCallTargetKey, 10); if (epSelfModifyingCallStubAddress === -1) { var epSelfModifyingCallStubTargetRva = epDirectCallTargetAddress - PE_Cached.imageBase, epSelfModifyingCallStubSectionIndex = -1; // A normal compiler has no reason to call a physically-backed writable code // area which reconstructs its own nearby instructions. The section check // excludes ordinary thunks before the instruction proof begins. for (var d = 0; d < PE_Cached.numberOfSections && epSelfModifyingCallStubSectionIndex === -1; d++) { var epSelfModifyingCallStubSectionRva = PE.getSectionVirtualAddress(d), epSelfModifyingCallStubSectionRawSize = PE.getSectionFileSize(d), epSelfModifyingCallStubSectionCharacteristics = PE.section[d].Characteristics; if (epSelfModifyingCallStubTargetRva >= epSelfModifyingCallStubSectionRva && epSelfModifyingCallStubTargetRva < epSelfModifyingCallStubSectionRva + epSelfModifyingCallStubSectionRawSize && (epSelfModifyingCallStubSectionCharacteristics & (SECTION_FLAGS_EXECUTE | SECTION_FLAGS_WRITE)) === (SECTION_FLAGS_EXECUTE | SECTION_FLAGS_WRITE)) { epSelfModifyingCallStubSectionIndex = d; } } if (epSelfModifyingCallStubSectionIndex !== -1) { // The strict profile advances through seven ordered semantic stages. var epSelfModifyingCallStubCurrentAddress = epDirectCallTargetAddress, epSelfModifyingCallStubState = 0, epSelfModifyingCallStubNeutralInstructionCount = 0, epSelfModifyingCallStubWriteAddress = -1; // The concrete profile follows fixed semantics rather than exact bytes: // consume the return address, preserve the accumulator, derive a DWORD, // atomically write it into the instruction immediately following Pause, // and restore state. // Two neutral instructions per stage tolerate simple polymorphic padding // without allowing unrelated code to assemble a proof accidentally. for (var epSelfModifyingCallStubIndex = 0; epSelfModifyingCallStubIndex < epSelfModifyingCallStubLookaheadLimit && epSelfModifyingCallStubAddress === -1; epSelfModifyingCallStubIndex++) { var epSelfModifyingCallStubCurrentRva = epSelfModifyingCallStubCurrentAddress - PE_Cached.imageBase; if (epSelfModifyingCallStubCurrentRva < epSelfModifyingCallStubSectionRva || epSelfModifyingCallStubCurrentRva >= epSelfModifyingCallStubSectionRva + epSelfModifyingCallStubSectionRawSize || PE.VAToOffset(epSelfModifyingCallStubCurrentAddress) === -1) break; var epSelfModifyingCallStubInstruction = PE.getDisasmString(epSelfModifyingCallStubCurrentAddress), epSelfModifyingCallStubOpCode = getAsmOpCode(epSelfModifyingCallStubInstruction), epSelfModifyingCallStubInstructionLength = PE.getDisasmLength(epSelfModifyingCallStubCurrentAddress), epSelfModifyingCallStubStageMatched = false, epSelfModifyingCallStubWrite; if (!epSelfModifyingCallStubOpCode || epSelfModifyingCallStubInstructionLength <= 0) break; switch (epSelfModifyingCallStubState) { case 0: // Stage 0 consumes the call return address into writable memory. epSelfModifyingCallStubStageMatched = epSelfModifyingCallStubReturnStorePattern.test(epSelfModifyingCallStubInstruction); break; case 1: // Stage 1 preserves the native accumulator before using EAX as a decoder. epSelfModifyingCallStubStageMatched = epSelfModifyingCallStubAccumulatorPushPattern.test(epSelfModifyingCallStubInstruction); break; case 2: // Stage 2 materializes a DWORD key or encoded instruction value. epSelfModifyingCallStubStageMatched = epSelfModifyingCallStubKeyLoadPattern.test(epSelfModifyingCallStubInstruction); break; case 3: // Stage 3 transforms that DWORD without requiring one concrete key. epSelfModifyingCallStubStageMatched = epSelfModifyingCallStubDecodePattern.test(epSelfModifyingCallStubInstruction); break; case 4: // Stage 4 atomically exchanges the decoded DWORD with executable bytes. epSelfModifyingCallStubWrite = epSelfModifyingCallStubInstruction.match(epSelfModifyingCallStubWritePattern); epSelfModifyingCallStubStageMatched = Boolean(epSelfModifyingCallStubWrite); if (epSelfModifyingCallStubWrite) { if (epSelfModifyingCallStubWrite[3]) { // The disassembler may print a direct absolute write target. epSelfModifyingCallStubWriteAddress = parseInt(epSelfModifyingCallStubWrite[3], 0); } else { // Resolve a RIP-relative displacement against the following instruction. var epSelfModifyingCallStubDisplacement = parseInt(epSelfModifyingCallStubWrite[2], 0); if (epSelfModifyingCallStubWrite[1] === "-") epSelfModifyingCallStubDisplacement = -epSelfModifyingCallStubDisplacement; epSelfModifyingCallStubWriteAddress = epSelfModifyingCallStubCurrentAddress + epSelfModifyingCallStubInstructionLength + epSelfModifyingCallStubDisplacement; } } break; case 5: // Stage 5 restores the accumulator saved before reconstruction. epSelfModifyingCallStubStageMatched = epSelfModifyingCallStubAccumulatorPopPattern.test(epSelfModifyingCallStubInstruction); break; case 6: // The bundled disassembler may canonicalize F3 90 to Nop even // though the prefix deliberately serializes the code rewrite. epSelfModifyingCallStubStageMatched = epSelfModifyingCallStubOpCode === "PAUSE" || PE.compare("F3 90", PE.VAToOffset(epSelfModifyingCallStubCurrentAddress)); break; } if (epSelfModifyingCallStubStageMatched) { // Advance exactly one stage and restart its local padding allowance. epSelfModifyingCallStubState++; epSelfModifyingCallStubNeutralInstructionCount = 0; if (epSelfModifyingCallStubState === 7 && epSelfModifyingCallStubWriteAddress === epSelfModifyingCallStubCurrentAddress + epSelfModifyingCallStubInstructionLength) { // The rewritten DWORD must begin immediately after the serializing Pause. epSelfModifyingCallStubAddress = epDirectCallTargetAddress; } } else if ((epSelfModifyingCallStubOpCode === "NOP" || epSelfModifyingCallStubOpCode === "FNOP" || epSimplePolymorphicInstructionPattern.test(epSelfModifyingCallStubInstruction) || epValuePreservingInstructionPattern.test(epSelfModifyingCallStubInstruction)) && epSelfModifyingCallStubNeutralInstructionCount < epSelfModifyingCallStubNeutralInstructionLimit) { // Permit a small amount of neutral polymorphism independently per stage. epSelfModifyingCallStubNeutralInstructionCount++; } else { // Any semantic mismatch rejects this strict stub candidate. break; } // Strict-profile matching is physical and linear within one section. epSelfModifyingCallStubCurrentAddress += epSelfModifyingCallStubInstructionLength; } if (epSelfModifyingCallStubAddress === -1) { // Fall back to a looser profile only when the exact seven-stage form failed. var epRepeatedSelfModifyingCallStubCurrentAddress = epDirectCallTargetAddress, epRepeatedSelfModifyingCallStubReturnReadPresent = false, epRepeatedSelfModifyingCallStubTransformPresent = false, epRepeatedSelfModifyingCallStubTransformRegister = String(); // The generic profile deliberately ignores exact registers and arithmetic. // Requiring three distinct called stubs in the same writable code section // compensates for that freedom. Each stub must recover its return address, // transform a register value, and write back into its own nearby code bytes. for (var epRepeatedSelfModifyingCallStubIndex = 0; epRepeatedSelfModifyingCallStubIndex < epRepeatedSelfModifyingCallStubLookaheadLimit && epSelfModifyingCallStubAddress === -1; epRepeatedSelfModifyingCallStubIndex++) { var epRepeatedSelfModifyingCallStubCurrentRva = epRepeatedSelfModifyingCallStubCurrentAddress - PE_Cached.imageBase; if (epRepeatedSelfModifyingCallStubCurrentRva < epSelfModifyingCallStubSectionRva || epRepeatedSelfModifyingCallStubCurrentRva >= epSelfModifyingCallStubSectionRva + epSelfModifyingCallStubSectionRawSize || PE.VAToOffset(epRepeatedSelfModifyingCallStubCurrentAddress) === -1) break; var epRepeatedSelfModifyingCallStubInstruction = PE.getDisasmString(epRepeatedSelfModifyingCallStubCurrentAddress), epRepeatedSelfModifyingCallStubOpCode = getAsmOpCode(epRepeatedSelfModifyingCallStubInstruction), epRepeatedSelfModifyingCallStubInstructionLength = PE.getDisasmLength(epRepeatedSelfModifyingCallStubCurrentAddress), epRepeatedSelfModifyingCallStubWrite = undefined; if (!epRepeatedSelfModifyingCallStubOpCode || epRepeatedSelfModifyingCallStubInstructionLength <= 0) break; if (!epRepeatedSelfModifyingCallStubReturnReadPresent) { // First prove that the called stub obtains its own return address. epRepeatedSelfModifyingCallStubReturnReadPresent = epRepeatedSelfModifyingCallStubReturnReadPattern.test(epRepeatedSelfModifyingCallStubInstruction); } else if (!epRepeatedSelfModifyingCallStubTransformPresent) { // Then bind the first following arithmetic transform to its destination. var epRepeatedSelfModifyingCallStubTransform = epRepeatedSelfModifyingCallStubInstruction.match(epRepeatedSelfModifyingCallStubTransformPattern); if (epRepeatedSelfModifyingCallStubTransform) { epRepeatedSelfModifyingCallStubTransformRegister = epRepeatedSelfModifyingCallStubTransform[1].toUpperCase(); epRepeatedSelfModifyingCallStubTransformPresent = true; } } if (epRepeatedSelfModifyingCallStubReturnReadPresent && epRepeatedSelfModifyingCallStubTransformPresent) { // A write is eligible only after both earlier semantic stages exist. epRepeatedSelfModifyingCallStubWrite = epRepeatedSelfModifyingCallStubInstruction.match(epRepeatedSelfModifyingCallStubWritePattern); if (epRepeatedSelfModifyingCallStubWrite && epRepeatedSelfModifyingCallStubWrite[4].toUpperCase() !== epRepeatedSelfModifyingCallStubTransformRegister) { // Reject a write sourced from a value unrelated to the transform. break; } } if (epRepeatedSelfModifyingCallStubWrite) { var epRepeatedSelfModifyingCallStubWriteAddress; if (epRepeatedSelfModifyingCallStubWrite[3]) { // Preserve an absolute write target exactly as decoded. epRepeatedSelfModifyingCallStubWriteAddress = parseInt(epRepeatedSelfModifyingCallStubWrite[3], 0); } else { // Convert RIP-relative syntax into the effective write VA. var epRepeatedSelfModifyingCallStubDisplacement = parseInt(epRepeatedSelfModifyingCallStubWrite[2], 0); if (epRepeatedSelfModifyingCallStubWrite[1] === "-") { epRepeatedSelfModifyingCallStubDisplacement = -epRepeatedSelfModifyingCallStubDisplacement; } epRepeatedSelfModifyingCallStubWriteAddress = epRepeatedSelfModifyingCallStubCurrentAddress + epRepeatedSelfModifyingCallStubInstructionLength + epRepeatedSelfModifyingCallStubDisplacement; } var epRepeatedSelfModifyingCallStubSectionAddress = PE_Cached.imageBase + epSelfModifyingCallStubSectionRva; // Require the write to remain inside the same executable/writable // section and close enough to reconstruct the current stub. if (epRepeatedSelfModifyingCallStubWriteAddress >= epRepeatedSelfModifyingCallStubSectionAddress && epRepeatedSelfModifyingCallStubWriteAddress < epRepeatedSelfModifyingCallStubSectionAddress + epSelfModifyingCallStubSectionRawSize && Math.abs(epRepeatedSelfModifyingCallStubWriteAddress - epDirectCallTargetAddress) <= epRepeatedSelfModifyingCallStubMaximumWriteDistance) { var epRepeatedSelfModifyingCallStubSectionKey = epSelfModifyingCallStubSectionIndex.toString(); // Initialize and increment the section-local evidence count. if (!epRepeatedSelfModifyingCallStubCountBySection[epRepeatedSelfModifyingCallStubSectionKey]) { epRepeatedSelfModifyingCallStubCountBySection[epRepeatedSelfModifyingCallStubSectionKey] = 0; } epRepeatedSelfModifyingCallStubCountBySection[epRepeatedSelfModifyingCallStubSectionKey]++; if (epRepeatedSelfModifyingCallStubCountBySection[epRepeatedSelfModifyingCallStubSectionKey] >= epRepeatedSelfModifyingCallStubRequiredCount) { // Three independent stubs compensate for the generic profile's freedom. epSelfModifyingCallStubAddress = epDirectCallTargetAddress; } } break; } // Never join instructions across a terminal control-flow boundary. if (epScanStopOpCodes.indexOf(epRepeatedSelfModifyingCallStubOpCode) !== -1) break; // Continue linearly inside the already validated writable code section. epRepeatedSelfModifyingCallStubCurrentAddress += epRepeatedSelfModifyingCallStubInstructionLength; } } } } // A Pop at a call target is not sufficient by itself: calling-convention adapters // may discard the return address without using it. Confirm that the recovered value // is read before it can be overwritten or invalidated by another control transfer. var epIpRecoveryRegister = epPopRegisterByAddress[epDirectCallTargetKey]; // Only targets beginning with Pop can carry a Call/Pop instruction-pointer proof. if (!epIpRecoveryRegister) continue; var epIpRecoveryCurrentAddress = parseInt(epDirectCallTargetKey, 10), epIpRecoveryPopLength = PE.getDisasmLength(epIpRecoveryCurrentAddress), epIpRecoveryRegisterMentionPattern = new RegExp("\\b" + epIpRecoveryRegister + "\\b", "i"), epIpRecoveryRegisterReadPattern = new RegExp("^(?:(?:ADD|ADC|SUB|SBB|AND|OR|XOR|IMUL|INC|DEC|NEG|NOT|SAL|SHL|SHR|SAR|ROL|ROR|RCL|RCR|XADD|XCHG|CMP|TEST|PUSH|CALL|JMP) " + epIpRecoveryRegister + "(?:,|$)|[^,]+,.*\\b" + epIpRecoveryRegister + "\\b|[^,]*\\[[^\\]]*\\b" + epIpRecoveryRegister + "\\b)", "i"), epIpRecoveryRegisterClearPattern = new RegExp("^(?:(?:XOR|SUB) " + epIpRecoveryRegister + ", " + epIpRecoveryRegister + "|AND " + epIpRecoveryRegister + ", (?:0X0+|0+)|IMUL " + epIpRecoveryRegister + ", " + epIpRecoveryRegister + ", (?:0X0+|0+))$", "i"), epIpRecoveryRegisterAlias; // A partial-register write also destroys a recovered native address. Build the // complete physical-register family without weakening proof to a partial read. if (/^R(?:8|9|1[0-5])$/.test(epIpRecoveryRegister)) { epIpRecoveryRegisterAlias = epIpRecoveryRegister + "(?:D|W|B)?"; } else { var epIpRecoveryRegisterSuffix = epIpRecoveryRegister.substring(1), epIpRecoveryByteRegister = /^(?:AX|BX|CX|DX)$/.test(epIpRecoveryRegisterSuffix) ? epIpRecoveryRegisterSuffix[0] + "(?:L|H)" : epIpRecoveryRegisterSuffix + "L"; epIpRecoveryRegisterAlias = "(?:" + (epIpRecoveryRegister[0] === "R" ? epIpRecoveryRegister + "|" : String()) + "E" + epIpRecoveryRegisterSuffix + "|" + epIpRecoveryRegisterSuffix + "|" + epIpRecoveryByteRegister + ")"; } var epIpRecoveryRegisterAliasMentionPattern = new RegExp("\\b" + epIpRecoveryRegisterAlias + "\\b", "i"); // Reject malformed POP decoding before moving to its first consumer. if (epIpRecoveryPopLength <= 0) continue; epIpRecoveryCurrentAddress += epIpRecoveryPopLength; // Inspect a short physical window after Pop; long-distance use would be too // easy to confuse with ordinary register data flow and would increase FP. for (var epIpRecoveryIndex = 0; epIpRecoveryIndex < epCallPopIpRecoveryLookaheadLimit; epIpRecoveryIndex++) { var epIpRecoveryCurrentRva = epIpRecoveryCurrentAddress - PE_Cached.imageBase, epIpRecoverySectionIndex = -1; // The proof lookahead is linear and intentionally short, but every decoded // address must still refer to executable physical bytes rather than padding. for (var d = 0; d < PE_Cached.numberOfSections && epIpRecoverySectionIndex === -1; d++) { var epIpRecoverySectionRva = PE.getSectionVirtualAddress(d), epIpRecoverySectionRawSize = PE.getSectionFileSize(d); if (epIpRecoveryCurrentRva >= epIpRecoverySectionRva && epIpRecoveryCurrentRva < epIpRecoverySectionRva + epIpRecoverySectionRawSize && (PE.section[d].Characteristics & SECTION_FLAGS_EXECUTE)) { epIpRecoverySectionIndex = d; } } if (epIpRecoverySectionIndex === -1 || PE.VAToOffset(epIpRecoveryCurrentAddress) === -1) break; var epIpRecoveryInstruction = PE.getDisasmString(epIpRecoveryCurrentAddress), epIpRecoveryOpCode = getAsmOpCode(epIpRecoveryInstruction), epIpRecoveryInstructionLength = PE.getDisasmLength(epIpRecoveryCurrentAddress), epIpRecoveryRegisterMentioned = epIpRecoveryRegisterMentionPattern.test(epIpRecoveryInstruction), epIpRecoveryRegisterAliasMentioned = epIpRecoveryRegisterAliasMentionPattern.test(epIpRecoveryInstruction), epIpRecoverySimplePolymorphism = epSimplePolymorphicInstructionPattern.test(epIpRecoveryInstruction), epIpRecoveryValuePreserving = epValuePreservingInstructionPattern.test(epIpRecoveryInstruction); if (!epIpRecoveryOpCode || epIpRecoveryInstructionLength <= 0) break; // Any partial-register access changes or observes only part of the recovered VA. if (epIpRecoveryRegisterAliasMentioned && !epIpRecoveryRegisterMentioned) break; if (epIpRecoveryRegisterMentioned && !epIpRecoverySimplePolymorphism && !epIpRecoveryValuePreserving) { // Accept the first meaningful full-width read unless it first clears the value. if (!epIpRecoveryRegisterClearPattern.test(epIpRecoveryInstruction) && epIpRecoveryRegisterReadPattern.test(epIpRecoveryInstruction)) { epCallPopIpRecoveryAddress = parseInt(epDirectCallTargetKey, 10); } // An explicit mention either consumes the proof or overwrites its register. break; } var epIpRecoveryRegisterWrite = epIpRecoveryInstruction.match(epFlagsNeutralRegisterWritePattern) || epIpRecoveryInstruction.match(epTrackedRegisterWritePattern), epIpRecoveryNeutralInstruction = epIpRecoveryOpCode === "NOP" || epIpRecoveryOpCode === "FNOP" || epIpRecoveryOpCode === "PAUSE" || epIpRecoveryOpCode === "CLC" || epIpRecoveryOpCode === "STC" || epIpRecoveryOpCode === "CMC" || epStackRoundTripPushPattern.test(epIpRecoveryInstruction) || epIpRecoverySimplePolymorphism || epIpRecoveryValuePreserving || epIpRecoveryRegisterWrite || /^(?:CMP|TEST)$/.test(epIpRecoveryOpCode); // Stop when the instruction cannot safely preserve a pending recovered pointer. if (!epIpRecoveryNeutralInstruction || epScanStopOpCodes.indexOf(epIpRecoveryOpCode) !== -1) break; // Advance only after the current instruction has preserved the pending proof. epIpRecoveryCurrentAddress += epIpRecoveryInstructionLength; } if (epCallPopIpRecoveryAddress !== -1) break; } // Clean up: release the control-flow worklist and its potentially dense visited set. epPolymorphismPendingAddresses = undefined; epPolymorphismVisitedAddresses = undefined; epPolymorphismTransformOpCodesFound = undefined; epRareBitOpCodesFound = undefined; epDecodedInstructionLengthByAddress = undefined; epDirectCallTargets = undefined; epPopRegisterByAddress = undefined; epInvalidBranchCandidates = undefined; epRedundantPrefixCandidates = undefined; } // Check for direct branches from reachable EP code into invalid image locations var isInvalidEpBranchTargetPresent = false; if (epInvalidBranchTargetAddress !== -1) { log(logType.emulation, "Invalid " + epInvalidBranchTargetOpCode[0] + epInvalidBranchTargetOpCode.substring(1).toLowerCase() + " target near EP: 0x" + epInvalidBranchSourceAddress.toString(16) + " -> 0x" + epInvalidBranchTargetAddress.toString(16)); isInvalidEpBranchTargetPresent = true; } if (isInvalidEpBranchTargetPresent) options = addOption(options, "Invalid branch target near EP"); // Check for repeated or conflicting x86 instruction-prefix groups in reachable EP code var isRedundantEpPrefixPresent = false; if (epConflictingPrefixPresent || epRedundantPrefixInstructionCount >= epRedundantPrefixRequiredInstructionCount || epMaximumRedundantPrefixCount >= epRedundantPrefixImmediateCount) { log(logType.emulation, "Redundant prefixes near EP: instructions/max: " + epRedundantPrefixInstructionCount + "/" + epMaximumRedundantPrefixCount); isRedundantEpPrefixPresent = true; } if (isRedundantEpPrefixPresent) options = addOption(options, "Redundant prefixes near EP"); // Check for a direct call whose executable target starts by recovering its return address var isCallPopEpIpRecoveryPresent = false; if (!PE_Cached.isDynamicLinkLibrary && epCallPopIpRecoveryAddress !== -1) { // The shared pass already validated both the Pop target and later pointer use. log(logType.emulation, "Call/Pop IP recovery near EP"); isCallPopEpIpRecoveryPresent = true; } if (isCallPopEpIpRecoveryPresent) options = addOption(options, "Call/Pop IP recovery near EP"); // Check for called stubs which reconstruct their own nearby executable bytes var isSelfModifyingEpCallStubPresent = false; if (epSelfModifyingCallStubAddress !== -1) { // Either one strict stub or three generic section-local stubs proved reconstruction. log(logType.emulation, "Self-modifying call stub near EP: 0x" + epSelfModifyingCallStubAddress.toString(16)); isSelfModifyingEpCallStubPresent = true; } if (isSelfModifyingEpCallStubPresent) options = addOption(options, "Self-modifying stubs near EP"); // Check for carry-fed bitstream decoding combined with repeated byte-string copying. // Neither operation is distinctive alone; the indirect transfer makes the dense pair // specific to a dispatcher-style unpacking loop while retaining register polymorphism. var isBitstreamEpUnpackerPresent = false; if (!PE_Cached.isDynamicLinkLibrary && epBitstreamAdcCount >= 8 && epBitstreamMovsbCount >= 2 && epBitstreamIndirectTransferPresent) { // Require all three independent components before reporting the compound behavior. log(logType.emulation, "Bitstream unpacker near EP: Adc: " + epBitstreamAdcCount + ", Rep Movsb: " + epBitstreamMovsbCount); isBitstreamEpUnpackerPresent = true; } if (isBitstreamEpUnpackerPresent) options = addOption(options, "Bitstream unpacker near EP"); // Check for repeated shifts or rotates whose immediate count is at least as wide as the operand var isNoncanonicalEpShiftPresent = false; if (!PE_Cached.isDynamicLinkLibrary && epNoncanonicalShiftCount >= 5) { // Five encodings establish a generator habit rather than one hand-written oddity. log(logType.emulation, "Noncanonical shifts near EP: " + epNoncanonicalShiftCount); isNoncanonicalEpShiftPresent = true; } if (isNoncanonicalEpShiftPresent) options = addOption(options, "Noncanonical shift near EP"); // Check for repeated arithmetic stack writes containing at least one complex bit transformation var isComplexEpStackMutationPresent = false; if (!PE_Cached.isDynamicLinkLibrary && epStackMutationCount >= 4 && epComplexStackMutationCount !== 0) { // Density supplies repetition; the uncommon transform supplies specificity. log(logType.emulation, "Stack mutations near EP: " + epStackMutationCount + " (complex: " + epComplexStackMutationCount + ")"); isComplexEpStackMutationPresent = true; } if (isComplexEpStackMutationPresent) options = addOption(options, "Complex stack mutation near EP"); // Check for a dense mixture of flag transfers, bidirectional rotates, carry arithmetic, // and at least five distinct rare bit-operation families. Prefer the concrete shift rule // when both match so that one polymorphic bit mixer does not produce duplicate verdicts. var isMixedEpBitOpsPresent = false; if (!PE_Cached.isDynamicLinkLibrary && epFlagsTransferPresent && epRolPresent && epRorPresent && epAdcPresent && epRareBitOpCount >= 5) { // Every mandatory family must be reachable; isolated arithmetic is insufficient. log(logType.emulation, "Mixed bit-operation families near EP: " + epRareBitOpCount); isMixedEpBitOpsPresent = true; } if (!isNoncanonicalEpShiftPresent && isMixedEpBitOpsPresent) options = addOption(options, "Mixed bit ops near EP"); // Check for a transform-heavy cyclic entry-point graph characteristic of generated // polymorphic dispatchers. No individual counter is trusted: stack traffic, calls, // branching, a backward edge and uncommon transforms must all be independently present. var isPolymorphicEpFlowPresent = false; if (!PE_Cached.isDynamicLinkLibrary && epPolymorphismBranchCount >= epPolymorphismBranchRequiredCount && epPolymorphismBackwardControlFlowCount !== 0 && epPolymorphismCallCount >= epPolymorphismCallRequiredCount && epPolymorphismPushCount >= epPolymorphismPushRequiredCount && epPolymorphismPushCount - epPolymorphismPopCount >= epPolymorphismStackImbalanceRequiredCount && epPolymorphismTransformCount >= epPolymorphismTransformRequiredCount && epPolymorphismTransformKindCount >= epPolymorphismTransformKindRequiredCount && epPolymorphismDominantTransformCount * 2 <= epPolymorphismTransformCount && epPolymorphismPushCount + epPolymorphismTransformCount >= epPolymorphismDensityRequiredCount) { // Independent minima keep every required behavior present. Unbalanced explicit stack // traffic rejects ordinary callees, transformation diversity and distribution exclude // fixed unpacking loops, and combined density rejects incidental mixtures. log(logType.emulation, "Irregular ctrl flow near EP: branches/back: " + epPolymorphismBranchCount + "/" + epPolymorphismBackwardControlFlowCount + ", Call: " + epPolymorphismCallCount + ", Push/Pop: " + epPolymorphismPushCount + "/" + epPolymorphismPopCount + ", transforms/kinds/max: " + epPolymorphismTransformCount + "/" + epPolymorphismTransformKindCount + "/" + epPolymorphismDominantTransformCount); isPolymorphicEpFlowPresent = true; } if (isPolymorphicEpFlowPresent) options = addOption(options, "Irregular ctrl flow near EP"); // Check for strange opcodes at entry point var isStrangeOpCodeNearEpPresent = false; var strangeOpCodesNearEp = [ "CPUID", // 0F A2 "RDTSC", // 0F 31 "RDTSCP", // 0F 01 F9 "RDRAND", // 0F C7 /6 "RDSEED" // 0F C7 /7 ], strangeOpCodeDetected = String(); if (PE_Cached.isArchX86) { for (var x = 0; x < strangeOpCodesNearEp.length; x++) { const opCodeToCheck = strangeOpCodesNearEp[x]; // Check the first 15 instructions for the current unusual opcode. if (!PE_Cached.isDynamicLinkLibrary) { for (var i = 0; i < 15 && !isStrangeOpCodeNearEpPresent; i++) { var asmInstruction = emulateAndGetAsmInstructionByIndex(i); if (!asmInstruction) break; // Safety check var currentEpOpCode = getAsmOpCode(asmInstruction); // Check whether the instruction matches the requested opcode. if (currentEpOpCode === opCodeToCheck) { log(logType.emulation, opCodeToCheck + " near EP found at instruction index: " + i); strangeOpCodeDetected = opCodeToCheck; isStrangeOpCodeNearEpPresent = true; } else if (epScanStopOpCodes.indexOf(currentEpOpCode) !== -1 && currentEpOpCode !== "CALL") { break; } } } } } if (isStrangeOpCodeNearEpPresent) options = addOption(options, strangeOpCodeDetected[0] + strangeOpCodeDetected.substring(1).toLowerCase() + " near EP"); // Check for TLS initialization paired with an unusable formal entry point. // A trap or immediate return delegates startup to TLS callbacks explicitly. An EP in // the virtual-only tail of an executable section is stronger evidence: no instruction // exists in the file, so TLS code must reconstruct it or prevent normal EP execution. var isTlsHiddenEpPresent = false; if (PE.isTLSPresent()) { const tlsEntryPointRva = PE_Cached.addressOfUnmanagedEntryPoint - PE_Cached.imageBase; var isTlsEntryPointInExecutableVirtualSection = false, isTlsEntryPointPhysicallyBacked = false, tlsEntryPointSectionIndex = -1; if (tlsEntryPointRva > 0) { for (var i = 0; i < PE_Cached.numberOfSections; i++) { const tlsEntryPointSectionRva = PE.getSectionVirtualAddress(i), tlsEntryPointSectionVirtualSize = PE.getSectionVirtualSize(i), tlsEntryPointSectionFileSize = PE.getSectionFileSize(i), tlsEntryPointSectionMappedSize = Math.max(tlsEntryPointSectionVirtualSize, tlsEntryPointSectionFileSize); if (tlsEntryPointRva >= tlsEntryPointSectionRva && tlsEntryPointRva < tlsEntryPointSectionRva + tlsEntryPointSectionMappedSize && (PE.section[i].Characteristics & SECTION_FLAGS_EXECUTE)) { var tlsEntryPointOffsetInSection = tlsEntryPointRva - tlsEntryPointSectionRva; isTlsEntryPointInExecutableVirtualSection = true; tlsEntryPointSectionIndex = i; // Check both the declared raw span and the actual file mapping. This // rejects zero-filled section tails as well as truncated raw data. if (tlsEntryPointOffsetInSection < tlsEntryPointSectionFileSize && PE.getSectionFileOffset(i) + tlsEntryPointOffsetInSection < PE_Cached.fileSize && PE.VAToOffset(PE_Cached.addressOfUnmanagedEntryPoint) !== -1) { isTlsEntryPointPhysicallyBacked = true; } } } } if (isTlsEntryPointInExecutableVirtualSection && !isTlsEntryPointPhysicallyBacked) { log(logType.any, "TLS virtual-only EP in section #" + tlsEntryPointSectionIndex); isTlsHiddenEpPresent = true; } else if (PE_Cached.isArchX86) { if (/^INT(?: )?3$/.test(PE_Cached.firstEpAsmInstruction) || // INT3 (0xCC) / INT 3 (0xCD 0x03) /^RET(?: \d+)?$/.test(PE_Cached.firstEpAsmInstruction)) { // All types of RET log(logType.emulation, "TLS startup bypasses the formal EP: " + PE_Cached.firstEpAsmInstruction); isTlsHiddenEpPresent = true; } } else if (PE_Cached.isARM || PE_Cached.isThumb) { // ARM/Thumb: Check for breakpoint or return instructions if (/^BKPT/.test(PE_Cached.firstEpAsmInstruction) || // BKPT (breakpoint) /^BX(?: )?LR$/.test(PE_Cached.firstEpAsmInstruction)) { // BX LR (ARM32 return) log(logType.emulation, "TLS startup bypasses the formal EP: " + PE_Cached.firstEpAsmInstruction); isTlsHiddenEpPresent = true; } } else if (PE_Cached.isARM64) { if (/^BRK/.test(PE_Cached.firstEpAsmInstruction) || // BRK (breakpoint) /^RET(?: )?/.test(PE_Cached.firstEpAsmInstruction)) { // RET (return) log(logType.emulation, "TLS startup bypasses the formal EP: " + PE_Cached.firstEpAsmInstruction); isTlsHiddenEpPresent = true; } } } if (isTlsHiddenEpPresent) options = addOption(options, "TLS hidden EP"); // Check for multiple JMPs leading to each other var proxyJumpsAtEpPresent = false; if (PE_Cached.isArchX86 && ( PE.compareEP("EB $$ EB") || PE.compareEP("EB $$ E9") || PE.compareEP("E9 ## ## ## ## EB") || PE.compareEP("E9 ## ## ## ## E9") )) { log(logType.any, "Proxy jump chain detected at EP"); proxyJumpsAtEpPresent = true; } if (proxyJumpsAtEpPresent) options = addOption(options, "Proxy jmp at EP"); // Check for sections with full RWX permissions (Read, Write, Execute) var isRwxSectionPresent = false; var rwxSectionIndex = -1; for (var i = 0; i < PE_Cached.numberOfSections && !isRwxSectionPresent; i++) { var currentSectionCharacteristics = PE.section[i].Characteristics; if ((currentSectionCharacteristics & SECTION_FLAGS_RWX_MASK) === SECTION_FLAGS_RWX_MASK || (currentSectionCharacteristics & SECTION_FLAGS_RWX_CODE_MASK) === SECTION_FLAGS_RWX_CODE_MASK) { log(logType.any, "RWX permissions detected in section #" + i + " (\"" + File.cleanString(PE.getSectionName(i)) + "\")"); isRwxSectionPresent = true; rwxSectionIndex = i; } } if (isRwxSectionPresent) options = addOption(options, "Section #" + rwxSectionIndex + " (\"" + cleanSectionName(PE.getSectionName(rwxSectionIndex)) + "\") has RWX"); // Check for write-execute sections without read permission var isWxWithoutReadSectionPresent = false; var wxWithoutReadSectionIndex = -1; for (var i = 0; i < PE_Cached.numberOfSections && !isWxWithoutReadSectionPresent; i++) { var currentSectionCharacteristics = PE.section[i].Characteristics; if ((currentSectionCharacteristics & (SECTION_FLAGS_EXECUTE | SECTION_FLAGS_WRITE)) === (SECTION_FLAGS_EXECUTE | SECTION_FLAGS_WRITE) && !(currentSectionCharacteristics & SECTION_FLAGS_READ)) { log(logType.any, "Write-execute section without read access detected: #" + i + " (\"" + File.cleanString(PE.getSectionName(i)) + "\")"); isWxWithoutReadSectionPresent = true; wxWithoutReadSectionIndex = i; } } if (isWxWithoutReadSectionPresent) options = addOption(options, "Section #" + wxWithoutReadSectionIndex + " (\"" + cleanSectionName(PE.getSectionName(wxWithoutReadSectionIndex)) + "\") has WX without R"); // Check for zero padding at the beginning of the entry point section var isEpSectionZeroPaddingPresent = false; var entryPointSectionIndex = -1; var isEpInNonExecutableSection = false; // Find the section that contains the entry point const entryPointRva = PE_Cached.addressOfUnmanagedEntryPoint - PE_Cached.imageBase; for (var i = 0; i < PE_Cached.numberOfSections && !isEpSectionZeroPaddingPresent; i++) { const sectionVirtualAddress = PE.getSectionVirtualAddress(i), sectionVirtualSize = PE.getSectionVirtualSize(i); // Check if the entry point is within this section if (entryPointRva >= sectionVirtualAddress && entryPointRva < sectionVirtualAddress + sectionVirtualSize) { entryPointSectionIndex = i; if (!(PE.section[i].Characteristics & SECTION_FLAGS_EXECUTE)) { log(logType.any, "Entry point is located in a non-executable section"); isEpInNonExecutableSection = true; } // Check for zero padding at the beginning of the section if (PE.compare("00 00 00", PE.getSectionFileOffset(entryPointSectionIndex))) { log(logType.any, "Zero padding detected at the beginning of EP section #" + entryPointSectionIndex); isEpSectionZeroPaddingPresent = true; } } } if (isEpSectionZeroPaddingPresent) options = addOption(options, "EP-section #" + entryPointSectionIndex + " (\"" + cleanSectionName(PE.getSectionName(entryPointSectionIndex)) + "\") zero padding"); if (isEpInNonExecutableSection) options = addOption(options, "EP in non-executable section"); // A physically backed entry point in an executable section should be marked as code. var isEpInNonCodeSection = false; if (!PE_Cached.isDynamicLinkLibrary && !PE_Cached.isDriver && (PE_Cached.imageSubsystem === 2 || PE_Cached.imageSubsystem === 3) && entryPointSectionIndex !== -1 && PE.isEntryPointCorrect() && PE.getSectionFileSize(entryPointSectionIndex) !== 0 && (PE.section[entryPointSectionIndex].Characteristics & SECTION_FLAGS_EXECUTE) && !(PE.section[entryPointSectionIndex].Characteristics & SECTION_FLAGS_CODE)) { log(logType.any, "Executable EP section is not marked as code"); isEpInNonCodeSection = true; } if (isEpInNonCodeSection) options = addOption(options, "EP in non-code section"); // A normal user-mode process cannot discard the section containing its entry point. var isDiscardableEpSectionPresent = false; if (!PE_Cached.isDynamicLinkLibrary && !PE_Cached.isDriver && (PE_Cached.imageSubsystem === 2 || PE_Cached.imageSubsystem === 3) && entryPointSectionIndex !== -1 && (PE.section[entryPointSectionIndex].Characteristics & SECTION_FLAGS_DISCARDABLE)) { log(logType.any, "Entry point section is marked discardable"); isDiscardableEpSectionPresent = true; } if (isDiscardableEpSectionPresent) options = addOption(options, "Discardable EP section"); // BaseOfCode is advisory and protectors may deliberately leave it pointing at a // virtual-only startup section. Treat this as a structural anomaly, not corruption. var isBaseOfCodeMismatchPresent = false; const baseOfCodeRva = PE.getImageOptionalHeader("BaseOfCode"); if (baseOfCodeRva !== 0) { var isBaseOfCodeMapped = false; for (var i = 0; i < PE_Cached.numberOfSections && !isBaseOfCodeMapped; i++) { const baseOfCodeSectionRva = PE.getSectionVirtualAddress(i), baseOfCodeSectionRawSize = PE.getSectionFileSize(i); if (baseOfCodeRva >= baseOfCodeSectionRva && baseOfCodeRva < baseOfCodeSectionRva + baseOfCodeSectionRawSize && PE.VAToOffset(PE_Cached.imageBase + baseOfCodeRva) !== -1) { isBaseOfCodeMapped = true; } } if (!isBaseOfCodeMapped) { log(logType.any, "BaseOfCode RVA 0x" + baseOfCodeRva.toString(16) + " is not file-backed"); isBaseOfCodeMismatchPresent = true; } } // A valid physical EP in executable code contradicts a zero SizeOfCode header value. var isUnreportedCodePresent = false; if (!PE_Cached.isDynamicLinkLibrary && !PE_Cached.isDriver && (PE_Cached.imageSubsystem === 2 || PE_Cached.imageSubsystem === 3) && entryPointSectionIndex !== -1 && PE.isEntryPointCorrect() && PE.getSectionFileSize(entryPointSectionIndex) !== 0 && (PE.section[entryPointSectionIndex].Characteristics & SECTION_FLAGS_EXECUTE) && PE.getSizeOfCode() === 0) { log(logType.any, "Executable code is present while SizeOfCode is zero"); isUnreportedCodePresent = true; } if (isUnreportedCodePresent) options = addOption(options, "Unreported code"); // Protectors may append physical code without updating the aggregate SizeOfCode field. // Allow two pages of slack because legacy linkers do not always account for per-section // file alignment consistently; substantially larger differences remain structural anomalies. var isUnderreportedCodePresent = false, isSeverelyUnderreportedCodePresent = false, isUnderreportedStartupCodePresent = false; if (!PE_Cached.isDynamicLinkLibrary && !PE_Cached.isDriver && (PE_Cached.imageSubsystem === 2 || PE_Cached.imageSubsystem === 3) && PE.getSizeOfCode() !== 0) { var physicalCodeSize = 0, physicalCodeSectionCount = 0; // Reconstruct the physical code total from non-empty sections explicitly marked as code. for (var i = 0; i < PE_Cached.numberOfSections; i++) { if ((PE.section[i].Characteristics & SECTION_FLAGS_CODE) && PE.getSectionFileSize(i) !== 0) { physicalCodeSize += PE.getSectionFileSize(i); physicalCodeSectionCount++; } } const codeSizeAlignmentTolerance = Math.max( 0x2000, PE.getImageOptionalHeader("FileAlignment") * physicalCodeSectionCount ); // Report only the excess beyond both the PE header value and alignment tolerance. if (physicalCodeSize > PE.getSizeOfCode() + codeSizeAlignmentTolerance) { isUnderreportedCodePresent = true; isSeverelyUnderreportedCodePresent = physicalCodeSize > PE.getSizeOfCode() * 2; log(logType.any, "Physical code size (0x" + physicalCodeSize.toString(16) + ") exceeds SizeOfCode (0x" + PE.getSizeOfCode().toString(16) + ")"); } } if (isUnderreportedCodePresent && PE_Cached.numberOfSections > 1 && entryPointSectionIndex !== -1 && PE.isEntryPointCorrect()) { // Treat the final section as a startup-code candidate only after the global // SizeOfCode mismatch has independently established underreported physical code. const protectedStartupSectionIndex = PE_Cached.numberOfSections - 1, protectedStartupSection = PE.section[protectedStartupSectionIndex], protectedStartupSectionCharacteristics = protectedStartupSection.Characteristics, protectedStartupSectionRequiredFlags = SECTION_FLAGS_CODE | SECTION_FLAGS_EXECUTE | SECTION_FLAGS_READ, protectedStartupSectionFileSize = PE.getSectionFileSize(protectedStartupSectionIndex), protectedStartupSectionStartAddress = PE_Cached.imageBase + PE.getSectionVirtualAddress(protectedStartupSectionIndex), protectedStartupSectionEndAddress = protectedStartupSectionStartAddress + Math.max( PE.getSectionVirtualSize(protectedStartupSectionIndex), protectedStartupSectionFileSize ); // Require a substantial, high-entropy, readable executable code section. Excluding // writable sections avoids conflating this rule with self-modifying startup code. if (protectedStartupSectionFileSize >= 0x1000 && (protectedStartupSectionCharacteristics & protectedStartupSectionRequiredFlags) === protectedStartupSectionRequiredFlags && !(protectedStartupSectionCharacteristics & SECTION_FLAGS_WRITE) && PE.calculateEntropy(PE.getSectionFileOffset(protectedStartupSectionIndex), protectedStartupSectionFileSize) > 6.6) { if (entryPointSectionIndex === protectedStartupSectionIndex) { // EP residing directly in the candidate section is a complete startup proof. isUnderreportedStartupCodePresent = true; } else if (PE_Cached.isArchX86) { // Otherwise prove that reachable EP control flow enters the candidate section. var protectedStartupPendingAddresses = [PE_Cached.addressOfUnmanagedEntryPoint], protectedStartupPendingAddressIndex = 0, protectedStartupVisitedAddresses = {}, protectedStartupVisitedInstructionCount = 0; // Explore a small reachable control-flow graph instead of sweeping physical bytes. // Direct calls and conditional branches keep both the target and fall-through; // an unconditional jump keeps only its target. The visited set handles loops, and // the global instruction limit bounds deliberately dense polymorphic dispatchers. while (protectedStartupPendingAddressIndex < protectedStartupPendingAddresses.length && protectedStartupVisitedInstructionCount < 256 && !isUnderreportedStartupCodePresent) { // Process one queued address and canonicalize it for loop detection. var protectedStartupCurrentAddress = protectedStartupPendingAddresses[protectedStartupPendingAddressIndex++], protectedStartupAddressKey = protectedStartupCurrentAddress.toString(); // Discard repeated and unmapped nodes before spending the instruction budget. if (protectedStartupVisitedAddresses[protectedStartupAddressKey] || PE.VAToOffset(protectedStartupCurrentAddress) === -1) continue; // Count each mapped address once, even when several branches converge on it. protectedStartupVisitedAddresses[protectedStartupAddressKey] = true; protectedStartupVisitedInstructionCount++; var protectedStartupInstruction = PE.getDisasmString(protectedStartupCurrentAddress), protectedStartupOpCode = getAsmOpCode(protectedStartupInstruction), protectedStartupInstructionLength = PE.getDisasmLength(protectedStartupCurrentAddress); if (!protectedStartupOpCode || protectedStartupInstructionLength <= 0) continue; var protectedStartupNextAddress = protectedStartupCurrentAddress + protectedStartupInstructionLength, protectedStartupDirectControlFlow = protectedStartupInstruction.match(/^(CALL|J[A-Z]+|LOOP(?:E|NE|NZ|Z)?) (0X[0-9A-F]+|\d+)$/i); if (protectedStartupDirectControlFlow) { // Resolve direct control flow before deciding which successors remain reachable. var protectedStartupTargetAddress = parseInt(protectedStartupDirectControlFlow[2], 0); if (protectedStartupTargetAddress >= protectedStartupSectionStartAddress && protectedStartupTargetAddress < protectedStartupSectionEndAddress && PE.VAToOffset(protectedStartupTargetAddress) !== -1) { // A physically backed target inside the candidate section completes the proof. isUnderreportedStartupCodePresent = true; log(logType.emulation, "EP control flow reaches underreported section #" + protectedStartupSectionIndex + " at 0x" + protectedStartupTargetAddress.toString(16)); } else if (protectedStartupOpCode === "JMP" || protectedStartupOpCode === "JMPF") { // Unconditional jumps retain only their explicit destination. protectedStartupPendingAddresses.push(protectedStartupTargetAddress); } else { // Calls and conditional branches may continue along both edges. protectedStartupPendingAddresses.push(protectedStartupTargetAddress); protectedStartupPendingAddresses.push(protectedStartupNextAddress); } } else if (protectedStartupOpCode === "CALL") { // An unresolved indirect call may return, so its fall-through stays reachable. protectedStartupPendingAddresses.push(protectedStartupNextAddress); } else if (epScanStopOpCodes.indexOf(protectedStartupOpCode) === -1) { // Ordinary instructions advance linearly; terminal opcodes add no successor. protectedStartupPendingAddresses.push(protectedStartupNextAddress); } } // Clean up: release the control-flow worklist and visited-address set protectedStartupPendingAddresses = undefined; protectedStartupVisitedAddresses = undefined; } } } const isSizeOfCodeMismatchPresent = isUnderreportedStartupCodePresent || isSeverelyUnderreportedCodePresent; const baseOrSizeOfCodeMismatchDetails = (isBaseOfCodeMismatchPresent ? "Base" : String()) + (isBaseOfCodeMismatchPresent && isSizeOfCodeMismatchPresent ? "/" : String()) + (isSizeOfCodeMismatchPresent ? "Size" : String()); if (isBaseOfCodeMismatchPresent || isSizeOfCodeMismatchPresent) options = addOption(options, baseOrSizeOfCodeMismatchDetails + " of code mismatch" + (isUnderreportedStartupCodePresent && !isBaseOfCodeMismatchPresent ? " (EP)" : String())); // Check for a lot of sections (more than 20 -> this is a lot for native builds) var tooManySectionsPresent = false; if (PE_Cached.numberOfSections > 20) { log(logType.any, "Unusually high section count detected"); tooManySectionsPresent = true; } if (tooManySectionsPresent) options = addOption(options, "Too many sections"); // Check for IAT directory presence var isIatDirEmpty = false; if (!_isResultPresent("linker", "Turbo Linker")) { // Ignore Delphi // Offset of the IAT directory (12 is the number of IMAGE_DIRECTORY_ENTRY_ entries, 8 is the size of each entry) const iatDirOffset = getOptHeaderOffset() + (PE_Cached.is64bit ? 0x70 : 0x60) + 12 * 8; if (PE_Cached.indexOfImportsSection !== -1 && PE.read_int32(iatDirOffset) === 0) { log(logType.any, "IAT data directory is empty despite existing imports"); isIatDirEmpty = true; } } if (isIatDirEmpty) options = addOption(options, "Empty IAT data directory"); // Check for unused exception section var isUnusedExceptionSectionPresent = false; if (PE.isSectionNamePresentExp(/^\.[xp]data$/)) { // Offset of the exception directory (12 is the number of IMAGE_DIRECTORY_ENTRY_ entries, 8 is the size of each entry) var exceptionDirOffset = getOptHeaderOffset() + (PE_Cached.is64bit ? 0x88 : 0x78); if (PE.read_int32(exceptionDirOffset) === 0 && PE.read_int32(exceptionDirOffset + 4) === 0) { log(logType.any, "Exception section has no matching data directory"); isUnusedExceptionSectionPresent = true; } } if (isUnusedExceptionSectionPresent) options = addOption(options, "Exceptions directory empty"); const dllCharacteristics = PE.getImageOptionalHeader("DllCharacteristics"); // ASLR requires base relocations to rebase the image safely var isAslrWithStrippedRelocs = false; const coffCharacteristics = PE.readByte(getOptHeaderOffset() - 2) | (PE.readByte(getOptHeaderOffset() - 1) << 8); if ((coffCharacteristics & IMAGE_FILE_RELOCS_STRIPPED) && (dllCharacteristics & IMAGE_DLLCHARACTERISTICS_DYNAMIC_BASE)) { log(logType.any, "ASLR is enabled while relocations are marked stripped"); isAslrWithStrippedRelocs = true; } if (isAslrWithStrippedRelocs) options = addOption(options, "ASLR with stripped relocs"); // Reserved bits (0-4) in DllCharacteristics must always be zero — no linker ever sets them var isDllCharsReservedBitsSet = false; if (dllCharacteristics & 0x001F) { // 0x001F -> bits 0-4 -> 0b0000000000011111 log(logType.any, "Reserved DLL characteristic flags are set"); isDllCharsReservedBitsSet = true; } if (isDllCharsReservedBitsSet) options = addOption(options, "Invalid DLL flags"); // High entropy VA (0x0020) widens ASLR to 64-bit range — requires ASLR (0x0040) to have any effect var isHighEntropyWithoutAslr = false; if ((dllCharacteristics & IMAGE_DLLCHARACTERISTICS_HIGH_ENTROPY_VA) && !(dllCharacteristics & IMAGE_DLLCHARACTERISTICS_DYNAMIC_BASE)) { log(logType.any, "High-entropy VA is enabled without ASLR"); isHighEntropyWithoutAslr = true; } if (isHighEntropyWithoutAslr) options = addOption(options, "High entropy VA without ASLR"); // High entropy VA (0x0020) is meaningless on 32-bit images — 4 GB address space is not "high entropy" var isHighEntropyOn32bit = false; if ((dllCharacteristics & IMAGE_DLLCHARACTERISTICS_HIGH_ENTROPY_VA) && !PE_Cached.is64bit) { log(logType.any, "High-entropy VA is enabled on a 32-bit image"); isHighEntropyOn32bit = true; } if (isHighEntropyOn32bit) options = addOption(options, "High entropy VA on 32-bit image"); // Force integrity (0x0080) demands a code-integrity check at load time, requiring a relocatable image (ASLR) var isForceIntegrityWithoutAslr = false; if ((dllCharacteristics & IMAGE_DLLCHARACTERISTICS_FORCE_INTEGRITY) && !(dllCharacteristics & IMAGE_DLLCHARACTERISTICS_DYNAMIC_BASE)) { log(logType.any, "Force integrity is enabled without ASLR"); isForceIntegrityWithoutAslr = true; } if (isForceIntegrityWithoutAslr) options = addOption(options, "Force integrity without ASLR"); // Control flow guard (0x4000) is only effective on ASLR images — MSVC always links CFG with DYNAMIC_BASE var isGuardCfWithoutAslr = false; if ((dllCharacteristics & IMAGE_DLLCHARACTERISTICS_GUARD_CF) && !(dllCharacteristics & IMAGE_DLLCHARACTERISTICS_DYNAMIC_BASE)) { log(logType.any, "CFG is enabled without ASLR"); isGuardCfWithoutAslr = true; } if (isGuardCfWithoutAslr) options = addOption(options, "CFG without ASLR"); // No-SEH flag (0x0400) is invalid on 64-bit images — they do not use table-based SEH var isNoSehOn64bit = false; if ((dllCharacteristics & IMAGE_DLLCHARACTERISTICS_NO_SEH) && PE_Cached.is64bit) { log(logType.any, "No-SEH is set on a 64-bit image"); isNoSehOn64bit = true; } if (isNoSehOn64bit) options = addOption(options, "No-SEH on 64-bit image"); // Check for strange exports (Non-ASCII names) var isStrangeExportsPresent = false; if (PE_Cached.numberOfUnmanagedExports > 0) { for (var i = 0; i < PE_Cached.numberOfUnmanagedExports && !isStrangeExportsPresent; i++) { const functionName = PE.getExportFunctionName(i); if (functionName && (/^\d/.test(functionName) || !isAsciiString(functionName))) { log(logType.any, "Unusual export name detected: \"" + File.cleanString(functionName) + "\""); isStrangeExportsPresent = true; } } } if (isStrangeExportsPresent) options = addOption(options, "Strange exports"); // Looks for sections whose names contain strange (or invalid) characters var isStrangeImportsPresent = false; const badImportChars = '=~!@#$%^&*()"№;%:?*():;,|\'`<> '; for (var i = 0; i < PE_Cached.numberOfUnmanagedImports && !isStrangeImportsPresent; i++) { const libraryName = PE.getImportLibraryName(i); if (isAsciiString(libraryName)) { for (var l = 0; l < badImportChars.length && !isStrangeImportsPresent; l++) { if (libraryName.indexOf(badImportChars[l]) !== -1) { log(logType.any, "Unusual import library name detected: \"" + File.cleanString(libraryName) + "\""); isStrangeImportsPresent = true; } } } else if (libraryName.indexOf("MZ") !== 0) { // 'MZ' -> IAT corrupted log(logType.any, "Non-ASCII import library name detected"); isStrangeImportsPresent = true; } } if (isStrangeImportsPresent) options = addOption(options, "Strange imports"); // Check for strange resources var isStrangeResourcesPresent = false; for (var i = 0; i < PE_Cached.numberOfUnmanagedResources && !isStrangeResourcesPresent; i++) { const resourceName = PE.getResourceNameByNumber(i); if (resourceName && (!isAsciiString(resourceName) || isNameObfuscated(resourceName))) { log(logType.any, "Strange resource name: \"" + File.cleanString(resourceName) + "\""); isStrangeResourcesPresent = true; } } if (isStrangeResourcesPresent) options = addOption(options, "Strange resources"); // Check for zero linker version var isZeroLinkerVersionPresent = false; if (PE.getMajorLinkerVersion() === 0 && PE.getMinorLinkerVersion() === 0) { log(logType.any, "Linker version is zero"); isZeroLinkerVersionPresent = true; } if (isZeroLinkerVersionPresent) options = addOption(options, "Zero linker version"); // Check for .NET metadata presence var isNetMetaDataInNativePresent = false; if (isNetMetaDataPresent() && PE_Cached.numberOfUnmanagedImports > 0) { log(logType.net, "Managed metadata detected in a native image"); isNetMetaDataInNativePresent = true; } if (isNetMetaDataInNativePresent) options = addOption(options, "DotNET meta"); // Check for .NET runtime attach functions var isNetRuntimeAttachFunctionPresent = false; if (PE.isFunctionPresent("_CorExeMain") || PE.isFunctionPresent("_CorDllMain")) { log(logType.net, ".NET runtime attachment import detected"); isNetRuntimeAttachFunctionPresent = true; } if (isNetRuntimeAttachFunctionPresent) options = addOption(options, "DotNET runtime attach"); // ASM Guard fake signatures if (PE.section[".asmg"] || PE.section["ASMGUARD"]) { for (var f = 0; f < 3; f++) _removeResult("packer", ["UPX", "MPRESS", "EP:MPRESS"][f]); } if (options.length !== 0) isDetected = true; if (isDetected) { _setResult("~protection", "Generic", String(), PE.isVerbose() ? options : String()); } } // #endregion /** * Returns the architecture label reported by the PE parser. * * @returns {string} The first `getOperationSystemOptions()` field, or an empty string. */ function getPeArchitecture() { const operationSystemOptions = PE.getOperationSystemOptions(); return operationSystemOptions ? operationSystemOptions.split(',')[0] : String(); } /** * Returns the architecture-aware assembly language label used by fallback heuristics. * * @returns {string} A generic or architecture-specific ASM label. */ function getAsmLanguageName() { switch (PE_Cached.architecture) { case "I386": // x86 return "ASMx86"; case "AMD64": // x86_64 return "ASMx64"; case "ARM": // ARM return "ASM ARM"; case "ARMNT": // ARMv7 Thumb-2 case "THUMB": // ARM Thumb return "ASM Thumb"; case "ARM64": // AArch64 return "ASM ARM64"; default: return "ASM"; } } const _patternSplitter = "|"; /** * Generates a delimiter-bounded pattern of instructions or opcodes from the entry point. * * @param {boolean} onlyOpCodes - If true, only the opcodes of the instructions are included in the pattern. * @param {number} numberOf - The number of instructions to include in the pattern. * @returns {string} A `|`-delimited pattern, truncated if disassembly ends early. * @example * emulateAndGetEpAsmPattern(true, 2); // "|PUSH|MOV|" */ function emulateAndGetEpAsmPattern(onlyOpCodes, numberOf) { // Initialize the result with a pattern splitter var result = _patternSplitter; // Iterate through instructions up to the specified number for (var i = 0; i < numberOf; i++) { // Reuse the shared linear cache instead of decoding the same EP prefix again. const asmInstruction = emulateAndGetAsmInstructionByIndex(i); if (!asmInstruction) break; // Safety check // Append either the opcode or the full instruction to the result result += ( onlyOpCodes ? getAsmOpCode(asmInstruction) : // "MOV" asmInstruction // "MOV EAX, 4" ) + _patternSplitter; } // Return the generated assembly pattern return result; } var epAsmInstructions = [], epAsmInstructionAddresses = []; /** * Retrieves the assembly instruction at the specified zero-based index from the PE entry point. * * @param {number} index - The index of the assembly instruction to retrieve. * @returns {string|undefined} The disassembled instruction, or undefined when unavailable. * @sideEffects Caches retrieved instructions and addresses in the global EP arrays. * @example * emulateAndGetAsmInstructionByIndex(0); // "PUSH EBP" */ function emulateAndGetAsmInstructionByIndex(index) { // Check if the instruction is already cached if (epAsmInstructions[index] !== undefined) { return epAsmInstructions[index] || undefined; } var firstUncachedIndex = epAsmInstructions.length, disasmAddress = PE_Cached.addressOfUnmanagedEntryPoint; // A failed decode terminates the physical instruction stream. Remembering that boundary // prevents every later request from retrying the same invalid address. if (firstUncachedIndex !== 0) { if (!epAsmInstructions[firstUncachedIndex - 1]) return undefined; disasmAddress = PE.getDisasmNextAddress(epAsmInstructionAddresses[firstUncachedIndex - 1]); } // Extend the cache only from its current tail. Sequential consumers therefore walk each // instruction boundary once instead of restarting from EP for every requested index. for (var i = firstUncachedIndex; i <= index; i++) { if (i !== firstUncachedIndex) disasmAddress = PE.getDisasmNextAddress(disasmAddress); epAsmInstructionAddresses[i] = disasmAddress; epAsmInstructions[i] = PE.getDisasmString(disasmAddress); if (!epAsmInstructions[i]) break; // Safety check } return epAsmInstructions[index] || undefined; } /** * Generates a subpattern string to search for instructions within a pattern divided by a separator. * * This function allows you to create a subpattern that can be used to search for specific instructions * within a larger pattern string that is divided by a separator (_patternSplitter). * * Examples: * - "|OPCODE1|OPCODE2|OPCODE3|".indexOf("|OPCODE2|") * - "|OPCODE1|OPCODE2|OPCODE3|".indexOf(getInstructionsAsmPattern("OPCODE2")) * - "|OPCODE1|OPCODE2|OPCODE3|".indexOf("|OPCODE2|OPCODE3|") * - "|OPCODE1|OPCODE2|OPCODE3|".indexOf(getInstructionsAsmPattern(["OPCODE2", "OPCODE3"])) * * @param {string|string[]} instruction - The instruction or array of instructions to create the subpattern for. * @returns {string} The generated subpattern string. */ function getInstructionsAsmPattern(instruction) { return _patternSplitter + ( Array.isArray(instruction) ? instruction.join(_patternSplitter) : instruction ) + _patternSplitter; } /** * Retrieves the first disassembled instruction at the PE entry point. * * @returns {string|undefined} The instruction text, or undefined if it cannot be disassembled. * @example * getFirstEpAsmInstruction(); // "JMP 0x401000" */ function getFirstEpAsmInstruction() { return emulateAndGetAsmInstructionByIndex(0); // return PE.getDisasmString(PE.OffsetToVA(PE.getEntryPointOffset())); } /** * Extracts the leading opcode token from a disassembled instruction. * * @param {string} instruction - The assembly instruction. * @returns {string|undefined} The opcode, the original one-token instruction, or a falsy input. * @example * getAsmOpCode("MOV EAX, 4"); // "MOV" */ function getAsmOpCode(instruction) { if (!instruction) return instruction; const separatorIndex = instruction.indexOf(" "); return separatorIndex === -1 ? instruction : instruction.substring(0, separatorIndex); } /** * Retrieves the first assembly operation code from the entry point of the executable. * * @returns {string} The assembly operation code of the first instruction at the entry point. */ function getFirstEpAsmOpCode() { return getAsmOpCode(PE_Cached.firstEpAsmInstruction); } /** * Checks if an item pattern appears to be a mangled symbol name. * * @param {string} itemPattern - The pattern string to check for mangling * @returns {boolean} True if the pattern appears to be a mangled symbol, false otherwise */ function isItemMangled(itemPattern) { // Check if the pattern is too short to be mangled if (itemPattern.length <= 5) { return false; } return ( (itemPattern[0] === '?' && (itemPattern.indexOf("@@") !== -1 || itemPattern[1] === '?')) || // MSVC mangling: starts with '?' and contains "@@" or has double '?' (itemPattern.substring(0, 2) === "_Z") || // GNU mangling: starts with "_Z" (itemPattern[0] === '@' && itemPattern.indexOf('$') !== -1) || // Borland C++ mangling: starts with '@' and contains '$' (itemPattern.substring(0, 2).toLowerCase() === '$s') // Swift mangling: starts with '$s' or '$S' ); } /** * Returns information about the compiler and language based on a mangled symbol pattern. * * @param {string} itemPattern - The mangled symbol pattern to analyze. * @returns {{compiler: string, language: string} | undefined} An object containing the compiler and language if recognized, otherwise undefined. */ function getManglerInfoByMangledItem(itemPattern) { if (!isItemMangled(itemPattern)) { return undefined; } // Visual C++ (MSVC) if (itemPattern[0] === '?') { return { compiler: "Microsoft Visual C++", language: "C++" }; } // MinGW (GNU) if (itemPattern.substring(0, 2) === "_Z") { return { compiler: "MinGW", language: "C++" }; } // Borland C++ if (itemPattern[0] === '@' && itemPattern.indexOf('$') !== -1) { return { compiler: "Borland C++", language: "C++" }; } // Swift if (itemPattern.substring(0, 2).toLowerCase() === '$s') { return { compiler: "Swift", language: "Swift" }; } return undefined; } /** * Extracts the unmangled name from a mangled C++ symbol. * * This function handles both Microsoft Visual C++ (MSVC) and GNU C++ (GCC) name mangling schemes: * - MSVC mangled names start with '?' and include patterns for functions, constructors, destructors, operators, and templates * - GCC mangled names start with '_Z' and include patterns for STL functions, namespaced functions, and regular functions * - Swift mangled names start with '$s' or '$S' and include patterns for types, functions, properties, and protocols * * @param {string} itemPattern - The mangled symbol name to decode * @returns {string} The unmangled function/symbol name, or the original pattern if it's not mangled or cannot be decoded * * @example * // MSVC examples * getNameOfMangledItem('?myFunction@@YAXXZ') // returns 'myFunction' * getNameOfMangledItem('??0MyClass@@QAE@XZ') // returns 'MyClass' (constructor) * getNameOfMangledItem('??$myTemplate@H@@YAXXZ') // returns 'myTemplate' * * @example * // GCC examples * getNameOfMangledItem('_Z9myFunctionv') // returns 'myFunction' * getNameOfMangledItem('_ZSt4cout') // returns 'cout' * getNameOfMangledItem('_ZN3std4cout') // returns 'cout' * * @example * // Borland examples * getNameOfMangledItem('@func$qi') // returns 'func' * getNameOfMangledItem('@Class@method$qpc') // returns 'method' * getNameOfMangledItem('@Class@$bctr$qi') // returns 'Class' (constructor) * * @example * // Swift examples * getNameOfMangledItem('$s11MaskStorages4SIMDPTl') // returns 'SIMD' * getNameOfMangledItem('$ss9_IndexBoxC6_unboxqd__SgySLRd__lF') // returns '_unbox' * getNameOfMangledItem('$sSn11descriptionSSvpMV') // returns 'description' */ function getNameOfMangledItem(itemPattern) { if (!isItemMangled(itemPattern)) { return itemPattern; } // MSVCPP mangled names start with '?' if (itemPattern[0] === '?') { // Regular function: ?myFunction@@YAXXZ if (itemPattern[1] !== '?') { return itemPattern.split("?")[1].split("@")[0]; } // Handle all ?? patterns if (itemPattern[1] === '?') { var parts, nameWithPrefix; // Simple template: ??$myTemplate@H@@YAXXZ if (itemPattern[2] === '$') { return itemPattern.split("$")[1].split("@")[0]; } // Complex template: ??0?$basic_string@DU?$char_traits@D@std@@V?$allocator@D@2@@std@@QAE@XZ if (itemPattern.indexOf('?$') !== -1) { var complexMatch = itemPattern.match(/\?\?\d\?\$([^@]+)@/); if (complexMatch) { return complexMatch[1]; } } // Special mangler: ??_7MyClass@@6B@, ??_EMyClass@@QAEPAXI@Z if (itemPattern[2] === '_') { parts = itemPattern.split("?"); if (parts.length >= 3) { nameWithPrefix = parts[2].split("@")[0]; // For _E, _G, _R patterns (destructor, vector destructor, RTTI), strip the prefix if (nameWithPrefix.length > 1 && /[EGR]/.test(nameWithPrefix[1])) { return nameWithPrefix.substring(2); } // For _7 and other patterns, remove underscore and digit var nameAfterUnderscore = nameWithPrefix.substring(2); return nameAfterUnderscore.indexOf('_') === 0 ? nameAfterUnderscore.substring(1) : nameAfterUnderscore; } } // Constructor/destructor/operators: ??0MyClass@@QAE@XZ, ??1MyClass@@QAE@XZ, ??2@YAPAXI@Z if (/[0-9A-D]/.test(itemPattern[2])) { parts = itemPattern.split("?"); if (parts.length >= 3) { nameWithPrefix = parts[2].split("@")[0]; // Global operator (single character) if (nameWithPrefix.length === 1 && /[0-9A-D]/.test(nameWithPrefix[0])) { var operatorNames = { '2': 'operator_new', '3': 'operator_delete', '4': 'operator_assign', '5': 'operator_shift_left', '6': 'operator_shift_right', '8': 'operator_equal', '9': 'operator_not_equal', 'A': 'operator_subscript', 'B': 'operator_not', 'C': 'operator_cast', 'D': 'operator_cast_const' }; return operatorNames[nameWithPrefix[0]] || 'operator_unknown'; } // Class functions - remove first character return nameWithPrefix.substring(1); } } // Fallback parts = itemPattern.split("?"); if (parts.length >= 3) { var name = parts[2].split("@")[0]; return (name.length > 0 && /[0-9A-D]/.test(name[0])) ? name.substring(1) : name; } } } // GNUCPP mangled names start with '_Z' if (itemPattern.substring(0, 2) === "_Z") { // STL mangler: _ZSt4cout, _ZSt3minIiET_RKS0_S2_ if (itemPattern[2] === 'S' && itemPattern[3] === 't') { var stlMatch = itemPattern.match(/_ZSt(\d+)(.+)/); if (stlMatch) { return stlMatch[2].substring(0, parseInt(stlMatch[1], 10)); } } else if (itemPattern[2] === 'N') { // Namespace mangler: _ZN...E var components = [], remaining = itemPattern.substring(3); // Skip "_ZN" // Parse all components while (remaining.length > 0 && remaining[0] !== 'E' && /^\d/.test(remaining)) { var lengthMatch = remaining.match(/^(\d+)/); if (lengthMatch) { var length = parseInt(lengthMatch[1], 10), nameStart = lengthMatch[1].length; if (remaining.length >= nameStart + length) { var name = remaining.substring(nameStart, nameStart + length); components.push(name); remaining = remaining.substring(nameStart + length); } else { break; } } else { break; } } // Return the last component (function name) if (components.length > 0) { return components[components.length - 1]; } } else { // Regular mangler: _Z9myFunctionv, _Z3fooi var regularMatch = itemPattern.match(/_Z(\d+)(.+)/); if (regularMatch) { var funcNameLength = parseInt(regularMatch[1], 10); return regularMatch[2].substring(0, funcNameLength); } } } // Borland C++ mangled names start with '@' if (itemPattern[0] === '@') { var parts = itemPattern.split('@'); // Simple function: @func$qi if (parts.length === 2) { return parts[1].split('$')[0]; } // Class method: @Class@method$qpc if (parts.length >= 3) { // Strip leading '$' before splitting (e.g. "$bctr$qi" → "bctr$qi") var methodName = parts[2].replace(/^\$/, '').split('$')[0]; // Constructor: @Class@$bctr$... if (methodName === 'bctr') { return parts[1]; // Return class name } // Destructor: @Class@$bdtr$... if (methodName === 'bdtr') { return '~' + parts[1]; // Return destructor name } // Regular method return methodName; } } // Swift mangled names start with '$s' or '$S' if (itemPattern.substring(0, 2).toLowerCase() === '$s') { var pos = 2, identifiers = []; // Parse length-prefixed identifier function parseIdentifier(str, startPos) { if (startPos >= str.length || !/^\d/.test(str[startPos])) { return null; } var match = str.substring(startPos).match(/^(\d+)/); if (!match) return null; var len = parseInt(match[1], 10), digitLen = match[1].length; if (startPos + digitLen + len > str.length) { return null; } return { name: str.substring(startPos + digitLen, startPos + digitLen + len), nextPos: startPos + digitLen + len }; } // Parse ALL length-prefixed identifiers, skipping markers/types/substitutions // We want to collect ALL readable names and return the last one var loopLimit = 50; // Safety limit to prevent infinite loops while (pos < itemPattern.length && loopLimit-- > 0) { var char = itemPattern[pos]; // Try to parse length-prefixed identifier if (/^\d/.test(char)) { var result = parseIdentifier(itemPattern, pos); if (result) { identifiers.push(result.name); pos = result.nextPos; continue; } } // Skip substitution references (x, q, y, z and combinations like qd__, yz, etc.) if (/^[xqyz]/.test(char)) { pos++; // Skip additional substitution characters while (pos < itemPattern.length && /^[xqyzd_0-9]/.test(itemPattern[pos])) { pos++; } continue; } // Skip single-letter markers if followed by digit or known pattern if (/^[a-zA-Z]$/.test(char)) { var nextChar = pos + 1 < itemPattern.length ? itemPattern[pos + 1] : String(); // Check for stdlib type codes (2 letters starting with S) if (char === 'S' && /^[a-zA-Z]/.test(nextChar)) { var afterStdlib = pos + 2 < itemPattern.length ? itemPattern[pos + 2] : String(); // If after stdlib type there's a marker + digit, skip stdlib and continue if (/^[a-z]$/.test(afterStdlib)) { var afterMarker = pos + 3 < itemPattern.length ? itemPattern[pos + 3] : String(); if (/^\d/.test(afterMarker)) { pos += 3; // Skip Sx + marker continue; } } // If directly followed by digit, skip stdlib type if (/^\d/.test(afterStdlib)) { pos += 2; continue; } } // Single letter followed by digit - skip it if (/^\d/.test(nextChar)) { pos++; continue; } } // Can't parse further - stop break; } // Return the last identifier (most specific name) if (identifiers.length > 0) { return identifiers[identifiers.length - 1]; } // Fallback: check for stdlib type codes at current position if (pos < itemPattern.length && itemPattern[pos] === 'S' && pos + 1 < itemPattern.length) { var typeCode = itemPattern.substring(pos, pos + 2); var stdlibTypes = { 'Sa': 'Array', 'Sb': 'Bool', 'Sc': 'UnicodeScalar', 'Sd': 'Double', 'Sf': 'Float', 'Sg': 'Optional', 'Sh': 'Set', 'Si': 'Int', 'SL': 'Collection', 'Sl': 'Slice', 'Sm': 'Mirror', 'Sn': 'Numeric', 'So': 'ObjectiveC', 'Sp': 'UnsafePointer', 'Sq': 'Optional', 'SR': 'Sequence', 'Sr': 'UnsafeRawPointer', 'Ss': 'String', 'SS': 'String', 'St': 'Range', 'ST': 'Equatable', 'Su': 'UInt', 'Sv': 'UnsafeRawBufferPointer', 'Sw': 'UnsafeBufferPointer', 'Sx': 'Strideable', 'SY': 'RawRepresentable', 'Sy': 'RawRepresentable', 'Sz': 'BinaryInteger', 'SD': 'Dictionary', 'SE': 'Encodable', 'SH': 'Hashable', 'Sk': 'KeyPath', 'SK': 'KeyPath' }; if (stdlibTypes[typeCode]) { return stdlibTypes[typeCode]; } } } return itemPattern; } /** * Matches import-position hashes against a signature database collection. * * Each entry stores the import position at index 2 and either one hash or an * array of acceptable hashes at index 3. The first matching entry is returned. * * @param {Array} dbCollection - The database collection to validate, where each entry is an array containing at least a position and hash. * @returns {Array|null} The first entry with a matching import position hash, or null if none found. * @example * validateImportHashes([["UPX", null, 0, 0xD4FDCAB1]]); */ function validateImportHashes(dbCollection) { for (var i = 0; i < dbCollection.length; i++) { var position = dbCollection[i][2], hash = dbCollection[i][3]; if (Array.isArray(hash)) { for (var j = 0; j < hash.length; j++) { if (PE.isImportPositionHashPresent(position, hash[j])) return dbCollection[i]; } } else if (PE.isImportPositionHashPresent(position, hash)) { return dbCollection[i]; } } return null; } /** * Matches PE section names against string or regular-expression signatures in a database collection. * * An entry whose name at index 0 is null acts as an exclusion and immediately * cancels the match. Entries with more than three fields are reported as malformed. * * @param {Array} dbCollection - The database collection to validate, where each item is expected to have a section signature at index 2. * @returns {Array|null} The first matching item from the collection, or null if no match is found. * @example * validateSectionNames([["UPX", null, "UPX0"]]); */ function validateSectionNames(dbCollection) { var result = null; for (var i = 0; i < dbCollection.length; i++) { if (dbCollection[i].length > 3) { stdout(">>> Invalid section signature entry for \"" + dbCollection[i][0] + "\": expected 3 elements, but found " + dbCollection[i].length + ". Extra signatures will be ignored. Use [ and ] to define a signature collection. <<<"); } var sectionSignature = dbCollection[i][2]; if (Array.isArray(sectionSignature)) { for (var j = 0; j < sectionSignature.length; j++) { if ((typeof sectionSignature[j] === "string" && PE.isSectionNamePresent(sectionSignature[j])) || (sectionSignature[j] instanceof RegExp && PE.isSectionNamePresentExp(sectionSignature[j]))) { if (dbCollection[i][0] === null) return null; if (result === null) result = dbCollection[i]; } } } else if ((typeof sectionSignature === "string" && PE.isSectionNamePresent(sectionSignature)) || (sectionSignature instanceof RegExp && PE.isSectionNamePresentExp(sectionSignature))) { if (dbCollection[i][0] === null) return null; if (result === null) result = dbCollection[i]; } } return result; } /** * Matches PE resource names against string or regular-expression signatures in a database collection. * * An entry whose name at index 0 is null acts as an exclusion and immediately * cancels the match. Entries with more than three fields are reported as malformed. * * @param {Array} dbCollection - The database collection to validate, where each item is expected to have a resource signature at index 2. * @returns {Array|null} The first matching item from the collection, or null if no match is found. * @example * validateResourceNames([["Installer", null, /^SETUP$/i]]); */ function validateResourceNames(dbCollection) { var result = null; for (var i = 0; i < dbCollection.length; i++) { if (dbCollection[i].length > 3) { stdout(">>> Invalid resource signature entry for \"" + dbCollection[i][0] + "\": expected 3 elements, but found " + dbCollection[i].length + ". Extra signatures will be ignored. Use [ and ] to define a signature collection. <<<"); } var resourceSignature = dbCollection[i][2]; if (Array.isArray(resourceSignature)) { for (var j = 0; j < resourceSignature.length; j++) { if ((typeof resourceSignature[j] === "string" && PE.isResourceNamePresent(resourceSignature[j])) || (resourceSignature[j] instanceof RegExp && PE.isResourceNamePresentExp(resourceSignature[j]))) { if (dbCollection[i][0] === null) return null; if (result === null) result = dbCollection[i]; } } } else if ((typeof resourceSignature === "string" && PE.isResourceNamePresent(resourceSignature)) || (resourceSignature instanceof RegExp && PE.isResourceNamePresentExp(resourceSignature))) { if (dbCollection[i][0] === null) return null; if (result === null) result = dbCollection[i]; } } return result; } /** * Matches complete .NET object-name collections against a signature database collection. * * Regular expressions are intentionally unsupported: every string in the collection stored * at index 2 must identify an exact metadata object, and every object must be present. * Entries with more than three fields are reported as malformed. * * @param {Array} dbCollection - The database collection to validate, where each item is expected to have an object-name collection at index 2. * @returns {Array|null} The first matching item from the collection, or null if no match is found. * @example * validateNetObjectNames([["ConfuserEx", null, ["Decrypt", "LzmaDecoder"]]]); */ function validateNetObjectNames(dbCollection) { for (var i = 0; i < dbCollection.length; i++) { if (dbCollection[i].length > 3) { stdout(">>> Invalid .NET object signature entry for \"" + dbCollection[i][0] + "\": expected 3 elements, but found " + dbCollection[i].length + ". Extra signatures will be ignored. Use [ and ] to define an object-name collection. <<<"); } var objectNames = dbCollection[i][2]; if (Array.isArray(objectNames) && isAllNetObjectsPresent(objectNames)) return dbCollection[i]; } return null; } /** * Calculates the file offset of the PE optional header. * * The function reads the e_lfanew field at offset 0x3C to find the PE header start, * then adds 4 bytes for the PE signature and 20 bytes for the COFF header to locate * the start of the optional header. * * @returns {number} The offset in bytes, derived from the DOS `e_lfanew` field. * @example * getOptHeaderOffset(); // e_lfanew + 24 */ function getOptHeaderOffset() { return PE.read_int32(0x3C) + 4 + 20; } /** * Checks whether the PE Optional Header contains a non-empty CLR Runtime Header directory. * * This low-level fallback reads `NumberOfRvaAndSizes`, then the CLR directory's * RVA and size. It supports PE32 and PE32+ and rejects header offsets outside * the file bounds. It does not validate the directory contents. * * @returns {boolean} True if the declared CLR directory has non-zero RVA and size; otherwise false. */ function isNetMetaDataPresent() { var optHeaderOffset = getOptHeaderOffset(); var numRvaAndSizesOffset, clrDirOffset; if (!PE_Cached.is64bit) { numRvaAndSizesOffset = optHeaderOffset + 0x5C; clrDirOffset = optHeaderOffset + 0xD0; // CLR offset for PE32 } else { numRvaAndSizesOffset = optHeaderOffset + 0x6C; clrDirOffset = optHeaderOffset + 0xE0; // CLR offset for PE64 } // Check if the offsets are within the bounds of the PE file size if (clrDirOffset + 8 > PE_Cached.fileSize || numRvaAndSizesOffset + 4 > PE_Cached.fileSize) { return false; } // Check if the number of RVA and sizes is valid if (PE.read_int32(numRvaAndSizesOffset) < 15) { return false; } // Read CLR directory RVA and size var clrRva = PE.read_int32(clrDirOffset), clrSize = PE.read_int32(clrDirOffset + 4); // Validate CLR directory RVA and size return clrRva !== 0 && clrSize !== 0; } /** * Checks if a compiler has been detected in the analysis results. * * @returns {boolean} Returns true if any compiler or compiler-related results are found, false otherwise. */ function isCompilerDetected() { return _getNumberOfResults("compiler") > 0 || _getNumberOfResults("~compiler") > 0; } /** * Checks if a linker has been detected in the analysis results. * * @returns {boolean} Returns true if any linker or linker-related results are found, false otherwise. */ function isLinkerDetected() { return _getNumberOfResults("linker") > 0 || _getNumberOfResults("~linker") > 0; } // #region "languages and compilers (.NET and Native)" function scanForLanguagesAndCompilers_NET_and_Native() { log(logType.nothing, "Scanning for programming language..."); var c_cpp = _isLangPresent("C/C++"); // Unknown; C or C++ const extdb = [ ["C++", "cpp"], ["Rust", "rs"], ["Java", "jar"], ["Python", "pyd"], ["Kotlin", "kt"] ]; for (var i = 0; i < extdb.length; i++) { const langName = extdb[i][0], langExtName = extdb[i][1]; if (PE.isSignaturePresent(PE_Cached.dosStubSize, PE_Cached.imageBodySize, "%% %% %% %% %% %% %% %% '." + langExtName + "' 00")) { log(logType.any, "Lines of ." + langExtName + " files (" + langName + ") detected"); _setLangByHeur(langName); } } if (!_isLangDetected("C++") && PE.isSignaturePresent(PE_Cached.dosStubSize, PE_Cached.imageBodySize, "%% %% %% %% %% %% %% %% %% %% '.c' 00")) { log(logType.any, "Lines of .c files (C) detected (not a C++)"); _setLangByHeur("C"); } for (var i = 0; i < PE_Cached.numberOfUnmanagedResources; i++) { var resourceOffset = PE.getResourceOffsetByNumber(i); if (resourceOffset !== -1) { var resourceSignature = PE.getString(resourceOffset, 0x40); if (resourceSignature.split(" ")[0] === "object" && resourceSignature.indexOf(": ") !== -1) { _setLangByHeur("Object Pascal"); break; } } } var isPpLibraryPresent = false, isCLibraryPresent = false; for (var i = 0; i < PE_Cached.numberOfUnmanagedImports; i++) { const libraryName = PE.getImportLibraryName(i).toLowerCase(); // Detect mangler in imported libraries for (var k = 0; k < PE.getNumberOfImportThunks(i) && !isPpLibraryPresent; k++) { const functionName = PE.getImportFunctionName(i, k); // import, thunk if (isItemMangled(functionName)) { log(logType.any, "Mangler detected -> \"" + libraryName + "\", at function \"" + getNameOfMangledItem(functionName) + "\""); var isNotCpp = false; if (!isCompilerDetected()) { var manglerInfo = getManglerInfoByMangledItem(functionName); if (manglerInfo) { _setResult("~compiler", manglerInfo.compiler, String(), String()); if (!_isLangDetected()) _setLangByHeur(manglerInfo.language); if (manglerInfo.language !== "C++") isNotCpp = true; } } isPpLibraryPresent = !isNotCpp; // if language is unknown } } if ( libraryName.indexOf("++") !== -1 || libraryName.indexOf("cpp") !== -1 || libraryName.indexOf("msvcp") !== -1 ) { log(logType.any, "C++ library present -> \"" + libraryName + "\""); isPpLibraryPresent = true; } if (libraryName.indexOf("msvcr") !== -1 || libraryName.indexOf("crtdll") !== -1) { log(logType.any, "C library present -> \"" + libraryName + "\""); isCLibraryPresent = true; } } // Detect mangler in exports for (var e = 0; e < PE_Cached.numberOfUnmanagedExports && !isPpLibraryPresent; e++) { var exportName = PE.getExportFunctionName(e); if (exportName && isItemMangled(exportName)) { log(logType.any, "Mangler detected at exported item \"" + getNameOfMangledItem(exportName) + "\""); var isNotCpp = false; if (!isCompilerDetected()) { var manglerInfo = getManglerInfoByMangledItem(exportName); if (manglerInfo) { _setResult("~compiler", manglerInfo.compiler, String(), String()); if (!_isLangDetected()) _setLangByHeur(manglerInfo.language); if (manglerInfo.language !== "C++") isNotCpp = true; } } isPpLibraryPresent = !isNotCpp; } } // Detect compilers by Rich signature if (!isCompilerDetected() && PE_Cached.isRichSignaturePresent) { for (var j = 0; j < PE_Cached.numberOfRichIDs; j++) { const richId = PE.getRichID(j), richVersion = PE.getRichVersion(j), richCount = PE.getRichCount(j); if (richId === 0x000d && richVersion === 9782 && richCount === 0x0001) { log(logType.any, "Microsoft Visual Basic 6.0 build metadata detected"); _setResult("~compiler", "Microsoft Visual Basic", "6.0", String()); _setLangByHeur("VB"); break; } else if (richId === 0x0103) { log(logType.any, "MASM build metadata detected"); _setResult("~compiler", "MASM", String(), String()); break; } } } // Detect IL2CPP by sections (thanks to AyukiDev) if (!isCompilerDetected()) { var isIl2cpp = false; if (PE.isSectionNamePresentExp(/^\.rtc\$[IT](?:AA|ZZ)$/)) { isIl2cpp = true; } else { const il2cppSections = [ "il2cpp", ".text$mn", ".rdata$zzzdbg" ]; for (var i = 0; i < il2cppSections.length && !isIl2cpp; i++) { if (PE.isSectionNamePresent(il2cppSections[i])) { isIl2cpp = true; } } } if (isIl2cpp) { log(logType.any, "IL2CPP build characteristics detected"); _setResult("~compiler", "IL2CPP Technology", String(), String()); _setLangByHeur("Native MSIL/C#"); } } var isCLikeLibsNotFound = !isCLibraryPresent && !isPpLibraryPresent; var majorLinkerVersion = PE.getMajorLinkerVersion(), minorLinkerVersion = PE.getMinorLinkerVersion(); if (!isCompilerDetected() && !isLinkerDetected() && !PE_Cached.isDotNet) { var ehFrameSection = PE.section[".eh_frame"], buildIdSection = PE.section[".build-id"]; if (!ehFrameSection && !buildIdSection && ( PE_Cached.isRichSignaturePresent || PE.compare("'MZ'90000300000004000000FFFF0000B800000000000000400000000000000000000000000000000000000000000000000000000000000000000000....00000E1FBA0E00B409CD21B8014CCD21'This program cannot be run in DOS mode.\r\r\n$'00000000") || PE.compare("'MZ'90000300000004000000FFFF0000B800000000000000400000000000000000000000000000000000000000000000000000000000000000000000....000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000") || PE.isSectionNamePresent(".00cfg") )) { if (PE.isSectionNamePresent(".gfids") || PE.isSectionNamePresent(".giats") || PE.isSectionNamePresent(".gljmp") || PE.isSectionNamePresent(".voltbl")) { log(logType.any, "Microsoft Visual Studio build markers detected"); _setResult("~tool", "Microsoft Visual Studio", String(), String()) } log(logType.any, "Microsoft linker and compiler characteristics detected"); _setResult("~linker", "Microsoft Linker", (majorLinkerVersion !== 0 ? (majorLinkerVersion + "." + minorLinkerVersion) : String()), String()); _setResult("~compiler", "Microsoft Visual C/C++", String(), String()); if (!_isLangDetected() && isCLikeLibsNotFound) { if (isNetMetaDataPresent()) { _setLangByHeur("MSIL/C/C++"); } else { _setLangByHeur("C/C++"); } } } else if ( ehFrameSection || buildIdSection || ( PE.isSectionNamePresent(".CRT") && PE.isSectionNamePresent(".rdata") && PE.isSectionNamePresent(".xdata") && PE.isSectionNamePresent(".idata") && (PE.isSectionNamePresent(".tls") || PE.isSectionNamePresent(".bss")) || PE.isSectionNamePresent(".buildid") )) { log(logType.any, "MinGW toolchain characteristics detected"); _setResult("~compiler", "MinGW", String(), String()); if (!_isLangDetected() && isCLikeLibsNotFound) { _setLangByHeur("C/C++"); } } else if (PE.isSectionNamePresent(".flat") && !isPpLibraryPresent) { log(logType.any, "FASM toolchain characteristics detected"); _setResult("~compiler", "FASM", String(), String()); _setLangByHeur(getAsmLanguageName()); } } const rdataSection = PE.section[".rdata"]; if (rdataSection) { if (c_cpp && // if C/C++ detected by DIE PE.isSignaturePresent( rdataSection.FileOffset, rdataSection.FileSize, convertStringToUnicodeSignature("Visual C++"))) { log(logType.any, "Embedded Visual C++ Runtime detected."); isPpLibraryPresent = true; // Visual C++ Runtime library in resources } } if (isPpLibraryPresent || (c_cpp && PE.isSignaturePresent(PE_Cached.dosStubSize, PE_Cached.imageBodySize, "' C++ '"))) { _setLangByHeur("C++"); } else if (!_isLangPresent("C++") && isCLibraryPresent && (PE.isFunctionPresent("_iob") || PE.isFunctionPresent("printf") || PE.isFunctionPresent("malloc") || PE.isFunctionPresent("memset"))) { _setLangByHeur("C"); } else if (PE.isLibraryPresentExp(/^api-ms-win-crt*/i) || PE.isSectionNamePresent(".msvcjmc") || PE.isSectionNamePresentExp(/\.CRT(?:\$[A-Z]{3})?$/i)) { _setLangByHeur("C/C++"); } else if (!_isLangDetected() && !_getNumberOfResults("compiler") && !PE_Cached.isDotNet) { if (!_getNumberOfResults("protector") && !_getNumberOfResults("cryptor") && !_getNumberOfResults("~cryptor") && !_getNumberOfResults("packer") && !_getNumberOfResults("~packer")) { _setLangByHeur(getAsmLanguageName()); } } } // #endregion /** * Sets the language based on heuristic analysis. * * @param {string} languageName - The name of the detected language. */ function _setLangByHeur(languageName) { log(logType.any, languageName + " language detected!"); _setLang(languageName, heurLabel); } /** * Adds a new option to the existing options text, separated by " + " if optionsText is not empty. * * @param {string} optionsText - The current options text. * @param {string} newOptionText - The new option to add. * @returns {string} The updated options text with the new option appended. */ function addOption(optionsText, newOptionText) { if (optionsText) optionsText += " + "; optionsText += newOptionText; return optionsText; } /** * Logs a message with a specific type and text. * * @param {number} messageTypeId - The type of the message. * -2: Error, * -1: About, * 1: Any, * 2: .NET, * 3: Instruction emulation. * @param {string} messageText - The text of the message to log. * Must not contain newline characters. * @throws Will throw an error if the messageText contains a newline character. */ function log(messageTypeId, messageText) { if (!messageText) { return; } else if (/\r|\n|\t/.test(messageText)) { messageText = messageText.replace(/[\r\n\t]+/g, " ").replace(/\s+/g, " ").trim(); // _error("Illegal char at log( ... )"); } var prefix = String(); if (messageTypeId !== logType.warning) { prefix = heurLabel; } if (messageTypeId > logType.warning && messageTypeId !== logType.nothing) { prefix += "/"; } switch (messageTypeId) { case logType.warning: prefix = "!"; break; case logType.about: prefix += "About"; break; case logType.any: prefix += "Any"; break; case logType.net: prefix += ".NET"; break; case logType.emulation: prefix += "EMU"; break; } if (typeof _log !== "undefined") { _log("[" + prefix + "] " + messageText); } else { _error("Unable to write log message"); } } // #region "malicious code (.NET and Native)" function scanForMaliciousCode_NET_and_Native() { log(logType.nothing, "Scanning for malicious code..."); var verdicts = []; const mayBeInfected = "May be infected, be careful!"; if (PE_Cached.isDotNet) { var isNjRatDetected = false, njRatVersion = String(); var njRatDataSeparator = "|'|'|", njRatDataSeparatorPattern = convertStringToUnicodeSignature(njRatDataSeparator) + "00"; njRatDataSeparatorPattern = njRatDataSeparatorPattern; // NjRAT Generic: Detect NjRAT via requests-separator or assembly name if (verdicts.length === 0) { if (PE.isNetUStringPresent("im523")) { njRatVersion = "0.7D Green Edition"; isNjRatDetected = true; } else if (PE_Cached.nameOfNetAssemblyName === "w" || PE_Cached.nameOfNetModuleName === "w.exe" || PE_Cached.nameOfNetAssemblyName === "k" || PE_Cached.nameOfNetModuleName === "k.exe" || PE.isNetObjectPresent("njLogger") || PE.isNetUStringPresent("|PWD| ") || PE.isNetUStringPresent(njRatDataSeparator) || PE.isSignatureInSectionPresent(0, njRatDataSeparatorPattern)) { isNjRatDetected = true; } } var requiredDotNetImports = [ "System.Diagnostics", "System.IO.Compression", "user32", "kernel32", "ntdll" // no ext ], foundImportsCount = 0, importSignature, pattern, allPatternImportsFound; for (var i = 0; i < requiredDotNetImports.length; i++) { importSignature = "00'" + requiredDotNetImports[i] + "'00"; if (PE.isSignatureInSectionPresent(0, importSignature)) { foundImportsCount++; } } if (verdicts.length === 0 && !njRatVersion && PE.isSignatureInSectionPresent(0, "00'System.Net.Sockets'00") && foundImportsCount >= 2) { var maliciousImportPatterns = [ { type: "NjRAT", edition: "0.7D Green Edition", references: [ "w", "System.Drawing", "System.Windows.Forms", "System.Runtime.InteropServices", "avicap32.dll", "capGetDriverDescriptionA", "kernel32", "KERNEL32.DLL", "ntdll", "user32", "GetWindowText", "EnumChildWindows", "SendMessage", "GetWindowTextLength", "user32.dll", "winmm.dll" ], isVbNet: true }, { type: "NjRAT", edition: "0.7D Lime Edition", references: [ "Stub", "System.Drawing", "System.Windows.Forms", "System.Runtime.InteropServices", "avicap32.dll", "capGetDriverDescriptionA", "kernel32", "ntdll", "NtSetInformationProcess", "user32", "MapVirtualKey", "ToUnicodeEx", "GetKeyboardState", "user32.dll", "wintrust.dll" ], isVbNet: true }, { type: "NjRAT", edition: "0.7D", references: [ "Stub", "System.Drawing", "System.Windows.Forms", "System.Runtime.InteropServices", "avicap32.dll", "capGetDriverDescriptionA", "kernel32", "ntdll", "NtSetInformationProcess", "user32", "MapVirtualKey", "ToUnicodeEx", "GetKeyboardState", "user32.dll" ], isVbNet: true }, { type: "NjRAT", edition: "0.11G-0.12G", references: [ "k", "System.Drawing", "System.Windows.Forms", "System.Runtime.InteropServices", "avicap32.dll", "capGetDriverDescriptionA", "kernel32", "ntdll", "NtsetInformationProcess", "user32", "MapVirtualKey", "ToUnicodeEx", "GetKeyboardState", "GetForegroundWindow", "GetWindowTextLengthA", "GetAsyncKeyState", "user32.dll" ], isVbNet: true }, { type: "NjRAT", edition: "0.7D Danger Edition", references: [ "Stub", "System.Drawing", "System.Windows.Forms", "System.Runtime.InteropServices", "avicap32.dll", "capGetDriverDescriptionA", "Kernel32.dll", "KERNEL32.DLL", "user32", "GetWindowText", "EnumChildWindows", "GetForegroundWindow", "SendMessage", "GetWindowTextLength", "SetWindowPos", "user32.dll", "winmm.dll" ], isVbNet: true }, { type: "NjRAT", edition: "0.7D Golden Edition", references: [ "Stub", "System.Drawing", "System.Windows.Forms", "System.Runtime.InteropServices", "avicap32.dll", "kernel32", "Kernel32.dll", "ntdll", "NtSetInformationProcess", "user32", "user32.dll", "GetForegroundWindow", "GetWindowTextA", "winmm.dll" ], isVbNet: true }, { type: "XWorm", edition: "3.0-5.0", references: [ "System.Core", "System.Drawing", "System.Management", "System.Windows.Forms", "System.Runtime.InteropServices", "avicap32.dll", "capCreateCaptureWindowA", "kernel32.dll", "SetThreadExecutionState", "user32.dll", "GetForegroundWindow", "GetWindowText" ], isVbNet: true }, { type: "VenomRAT", edition: "6.X", references: [ "Microsoft.CSharp", "System.Core", "System.Drawing", "System.Management", "System.Windows.Forms", "System.Runtime.InteropServices", "kernel32.dll", "SetThreadExecutionState", "ntdll.dll", "RtlSetProcessIsCritical", "user32.dll", "GetWindowText", "GetForegroundWindow" ], isVbNet: true }, { type: "RevengeRAT", edition: "0.3", references: [ "System.Management", "System.Windows.Forms", "avicap32.dll", "capGetDriverDescriptionA", "kernel32", "psapi", "EmptyWorkingSet", "user32", "GetWindowText" ], isVbNet: true }, { type: "Webremote TorCT", edition: undefined, references: [ "System.Windows.Forms", "avicap32.dll", "capGetDriverDescriptionA", "capCreateCaptureWindowA", "user32", "SendMessage", "SetWindowPos", "DestroyWindow", "user32.dll", "GetForegroundWindow", "GetWindowText" ], isVbNet: true }, { // NjRAT Generic 1 type: "NjRAT", edition: undefined, references: [ "System.Drawing", "System.Windows.Forms", "GetForegroundWindow", "System.Runtime.InteropServices", "System.Security.Cryptography", "GetWindowText", "avicap32.dll", "A", "kl", "OK" ], isVbNet: true }, { // NjRAT Generic 2 (with USB-spreader) type: "NjRAT", edition: undefined, references: [ "System.Drawing", "System.Windows.Forms", "GetForegroundWindow", "System.Runtime.InteropServices", "System.Security.Cryptography", "GetWindowText", "avicap32.dll", "A", "kl", "USB" ], isVbNet: true } ]; for (var j = 0; j < maliciousImportPatterns.length; j++) { pattern = maliciousImportPatterns[j]; allPatternImportsFound = true; for (var k = 0; k < pattern.references.length && allPatternImportsFound; k++) { importSignature = "00'" + pattern.references[k] + "'00"; if (!PE.isSignatureInSectionPresent(0, importSignature)) { allPatternImportsFound = false; } } // If not all pattern imports were found, skip this pattern if (!allPatternImportsFound) { continue; } // Check for VB.NET standard library references if (pattern.isVbNet && !PE_Cached.isVbNetStandardLibraryPresent) { continue; } verdicts.push({ type: pattern.type, version: pattern.edition ? pattern.edition : String(), details: String() }); break; } } if (verdicts.length === 0 && isNjRatDetected) { verdicts.push({ type: "NjRAT", version: njRatVersion, details: String() }); } if (verdicts.length === 0 && ( PE.isNetObjectPresent("avfucker") || ( PE.isNetUStringPresent("SharpInvoker") && PE.isNetUStringPresent("myres") ) )) { verdicts.push({ type: "RAT Injector", version: String(), details: String() }); } // https://malpedia.caad.fkie.fraunhofer.de/details/win.asyncrat if (verdicts.length === 0 && ( PE_Cached.nameOfNetAssemblyName === "AsyncClient" || PE_Cached.nameOfNetModuleName === "AsyncClient.exe" || (PE.isNetObjectPresent("Pastebin") && PE.isNetObjectPresent("Packet")) || (PE.isNetObjectPresent("Antivirus") && PE.isNetObjectPresent("Pong")) )) { verdicts.push({ type: "AsyncRAT", version: String(), details: String() }); } // https://malpedia.caad.fkie.fraunhofer.de/details/win.revenge_rat if (verdicts.length === 0 && ( PE_Cached.nameOfNetAssemblyName === "Nuclear Explosion" || PE_Cached.nameOfNetModuleName === "Nuclear Explosion.exe" )) { verdicts.push({ type: "RevengeRAT", version: "0.3", details: String() }); } // https://malpedia.caad.fkie.fraunhofer.de/details/win.stormkitty_rat if (verdicts.length === 0 && (PE.isNetUStringPresent("\nStormKitty v") || (PE.isNetUStringPresent("---") && PE.isNetUStringPresent("btc")))) { verdicts.push({ type: "StormKitty", version: String(), details: String() }); } // https://malpedia.caad.fkie.fraunhofer.de/details/win.nanocore if (verdicts.length === 0 && (PE.isNetObjectPresent("ClientLoaderForm") || PE_Cached.nameOfNetAssemblyName === "NanoCore Client")) { verdicts.push({ type: "NanoCore RAT", version: String(), details: String() }); } // https://malpedia.caad.fkie.fraunhofer.de/details/win.orcus_rat if (verdicts.length === 0 && (PE_Cached.nameOfNetModuleName === "Orcus.exe" || PE.isNetObjectPresent("Orcus.Connections") || PE.isNetUStringPresent(".orcusInstallation"))) { verdicts.push({ type: "Orcus RAT", version: "1.3-1.9.1", details: String() }); } // https://www.broadcom.com/support/security-center/protection-bulletin/liberium-rat-malware if (verdicts.length === 0 && (PE_Cached.nameOfNetModuleName === "Client.exe" && PE.isNetObjectPresent("System.Security.Cryptography.X509Certificates"))) { verdicts.push({ type: "Liberium RAT", version: String(), details: String() }); } // https://web.archive.org/web/20260605100749/https://github.com/0x25bit/Echelon-Stealer-v5 if (verdicts.length === 0 && (PE_Cached.nameOfNetModuleName === "Echelon.exe" || PE.isNetObjectPresent("TGrabber") || PE.isNetUStringPresent("\n\ud83d\udcc2 FileGrabber: "))) { verdicts.push({ type: "Echelon Stealer", version: String(), details: String() }); } if (verdicts.length === 0 && PE.isNetObjectPresent("button_unlock_Click")) { verdicts.push({ type: "Liberium WinLocker", version: String(), details: String() }); } if (verdicts.length === 0 && PE_Cached.nameOfNetModuleName === "Kheir.exe") { verdicts.push({ type: "jnRAT", version: String(), details: String() }); } // https://blackhatrussia.com/qatar-rat-2026/ if (verdicts.length === 0 && PE_Cached.nameOfNetModuleName === "QatarC.exe") { verdicts.push({ type: "Qatar RAT", version: "1.2", details: String() }); } // https://malpedia.caad.fkie.fraunhofer.de/details/win.darkrat if (verdicts.length === 0 && (PE_Cached.nameOfNetModuleName === "Client.exe" && PE.isNetObjectPresent("fusion"))) { verdicts.push({ type: "DarkRAT", version: String(), details: String() }); } // https://malpedia.caad.fkie.fraunhofer.de/details/win.icarus (??) if (verdicts.length === 0 && PE.isNetObjectPresent("tosuck")) { verdicts.push({ type: "ICARUS Evil Worm RAT", version: String(), details: String() }); } // https://malpedia.caad.fkie.fraunhofer.de/details/win.noneuclid_rat if (verdicts.length === 0 && PE.isNetObjectPresent("isVM_by_wim_temper")) { verdicts.push({ type: "NonEuclid RAT", version: String(), details: String() }); } // https://malpedia.caad.fkie.fraunhofer.de/details/win.imminent_monitor_rat if (verdicts.length === 0 && ((PE.isNetObjectPresent("LZLoader") && PE.isNetObjectPresent("Injector")) || PE.isNetUStringPresent("nAvgBytesPerSec: "))) { verdicts.push({ type: "Imminent Monitor RAT", version: String(), details: String() }); } // https://www.sonicwall.com/blog/russian-threat-group-cryptobytes-is-still-active-in-the-wild-with-uxcryptor if (verdicts.length === 0 && ( (PE_Cached.nameOfNetModuleName === "lc.exe" || PE_Cached.nameOfNetAssemblyName === "lc") || PE.isNetObjectPresent("0xC00000FD: The memory location at the specified address returned \"null\"") || PE.isNetUStringPresent("%temp%\\$unlocker_id.ux-cryptobytes") || PE.isNetUStringPresent("\r\nMemory section at address 0x0424* is locked!") || PE.isNetUStringPresent("\r\nService UXCryptor started.") || PE.isNetUStringPresent("\r\n\r\n * Windows blocked!") || PE.isNetUStringPresent("attrib $h $s $r $i /D ") || PE.isNetUStringPresent("lc.exe error") || PE.isNetUStringPresent("ByPassWarnMsg") || PE.isNetUStringPresent("ByPassMessage") || PE.isNetUStringPresent("ux-cryptor.exe") || PE.isNetUStringPresent("YOU ARE HACKED!\\1") || PE.isNetUStringPresent("OneDrive10293") || ( PE.isNetUStringPresent("_o_program") && PE.isNetUStringPresent("border_1") ) )) { verdicts.push({ type: "UX-Locker", version: String(), details: String() }); } if (PE.isSignatureInSectionPresent(0, "00" + convertStringToUnicodeSignature(" RAT") + "00") && ( PE.isNetObjectPresent("System.Net.Sockets") || PE.isNetObjectPresent("GetWindowText") || PE.isNetObjectPresent("avicap32.dll") || PE.isNetObjectPresent("avicap32") ) || ( PE.isNetObjectPresent("AntiTaskManager") || PE.isNetObjectPresent("BlockAvSites") || PE.isNetObjectPresent("UpdaterEXE")) || ( // System.Net.Sockets + GetForegroundWindow + GetWindowsText PE.isNetObjectPresent("System.Net.Sockets") && PE.isNetObjectPresent("GetForegroundWindow") && PE.isNetObjectPresent("GetWindowText") && ( // Search for 'AntivirusProduct' or 'DisableTaskMgr' PE.isSignatureInSectionPresent(0, convertStringToUnicodeSignature("AntivirusProduct")) || PE.isSignatureInSectionPresent(0, convertStringToUnicodeSignature("DisableTaskMgr")) ) ) || ( // GetForegroundWindow + GetWindowText + NtSetInformationProcess PE.isNetObjectPresent("GetForegroundWindow") && PE.isNetObjectPresent("GetWindowText") && PE.isNetObjectPresent("NtSetInformationProcess") )) { verdicts.push({ type: "RAT", version: String(), details: "General signs" }); } var signGroupsToCheck = [{ names: ["Zcash", "ZCash", "zcash"], strings: [ "\\Wallets\\Zcash\\", "Zcash", "zcash" ] }, { names: ["TotalCommander", "Totalcommander", "totalcommander"], strings: [ "\\FTP\\Total Commander", "wcx_ftp.ini", "\\FTP\\Total Commander\\wcx_ftp.ini" ] }, { names: ["Telegram", "telegram", "tdata"], strings: [ "\\Telegram_", "\\Telegram.exe", "Telegram.exe", "\\tdata", "tdata", "Telegram Desktop" ] }, { names: ["Creds"], strings: [ "cookies.sqlite", "\\cookies.sqlite", "logins.json", "key3.db", "\\keys3.db", "key4.db", "\\keys4.db", "Amex Card", "BCGlobal", "Carte Blanche Card", "Diners Club Card", "Discover Card", "Insta Payment Card", "JCB Card", "KoreanLocalCard", "Laser Card", "Maestro Card", "Mastercard", "Solo Card", "Switch Card", "Union Pay Card", "Visa Card", "Visa Master Card", "Express Card" ] }, { names: ["Psi"], strings: [ "\\Psi+\\profiles\\default\\", "\\Jabber\\Psi+\\profiles\\default\\", "\\Psi\\profiles\\default\\", "\\Jabber\\Psi\\profiles\\default\\" ] }, { names: ["ProtonVPN", "Protonvpn", "protonvpn", "ProtonVPNRule"], strings: [ "\\ProtonVPN", "\\ProtonVPN\\ProtonVPN.exe", "\\Vpn\\ProtonVPN\\", "ProldCharotonVPN" ] }, { names: ["Outlook", "OutLook", "outlook"], strings: [ "Software\\Microsoft\\Windows NT\\CurrentVersion\\Windows Messaging Subsystem\\Profiles\\Outlook\\9375CFF0413111d3B88A00104B2A6676", "Software\\Microsoft\\Windows Messaging Subsystem\\Profiles\\9375CFF0413111d3B88A00104B2A6676", "\\EmailClients\\Outlook", "Software\\Microsoft\\Office\\15.0\\Outlook\\Profiles\\Outlook\\9375CFF0413111d3B88A00104B2A6676", "Software\\Microsoft\\Office\\16.0\\Outlook\\Profiles\\Outlook\\9375CFF0413111d3B88A00104B2A6676", "{220D5CC1-853A-11D0-84BC-00C04FD43F8F}", "{417E2D75-84BD-11D0-84BB-00C04FD43F8F}" ] }, { names: ["OpenVPN", "OpenVpn", "openvpn"], strings: [ "OpenVPN", "\\VPN\\OpenVPN", "\\OpenVPN\\config\\conf\\", "\\VPN\\OpenVPN\\config\\conf\\", "OpenVPN Connect\\profiles", "ovpn" ] }, { names: ["PIAVPN", "PiaVPN", "PiaVpn", "Piavpn", "piavpn"], strings: [ "\\pia_manager", "pia_manager" ] }, { names: ["ExpressVPN", "ExpressVpn", "Expressvpn", "expressvpn"], strings: [ "\\ExpressVPN", "ExpressVPN" ] }, { names: ["NordVPN", "NordVpn", "Nordvpn", "nordvpn", "NordApp"], strings: [ "\\NordVPN\\", "NordVPN", "NordVpn.exe*", "\\Vpn\\NordVPN" ] }, { names: ["Proxifier", "proxifier"], strings: [ "\\Roaming\\Proxifier\\Profiles\\Default.ppx", "Default.ppx" ] }, { names: ["Monero", "monero"], strings: [ "monero-project", "monero-core", "wallet_path", "\\Wallets\\Monero\\", "Wallets\\Monero", "Wallets\\Monero\\" ] }, { names: ["Litecoin", "LiteCoin"], strings: [ "Litecoin", "\\Wallets\\LitecoinCore\\", "\\wallet.dat", "\\LitecoinCore\\wallet.dat", "LitecoinCore\\wallet.dat" ] }, { names: ["Jaxx", "jaxx"], strings: [ "\\com.liberty.jaxx\\IndexedDB\\file__0.indexeddb.leveldb\\", "\\Wallets\\Jaxx\\com.liberty.jaxx\\IndexedDB\\file__0.indexeddb.leveldb\\", "com.liberty.jaxx", "file_0.indexeddb.leveldb" ] }, { names: ["FileZilla", "Filezilla", "filezilla"], strings: [ "\\FileZilla", "\\FileZilla\\FileZilla.log", "RecentServers", "\\FileZilla\\recentservers.xml", "FileZilla\\recentservers.xml", "\\FileZilla\\sitemanager.xml", "FileZilla.log" ] }, { names: ["Exodus", "exodus", "ExodusRule"], strings: [ "\\Exodus\\exodus.wallet\\", "\\Wallets\\Exodus\\", "exodus.wallet", "\\exodus.wallet" ] }, { names: ["Ethereum", "ethereum"], strings: [ "\\Ethereum\\keystore", "Ethereum\\keystore", "\\Wallets\\Ethereum\\", "Wallets\\Ethereum\\" ] }, { names: ["FireFox", "Firefox", "firefox", "GetFirefox", "Gecko"], strings: [ "\\mozglue.dll", "mozcrt19.dll", "nss3.dll", "mozsqlite3.dll", "softokn3.dll", "\\Firefox\\Profiles\\", "signons.sqlite", "2a864886f70d0209", "2a864886f70d010c050103", "moz_places", "moz_cookies", "moz_bookmarks", "\\Mozilla Firefox\\", "Mozilla Firefox\\", "\\Mozilla\\Firefox\\Profiles", "webappsstore.sqlite", "SELECT * FROM moz_disabledHosts;", "SELECT * FROM moz_logins;", "SELECT * FROM moz_places", "moz_logins", "encryptedPassword", "NSS_Init" ] }, { names: ["Yahoo", "yahoo", "YahooMessenger"], strings: [ "\\Local Settings\\Application Data\\Yahoo Messenger\\", "\\ys.scr", "ys.scr", "Software\\Yahoo\\pager", "SOFTWARE\\Yahoo\\pager", "\\Yahoo!\\Messenger\\Profiles", "\\Archive\\Messages\\", "\\Archive\\Messages", "Archive\\Messages\\", "Archive\\Messages" ] }, { names: ["Kazaa", "GetKazaa"], strings: [ "Software\\Kazaa\\LocalContent\\ChannelsDir", "SOFTWARE\\Kazaa\\LocalContent\\ChannelsDir" ] }, { names: ["Digsby", "digsby", "GetDigsby", "DigsbyMessenger"], strings: [ "\\Digsby Logs\\", "\\Digsby Logs", "Digsby Logs\\", "Digsby Logs" ] }, { names: ["Electrum", "electrum", "ElectrumRule"], strings: [ "\\Electrum\\wallets", "Electrum\\wallets", "\\Wallets\\Electrum\\", "Wallets\\Electrum\\", "\\electrum_data", "electrum_data", "\\electrum_data\\wallets", "\\electrum_data\\wallets\\" ] }, { names: ["Edge", "edge", "msedge", "MicrosoftEdge"], strings: [ "2F1A6504-0641-44CF-8BB5-3612D865F2E5", /* —> */ "Windows Secure Note", "3CCD5499-87A8-4B10-A215-608888DD3B55", /* —> */ "Windows Web Password Credential", "154E23D0-C644-4E6F-8CE6-5069272F999F", /* —> */ "Windows Credential Picker Protector", "4BF4C442-9B8A-41A0-B380-DD4A704DDB28", /* —> */ "Web Credentials", "77BC582B-F0A6-4E15-4E80-61736B6F3B29", /* —> */ "Windows Credentials", "E69D7838-91B5-4FC9-89D5-230D4D4CC2BC", /* —> */ "Windows Domain Certificate Credential", "3E0E35BE-1B77-43E7-B873-AED901B6275B", /* —> */ "Windows Domain Password Credential", "3C886FF3-2669-4AA2-A8FB-3F6759A77548", /* —> */ "Windows Extended Credential", "pResourceElement", "pIdentityElement", /* —> */ "pAuthenticatorElement", "\\Microsoft\\Edge\\User Data\\Default\\Cookies", "\\Microsoft\\Edge\\User Data" ] }, { names: ["Discord", "discord"], strings: [ "\\Discord\\Local Storage\\leveldb\\", "\\discord\\Local Storage\\leveldb\\", "\\discordcanary\\Local Storage\\leveldb\\", "https://discordapp.com/api/v6/users/@me", "https://discordapp.com/api/v7/users/@me", "https://discordapp.com/api/v8/users/@me", "https://cdn.discordapp.com/avatars/", "\\Discord", "Discord Canary\\leveldb", "Discord PTB\\Local Storage\\leveldb", "Discord\\Local Storage\\leveldb" ] }, { names: ["DashCore", "Dashcore", "dashcore"], strings: [ "Dash-Qt", "\\Wallets\\DashCore\\", "\\Wallets\\DashCore", "Wallets\\DashCore\\", "Wallets\\DashCore", "\\DashCore\\wallet.dat" ] }, { names: ["InternetExplorer", "IE7", "IE78", "CIEPasswords", "CIE7Passwords", "CIEPasswords"], strings: [ "Software\\Microsoft\\Internet Explorer\\IntelliForms\\Storage1", "Software\\Microsoft\\Internet Explorer\\IntelliForms\\Storage2", "\\AppData\\Roaming\\Microsoft\\Windows\\Cookies", "abe2869f-9b47-4cd9-a358-c22904dba7f7\0", "{5E7E8100-9138-11D1-945A-00C04FC308FF}", "Software\\Microsoft\\FTP\\Accounts", "pstorec.dll" ] }, { names: ["MSN", "GetMSN", "getMSN75Passwords", "CMSNMessengerPasswords"], strings: [ "\\MSN Messenger\\msidcrl.dll", "msidcrl.dll", "Software\\Microsoft\\MessengerService", "Software\\Microsoft\\MSNMessenger", "82BD0E67-9FEA-4748-8672-D5EFE5B779B0\0", "msidcrl.dll", "WindowsLive:name=*", "\\My Received Files\\", "\\My Received Files" ] }, { names: ["Skype", "skype", "GetSkype"], strings: [ "\\Skype\\", "\\Skype" ] }, { names: ["IMVU", "GetIMVU", "GetImvu", "Getimvu", "getimvu", "GETIMVU"], strings: [ "HKEY_CURRENT_USER\\Software\\IMVU\\username\\", "HKEY_CURRENT_USER\\Software\\IMVU\\password\\" ] }, { names: ["Paltalk", "paltalk", "GetPaltalk"], strings: [ "Software\\Paltalk", "SOFTWARE\\Paltalk", "HKEY_CURRENT_USER\\Software\\Paltalk\\", "HKEY_CURRENT_USER\\SOFTWARE\\Paltalk\\" ] }, { names: ["Chromium", "chromium", "chrome", "GetChrome"], strings: [ "\\Google\\Chrome\\User Data\\Local State", "\\cookies.db", "\\..\\..\\Local State", "\\Google\\Chrome\\User Data\\Default\\Web Data", "\\Google\\Chrome\\User Data\\default\\Login Data", "encrypted_key", "credit_cards", "card_number_encrypted", "LocalPrefs.json", "password_value" ] }, { names: ["Opera", "opera", "GetOpera"], strings: [ "\\Opera Software\\Opera Stable\\Cookies", "\\Opera Software\\Opera GX Stable\\Cookies", "\\Cookies", "\\Opera\\Opera\\wand.dat", "\\Opera\\Opera\\profile\\wand.dat", "wand.dat" ] }, { names: ["Bytecoin", "ByteCoin", "bytecoin"], strings: [ "\\bytecoin", "\\Bytecoin", "\\Wallets\\Bytecoin\\", "\\Wallets\\Bytecoin", "Wallets\\Bytecoin\\", "Wallets\\Bytecoin", ".wallet", "\\Bytecoin\\", ] }, { names: ["BitcoinCore", "Bitcoincore", "BitCoinCore", "bitcoincore"], strings: [ "Bitcoin-Qt", "\\Wallets\\BitcoinCore\\", "\\Wallets\\BitcoinCore", "Wallets\\BitcoinCore\\", "Wallets\\BitcoinCore", "\\BitcoinCore\\wallet.dat", "\\wallet.dat", "wallet.dat", "\\Bitcoin\\wallet.dat" ] }, { names: ["AtomicWallet", "Atomicwallet", "atomicwallet", "AtomicRule"], strings: [ "\\atomic\\Local Storage\\leveldb\\", "\\Wallets\\Atomic\\Local Storage\\leveldb\\", ] }, { names: ["Guarda"], strings: [ "Guarda" ] }, { names: ["MetaMask", "Metamask", "metamask"], strings: [ "\\Wallets\\Metamask", "Metamask_Extension_" ] }, { names: ["Coinomi", "coinomi"], strings: [ "\\Coinomi", "Coinomi" ] }, { names: ["Armory", "armory", "ArmoryWallet", "Armorywallet", "armorywallet", "ArmoryRule"], strings: [ "\\Armory\\", "\\Wallets\\Armory\\" ] }, { names: ["Steam", "steam"], strings: [ "HKEY_LOCAL_MACHINE\\SOFTWARE\\Valve\\Steam", "HKEY_CURRENT_USER\\SOFTWARE\\Valve\\Steam", "SteamPath", "Steampath", "\\Steam.dll", "SteamDecryptDataForThisMachine", "Software\\Valve\\Steam", "SOFTWARE\\Valve\\Steam", "\\config\\SteamAppData.vdf", "\\ClientRegistry.blob", "\\config\\config.vdf", "Steam\\Config\\config.vdf", "\\config\\loginusers.vdf", "\\Config\\SteamAppData.vdf", "Steam\\Config\\SteamAppData.vdf", "\\Steam\\Config\\SteamAppData.vdf", "\\Steam\\config\\SteamAppData.vdf" ] }, { names: ["Trillian", "trillian", "GetTrillian", "Trillian_Decrypt"], strings: [ "\\Trillian\\users\\global\\accounts.ini" ] }, { names: ["CoreFTP", "CoreFtp", "Coreftp", "coreftp", "GetCoreFTP"], strings: [ "HKEY_CURRENT_USER\\Software\\FTPWare\\CoreFTP\\Sites\\4\\Host", "HKEY_CURRENT_USER\\Software\\FTPWare\\CoreFTP\\Sites\\4\\Port", "HKEY_CURRENT_USER\\Software\\FTPWare\\CoreFTP\\Sites\\4\\User", "HKEY_CURRENT_USER\\Software\\FTPWare\\CoreFTP\\Sites\\4\\PW", "HKEY_CURRENT_USER\\Software\\FTPWare\\COREFTP\\Sites\\", "\\CoreFTP\\sites.idx" ] }, { names: ["LastPass", "Lastpass", "lastpass", "GetLastPass"], strings: [ "HKEY_CURRENT_USER\\SOFTWARE\\LastPass", "HKEY_CURRENT_USER\\SOFTWARE\\LastPass", "LoginUsers", "wxlastpasscookie" ] }, { names: ["FlashFXP", "FlashFxp", "Flashfxp", "flashfxp"], strings: [ "\\FlashFXP\\3\\quick.dat", "FlashFXP\\3\\quick.dat", "\\FlashFXP\\4\\quick.dat", "FlashFXP\\4\\quick.dat", "C:\\ProgramData\\FlashFXP\\3\\quick.dat", "C:\\ProgramData\\FlashFXP\\4\\quick.dat" ] }, { names: ["SmartFTP", "Smartftp", "smartftp"], strings: [ "\\SmartFTP\\Client 2.0\\Favorites\\Quick Connect\\", "\\SmartFTP\\Client 2.0\\Favorites\\Quick Connect\\*.xml", ] }, { names: ["Pidgin", "pidgin", "GetPidgin"], strings: [ "\\.purple\\accounts.xml", ".purple\\accounts.xml" ] }, { names: ["NoIP", "NoIp", "GetNoIP"], strings: [ "HKEY_LOCAL_MACHINE\\SOFTWARE\\Wow6432Node\\Vitalwerks\\DUC", "HKEY_LOCAL_MACHINE\\SOFTWARE\\Vitalwerks\\DUC" ] }, { names: ["RuneScape"], strings: [ "\\RSBot_Accounts.ini", "RSBot_Accounts.ini" ] }, { names: ["ICQRecover"], strings: [ "\\ICQ", "\\Owner.mdb", "StoredPassword" ] }, { names: ["jDownloader"], strings: [ "\\jDownloader\\config\\database.script", "INSERT INTO CONFIG VALUES('AccountController','" ] }, { names: ["MicrosoftOffice", "GetOffice"], strings: [ // Office 2003 (11.0) "SOFTWARE\\Microsoft\\Office\\11.0\\Registration\\{90110409-6000-11D3-8CFE-0150048383C9}", "SOFTWARE\\Microsoft\\Office\\12.0\\Registration\\{90110409-6000-11D3-8CFE-0150048383C9}", // Office 2007 (12.0) "SOFTWARE\\Microsoft\\Office\\12.0\\Registration\\{90120000-0030-0000-0000-0000000FF1CE}", "SOFTWARE\\Microsoft\\Office\\12.0\\Registration\\{90120000-0011-0000-0000-0000000FF1CE}", "SOFTWARE\\Microsoft\\Office\\12.0\\Registration\\{90120000-0012-0000-0000-0000000FF1CE}", // Office 2010 (14.0) "SOFTWARE\\Microsoft\\Office\\14.0\\Registration\\{90140000-0011-0000-0000-0000000FF1CE}", "SOFTWARE\\Microsoft\\Office\\14.0\\Registration\\{90140000-0012-0000-0000-0000000FF1CE}", "SOFTWARE\\Microsoft\\Office\\14.0\\Registration\\{90140000-003A-0000-0000-0000000FF1CE}", // Office 2013 (15.0) "SOFTWARE\\Microsoft\\Office\\15.0\\Registration\\{90150000-0011-0000-0000-0000000FF1CE}", "SOFTWARE\\Microsoft\\Office\\15.0\\Registration\\{90150000-0012-0000-0000-0000000FF1CE}", "SOFTWARE\\Microsoft\\Office\\15.0\\Registration\\{91150000-0011-0000-0000-0000000FF1CE}", // Office 2016 / 2019 / 2021 (16.0) "SOFTWARE\\Microsoft\\Office\\16.0\\Registration\\{90160000-0011-0000-0000-0000000FF1CE}", "SOFTWARE\\Microsoft\\Office\\16.0\\Registration\\{90160000-0012-0000-0000-0000000FF1CE}", "SOFTWARE\\Microsoft\\Office\\16.0\\Registration\\{91160000-0011-0000-0000-0000000FF1CE}" ] }, { names: ["FTPCommander", "FtpCommander", "Ftpcommander", "ftpcommander"], strings: [ "HKEY_LOCAL_MACHINE\\SOFTWARE\\Microsoft\\Windows\\CurrentVersion\\Uninstall\\FTP Commander\\UninstallString", "FTP Commander", "Ftplist.txt" ] }, { names: ["PuTTY", "Putty", "putty"], strings: [ "Software\\SimonTatham\\PuTTY\\Sessions", "Software\\SimonTatham\\PuTTY\\Sessions\\", "Software\\SimonTatham\\PuTTY\\SshHostKeys", "SOFTWARE\\SimonTatham\\PuTTY\\Sessions", "SOFTWARE\\SimonTatham\\PuTTY\\Sessions\\", "SOFTWARE\\SimonTatham\\PuTTY\\SshHostKeys", "SshHostKeys" ] }, { names: ["GameKeys", "FuckTheKey", "StealKeys", "GrabKeys"], strings: [ "ergc", "CDKey", "SERIAL", "DiscKey_SCCT", "codkey", "CDKEY", "LicenseNumber", "DigitalProductId", "RegNumber", "SerialNo", "Unlock Code", "ProductKey", "Serial", "SerialNumber", "RegistrationCode", "HKEY_LOCAL_MACHINE\\SOFTWARE\\Electronic Arts\\Electronic Arts\\Crysis\\ergc", "HKEY_CURRENT_USER\\Software\\Electronic Arts\\Electronic Arts\\Crysis Wars\\ergc", "HKEY_LOCAL_MACHINE\\SOFTWARE\\Electronic Arts\\EA GAMES\\Battlefield 2\\ergc", "HKEY_LOCAL_MACHINE\\SOFTWARE\\Electronic Arts\\EA GAMES\\Battlefield 2 Special Forces\\ergc", "HKEY_LOCAL_MACHINE\\SOFTWARE\\Electronic Arts\\EA GAMES\\Battlefield 2142\\ergc", "HKEY_LOCAL_MACHINE\\SOFTWARE\\CRYTEK\\FARCRY2\\UBI.COM", "HKEY_LOCAL_MACHINE\\SOFTWARE\\Activision\\Call of Duty WAW", "HKEY_LOCAL_MACHINE\\SOFTWARE\\Rockstar Games\\Grand Theft Auto IV", "HKEY_LOCAL_MACHINE\\SOFTWARE\\Electronic Arts\\EA GAMES\\Need For Speed Hot Pursuit 2\\ergc", "HKEY_LOCAL_MACHINE\\SOFTWARE\\Electronic Arts\\Electronic Arts\\Need for Speed ProStreet\\ergc", "HKEY_LOCAL_MACHINE\\SOFTWARE\\Electronic Arts\\EA Sports\\FIFA 09\\ergc", "HKEY_LOCAL_MACHINE\\SOFTWARE\\Electronic Arts\\Electronic Arts\\Need for Speed Carbon", "HKEY_LOCAL_MACHINE\\SOFTWARE\\Electronic Arts\\EA GAMES\\Need For Speed Hot Pursuit 2", "HKEY_LOCAL_MACHINE\\SOFTWARE\\Electronic Arts\\EA GAMES\\Need for Speed Most Wanted", "HKEY_LOCAL_MACHINE\\SOFTWARE\\Electronic Arts\\Electronic Arts\\Need for Speed ProStreet", "HKEY_LOCAL_MACHINE\\SOFTWARE\\Electronic Arts\\EA GAMES\\Need For Speed Underground", "HKEY_LOCAL_MACHINE\\SOFTWARE\\Electronic Arts\\EA Games\\Need for Speed Underground 2", "HKEY_LOCAL_MACHINE\\SOFTWARE\\SEGA\\Medieval II Total War", "HKEY_LOCAL_MACHINE\\SOFTWARE\\Adobe\\Golive\\5.0\\Registration", "HKEY_LOCAL_MACHINE\\SOFTWARE\\ahead\\Installation\\BAK\\Nero 7\\Info", "HKEY_LOCAL_MACHINE\\SOFTWARE\\Ubisoft\\Splinter Cell Pandora Tomorrow", "HKEY_LOCAL_MACHINE\\SOFTWARE\\Activision\\Call of Duty", "HKEY_LOCAL_MACHINE\\SOFTWARE\\Activision\\Call of Duty 2", "HKEY_LOCAL_MACHINE\\SOFTWARE\\Activision\\Call of Duty United Offensive", "HKEY_LOCAL_MACHINE\\SOFTWARE\\Activision\\Call of Duty 4", "HKEY_LOCAL_MACHINE\\SOFTWARE\\Activision\\Call of Duty WAW", "HKEY_LOCAL_MACHINE\\SOFTWARE\\THQ\\Dawn of War", "HKEY_LOCAL_MACHINE\\SOFTWARE\\THQ\\Dawn of War - Dark Crusade", "HKEY_LOCAL_MACHINE\\SOFTWARE\\ACD Systems\\PicaView", "HKEY_LOCAL_MACHINE\\SOFTWARE\\Eugen Systems\\ActOfWa", "HKEY_LOCAL_MACHINE\\SOFTWARE\\Adobe\\Photoshop\\7.0\\Registration", "HKEY_LOCAL_MACHINE\\SOFTWARE\\Elcom\\Advanced PDF Password Recovery\\Registration", "HKEY_LOCAL_MACHINE\\SOFTWARE\\Elcom\\Advanced PDF Password Recovery Pro\\Registration", "HKEY_LOCAL_MACHINE\\SOFTWARE\\Elcom\\Advanced ZIP Password Recovery\\Registration", "HKEY_LOCAL_MACHINE\\Software\\Electronic Arts\\EA GAMES\\Battlefield Vietnam", "HKEY_LOCAL_MACHINE\\Software\\Electronic Arts\\EA GAMES\\Black and White", "HKEY_LOCAL_MACHINE\\Software\\Electronic Arts\\Black and White 2", "HKEY_LOCAL_MACHINE\\SOFTWARE\\Electronic Arts\\EA Games\\Boulder Dash Rocks", "HKEY_LOCAL_MACHINE\\SOFTWARE\\Electronic Arts\\EA Games\\Burnout Paradise", "HKEY_LOCAL_MACHINE\\SOFTWARE\\Sunflowers\\Anno 1701", "HKEY_LOCAL_MACHINE\\SOFTWARE\\ashampoo\\Ashampoo WinOptimizer Platinum 3", "HKEY_LOCAL_MACHINE\\SOFTWARE\\@stake\\LC5\\Registration", "HKEY_LOCAL_MACHINE\\SOFTWARE\\Electronic Arts\\EA GAMES\\Battlefield 1942", "HKEY_LOCAL_MACHINE\\SOFTWARE\\Electronic Arts\\EA GAMES\\Battlefield 1942 Secret Weapons of WWII", "HKEY_LOCAL_MACHINE\\SOFTWARE\\Electronic Arts\\EA GAMES\\Battlefield 1942 The Road to Rome", "HKEY_LOCAL_MACHINE\\SOFTWARE\\Electronic Arts\\EA Games\\Battlefield 2", "HKEY_LOCAL_MACHINE\\SOFTWARE\\Electronic Arts\\EA GAMES\\Battlefield 2142", "HKEY_LOCAL_MACHINE\\SOFTWARE\\TechSmith\\Camtasia Studio\\6.0", "HKEY_LOCAL_MACHINE\\SOFTWARE\\Techland\\Chrome", "HKEY_LOCAL_MACHINE\\SOFTWARE\\Codec Tweak Tool", "HKEY_LOCAL_MACHINE\\SOFTWARE\\Electronic Arts\\EA GAMES\\Generals", "HKEY_LOCAL_MACHINE\\SOFTWARE\\Electronic Arts\\EA GAMES\\Command and Conquer Generals Zero Hour", "HKEY_LOCAL_MACHINE\\SOFTWARE\\Westwood\\Red Alert 2", "HKEY_LOCAL_MACHINE\\SOFTWARE\\Westwood\\Red Alert", "HKEY_LOCAL_MACHINE\\SOFTWARE\\Westwood\\Tiberian Sun", "HKEY_LOCAL_MACHINE\\SOFTWARE\\Electronic Arts\\Command and Conquer 3", "HKEY_LOCAL_MACHINE\\SOFTWARE\\Electronic Arts\\Electronic Arts\\Command and Conquer 3", "HKEY_LOCAL_MACHINE\\SOFTWARE\\THQ\\Company of Heroes", "HKEY_LOCAL_MACHINE\\SOFTWARE\\Valve\\Counter-Strike\\Settings", "HKEY_LOCAL_MACHINE\\SOFTWARE\\Electronic Arts\\Electronic Arts\\Crysis", "HKEY_LOCAL_MACHINE\\SOFTWARE\\Cyberlink\\PowerDVD", "HKEY_LOCAL_MACHINE\\SOFTWARE\\Cyberlink\\PowerBar", "HKEY_LOCAL_MACHINE\\SOFTWARE\\CyberLink\\PowerProducer\\3.0\\UserReg", "HKEY_LOCAL_MACHINE\\SOFTWARE\\Valve\\Day of Defeat\\Settings", "HKEY_LOCAL_MACHINE\\SOFTWARE\\Electronic Arts\\Electronic Arts\\The Battle for Middle-earth II", "HKEY_LOCAL_MACHINE\\SOFTWARE\\Electronic Arts\\EA Games\\The Sims 2", "HKEY_LOCAL_MACHINE\\SOFTWARE\\Electronic Arts\\EA Games\\The Sims 2 University", "HKEY_LOCAL_MACHINE\\SOFTWARE\\Electronic Arts\\EA Games\\The Sims 2 Nightlife", "HKEY_LOCAL_MACHINE\\SOFTWARE\\Electronic Arts\\EA Games\\The Sims 2 Open For Business", "HKEY_LOCAL_MACHINE\\SOFTWARE\\Electronic Arts\\EA Games\\The Sims 2 Pets", "HKEY_LOCAL_MACHINE\\SOFTWARE\\Electronic Arts\\EA Games\\The Sims 2 Seasons", "HKEY_LOCAL_MACHINE\\SOFTWARE\\Electronic Arts\\EA Games\\The Sims 2 Glamour Life Stuff", "HKEY_LOCAL_MACHINE\\SOFTWARE\\Electronic Arts\\EA Games\\The Sims 2 Celebration Stuff", "HKEY_LOCAL_MACHINE\\SOFTWARE\\Electronic Arts\\EA Games\\The Sims 2 H M Fashion Stuff", "HKEY_LOCAL_MACHINE\\SOFTWARE\\Valve\\Half-Life\\Settings" ] }, { names: ["Minecraft", "minecraft", "MinecraftLauncher"], strings: [ "\\.minecraft\\launcher_profiles.json", "launcherconfig", ".lunarclient", "TlauncherProfiles.json", "meteor-client", "alts.novo", "paladium-group", "PolyMC", "Badlion Client", "intentlauncher", "cheatbreaker_accounts.json", "launcher_accounts_microsoft_store.json", "\\VimeWorld", "Software\\VimeWorld", "SOFTWARE\\VimeWorld", "\\.vimeworld" ] }, { names: ["Riot"], strings: [ "\\RiotGamesPrivateSettings.yaml", "RiotGamesPrivateSettings.yaml" ] }, { names: ["Roblox"], strings: [ ":SOFTWARE\\Roblox\\RobloxStudioBrowser\\roblox.com -Name .ROBLOSECURITY" ] }, { names: [/* Generic --> It's not subject to strict classification. It can be found in legitimate .NET programs, but the RiskScore threshold will not trigger false positives. */], strings: [ "90059c37-1320-41a4-b58d-2b75a9850d2f", "EnableLUA", "DisableCMD", "DisableTaskMgr", "DisableRegistry", "discordtokenprotector", "*.txt", "*.rdp", "virustotal.com", "avast.com", "totalav.com", "scanguard.com", "totaladblock.com", "pcprotect.com", "mcafee.com", "bitdefender.com", "us.norton.com", "avg.com", "malwarebytes.com", "pandasecurity.com", "avira.com", "norton.com", "eset.com", "zillya.com", "kaspersky.com", "usa.kaspersky.com", "sophos.com", "home.sophos.com", "adaware.com", "bullguard.com", "clamav.net", "drweb.com", "emsisoft.com", "f-secure.com", "zonealarm.com", "trendmicro.com", "ccleaner.com", "keyscrambler", "^(1|3|bc1)[a-zA-Z0-9]{25,39}$", "^(L|M|3)[a-zA-Z0-9]{26,33}$", "^0x[a-fA-F0-9]{40}$", "^4[0-9AB][1-9A-HJ-NP-Za-km-z]{93}$", "^[1-9A-HJ-NP-Za-km-z]{32,44}$", "^X[1-9A-HJ-NP-Za-km-z]{33}$", "^r[0-9a-zA-Z]{24,34}$", "^T[1-9A-HJ-NP-Za-km-z]{33}$", "^(bitcoincash:)?(q|p)[a-z0-9]{41}$", "\\(?.*?)\\", "^3[47][0-9]{13}$", "^(6541|6556)[0-9]{12}$", "^389[0-9]{11}$", "^3(?:0[0-5]|[68][0-9])[0-9]{11}$", "6(?:011|5[0-9]{2})[0-9]{12}$", "^63[7-9][0-9]{13}$", "^(?:2131|1800|35\\\\d{3})\\\\d{11}$", "^(6304|6706|6709|6771)[0-9]{12,15}$", "^(5018|5020|5038|6304|6759|6761|6763)[0-9]{8,15}$", "5[1-5][0-9]{14}$", "^(6334|6767)[0-9]{12}|(6334|6767)[0-9]{14}|(6334|6767)[0-9]{15}$", "^(62[0-9]{14,17})$", "4[0-9]{12}(?:[0-9]{3})?$", "^(?:4[0-9]{12}(?:[0-9]{3})?|5[1-5][0-9]{14})$", "3[47][0-9]{13}$", "\r\nPassword: ", "\r\nPassword : ", "Password : ", "Password: ", "PASSWORD: ", "PASSWORD : ", "Software\\Microsoft\\Active Setup\\Installed Components\\{89850105-ECBD-11cf-8B85-00AA005B4340}", "SOFTWARE\\Microsoft\\Active Setup\\Installed Components\\{89850105-ECBD-11cf-8B85-00AA005B4340}", "{89850105-ECBD-11cf-8B85-00AA005B4340}", // Blacklisted computer/user names and GUIDs "7AB5C494-39F5-4941-9163-47F54D6D5016", "032E02B4-0499-05C3-0806-3C0700080009", "03DE0294-0480-05DE-1A06-350700080009", "11111111-2222-3333-4444-555555555555", "6F3CA5EC-BEC9-4A4D-8274-11168F640058", "ADEEEE9E-EF0A-6B84-B14B-B83A54AFC548", "4C4C4544-0050-3710-8058-CAC04F59344A", "00000000-0000-0000-0000-AC1F6BD04972", "49434D53-0200-9065-2500-65902500E439", "49434D53-0200-9036-2500-36902500F022", "5BD24D56-789F-8468-7CDC-CAA7222CC121", "777D84B3-88D1-451C-93E4-D235177420A7", "49434D53-0200-9036-2500-369025000C65", "B1112042-52E8-E25B-3655-6A4F54155DBF", "00000000-0000-0000-0000-AC1F6BD048FE", "EB16924B-FB6D-4FA1-8666-17B91F62FB37", "A15A930C-8251-9645-AF63-E45AD728C20C", "67E595EB-54AC-4FF0-B5E3-3DA7C7B547E3", "C7D23342-A5D4-68A1-59AC-CF40F735B363", "63203342-0EB0-AA1A-4DF5-3FB37DBB0670", "44B94D56-65AB-DC02-86A0-98143A7423BF", "6608003F-ECE4-494E-B07E-1C4615D1D93C", "D9142042-8F51-5EFF-D5F8-EE9AE3D1602A", "49434D53-0200-9036-2500-369025003AF0", "8B4E8278-525C-7343-B825-280AEBCD3BCB", "4D4DDC94-E06C-44F4-95FE-33A1ADA5AC27", "79AF5279-16CF-4094-9758-F88A616D81B4", "FE822042-A70C-D08B-F1D1-C207055A488F", "76122042-C286-FA81-F0A8-514CC507B250", "481E2042-A1AF-D390-CE06-A8F783B1E76A", "F3988356-32F5-4AE1-8D47-FD3B8BAFBD4C", "9961A120-E691-4FFE-B67B-F0E4115D5919", "bee7370c-8c0c-4", "desktop-nakffmt", "win-5e07cos9alr", "b30f0242-1c6a-4", "desktop-vrsqlag", "q9iatrkprh", "xc64zb", "desktop-d019gdm", "desktop-wi8clet", "server1", "lisa-pc", "john-pc", "desktop-b0t93d6", "desktop-1pykp29", "desktop-1y2433r", "wileypc", "work", "6c4e733f-c2d9-4", "ralphs-pc", "desktop-wg3myjs", "desktop-7xc6gez", "desktop-5ov9s0o", "qarzhrdbpj", "oreleepc", "archibaldpc", "julia-pc", "d1bnjkfvlh", "compname_5076", "desktop-vkeons4", "NTT-EFF-2W11WSS", "wdagutilityaccount", "abby", "peter wilson", "hmarc", "patex", "john-pc", "rdhj0cnfevzx", "keecfmwgj", "frank", "8nl0colnq5bq", "lisa", "john", "george", "pxmduopvyx", "8vizsm", "w0fjuovmccp5a", "lmvwjj9b", "pqonjhvwexss", "3u2v9m8", "julia", "heuerzl", "harry johnson", "j.seance", "a.monaldo", "tvm", "outpost" ] }, { names: [/* Generic */], strings: [ // Cryptowallets extension IDs "nkbihfbeogaeaoehlefnkodbefgpgknn", "ejbalbakoplchlghecdalmeeeajnimhm", "fhbohimaelbohpjbbldcngcnapndodjp", "hnfanknocfeofbddgcijnmhnfnkdnaad", "fnjhmkhhmkbjkkabndcnnogagogbneec", "egjidjbpglichdcondbcbdnbeeppgdph", "ojggmchlghnjlapmfbnjholfjkiidbch", "opcgpfmipidbgpenhmajoajpbobppdil", "efbglgofoippbgcjepnhiblaibcnclgk", "ibnejdfjmmkpcnlpebklmnkoeoihofec", "ejjladinnckdgjemekebdpeokbikhfci", "phkbamefinggmakgklpkljjmgibohnba", "ebfidpplhabeedpnhjnobghokpiioolj", "afbcbjpbpfadlkmhmclhkeeodmamcflc", "aeachknmefphepccionboohckonoeemg", "bhghoamapcdpbohphigoooaddinpkbai", "aholpfdialjgjfhomihkjbmgjidlcdno", "bfnaelmomeimhlpmgjnjophhpkkoljpa", "agoakfejjabomempkjlepdflaleeobhb", "mfgccjchihfkkindfppnaooecgfneiii", "lgmpcpglpngdoalbgeoldeajfclnhafa", "bhhhlbepdkbapadjdnnojkbgioiodbic", "jblndlipeogpafnldhgmapagcccfchpi", "kncchdigobghenbbaddojjnnaogfppfj", "ffnbelfdoeiohenkjibnmadjiehjhajb", "hpglfhgfnhbgpjdenjgmdgoeiappafln", "cjelfplplebdjjenllpjcblmjkfcffne", "amkmjjmmflddogmhpjloimipbofnfjih", "fhilaheimglignddkjgofkcbgekhenbh", "nlbmnnijcnlegkjjpcfjclmcfggfefdm", "nanjmdknhkinifnkgdcggcfnhdaammmj", "nkddgncdjgjfcddamfgcmfnlhccnimig", "aiifbnbfobpmeekipheeijimdpnlpgpp", "fnnegphlobjdpkhecapkijjdkgcjhkib", "cgeeodpfagjceefieflmdfphplkenlfk", "pdadjkfkgcafgbceimcpbkalnfnepbnk", "mgffkfbidihjpoaomajlbgchddlicgpn", "aodkkagnadcbobfpggfnjeongemjbjca", "kpfopkelmapcoipemfendmdcghnegimn", "hmeobnfnfcmdkdcmlblgagmfpfboieaf", "lpfcbjknijpeeillifnkikgncikgfhdo", "dngmlblcodfobpdpecaadgfbcggfjfnm", "eigblbgjknlfbajkfhopmcojidlgcehm", "oeljdldpnmdbchonielidgobddffflal", "naepdomgkenhinolocfifgehidddafch", "bmikpgodpkclnkgmnpphehdgcimmided", "jhfjfclepacoldmjmkmdlmganfaalklb", "chgfefjpcobfbnpmiokfjjaglahmnded", "igkpcodhieompeloncfnbekccinhapdb", "admmjipmmciaobhojoghlmleefbicajg", "caljgklbbfbcjjanaijlacgncafpegll", "imloifkgjagghnncjkhggdhalmcnfklk", "cphhlgmgameodnhkjdmkpanlelnlohao", "dmkamcknogkgcdfhhbddcghachkejeap", "fhmfendgdocmcbmfikdcogofphimnkno", "flpiciilemghbmfalicajoolhkkenfel", "hcflpincpppdclinealmandijcmnkbgn", "mnfifefkajgofkcjkemidiaecocnkjeh", "nlgbhdfgdhgbiamfdfmbikcdghidoadd", "ilbbpajmiplgpehdikmejfemfklpkmke", "pknlccmneadmjbkollckpblgaaabameg", "pfknkoocfefiocadajpngdknmkjgakdg", "fcglfhcjfpkgdppjbglknafgfffkelnm", "idkppnahnmmggbmfkjhiakkbkdpnmnon", "mhonjhhcgphdphdjcdoeodfdliikapmj", "bhmlbgebokamljgnceonbncdofmmkedg", "hieplnfojfccegoloniefimmbfjdgcgp", "pidhddgciaponoajdngciiemcflpnnbg", "blbpgcogcoohhngdjafgpoagcilicpjh", "gflpckpfdgcagnbdfafmibcmkadnlhpj", "doljkehcfhidippihgakcihcmnknlphh", "nbokbjkelpmlgflobbohapifnnenbjlh", "apjdnokplgcjkejimjdfjnhmjlbpgkdi", "ieedgmmkpkbiblijbbldefkomatsuahh", "jifanbgejlbcmhbbdbnfbfnlmbomjedj", "dojmlmceifkfgkgeejemfciibjehhdcl", "jpxupxjxheguvfyhfhahqvxvyqthiryh", "pfkcfdjnlfjcmkjnhcbfhfkkoflnhjln", "hbpfjlflhnmkddbjdchbbifhllgmmhnm", "ocmfilhakdbncmojmlbagpkjfbmeinbd", "dbhklojmlkgmpihhdooibnmidfpeaing", "mammpjaaoinfelloncbbpomjcihbkmmc", "khcodhlfkpmhibicdjjblnkgimdepgnd", "bfbdnbpibgndpjfhonkflpkijfapmomn", "gjffdbjndmcafeoehgdldobgjmlepcal", "eidlicjlkaiefdbgmdepmmicpbggmhoj", "bobfejfdlhnabgglompioclndjejolch", "elokfmmmjbadpgdjmgglocapdckdcpkn", "ppdjlkfkedmidmclhakfncpfdmdgmjpm", "cfoajccjibkjhbdjnpkbananbejpkkjb", "flikjlpgnpcjdienoojmgliechmmheek", "gofhklgdnbnpcdigdgkgfobhhghjmmkj", "hppmchachflomkejbhofobganapojjol", "lbfeahdfdkibininjgejjgpdafeopflb", "kgeohlebpjgcfiidfhhdlnnkhefajmca", "inljaljiffkdgmlndjkdiepghpolcpki", "njgnlkhcjgmjfnfahdmfkalpjcneebpl", "gabedfkgnbglfbnplfpjddgfnbibkmbb", "jbdaocneiiinmjbjlgalhcelgbejmnid", "ocefimbphcgjaahbclemolcmkeanoagc", "fihkakfobkmkjojpchpfgcmhfjnmnfpi", "nfinomegcaccbhchhgflladpfbajihdf", "nphplpgoakhhjchkkhmiggakijnkhfnd", "penjlddjkjgpnkllboccdgccekpkcbin", "fldfpgipfncgndfolcbkdeeknbbbnhcc", "pnccjgokhbnggghddhahcnaopgeipafg", "imlcamfeniaidioeflifonfjeeppblda", "ajkifnllfhikkjbjopkhmjoieikeihjb", "kkpllkodjeloidieedojogacfhpaihoh", "kgdijkcfiglijhaglibaidbipiejjfdp", "onhogfjeacnfoofkfgppdlbmlmnplgbn", "fijngjgcjhjmmpcmkeiomlglpeiijkld", "mlbafbjadjidk1bhgopoamemfibcpdfi", "jnlgamecbpmbajjfhmmmlhejkemejdma", "ppbibelpcjmhbdihakflkdcoccbgbkpo", "mcohilncbfahbmgdjkbpemcciiolgcge", "enabgbdfcbaehmbigakijjabdpdnimlg", "fopmedgnkfpebgllppeddmmochcookhc", "khpkpbbcccdmmclmpigdgddabeilkdpd", "lnnnmfcpbkafcpgdilckhmhbkkbpkmid", "kilnpioakcdndlodeeceffgjdpojajlo", "mdjmfdffdcmnoblignmgpommbefadffd", "aijcbedoijmgnlmjeegjaglmepbmpkpi", "ookjlbkiijinhpmnjffcofjonbfbgaoc", "jnmbobjmhlngoefaiojfljckilhhlhcj", "acmacodkjbdgmoleebolmdjonilkdbch" ] }]; var detectedRiskScoreCounter = 0; for (var i = 0; i < signGroupsToCheck.length; i++) { var groupToCheck = signGroupsToCheck[i], isElementFromGroupDetected = false; for (var j = 0; j < groupToCheck.strings.length; j++) { if (PE.isNetUStringPresent(groupToCheck.strings[j])) { isElementFromGroupDetected = true; } } if (!isElementFromGroupDetected) { for (var k = 0; k < groupToCheck.names.length; k++) { if (PE.isNetObjectPresent(groupToCheck.names[k])) { isElementFromGroupDetected = true; } } } if (isElementFromGroupDetected) detectedRiskScoreCounter++; if (detectedRiskScoreCounter >= 9) break; } if (detectedRiskScoreCounter >= 3) { const percentageOfRiskScore = Math.min(65 + Math.round((detectedRiskScoreCounter - 3) * (35 / 6)), 100); verdicts.push({ type: "Stealer", version: String(), details: "RiskScore " + percentageOfRiskScore + "%" + (percentageOfRiskScore < 70 ? " — " + mayBeInfected : String()) }); } else { var signsToCheck = [ "KeyLogger", "Keylogger", "keylogger", "ClipLogger", "Cliplogger", "cliplogger", "Stealer", "stealer", "Grabber", "grabber", "potentiallyVulnerablePasswords", "ScanGeckoBrowsersPaths", "ScannedWallets", "ScanWallets", "GrabPasswords", "Grabpasswords", "grabpasswords", "StealPasswords", "Stealpasswords", "stealpasswords", "ScanSteam", "TGrabber", "DGrabber", "GrabFiles", "Grabfiles", "grabfiles", "StealFiles", "Stealfiles", "stealfiles", "GrabCookies", "Grabcookies", "grabcookies", "grabcookies", "WalletsStealer", "Walletsstealer", "walletsstealer", "WalletsGrabber", "Walletsgrabber", "walletsgrabber" ]; for (var x = 0; x < signsToCheck.length; x++) { if (PE.isNetObjectPresent(signsToCheck[x])) { verdicts.push({ type: "Stealer", version: String(), details: "General signs" }); break; } } } // Clean up: release the dictionary signGroupsToCheck = undefined; } // https://malpedia.caad.fkie.fraunhofer.de/details/win.ramnit if (verdicts.length === 0 && ( PE.isSectionNamePresent(".rmnet") || (PE_Cached.isI386 && PE.compareEP( "60" + // pushal "E8 00 00 00 00" + // call $+5 "5D" + // pop ebp "8B C5" + // mov eax, ebp "81 ED .. .. 01 20" + // sub ebp, ... "2B 85 .. .. .. .." + // sub eax, dword ptr [ebp + ...] "89 85 .. .. .. .." + // mov dword ptr [ebp + ...], eax "B0 00" // mov al, 0 )))) { verdicts.push({ type: "Ramnit", version: String(), details: "infected" }); } // https://malpedia.caad.fkie.fraunhofer.de/details/win.neshta if (verdicts.length === 0 && PE_Cached.isArchX86 && PE.compareEP( "55" + // push ebp "8B EC" + // mov ebp, esp "83 C4 E0" + // add esp, -0x20 "33 C0" + // xor eax, eax "89 45 E0" + // mov dword ptr [ebp - 0x20], eax "89 45 E8" + // mov dword ptr [ebp - 0x18], eax "89 45 E4" + // mov dword ptr [ebp - 0x1c], eax "89 45 EC" + // mov dword ptr [ebp - 0x14], eax "B8 54 80 40 00" + // mov eax, 0x408054 "E8 12 BE FF FF" + // call $+5 - 0x41EE "33 C0" + // xor eax, eax "55" + // push ebp "68 20 82 40 00" + // push 0x408220 "64 FF 30" + // push dword ptr fs:[eax] "64 89 20" + // mov dword ptr fs:[eax], esp "B8 A8 91 40 00" + // mov eax, 0x4091a8 "B9 0B 00 00 00" // mov ecx, 0xb )) { verdicts.push({ type: "Neshta", version: String(), details: "infected" }); } // https://www.securityhome.eu/malware/malware.php?mal_id=17234082845c832fbf144826.79985377 if (verdicts.length === 0 && PE_Cached.isArchX86 && PE.compareEP( "60" + // pushal "E8 00 00 00 00" + // call $+5 "5B 81 EB .. .. .. .." + // sub ebx, ... "83 EC 74" + // sub esp, 0x74 "8B EC" + // mov ebp, esp "8B 83 .. .. .. .." + // mov eax, dword ptr [ebx + ...] "89 45 00" // mov dword ptr [ebp], eax )) { verdicts.push({ type: "Slugin", version: String(), details: "infected" }); } // https://en.wikipedia.org/wiki/CIH_(computer_virus) if (verdicts.length === 0 && PE_Cached.isArchX86 && !PE_Cached.is64bit && PE.compareEP("55")) { // push ebp const instructionsOfWin9xCih = [ "8D 44 24 F8" + // lea eax, [esp - 8] ".. DB", // xor/sub/... ebx, ebx // === "64 87 03", // xchg byte ptr fs:[ebx], eax // === "E8 00 00 00 00", // call $+5 // === "51" + // push ecx "50" // push eax ]; var offsetSearchFrom = PE.VAToOffset(PE_Cached.addressOfUnmanagedEntryPoint); for (var i = 0; i < instructionsOfWin9xCih.length; i++) { var foundInstructionPatternAt = PE.findSignature(offsetSearchFrom, 0x20, instructionsOfWin9xCih[i]); if (foundInstructionPatternAt === -1) { break; } else { offsetSearchFrom = foundInstructionPatternAt + 1; } if (i === instructionsOfWin9xCih.length - 1) { verdicts.push({ type: "Win9X.CIH", version: String(), details: "infected" }); } } } // https://community.avast.com/t/win95-dupator/591940 if (verdicts.length === 0 && ((PE_Cached.isI386 && PE.compareEP( "60" + // pushal "8B E8" + // mov ebp, eax "81 ED 00 10 40 00" + // sub ebp, 0x401000 "8B 85" // mov eax, dword ptr [ebp + ...] )) || PE.section[PE.nLastSection].Name === "DUPATOR!")) { verdicts.push({ type: "Win9X.Dupator", version: String(), details: "infected" }); } // https://malpedia.caad.fkie.fraunhofer.de/details/win.parite if (verdicts.length === 0 && PE_Cached.isArchX86) { var pariteAddress = PE_Cached.addressOfUnmanagedEntryPoint, pariteLoopCount = 0, pariteState = 0; // 0 = initial, 1 = Push/Xor found, 2 = Sub found for (var i = 0; i < 364; i++) { var currentInstruction = PE.getDisasmString(pariteAddress), currentOpCode = getAsmOpCode(currentInstruction); if (!currentInstruction) break; // Safety check switch (pariteState) { case 0: if (/^(?:PUSH|XOR) DWORD PTR \[E[A-Z]{2} \+ E[A-Z]{2}\]/.test(currentInstruction)) { pariteState = 1; } break; case 1: if (currentOpCode === "SUB" && currentInstruction.match(/^SUB E[A-Z]{2}, [2-4]$/)) { pariteState = 2; } else if (currentOpCode === "JNE" || currentOpCode === "JMP") { pariteState = 0; } break; case 2: if (currentOpCode === "JNE") { var jneTargetMatch = currentInstruction.match(/^JNE 0X([0-9A-F]+)$/); if (jneTargetMatch && parseInt(jneTargetMatch[1], 16) < pariteAddress) { pariteLoopCount++; } pariteState = 0; } else if (currentOpCode === "JMP") { pariteState = 0; } break; } pariteAddress = PE.getDisasmNextAddress(pariteAddress); } if (pariteLoopCount >= 0x20) { verdicts.push({ type: "Parite", version: String(), details: "infected" }); } } // http://www.microsoft.com/security/portal/threat/encyclopedia/entry.aspx?Name=Virus%3AWin32%2FPolip.A if (verdicts.length === 0 && PE_Cached.isArchX86 && !PE_Cached.is64bit && PE_Cached.numberOfSections >= 2 && ( getAsmOpCode(PE_Cached.firstEpAsmInstruction) === "PUSH" || getAsmOpCode(PE_Cached.firstEpAsmInstruction) === "CALL" )) { for (var x = 1; x < PE_Cached.numberOfSections; x++) { if (PE.getSectionName(x) === String()) { var currentSection = PE.section[x]; if ((currentSection.Characteristics & SECTION_FLAGS_RWX_MASK) === SECTION_FLAGS_RWX_MASK && PE_Cached.indexOfEntryPointSection !== x && PE_Cached.indexOfResourceSection !== x && PE_Cached.indexOfImportsSection !== x && PE_Cached.indexOfExportsSection !== x && PE_Cached.indexOfRelocsSection !== x && PE_Cached.indexOfTlsSection !== x && PE.calculateEntropy(currentSection.FileOffset, currentSection.FileSize) > 7.25 && PE.isSignatureInSectionPresent(x, "55" + // push ebp "8B EC" + // mov ebp, esp "83 EC .." + // sub esp, ... "60" // pushal )) { verdicts.push({ type: "Polip", version: String(), details: "infected" }); break; } } } } // https://web.archive.org/web/20260611044606/https://blog.sekoia.io/mars-a-red-hot-information-stealer/ if (verdicts.length === 0 && PE.isSectionNamePresent("LLCPPC")) { verdicts.push({ type: "Mars Stealer", version: String(), details: String() }); } // https://malpedia.caad.fkie.fraunhofer.de/details/win.wannacryptor if (verdicts.length === 0 && PE_Cached.isArchX86 && PE.compareEP( "55" + // push ebp "8B EC" // mov ebp, esp )) { var isWannaCryDetected = false; if (PE_Cached.numberOfUnmanagedResources > 0 && ( /^[RW]$/.test(PE.getResourceNameByNumber(0)) || // "R" or "W" /^[A-Z]{3}$/.test(PE.getResourceNameByNumber(0)) // like "XIA" )) { if (PE.isSignaturePresent(PE.resource[0].FileOffset, 32, "'MZ' 90") && // Embedded PE PE.isSignaturePresent(PE.resource[0].FileOffset, PE.resource[0].FileSize, "%% '.wnry'")) { isWannaCryDetected = true; } else { // Embedded ZIP and other modified variants var rsrcSection = PE.section[".rsrc"], dataSection = PE.section[".data"]; const cmdSignature = convertStringToUnicodeSignature("%s\\ProgramData") + "00 00 00 00 00 'cmd.exe /c ' 22 '%s' 22"; if ((rsrcSection && PE.isSignaturePresent(rsrcSection.FileOffset, rsrcSection.FileSize, cmdSignature)) || (dataSection && PE.isSignaturePresent(dataSection.FileOffset, dataSection.FileSize, cmdSignature))) { isWannaCryDetected = true; } } if (isWannaCryDetected) { verdicts.push({ type: "WannaCry", version: String(), details: String() }); } } } // https://citec.us/forum/showthread.php/15998-ConsoleDevil if (verdicts.length === 0 && PE_Cached.isArchX86 && PE.compareEP( "68 .. .. .. .." + // push [...] "68 00 00 00 00" + // push 0 "68 .. .. .. .." + // push [...] "E8 .. .. .. .." + // call [...] "83 C4 0C" + // add esp, 0xc "68 00 00 00 00" // push 0 )) { var consoleDevilVersion = String(); if (PE.isImportPositionHashPresent(0, 0xb27ceda0) && PE.isImportPositionHashPresent(1, 0xa77d975c)) { consoleDevilVersion = "1.0"; } else if (PE.isImportPositionHashPresent(0, 0x1dec6c44) && PE.isImportPositionHashPresent(1, 0xf1ed6358)) { consoleDevilVersion = "1.2"; } if (consoleDevilVersion) { verdicts.push({ type: "ConsoleDevil RAT", version: consoleDevilVersion, details: String() }); } } // https://malpedia.caad.fkie.fraunhofer.de/details/win.poison_ivy if (verdicts.length === 0 && PE_Cached.isArchX86 && PE.compareEP( "B8 00 04 40 00" + // mov eax, 0x400400 "FF D0" + // call eax "6A 00" // push 0 ) && PE.isImportPositionHashPresent(0, 0xbd365a2a)) { verdicts.push({ type: "Poison Lvy RAT", version: String(), details: String() }); } // https://malpedia.caad.fkie.fraunhofer.de/details/win.ghost_rat if (verdicts.length === 0 && PE.isExportFunctionPresent("Ip")) { verdicts.push({ type: "Gh0st RAT", version: String(), details: String() }); } // https://malpedia.caad.fkie.fraunhofer.de/details/win.darkcomet if (verdicts.length === 0 && // Todo fix /* (PE.isImportPositionHashPresent(0, 0xcf603a7d) && PE.isImportPositionHashPresent(1, 0xe6aa7d45) && PE.isImportPositionHashPresent(2, 0xa26edfd0)) || */ // UPX packed (PE.isImportPositionHashPresent(0, 0xf375ee03) && PE.isImportPositionHashPresent(1, 0x6043ad68) && PE.isImportPositionHashPresent(2, 0x49e8b6dd)) || // MPRESS packed (PE.isImportPositionHashPresent(0, 0x174efb84) && PE.isImportPositionHashPresent(1, 0x4afe3a51) && PE.isImportPositionHashPresent(2, 0x3d9a43d0)) ) { verdicts.push({ type: "DarkComet RAT", version: "5.3", details: String() }); } // https://malpedia.caad.fkie.fraunhofer.de/details/win.bit_rat if (verdicts.length === 0 && PE_Cached.numberOfUnmanagedImports === 1) { var isBitRatDetected = false, isBitRatTorStubVersion = false; if (PE.isImportPositionHashPresent(0, 0x202c6668)) { isBitRatDetected = true; } else if (PE.isImportPositionHashPresent(0, 0x249e9ed3)) { isBitRatDetected = true; isBitRatTorStubVersion = true; } if (isBitRatDetected) { verdicts.push({ type: "BitRAT", version: String(), details: isBitRatTorStubVersion ? "Tor" : String() }); } } if (verdicts.length === 0 && (PE.isImportPositionHashPresent(0, 0x991f7f9a) && PE.isImportPositionHashPresent(1, 0x3495fca1)) || (PE.isImportPositionHashPresent(0, 0x7d78c0f9) && PE.isImportPositionHashPresent(1, 0x4b76cb0f))) { verdicts.push({ type: "Furax RAT", version: String(), details: String() }); } // https://malpedia.caad.fkie.fraunhofer.de/details/ps1.octopus if (verdicts.length === 0 && (PE.isImportPositionHashPresent(0, 0x42600eef) && PE.isSignatureInSectionPresent(0, "'Octopus'"))) { verdicts.push({ type: "Octopus RAT", version: String(), details: String() }); } if (verdicts.length === 0 && (PE_Cached.numberOfUnmanagedImports === 1 && PE.isImportPositionHashPresent(0, 0x06a04d16)) || (PE_Cached.numberOfUnmanagedImports > 4 && PE.isImportPositionHashPresent(0, 0xa719918b) && PE.isImportPositionHashPresent(-1, 0x9f8ccfc6)) ) { verdicts.push({ type: "Biorante", version: "1.X", details: String() }); } if (verdicts.length === 0 && PE.isImportPositionHashPresent(0, 0xb3318086) && // UPX packed PE.isImportPositionHashPresent(1, 0x3d9a43d0) && PE.isImportPositionHashPresent(7, 0xf3b2674d)) { verdicts.push({ type: "Smart RAT", version: "1.0.X", details: String() }); } if (verdicts.length === 0 && PE_Cached.numberOfUnmanagedImports === 1 && PE.isImportPositionHashPresent(0, 0x1b62aa20)) { verdicts.push({ type: "Ace RAT", version: "1.X", details: String() }); } if (verdicts.length === 0 && PE_Cached.numberOfUnmanagedImports === 1 && PE_Cached.isRichSignaturePresent && PE.isImportPositionHashPresent(0, 0x2d6f7e88)) { verdicts.push({ type: "Aquates RAT", version: "1.0", details: String() }); } if (verdicts.length === 0 && PE.section[0].Name === "\uFFFD\uFFFD50") { verdicts.push({ type: "Exception RAT", version: "1.0", details: String() }); } // https://habr.com/ru/companies/mws/articles/350974/ if (verdicts.length === 0 && PE.section["LineRecs"]) { verdicts.push({ type: "Slingshot APT", version: String(), details: String() }); } // https://malpedia.caad.fkie.fraunhofer.de/details/win.olympic_destroyer if (verdicts.length === 0 && PE_Cached.numberOfUnmanagedResources > 0) { if (PE.compare("C1 7E 20 A2 DA F9 56 45 61 39 EB DB 51 9D A7 4F", PE.resource[0].FileOffset)) { verdicts.push({ type: "Olympic Destroyer", version: String(), details: String() }); } } // https://en.wikipedia.org/wiki/Equation_Group if (verdicts.length === 0 && PE_Cached.numberOfUnmanagedResources > 0) { for (var i = 0; i < PE_Cached.numberOfUnmanagedResources; i++) { if (PE.resource[i].Name === "BINRES" && PE.compare("4D 5A 90", PE.resource[i].FileOffset)) { verdicts.push({ type: "Equation Group", version: String(), details: String() }); break; } } } // https://malpedia.caad.fkie.fraunhofer.de/details/win.bozok if (verdicts.length === 0 && !PE_Cached.isRichSignaturePresent && PE.isImportPositionHashPresent(0, 0xbd365a2a)) { // kernel32.dll->ExitProcess var bozokRatVersion = String(); if (PE.isImportPositionHashPresent(1, 0x0ed01f83)) { bozokRatVersion = "1.0"; } else if (PE.isImportPositionHashPresent(1, 0xefd4be65)) { bozokRatVersion = "1.2-1.3"; } else if (PE.isImportPositionHashPresent(1, 0x58133a50)) { bozokRatVersion = "1.4-1.5.X"; } if (bozokRatVersion) { verdicts.push({ type: "Bozok RAT", version: bozokRatVersion, details: String() }); } } if (verdicts.length === 0 && !PE_Cached.isRichSignaturePresent) { // VB5 var aquaRatVersion = String(); if (PE.isImportPositionHashPresent(0, 0xfc2095af)) { aquaRatVersion = "0.0.1"; } else if (PE.isImportPositionHashPresent(0, 0x8bc8445e)) { aquaRatVersion = "0.2"; } if (aquaRatVersion && PE.isSignatureInSectionPresent(0, "'Aqua'")) { verdicts.push({ type: "Aqua RAT", version: aquaRatVersion, details: String() }); } } if (verdicts.length === 0 && (PE.isImportPositionHashPresent(0, 0x90648ec6) && PE.isImportPositionHashPresent(1, 0x199ccb02) && PE.isImportPositionHashPresent(2, 0x4bb25d37)) || (PE.isImportPositionHashPresent(0, 0x41b27a00) && PE.isImportPositionHashPresent(1, 0x56a9aa44) && PE.isImportPositionHashPresent(2, 0x4bb25d37)) || (PE.isImportPositionHashPresent(0, 0xc649d485) && PE.isImportPositionHashPresent(1, 0xf2e69b15) && PE.isImportPositionHashPresent(2, 0x4bb25d37)) || (PE.isImportPositionHashPresent(0, 0xe27edc0d) && PE.isImportPositionHashPresent(1, 0x1da5760e) && PE.isImportPositionHashPresent(2, 0x42779d34)) || // UPX packed (PE.isImportPositionHashPresent(0, 0x3d9a43d0) && PE.isImportPositionHashPresent(1, 0xc28f4fe3) && PE.isImportPositionHashPresent(2, 0x5d1bddcc)) || (PE.isImportPositionHashPresent(0, 0xf375ee03) && PE.isImportPositionHashPresent(1, 0x3d9a43d0) && PE.isImportPositionHashPresent(2, 0xc28f4fe3) && (PE.isImportPositionHashPresent(3, 0x5d1bddcc) || PE.isImportPositionHashPresent(3, 0xa2163984))) ) { verdicts.push({ type: "Babylon RAT", version: String(), details: String() }); } if (verdicts.length === 0 && PE.isImportPositionHashPresent(0, 0x519fb9a6) && PE.isImportPositionHashPresent(1, 0x69561fa8) && PE.isImportPositionHashPresent(2, 0xe6aa7d45)) { verdicts.push({ type: "Sinique RAT", version: "1.0", details: String() }); } // https://malpedia.caad.fkie.fraunhofer.de/details/win.netwire if (verdicts.length === 0 && PE.isImportPositionHashPresent(1, 0x5d1bddcc)) { var netWireVersion = String(); if (PE.isImportPositionHashPresent(0, 0xfa0dd025)) { netWireVersion = "1.2.X"; } else if (PE.isImportPositionHashPresent(0, 0x7845f465)) { netWireVersion = "1.4"; } if (netWireVersion) { verdicts.push({ type: "NetWire RAT", version: netWireVersion, details: String() }); } } // https://malpedia.caad.fkie.fraunhofer.de/details/win.remcos if (verdicts.length === 0 && PE.isImportPositionHashPresent(0, 0xf25ba92f) && PE.isImportPositionHashPresent(1, 0xf740ae08) && PE.isImportPositionHashPresent(2, 0xba3a1112)) { verdicts.push({ type: "Remcos RAT", version: String(), details: String() }); } // https://web.archive.org/web/20260108153947/https://indetectables.net/viewtopic.php?t=20716 if (verdicts.length === 0 && !PE_Cached.isRichSignaturePresent && PE_Cached.numberOfUnmanagedImports === 20 && PE.isImportPositionHashPresent(9, 0x5206612e) && PE.isImportPositionHashPresent(16, 0xf3b2674d) && PE.isImportPositionHashPresent(19, 0xabde73fb)) { verdicts.push({ type: "Daleth RAT", version: "1.0", details: String() }); } // https://web.archive.org/web/20090902161812/http://synsecurity.net/index.php?page=downloads if (verdicts.length === 0 && !PE_Cached.isRichSignaturePresent && PE_Cached.numberOfUnmanagedImports === 31 && PE.isImportPositionHashPresent(0, 0xcf603a7d) && PE.isImportPositionHashPresent(-1, 0x0201b2be) && PE.isImportPositionHashPresent(-1, 0x3ca208f6)) { verdicts.push({ type: "Syndrome RAT", version: "4.3.X", details: String() }); } // https://web.archive.org/web/20251224000209/https://sumhex151.github.io/ (decryptor) if (verdicts.length === 0 && !PE_Cached.isRichSignaturePresent && PE.isTLSPresent() && ( (PE.isImportPositionHashPresent(18, 0xc3df3568) && PE.isSignatureInSectionPresent(0, "'lock_text'00")) || (PE.isImportPositionHashPresent(4, 0xc3df3568) && PE.isImportPositionHashPresent(5, 0x622ae906)) // UPX packed )) { verdicts.push({ type: "Amp WinLocker", version: "Sumhex Edition", details: String() }); } // https://max[.]ru/ (Malicious site; Be careful!) if (verdicts.length === 0 && ( PE.getVersionStringInfo("ProductName") === "Max" || PE.getVersionStringInfo("CompanyName") === "Communication Platform LLC" )) { verdicts.push({ type: "MAX Spyware", version: String(), details: String() }); } // Many not-so-smart virus writers use base64 to pack // or hide malicious code, but do not realize that this // is very easily detected by heuristic analysis. var isBase64Payload = false; var base64Version = String(); const maxBase64PeHeaderAnchorCandidates = 64; const base64PeHeaderPostfixSigns = [ "oAAA", // ~[00 00 00 00 00] "qQAA", // ~[90 00 03] "pQAA", // ~[50 00 02] "p4AA" // ~[78 00 01] ]; function isBase64PeHeaderPresent(areaOffset, areaSize, isUnicode) { var anchorPattern = isUnicode ? convertStringToUnicodeSignature("TV") : "TV", postfixOffsetDelta = isUnicode ? 4 : 2, completePatternSize = isUnicode ? 12 : 6, currentOffset = areaOffset, remainingSize = areaSize, candidateCount = 0; while (remainingSize >= completePatternSize) { var anchorOffset = isUnicode ? PE.findSignature(currentOffset, remainingSize, anchorPattern) : PE.findString(currentOffset, remainingSize, anchorPattern); if (anchorOffset === -1) break; if (anchorOffset + completePatternSize <= areaOffset + areaSize) { for (var i = 0; i < base64PeHeaderPostfixSigns.length; i++) { var postfixPattern = isUnicode ? convertStringToUnicodeSignature(base64PeHeaderPostfixSigns[i]) : "'" + base64PeHeaderPostfixSigns[i] + "'"; if (PE.compare(postfixPattern, anchorOffset + postfixOffsetDelta)) return true; } } var advanced = (anchorOffset - currentOffset) + postfixOffsetDelta; currentOffset += advanced; remainingSize -= advanced; // Short anchors normally reduce four full scans to one. If an area is // unusually rich in `TV`, stop enumerating candidates and let the native // search routine look for the four complete, selective patterns instead. if (++candidateCount === maxBase64PeHeaderAnchorCandidates) { for (var i = 0; i < base64PeHeaderPostfixSigns.length; i++) { var completePattern = "TV" + base64PeHeaderPostfixSigns[i]; if (isUnicode) completePattern = convertStringToUnicodeSignature(completePattern); if ((isUnicode ? PE.findSignature(currentOffset, remainingSize, completePattern) : PE.findString(currentOffset, remainingSize, completePattern)) !== -1) return true; } break; } } return false; } function scanArea(areaOffset, areaSize) { if (areaSize <= 0 || areaOffset <= 0 || isBase64Payload) return; if (isBase64PeHeaderPresent(areaOffset, areaSize, false)) { base64Version = "ASCII"; isBase64Payload = true; return; } if (isBase64PeHeaderPresent(areaOffset, areaSize, true)) { base64Version = "UTF-16LE"; isBase64Payload = true; } } if (_getNumberOfResults("packer") === 0 || _getNumberOfResults("packer") > 1 || _getNumberOfResults("protector") > 0) { for (var i = 0; i < PE_Cached.numberOfSections && !isBase64Payload; i++) { var sectionOffset = PE.getSectionFileOffset(i), sectionSize = PE.getSectionFileSize(i), sectionName = PE.getSectionName(i); // Optimizations: Skip known sections that are unlikely to contain encoded payloads if (PE_Cached.numberOfSections > 1) { if (sectionOffset === 0 || sectionSize < 0x2000) { continue; } var isBase64PrimaryNativeCodeSection = !PE_Cached.isDotNet && i === PE_Cached.indexOfEntryPointSection && sectionName === ".text" && i === 0; var isBase64DedicatedResourceSection = i === PE_Cached.indexOfResourceSection && sectionName === ".rsrc" && i !== PE_Cached.indexOfImportsSection && i !== PE_Cached.indexOfEntryPointSection && i !== PE_Cached.indexOfTlsSection && i !== PE_Cached.indexOfRelocsSection; var isBase64DedicatedImportsSection = i === PE_Cached.indexOfImportsSection && sectionName === ".idata" && i !== PE_Cached.indexOfResourceSection && i !== PE_Cached.indexOfEntryPointSection && i !== PE_Cached.indexOfTlsSection && i !== PE_Cached.indexOfRelocsSection; var isBase64DedicatedRelocsSection = i === PE_Cached.indexOfRelocsSection && sectionName === ".reloc" && i !== PE_Cached.indexOfResourceSection && i !== PE_Cached.indexOfEntryPointSection && i !== PE_Cached.indexOfTlsSection && i !== PE_Cached.indexOfImportsSection; if (isBase64PrimaryNativeCodeSection || isBase64DedicatedResourceSection || isBase64DedicatedImportsSection || isBase64DedicatedRelocsSection) { continue; } } scanArea(sectionOffset, sectionSize); } } if (!isBase64Payload && PE_Cached.numberOfUnmanagedResources > 0) { for (var r = 0; r < PE_Cached.numberOfUnmanagedResources && !isBase64Payload; r++) { var resourceOffset = PE.getResourceOffsetByNumber(r), resourceSize = PE.getResourceSizeByNumber(r); // Target actual payloads (> 4 KB) and skip bitmaps if (resourceOffset > 0 && resourceSize >= 0x1000 && !PE.compare("28 00 00 00 ?? ?? 00 00 ?? ?? 00 00 01 00 ?? 00 00 00 00 00", resourceOffset)) { scanArea(resourceOffset, resourceSize); } } } if (!isBase64Payload) { scanArea(PE_Cached.overlayOffset, PE_Cached.overlaySize); } if (isBase64Payload) { verdicts.push({ type: "Encoded executable payload", version: "Base64".append(base64Version), details: mayBeInfected }); } // Detect encrypted PE files in resources, sections AND overlay via KPA (Known Plaintext Attack) // Supports MULTIPLE algorithms (XOR, XNOR, ADD, SUB, SUB-REV) and key lengths up to 20 bytes var isEncPePresent = false; var detectedAlgo = String(); var Uint8Arr = typeof Uint8Array !== "undefined" ? Uint8Array : Array; // PRE-CALCULATION BLOCK: O(1) Offset mapping for key lengths up to 20 bytes var offsetKey0 = new Uint8Arr(21), offsetKey1 = new Uint8Arr(21), offsetKey3 = new Uint8Arr(21), offsetLfa0 = new Uint8Arr(21), offsetLfa1 = new Uint8Arr(21), offsetLfa2 = new Uint8Arr(21), offsetLfa3 = new Uint8Arr(21); for (var keyLen = 1; keyLen <= 20; keyLen++) { offsetKey0[keyLen] = 40 + ((keyLen - (40 % keyLen)) % keyLen); offsetKey1[keyLen] = 40 + ((1 + keyLen - (40 % keyLen)) % keyLen); offsetKey3[keyLen] = 40 + ((3 + keyLen - (40 % keyLen)) % keyLen); offsetLfa0[keyLen] = 40 + (20 % keyLen); offsetLfa1[keyLen] = 40 + (21 % keyLen); offsetLfa2[keyLen] = 40 + (22 % keyLen); offsetLfa3[keyLen] = 40 + (23 % keyLen); } // Strict PE header verification function function verifyPeSignature(dataBuffer, peStartOffset, maxValidLfaNew, keyLength, mode) { var encZero, cipherByte, valLfa2, valLfa1, valLfa0; encZero = dataBuffer[peStartOffset + offsetLfa2[keyLength]]; cipherByte = dataBuffer[peStartOffset + 0x3E]; valLfa2 = mode === 0 ? (cipherByte ^ encZero) : (mode === 1 ? ((cipherByte - encZero) & 0xFF) : ((encZero - cipherByte) & 0xFF)); if ((valLfa2 << 16) >= maxValidLfaNew) return false; encZero = dataBuffer[peStartOffset + offsetLfa1[keyLength]]; cipherByte = dataBuffer[peStartOffset + 0x3D]; valLfa1 = mode === 0 ? (cipherByte ^ encZero) : (mode === 1 ? ((cipherByte - encZero) & 0xFF) : ((encZero - cipherByte) & 0xFF)); encZero = dataBuffer[peStartOffset + offsetLfa0[keyLength]]; cipherByte = dataBuffer[peStartOffset + 0x3C]; valLfa0 = mode === 0 ? (cipherByte ^ encZero) : (mode === 1 ? ((cipherByte - encZero) & 0xFF) : ((encZero - cipherByte) & 0xFF)); var lfaNewOffset = (valLfa0 | (valLfa1 << 8) | (valLfa2 << 16)) >>> 0; if (lfaNewOffset > 0x40 && lfaNewOffset < maxValidLfaNew) { var peHeaderOffset = lfaNewOffset, baseZeroOffset = peStartOffset + 40, peSignatureOffset = peStartOffset + peHeaderOffset, baseRemainder = (lfaNewOffset - 40) % keyLength; encZero = dataBuffer[baseZeroOffset + ((baseRemainder + 0) % keyLength)]; cipherByte = dataBuffer[peSignatureOffset + 0]; if ((mode === 0 ? (cipherByte ^ encZero) : mode === 1 ? ((cipherByte - encZero) & 0xFF) : ((encZero - cipherByte) & 0xFF)) !== 0x50) return false; encZero = dataBuffer[baseZeroOffset + ((baseRemainder + 1) % keyLength)]; cipherByte = dataBuffer[peSignatureOffset + 1]; if ((mode === 0 ? (cipherByte ^ encZero) : mode === 1 ? ((cipherByte - encZero) & 0xFF) : ((encZero - cipherByte) & 0xFF)) !== 0x45) return false; encZero = dataBuffer[baseZeroOffset + ((baseRemainder + 2) % keyLength)]; cipherByte = dataBuffer[peSignatureOffset + 2]; if ((mode === 0 ? (cipherByte ^ encZero) : mode === 1 ? ((cipherByte - encZero) & 0xFF) : ((encZero - cipherByte) & 0xFF)) !== 0x00) return false; encZero = dataBuffer[baseZeroOffset + ((baseRemainder + 3) % keyLength)]; cipherByte = dataBuffer[peSignatureOffset + 3]; if ((mode === 0 ? (cipherByte ^ encZero) : mode === 1 ? ((cipherByte - encZero) & 0xFF) : ((encZero - cipherByte) & 0xFF)) !== 0x00) return false; var magicByte1, magicByte2; encZero = dataBuffer[baseZeroOffset + ((baseRemainder + 0x18) % keyLength)]; cipherByte = dataBuffer[peSignatureOffset + 0x18]; magicByte1 = mode === 0 ? (cipherByte ^ encZero) : mode === 1 ? ((cipherByte - encZero) & 0xFF) : ((encZero - cipherByte) & 0xFF); encZero = dataBuffer[baseZeroOffset + ((baseRemainder + 0x19) % keyLength)]; cipherByte = dataBuffer[peSignatureOffset + 0x19]; magicByte2 = mode === 0 ? (cipherByte ^ encZero) : mode === 1 ? ((cipherByte - encZero) & 0xFF) : ((encZero - cipherByte) & 0xFF); var headerMagic = magicByte1 | (magicByte2 << 8); if (headerMagic !== 0x010B && headerMagic !== 0x020B) return false; var numSections1, numSections2; encZero = dataBuffer[baseZeroOffset + ((baseRemainder + 0x06) % keyLength)]; cipherByte = dataBuffer[peSignatureOffset + 0x06]; numSections1 = mode === 0 ? (cipherByte ^ encZero) : mode === 1 ? ((cipherByte - encZero) & 0xFF) : ((encZero - cipherByte) & 0xFF); encZero = dataBuffer[baseZeroOffset + ((baseRemainder + 0x07) % keyLength)]; cipherByte = dataBuffer[peSignatureOffset + 0x07]; numSections2 = mode === 0 ? (cipherByte ^ encZero) : mode === 1 ? ((cipherByte - encZero) & 0xFF) : ((encZero - cipherByte) & 0xFF); var totalSections = numSections1 | (numSections2 << 8); if (totalSections === 0 || totalSections > 48) return false; var charByte1, charByte2; encZero = dataBuffer[baseZeroOffset + ((baseRemainder + 0x16) % keyLength)]; cipherByte = dataBuffer[peSignatureOffset + 0x16]; charByte1 = mode === 0 ? (cipherByte ^ encZero) : mode === 1 ? ((cipherByte - encZero) & 0xFF) : ((encZero - cipherByte) & 0xFF); encZero = dataBuffer[baseZeroOffset + ((baseRemainder + 0x17) % keyLength)]; cipherByte = dataBuffer[peSignatureOffset + 0x17]; charByte2 = mode === 0 ? (cipherByte ^ encZero) : mode === 1 ? ((cipherByte - encZero) & 0xFF) : ((encZero - cipherByte) & 0xFF); if (!((charByte1 | (charByte2 << 8)) & 0x0002)) return false; return true; } return false; } // Core scanner function scanBuffer(dataBuffer, bufferSize) { var maxSearchIndex = bufferSize - 0x100, offset = 0, keyLength = 1, cipherM, cipherZ, keyXorM, keyXorZ, cipherByte3, cipherLfaMsb, cipherByteKey0, keyAddM, keyAddZ, keyRevM, keyRevZ, maxLfaValid, cacheKey0 = offsetKey0, cacheKey1 = offsetKey1, cacheKey3 = offsetKey3, cacheLfa3 = offsetLfa3; for (; offset < maxSearchIndex; offset++) { cipherM = dataBuffer[offset]; cipherZ = dataBuffer[offset + 1]; keyXorM = cipherM ^ 0x4D; keyXorZ = cipherZ ^ 0x5A; if (keyXorM === 0x00 && keyXorZ === 0x00) continue; cipherByte3 = dataBuffer[offset + 3]; cipherLfaMsb = dataBuffer[offset + 0x3F]; maxLfaValid = bufferSize - offset - 0x20; for (keyLength = 1; keyLength <= 20; keyLength++) { if (cipherByte3 !== dataBuffer[offset + cacheKey3[keyLength]]) continue; if (cipherLfaMsb !== dataBuffer[offset + cacheLfa3[keyLength]]) continue; cipherByteKey0 = dataBuffer[offset + cacheKey0[keyLength]]; if (cipherByteKey0 === keyXorM && dataBuffer[offset + cacheKey1[keyLength]] === keyXorZ) { if (verifyPeSignature(dataBuffer, offset, maxLfaValid, keyLength, 0)) { detectedAlgo = "XOR-XNOR"; return true; } } else { keyAddM = (cipherM - 0x4D) & 0xFF; if (cipherByteKey0 === keyAddM) { keyAddZ = (cipherZ - 0x5A) & 0xFF; if (dataBuffer[offset + cacheKey1[keyLength]] === keyAddZ) { if (verifyPeSignature(dataBuffer, offset, maxLfaValid, keyLength, 1)) { detectedAlgo = "ADD-SUB"; return true; } } } else { keyRevM = (cipherM + 0x4D) & 0xFF; if (cipherByteKey0 === keyRevM) { keyRevZ = (cipherZ + 0x5A) & 0xFF; if (dataBuffer[offset + cacheKey1[keyLength]] === keyRevZ) { if (verifyPeSignature(dataBuffer, offset, maxLfaValid, keyLength, 2)) { detectedAlgo = "SUB-REV"; return true; } } } } } } } return false; } // 1. Scan Resources for (var i = 0; i < PE_Cached.numberOfUnmanagedResources && !isEncPePresent; i++) { var resourceOffset = PE.getResourceOffsetByNumber(i), resourceSize = PE.getResourceSizeByNumber(i); if (resourceOffset > 0 && resourceSize > 0x1000 && !PE.compare("28 00 00 00 ?? ?? 00 00 ?? ?? 00 00 01 00 ?? 00 00 00 00 00", resourceOffset)) { var maxScanSize = Math.min(resourceSize, 0x1000), dataBuffer = PE.readBytes(resourceOffset, maxScanSize); if (scanBuffer(dataBuffer, maxScanSize)) { isEncPePresent = true; break; } } } // 2. Scan Overlay if (!isEncPePresent) { var overlayOffset = PE_Cached.overlayOffset, overlaySize = PE_Cached.overlaySize; if (overlayOffset > 0 && overlaySize > 0x1000 && !PE.isSigned()) { var maxScanSize = Math.min(overlaySize, 0x14000), dataBuffer = PE.readBytes(overlayOffset, maxScanSize); if (scanBuffer(dataBuffer, maxScanSize)) isEncPePresent = true; } } // 3. Scan sections if (!isEncPePresent) { for (var i = 0; i < PE_Cached.numberOfSections && !isEncPePresent; i++) { var sectionOffset = PE.getSectionFileOffset(i), sectionSize = PE.getSectionFileSize(i), sectionName = PE.getSectionName(i); if (PE_Cached.numberOfSections > 1) { if (sectionOffset === 0 || sectionSize < 0x2500) { continue; } var isEncryptedPePrimaryNativeCodeSection = !PE_Cached.isDotNet && i === PE_Cached.indexOfEntryPointSection && sectionName === ".text" && i === 0; var isEncryptedPeDedicatedResourceSection = i === PE_Cached.indexOfResourceSection && sectionName === ".rsrc" && i !== PE_Cached.indexOfImportsSection && i !== PE_Cached.indexOfEntryPointSection && i !== PE_Cached.indexOfTlsSection && i !== PE_Cached.indexOfRelocsSection; var isEncryptedPeDedicatedImportsSection = i === PE_Cached.indexOfImportsSection && sectionName === ".idata" && i !== PE_Cached.indexOfResourceSection && i !== PE_Cached.indexOfEntryPointSection && i !== PE_Cached.indexOfTlsSection && i !== PE_Cached.indexOfRelocsSection; var isEncryptedPeDedicatedRelocsSection = i === PE_Cached.indexOfRelocsSection && sectionName === ".reloc" && i !== PE_Cached.indexOfResourceSection && i !== PE_Cached.indexOfEntryPointSection && i !== PE_Cached.indexOfTlsSection && i !== PE_Cached.indexOfImportsSection; if (isEncryptedPePrimaryNativeCodeSection || isEncryptedPeDedicatedResourceSection || isEncryptedPeDedicatedImportsSection || isEncryptedPeDedicatedRelocsSection) { continue; } } var maxScanSize = (PE_Cached.isDotNet && i === 0) ? Math.min(sectionSize, 0x64000) : Math.min(sectionSize, sectionName.match(/^\.[rex]?data$/i) ? 0x12000 : 0x6000), dataBuffer = PE.readBytes(sectionOffset, maxScanSize); if (scanBuffer(dataBuffer, maxScanSize)) { isEncPePresent = true; break; } } } if (isEncPePresent) { verdicts.push({ type: "Encrypted executable payload", version: "Algo: " + detectedAlgo, details: mayBeInfected }); } // Check for fake copyright info that may indicate a builder was used or the malware is masquerading as a legitimate software. const legalCopyrightInfo = PE.getVersionStringInfo("LegalCopyright"); if (legalCopyrightInfo && !/(?:\(C\)|©|\bcopyright\b)/i.test(legalCopyrightInfo)) { const legalCopyrightInfoLowerCase = legalCopyrightInfo.toLowerCase(); const companyNamesDict = [ // Big Tech — most commonly faked "microsoft", "google", "apple", "amazon", // Software & games "adobe", "oracle", "mozilla", "vmware", "valve", "rockstar", "blizzard", "ubisoft", "roblox", "ea digital", "cd projekt", "epic games", // Messaging & social "discord", "telegram", "whatsapp", // Security vendors (faked to appear "trusted") "kaspersky", "avast", "malwarebytes", "doctor web", "bitdefender", "norton", "mcafee", "eset", "avira", "sophos", // Hardware "nvidia", "intel" ]; for (var i = 0; i < companyNamesDict.length; i++) { if (new RegExp("\\b" + companyNamesDict[i] + "\\b").test(legalCopyrightInfoLowerCase) && legalCopyrightInfo.indexOf("Microsoft.") !== 0) { verdicts.push({ type: "Fake build info", version: String(), details: mayBeInfected }); break; } } } // Some malwares masquerade as critical system files to avoid detection, but they often forget // to change the internal and original file names, which can be easily checked. // Also check if such files are signed and have a rich signature, as legitimate // system files should have these features. const systemSignedFileNamesDict = [ "csrss.exe", "wininit.exe", "lsass.exe", "svchost.exe", "taskhostw.exe", "ntkrnlmp.exe" /* -> (ntoskrnl.exe) */, "RuntimeBroker.exe", "smss.exe", "SecurityHealthService.exe" ]; const systemFileNamesDict = systemSignedFileNamesDict.concat([ "dwm.exe", "winlogon.exe", "services.exe", "spoolsv.exe", "OneDrive.exe", "fontdrvhost.exe", "sihost.exe", "winresume.exe", "SystemSettings.exe", "ShellExperienceHost.exe", "StartMenuExperienceHost.exe", "conhost.exe" ]); const originalFileName = PE.getVersionStringInfo("OriginalFilename"), internalFileName = PE.getVersionStringInfo("InternalName"), originalFileNameLowerCase = originalFileName.toLowerCase(), internalFileNameLowerCase = internalFileName.toLowerCase(), netAssemblyNameLowerCase = PE_Cached.nameOfNetAssemblyName.toLowerCase(), netModuleNameLowerCase = PE_Cached.nameOfNetModuleName.toLowerCase(); var isFakeOrInfected = false; function compareOriginalAndInternalNames(inputName) { if (!inputName) return false; inputName = inputName.toLowerCase(); return ( originalFileNameLowerCase === inputName || internalFileNameLowerCase === inputName ); } for (var j = 0; j < systemSignedFileNamesDict.length && !isFakeOrInfected; j++) { if (compareOriginalAndInternalNames(systemSignedFileNamesDict[j])) { if (!PE.isSigned()) { isFakeOrInfected = true; } } } if (PE_Cached.isDotNet || !PE_Cached.isRichSignaturePresent) { if (_getNumberOfResults("packer") > 0 || _getNumberOfResults("protector") > 0) { for (var k = 0; k < systemFileNamesDict.length && !isFakeOrInfected; k++) { if (compareOriginalAndInternalNames(systemFileNamesDict[k])) { isFakeOrInfected = true; } } } } if (isFakeOrInfected) { verdicts.push({ type: "Fake " + (!PE_Cached.isDotNet ? "or infected " : String()) + "system file", version: String(), details: mayBeInfected }); } // Check for obfuscated names and anomalous signs in .NET and native assemblies if (PE_Cached.isDotNet && ( netAssemblyNameLowerCase === "stub" || netModuleNameLowerCase.indexOf("stub.") === 0 || netAssemblyNameLowerCase.indexOf("crypted") !== -1 || netModuleNameLowerCase.indexOf("crypted") !== -1 || netAssemblyNameLowerCase.indexOf("payload") !== -1 || netModuleNameLowerCase.indexOf("payload") !== -1 || isNameObfuscated(PE_Cached.nameOfNetAssemblyName) || isNameObfuscated(PE_Cached.nameOfNetModuleName) ) || originalFileNameLowerCase.indexOf("stub.") === 0 || internalFileNameLowerCase.indexOf("stub.") === 0 || (function () { const fieldsToCheck = [ "Comments", "CompanyName", "FileDescription", "ProductName", "LegalCopyright", "LegalTrademarks", "OriginalFilename", "InternalName" ]; for (var i = 0; i < fieldsToCheck.length; i++) { if (isNameObfuscated(PE.getVersionStringInfo(fieldsToCheck[i]))) { return true; } } return false; })() ) { verdicts.push({ type: "Anomalous build info", version: String(), details: mayBeInfected }); } else if (/(?:^tmp|\.tmp$)/.test(PE_Cached.nameOfNetModuleName)) { verdicts.push({ type: "Generated temporary assembly", version: String(), details: mayBeInfected }); } // Check for multiple packers, cryptors, and protectors, which may indicate the use of a builder or attempts to hide the true nature of the file. var protectionResultsCount = _getNumberOfResults("packer") + _getNumberOfResults("cryptor") + _getNumberOfResults("protector") + _getNumberOfResults("protection"); if (protectionResultsCount > 3) { verdicts.push({ type: "Anomalous build content", version: String(), details: mayBeInfected }); } // Check for anomalous resource names in .NET and native assemblies, which may // indicate the presence of a builder or attempts to hide the true nature of the malware. if (PE.isResourceNamePresentExp(/^(?:STUB|SERVER)(?:\.[A-Z]{3})?$/) || PE.isResourceNamePresentExp(/(?:PAYLOAD|SHELLCODE|INJECT|CRYPTED|DECRYPTOR)/) || // Why virus-makers use such resource names is beyond me, but they do. PE.isResourceNamePresentExp(/^(?:TROJAN|VIRUS|MALWARE|DROPPER)$/)) { verdicts.push({ type: "Anomalous resources", version: String(), details: mayBeInfected }); } for (var v = 0; v < verdicts.length; v++) { var verdict = verdicts[v]; log(logType.any, "Potential malware match: " + verdict.type + (verdict.version ? " " + verdict.version : String())); _setResult("~malware", verdict.type, verdict.version, verdict.details); } } // #endregion // #region "interesting markers (.NET and Native)" function scanForInterestingMarkers_NET_and_Native() { log(logType.nothing, "Scanning for interesting markers..."); var options = String(); var isDetected = Boolean(); const dllCharacteristics = PE.getImageOptionalHeader("DllCharacteristics"); // https://learn.microsoft.com/en-us/previous-versions/windows/desktop/legacy/ee417681(v=vs.85)#:~:text=Root%20Element:%20GameDefinitionFile,GameDefinition%20Attributes var isGdfSchemaPresent = false; for (var i = 0; i < PE_Cached.numberOfUnmanagedResources && !isGdfSchemaPresent; i++) { if (PE.getResourceNameByNumber(i) === "__GDF_XML") { const gdfResourceOffset = PE.getResourceOffsetByNumber(i); if (gdfResourceOffset !== -1 && PE.isSignaturePresent( gdfResourceOffset, PE.getResourceSizeByNumber(i), convertStringToUnicodeSignature("GameDefinitionFile"))) { log(logType.any, "Image contains a Windows Game Definition File"); isGdfSchemaPresent = true; } } } if (isGdfSchemaPresent) options = "GDF Schema"; // https://learn.microsoft.com/en-us/windows/win32/secauthz/appcontainer-isolation var isAppContainerImage = false; if (dllCharacteristics & IMAGE_DLLCHARACTERISTICS_APPCONTAINER) { log(logType.any, "Image requests AppContainer isolation"); isAppContainerImage = true; } if (isAppContainerImage) options = addOption(options, "AppContainer Sandboxed Image"); if (options.length !== 0) isDetected = true; if (isDetected) { _setResult("~marker", "Contains", String(), PE.isVerbose() ? options : String()); } } // #endregion // #region "extensions (.NET and Native)" function scanNameForAnomalies_FileSystem_NET_and_Native() { log(logType.nothing, "Checking file system executable extension..."); var options = String(); var isDetected = Boolean(); const executableFileSystemExtension = File.getFileSuffix().toLowerCase(), extTypes = { // Can be launched from Windows Explorer /* EXEC */ "exe": null, /* EXEC */ "com": "MS-DOS Executable", /* EXEC */ "pif": "MS-DOS Executable Link", /* EXEC */ "scr": "Windows Screensaver", /* EXEC */ "bat": "Windows Batch", /* EXEC */ "cmd": "Windows Batch", // System, host-loaded, and resource modules /* HOST */ "dll": null, /* DRVR */ "drv": "Windows Driver (Legacy)", /* DRVR */ "sys": "Windows System Component", /* FWRM */ "efi": "UEFI Component", /* META */ "winmd": "Windows Runtime Metadata", /* HOST */ "ocx": "ActiveX Component", /* HOST */ "cpl": "Control Panel Applet", /* HOST */ "acm": "Audio Compression Manager Driver", /* HOST */ "ax": "DirectShow Filter", /* HOST */ "ime": "Input Method Editor", /* HOST */ "tsp": "TAPI Service Provider", /* RSRC */ "rll": "Resource Library", /* RSRC */ "mui": "Multilingual Resource Module", /* RSRC */ "msstyles": "Windows Visual Style", /* RSRC */ "mun": "Windows Resource Module", /* RSRC */ "dat": "Resource Module", // Programming language and third-party modules /* META */ "netmodule": ".NET Module", /* PLUG */ "xll": "Microsoft Excel Add-In", /* PLUG */ "bpl": "Borland Package", /* PLUG */ "dpl": "Delphi Package (Legacy)", /* PLUG */ "pyd": "CPython Module", /* PLUG */ "node": "Node.js Module", /* PLUG */ "mexw32": "MATLAB x86 MEX Module", /* PLUG */ "mexw64": "MATLAB x64 MEX Module", /* PLUG */ "api": "Adobe Acrobat Plug-In", /* PLUG */ "uxpaddon": "Adobe UXP Native Add-On", /* PLUG */ "arx": "AutoCAD ObjectARX Module", /* PLUG */ "dbx": "AutoCAD ObjectDBX Module", /* PLUG */ "mll": "Autodesk Maya Plug-In", /* PLUG */ "rhp": "Rhinoceros Plug-In", /* PLUG */ "gha": "Grasshopper Assembly", /* PLUG */ "asi": "ASI Game Plug-In", /* PLUG */ "vst3": "VST3 Audio Plug-In", /* PLUG */ "clap": "CLAP Audio Plug-In", /* PLUG */ "ofx": "OpenFX Visual Effects Plug-In", /* PLUG */ "wcx": "Total Commander Packer Plug-In", /* PLUG */ "wfx": "Total Commander File System Plug-In", /* PLUG */ "wlx": "Total Commander Lister Plug-In", /* PLUG */ "wdx": "Total Commander Content Plug-In", /* PLUG */ "wcx64": "Total Commander x64 Packer Plug-In", /* PLUG */ "wfx64": "Total Commander x64 File System Plug-In", /* PLUG */ "wlx64": "Total Commander x64 Lister Plug-In", /* PLUG */ "wdx64": "Total Commander x64 Content Plug-In", /* PLUG */ "dlo": "3ds Max Procedural Object Plug-In", /* PLUG */ "dle": "3ds Max Scene Export Plug-In", /* PLUG */ "dli": "3ds Max Scene Import Plug-In", /* PLUG */ "dlm": "3ds Max Modifier Plug-In", /* PLUG */ "dlc": "3ds Max Controller Plug-In", /* PLUG */ "dlt": "3ds Max Materials and Textures Plug-In", /* PLUG */ "bmi": "3ds Max Bitmap Manager I/O Plug-In", /* PLUG */ "dlr": "3ds Max Renderer Plug-In", /* PLUG */ "dlv": "3ds Max Rendering Effects Plug-In", /* PLUG */ "dlu": "3ds Max Utility Plug-In", /* PLUG */ "gup": "3ds Max Global Utility Plug-In", /* PLUG */ "bmf": "3ds Max Bitmap Manager Filter Plug-In", /* PLUG */ "bms": "3ds Max Bitmap Manager Storage Module", /* PLUG */ "dlb": "3ds Max Shader Plug-In", /* PLUG */ "dlf": "3ds Max Font Loader", /* PLUG */ "dlh": "3ds Max Sampler Plug-In", /* PLUG */ "dlk": "3ds Max Filter Kernel", /* PLUG */ "dls": "3ds Max Object Snap Plug-In", /* PLUG */ "dlx": "3ds Max MAXScript Plug-In", /* PLUG */ "flt": "3ds Max Image Filter Plug-In", /* PLUG */ "whl": "Windhawk Tweaker Component", // Malware-analysis and quarantine suffixes /* LABS */ "infected": "Infected File", /* LABS */ "virusscan": "Infected File (VirusScan Lab.)", }, hasExecutableFileSystemExtensionType = Object.prototype.hasOwnProperty.call( extTypes, executableFileSystemExtension ), executableFileSystemExtensionType = hasExecutableFileSystemExtensionType ? extTypes[executableFileSystemExtension] : undefined; // If the file extension is present in the database and differs from vanilla (generic) .exe/.dll var isSpecializedExecutableFileSystemExtension = false; if (hasExecutableFileSystemExtensionType && executableFileSystemExtensionType !== null) { log(logType.any, "Image uses the recognized ." + File.cleanString(executableFileSystemExtension.toUpperCase()) + " extension for " + executableFileSystemExtensionType); isSpecializedExecutableFileSystemExtension = true; } if (isSpecializedExecutableFileSystemExtension) options = "Extension of " + executableFileSystemExtensionType; // Check for missing file extension var isExecutableFileSystemExtensionEmpty = false; if (executableFileSystemExtension === String()) { log(logType.any, "Executable image has no filename extension"); isExecutableFileSystemExtensionEmpty = true; } if (isExecutableFileSystemExtensionEmpty) options = addOption(options, "No extension"); // Check for a non-standard (custom) file extension var isCustomExecutableFileSystemExtension = false; if (executableFileSystemExtension.length <= 0xC && !isExecutableFileSystemExtensionEmpty && !executableFileSystemExtensionType && executableFileSystemExtensionType !== null) { log(logType.any, "Executable image uses the unrecognized ." + File.cleanString(executableFileSystemExtension.toUpperCase()) + " extension"); isCustomExecutableFileSystemExtension = true; } if (isCustomExecutableFileSystemExtension) options = addOption(options, "Extension (\"." + executableFileSystemExtension.toUpperCase() + "\")"); // Checking for DLL extension in executable PE files // Occurs in non-standard protections and SDK-style .NET application assemblies. For example: // * app.deps.json -> Dependency manifest // * app.runtimeconfig.json -> Runtime configuration // * app.exe -> Native apphost executable // * [app.dll] -> Managed application assembly without IMAGE_FILE_DLL var hasDllExtensionWithoutDllCharacteristic = false; if (executableFileSystemExtension === "dll" && !PE_Cached.isDynamicLinkLibrary) { log(logType.any, "Application image uses the .DLL extension. A host process may be expected."); hasDllExtensionWithoutDllCharacteristic = true; } if (hasDllExtensionWithoutDllCharacteristic) options = addOption(options, "Application Image with \".DLL\" extension"); // Checking for EXE extension in PE files with the DLL characteristic var hasExeExtensionWithDllCharacteristic = false; if (executableFileSystemExtension === "exe" && PE_Cached.isDynamicLinkLibrary) { log(logType.any, "DLL image uses the .EXE extension"); hasExeExtensionWithDllCharacteristic = true; } if (hasExeExtensionWithDllCharacteristic) options = addOption(options, "DLL Image with \".EXE\" extension"); if (options.length !== 0) isDetected = true; if (isDetected) { _setResult("~other", "Generic", "FS", options); } } // #endregion // Every time I start writing bad code I get hit with my head on the keybofewuihdsowefjfqodgsa79dowqhdsioefurogrwhuoguethuhofrwyioguqwehuf