// 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, thorough file content         │
//       │     evaluation, and much more! >:)                          │
//       │                                                             │
//       │     Supported archictures:                                  │
//       │      1.  I386 (x86_32)            2.  AMD64 (x86_64)        │
//       │      3.  ARM                      4.  ARM THUMB             │
//       │      5.  ARMNT (ARMv7)                                      │
//       │                                                             │
//       └─────────────────────────────────────────────────────────────┘

//       ┌─────────────────────────┤ CONTACTS ├────────────────────────┐
//       │       ┌ Author is DosX                                      │
//       │       └┬─ E-Mail: collab@kay-software.ru                    │
//       │        ├─ GitHub: https://github.com/DosX-dev               │
//       │        └─ 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 ─┘                                             │
//       └─────────────────────────────────────────────────────────────┘

//              ┌───────────────────────────────────────────────┐
//              │   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
    },
    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 anomalies specific to Native
            scanForObfuscations_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();
        }

        // >> 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 === "undefined") {
        stdout(">>> Update DIE Engine to 3.20 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 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.
 *
 * Side effects:
 *  - Mutates the global PE_Cached object by adding/updating the keys listed above.
 *
 * @function initializeCache
 * @returns {void} No return value; updates PE_Cached as a side effect.
 */
function initializeCache() {
    var cacheMap = {
       /* Local functions */
       /* Boolean */ isArchX86: isArchX86,
       /* Boolean */ isCppClrLikeApp: isCppClrLikeApp,
       /* Boolean */ isVbNetStandardLibraryPresent: isVbNetStandardLibraryPresent,
       /* Boolean */ isJscriptNetStandardLibraryPresent: isJscriptNetStandardLibraryPresent,
       /* String  */ firstEpAsmInstruction: getFirstEpAsmInstruction,
       /* Native functions */
       /* Boolean */ is64bit: PE["is64"],
       /* Boolean */ isDotNet: PE["isNet"],
       /* Boolean */ isDynamicLinkLibrary: PE["isDll"],
       /* Boolean */ isNetGlobalCctorPresent: PE["isNetGlobalCctorPresent"],
       /* Boolean */ isRichSignaturePresent: PE["isRichSignaturePresent"],
       /* Number  */ numberOfSections: PE["getNumberOfSections"],
       /* Number  */ numberOfRichIDs: PE["getNumberOfRichIDs"],
       /* Number  */ numberOfUnmanagedResources: PE["getNumberOfResources"],
       /* Number  */ numberOfUnmanagedImports: PE["getNumberOfImports"],
       /* Number  */ numberOfUnmanagedExports: PE["getNumberOfExports"],
       /* Number  */ indexOfImportsSection: PE["getImportSection"],
       /* Number  */ indexOfExportsSection: PE["getExportSection"],
       /* String  */ nameOfNetAssemblyName: PE["getNetAssemblyName"],
       /* String  */ nameOfNetModuleName: PE["getNetModuleName"]
    };

    for (var key in cacheMap) {
        const functionBody = cacheMap[key];

        if (functionBody) {
            PE_Cached[key] = functionBody();
        } else {
            stdout(">>> PE API function '" + key + "' is undefined! <<<");
            debugger;
        }
    }
}



/**
 * Clears the PE cache by setting it to undefined.
 * 
 * @function unloadCache
 * @returns {void} No return value; sets PE_Cached to undefined.
 */
function unloadCache() {
    PE_Cached = undefined;
}



// #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", // F# entry
        "main@", // F# entry
        "<Main>$", // 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 (isAllNetReferencesMissing(vbNetEntries)) {
                isEntryPointModified = true;
            }
        } else if (isAllNetReferencesMissing(defaultEntries)) {
            isEntryPointModified = true;
        }

        if (isEntryPointModified) {
            log(logType.net, "No \"Main\" method found");
        }
    }

    if (isEntryPointModified) options = "Modified managed EP";




    // <Module>.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; s++) {
            const headerName = netMetaHeaders[s];

            // Check if the signature is not present in the default .NET section
            if (!PE.isSignatureInSectionPresent(defaultNetSection, "00'#" + headerName + "'00")) {
                isStrangeEpPosition = true;
                break;
            }
        }
    }

    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.is64bit && PE_Cached.isArchX86) {
            // FF2500????00: jmp dword ptr [ ... ]
            const firstOpCode = getFirstEpAsmOpCode();

            if (firstOpCode !== "JMP") {
                if (PE.VAToOffset(PE.getAddressOfEntryPoint()) !== -1) {
                    log(logType.net, "Very strange EP pattern: " + getEpAsmPattern(true, 4).split("|").join(" .. "));
                } else {
                    log(logType.net, "No native entry point");
                }

                isNativeEntryPointModified = true;
            }
        } else { // AMD64
            if (PE.VAToOffset(PE.getAddressOfEntryPoint()) !== 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.getDosStubOffset(), PE.getDosStubSize(), "'This program cannot be run in DOS mode.'") === -1) {
        log(logType.net, "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.net, "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"]) { 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) {
                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.substr(1, sectionName.length).indexOf(badChars[d]) !== -1) {
                    isStrangeSectionsPresent = true;
                }
            }

            if (!isAsciiString(sectionName) && !isStrangeSectionsPresent) {
                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.net, "Strange unmanaged resource name: " + 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) {
        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 (
        isAllNetReferencesPresent(
            [
                "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 (
        isAllNetReferencesPresent(
            [
                "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()
    ) {
        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")) {
        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 afterCtorOffset = PE.findSignature(PE.getDosStubOffset() + PE.getDosStubSize(), PE.getSize() - PE.getOverlaySize(), "'<Module>'00**") + 8;




    // 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 (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 = 1; 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 <Module> .ctor (like "a1b2c3d4e5f6g7h8i9j0")

    var badNamings = false;

    var buffer = String();

    // get next 0x12c bytes after <Module> .ctor
    for (var i = 0; i < 0x12c; i++) {
        var currentByte = PE.readByte(afterCtorOffset + i).toString(16);

        if (currentByte === '0') currentByte += '0';

        buffer += currentByte;
        buffer += ' ';
    }

    var bufferString = String();

    // 0x00 to [0x20, 0x20, 0x20]
    const bufferArray = replaceAllInString(buffer, '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("<PrivateImplementationDetails>");

    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)
            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 “<Module>”, then this is a fakeee!

    var isModuleCtorMultiple = false;

    var currentCtorOffset = PE.findSignature(PE.getDosStubOffset() + PE.getDosStubSize(), PE.getSize() - PE.getOverlaySize(), "00'<Module>'00");

    if (currentCtorOffset !== -1) {
        var secondCtorNameOffset = PE.findSignature(currentCtorOffset + 10, PE.getSize() - PE.getOverlaySize(), "'<Module>'");

        if (secondCtorNameOffset !== -1 && PE.readByte(secondCtorNameOffset + 8) !== 0x00) {
            log(logType.net, "Fake <Module> 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(replaceAllInString(template, "%s", opCodes._unknown))) break; // No math mutations

            const pattern = replaceAllInString(template, "%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();

    const 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: "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: "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
        "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
        "SkiDzEX", // ConfuserEx based
        "Sixxpack", // Sixxpack
        "____KILL", // CodeVeil
        "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) {
                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
            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) {
        isBuildInfoModified = true;

        if (PE_Cached.isJscriptNetStandardLibraryPresent && buildModuleName === "JScript Module") {
            isBuildInfoModified = false;
        }
    }

    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()
    ) {
        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
            isAllNetReferencesPresent( // 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'
                ]
            ) ||
            isAllNetReferencesPresent( // 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()'
                ]
            ) ||
            isAllNetReferencesPresent( // samples by: MindLated
                [
                    "System.Runtime.InteropServices", // System.Runtime.InteropServices.dll
                    "Marshal", // MSIL: '*.Marshal::GetHINSTANCE( ... )'
                    "GetHINSTANCE", // MSIL: '*.Marshal::GetHINSTANCE( ... )'
                    "IntPtr", // MSIL: 'System.IntPtr'
                    "CopyBlock",
                    "InitBlock"
                ]
            )
        )
    ) {
        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 + "'")
        ) {
            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 + "'")
        ) {
            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 + "'")
            ) {
                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 + "'")
            ) {
                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 + "'")
            ) {
                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 + "'")
            ) {
                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 + "'")
    )) {
        isAntiWinePresent = true;
    }

    if (isAntiWinePresent) options = addOption(options, "Anti-Wine");




    var isAntiVmPresent = false;

    if (validateNetUnicodeString("vmware") ||
        validateNetUnicodeString("VirtualBox") ||
        validateNetUnicodeString("VMBusHID") ||
        validateGlobalUnicodeString("WDAGUtilityAccount")) {
        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




/**
 * 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 = "FE09????";           // 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 replaceAllInString(opCodeMask, "??", "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());
}



/**
 * Replaces all occurrences of a specified substring within a given string with a new substring.
 * Uses split/join algorithm for single-pass replacement.
 *
 * @param {string} inputString - The original string in which to perform the replacements.
 * @param {string} search - The substring to search for within the input string.
 * @param {string} replacement - The substring to replace each occurrence of the search substring with.
 * @returns {string} - The modified string with all occurrences of the search substring replaced by the replacement substring.
 */
function replaceAllInString(inputString, search, replacement) {
    // Type validation to prevent runtime errors
    if (!inputString || typeof inputString !== "string") {
        return String();
    }

    if (!search || typeof search !== "string") {
        return inputString;
    }

    if (typeof replacement !== "string") {
        replacement = String();
    }

    // Split on search string and join with replacement (single pass)
    return inputString.split(search).join(replacement);
}



/**
 * 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 clearSectionName(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_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

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" && isAllNetReferencesPresent( // 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[])'
            ]
        )) {
            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");
    }




    // 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.nothing, "SFX overlay pattern: " + overlayPatterns[l]);

                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.nothing, "EP like SFX: \"" + sfxEntries[k] + "\"");

                        isSfx = true;
                    }
                }
            }
        }
    }

    if (isSfx) options = addOption(options, "SFX");




    var entryLikePacker = false;

    const entries = [
        "53565755488D35........488DBE", // samples by: UPX (x64)
        "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)
        "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)
        "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)
        "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.nothing, "EP like a packer: \"" + entryToCheck + "\"");

                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;

    if (PE_Cached.numberOfSections === 3 &&
        (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_READ) === SECTION_FLAGS_READ ||
            (PE.section[2].Characteristics & (SECTION_FLAGS_READ | SECTION_FLAGS_WRITE)) === (SECTION_FLAGS_READ | SECTION_FLAGS_WRITE)
        ) &&
        PE.isFunctionPresent("VirtualProtect") &&
        PE.isFunctionPresent("GetProcAddress") && (
            PE.isFunctionPresent("ExitProcess") || // Original
            PE.isFunctionPresent("CopyContext")    // https://github.com/DosX-dev/UPX-Patcher
        )
    ) {
        log(logType.nothing, "UPX-like structure detected: [0]RWX/[1]RWX/[2]R");

        isUpxLikeStructure = true;
    }

    if (isUpxLikeStructure) options = addOption(options, "Structure 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.nothing, "\"" + firstEpAsmOpCode + "\" at EP");

                isStartsWithStackOperation = true;
        }
    } else {
        firstEpAsmOpCode = getFirstEpAsmOpCode();

        switch (firstEpAsmOpCode) {
            case "POP":
            case "STMDB":
            case "LDMIA":
            case "STMFD":
            case "LDMFD":
                log(logType.nothing, "\"" + firstEpAsmOpCode + "\" at EP");

                isStartsWithStackOperation = true;
        }
    }

    if (isStartsWithStackOperation) options = addOption(options, firstEpAsmOpCode[0].toUpperCase() + firstEpAsmOpCode.toLowerCase().substring(1, firstEpAsmOpCode.length) + " at EP");




    // Check if the entry point is in the last section

    var isLastSectionEP = false;

    if (!PE_Cached.isDynamicLinkLibrary) {
        if (PE_Cached.numberOfSections > 1) {
            // Get addresses of the last section and entry point

            var lastSectionAddress = -1,
                entryPointAddress = -1;


            // Get last section with non -1 address
            for (var i = 1; lastSectionAddress == -1; i++) {
                lastSectionAddress = PE.OffsetToVA(PE.getSectionFileOffset(PE_Cached.numberOfSections - i));
            }

            entryPointAddress = PE.getAddressOfEntryPoint();


            // Check if the entry point is greater than or equal to the last section address
            if (entryPointAddress >= lastSectionAddress) {
                log(logType.nothing, "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.nothing, "Strange " + firstOpCode.toLowerCase() + " at EP to address: " + PE_Cached.firstEpAsmInstruction.split(" ")[1].toLowerCase());

                isStrangeCallOrJmpPresent = true;
            }
        }
    } else {
        // ARM: 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.nothing, "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, 0, 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],
        ["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
        ["tElock", "0.98", 0, 0x3651f68d, 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/
        ["ANDPakk2", "0.18", -1, 0x29188619, 0],
        ["RLPack", "1.1-1.2", 0, 0x5bd1d713, 0],
        ["RLPack", "1.16+", 0, 0x6889715b, 0],
        ["Petite", "2.2", -1, 0x61e53682, 0], // https://www.un4seen.com/petite/
        ["bambam", "0.1-0.4", 0, 0xb5b153cd, 0],
        ["py2exe", null, 2, 0xe5907ffa, 0],
        ["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],
        ["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],
        // cryptors
        ["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],
        ["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],
        ["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]
    ];

    const importValidatingResult = validateImportHashes(dbCollectionOfHashesDict);

    var versionByImportsDetected;

    if (importValidatingResult != null) {
        versionByImportsDetected = importValidatingResult[1];

        log(logType.nothing, "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 = [
        ["DBPE", null, "        "],
        ["Themida", "2.XX", "   "],                                                  // https://www.oreans.com/Themida.php
        ["Themida", "3.XX", [".imports", ".loadcon", ".themida", ".winlice"]],       // .
        ["Themida", null, [".stub01", "WinLicen", ".v-lizer", "Themida "]],          // .
        ["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/
        ["ANDpakk2", null, "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", null, /^\.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"],                                             // 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"],                                               // 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"]],
        ["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"],                                      // https://www.arteam.accessroot.com/download39d1.html
        ["XComp", null, /^\.XComp(0)?$/],                                            // http://www.soft-lab.de/JoKo/
        ["XVolkolak", null, ".xvlk"],                                                // unpacker & reconstructor
        ["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
        ["Alloy", null, ".alloy32"],
        ["Crinkler", null, "lz32.dll"],
        ["Crunch", "2.0", "BitArts"],
        ["CrypToCrack", null, ".ccp3p"],
        ["DxPack", "1.0", ["coderpub", ".reloc1"]],
        ["EXE Pack", null, "!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]$/],
        ["NativeCryptor", null, /^(NATIVES|CONFIG|(F)?CRYPT)~$/],
        ["NeoLite", null, ".neolite"],
        ["PE-PACK", "1.0", "PEPACK!!"],
        ["Perplex", null, ".perplex"],
        ["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"]],
        ["Huan", null, ".huan"],
        ["Morphnah", "1.0.X", ".nah"],
        ["TSULoader", null, ".tsustub"],
        ["RPCrypt", null, /^\.R(Cryptor|PCrypt)$/],
        ["ExE Pack", null, ["!EPack", ".!ep"]],
        ["XerinFuscator", null, ".Xerin"],
        ["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"]
    ];

    const sectionNamesValidatingResult = validateSectionNames(dbCollectionOfSectionNamesDict.concat(
        (function () {
            var dbCollectionOfFakeSectionNamesDict = [
                ["Enigma", /^(\.)?enigma$/i],
                ["UPX", [/^\.upx/i, /^upx/]],
                ["VMProtect", /^(\.)?vmp$/i],
                ["MPRESS", /^mpress/i],
                ["Denuvo", /denuvo/i],
                ["Themida", ".Themida"]
            ];

            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$)prot|safe|lock|sec|virt|obf|guard|sh(ie)?ld/i, /DRM|drm/]]
        ]
    ));

    var versionBySectionDetected = String();

    if (sectionNamesValidatingResult != null) {
        versionBySectionDetected = sectionNamesValidatingResult[1];

        log(logType.nothing, "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}$/],
        ["Fatpack", null, "FPACK"],
        ["Quick Batch File Compiler", null, "PLATFORMTARGETS"],
        ["ScriptCryptor", null, "AOPT"],
        ["ExeStealth", null, "TN"],
        [".NET Reactor", null, "__"],
        ["SoftEntry", null, "SENTRYABORTDLG"],
        ["ASM Guard", null, "ASMG.DLL"],
        ["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.nothing, "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.nothing, "Section names collision: \"" + clearSectionName(sectionNameCollision) + "\"");

        isCollisionInSectionsPresent = true;
    }

    if (isCollisionInSectionsPresent) options = addOption(options, "Sections collision (\"" + clearSectionName(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.nothing, "Section names repeating: \"" + clearSectionName(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;

    for (var t = 0; t < PE_Cached.numberOfSections && !isCompressedSectionPresent; t++) {
        compressedSectionIndex = t;
        if (PE.calculateEntropy(PE.getSectionFileOffset(compressedSectionIndex), PE.getSectionFileSize(compressedSectionIndex)) > 7.4) {
            log(logType.any, "Section #" + compressedSectionIndex + " (\"" + clearSectionName(PE.getSectionName(compressedSectionIndex)) + "\") entropy: " + PE.calculateEntropy(PE.getSectionFileOffset(compressedSectionIndex), PE.getSectionFileSize(compressedSectionIndex)));

            isCompressedSectionPresent = true;
        }
    }

    if (isCompressedSectionPresent) options = addOption(options, "Section #" + compressedSectionIndex + " (\"" + clearSectionName(PE.getSectionName(compressedSectionIndex)) + "\") compressed");




    var standardSectionsWithUnusualValuesDict = [".bss", "BSS", ".tls", "hydrated"];




    // Check for sections with strange characteristics

    var isSectionHasWrongOffsetOrSize = false;

    var sectionWithWrongOffsetOrSizeIndex = -1;

    var wrongOffset = false,
        wrongSize = false;

    for (var t = 0; t < PE_Cached.numberOfSections && !isSectionHasWrongOffsetOrSize; t++) {
        sectionWithWrongOffsetOrSizeIndex = t;
        if (standardSectionsWithUnusualValuesDict.indexOf(PE.getSectionName(sectionWithWrongOffsetOrSizeIndex)) === -1) {
            const currentSection = PE.section[sectionWithWrongOffsetOrSizeIndex];

            // Todo: add more checks
            if (currentSection.FileOffset === 0x00 || currentSection.FileOffset > (PE.getSize() - PE.getOverlaySize())) {
                wrongOffset = true;
            }

            if (currentSection.FileSize === 0x00 || currentSection.FileSize > (PE.getSize() - PE.getOverlaySize())) {
                wrongSize = true;
            }

            isSectionHasWrongOffsetOrSize = wrongOffset || wrongSize;

            if (isSectionHasWrongOffsetOrSize) {
                log(logType.any,
                    "Section #" + sectionWithWrongOffsetOrSizeIndex + " (\"" + clearSectionName(PE.getSectionName(sectionWithWrongOffsetOrSizeIndex)) + "\") has wrong " +
                    (wrongOffset ? "offset (0x" + currentSection.FileOffset.toString(16) + ")" : String()) + (wrongOffset && wrongSize ? ", " : String()) + (wrongSize ? "size (0x" + currentSection.FileSize.toString(16) + ")" : String()));
            }
        }
    }

    if (isSectionHasWrongOffsetOrSize) options = addOption(options, "Section #" + sectionWithWrongOffsetOrSizeIndex + " (\"" + clearSectionName(PE.getSectionName(sectionWithWrongOffsetOrSizeIndex)) + "\") has wrong " + (wrongOffset ? "offset" : String()) + (wrongOffset && wrongSize ? " and " : String()) + (wrongSize ? "size" : String()));




    // 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] + " (\"" + clearSectionName(PE.getSectionName(sectionOffsetsDict[currentSectionOffset][0])) + "\") and #" + currentSectionIndex + " (\"" + clearSectionName(PE.getSectionName(currentSectionIndex)) + "\") have one offset: 0x" + currentSectionOffset.toString(16));

                    firstSectionIndex = sectionOffsetsDict[currentSectionOffset][0];
                    secondSectionIndex = currentSectionIndex;

                    firstSectionName = clearSectionName(PE.getSectionName(firstSectionIndex));
                    secondSectionName = clearSectionName(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 'MZ' signature in the overlay

    var isPeAtOverlayDetected = false;

    if (PE.isOverlayPresent() &&
        PE.getOverlaySize() >= 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.getOverlayOffset(), PE.getOverlaySize());

        if (
            PE.getOverlaySize() > 150 && overlayEntropy > 7 ||
            PE.getOverlaySize() > (PE.getSize() - PE.getOverlaySize())
        ) {
            log(logType.any, "Overlay size: " + PE.getOverlaySize() + " 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 (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(0x00, PE.getSize() - PE.getOverlaySize()) > 7.3) {
        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.nothing, "Recurring import name: \"" + 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) {
            _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")) {
            isLicenseCheckingPresent = true;
        }

        if (isLicenseCheckingPresent) options = "DotNET methods";




        // https://learn.microsoft.com/en-us/dotnet/api/system.componentmodel.licenseproviderattribute

        var isLicenseProviderPresent = false;

        if (validateNetObject("LicenseProviderAttribute")) {
            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")) {
            isLicenseManagerPresent = true;
        }

        if (isLicenseManagerPresent) options = addOption(options, "License manager");

    }




    // Check for licensing strings

    var isInterestingStringsFound = false;

    const licesingStrings = [ /*[E]*/ "nter serial ", /*[S]*/ "erial key ", " activate ", " trial ", /*[W]*/ "rong activation", /*[W]*/ "rong licens", /*[L]*/ "icense expire", "valid license", /*[L]*/ "icense key", " full version", " purchase a "];

    for (var i = 0; i < licesingStrings.length; i++) {
        const currentPatternToFind = licesingStrings[i];

        if (PE.isSignaturePresent(0x00, PE.getSize(), "'" + currentPatternToFind + "'") ||
            PE.isSignaturePresent(0x00, PE.getSize(), "'" + generateUnicodeSignatureMask(currentPatternToFind) + "'")) {
            isInterestingStringsFound = true;
            break;
        }
    }

    if (isInterestingStringsFound) options = addOption(options, "Strings");




    var isDenuvoLibraryPresent = false;

    if (PE.isLibraryPresentExp(/(Core\\Activation|Activation64)/i)) {
        isDenuvoLibraryPresent = true;
    }

    if (isDenuvoLibraryPresent) options = addOption(options, "Denuvo DRM");




    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;

    if (!PE.isEntryPointCorrect()) {
        isEntryPointIncorrect = true;
    }

    if (isEntryPointIncorrect) options = addOption(options, "EntryPoint");




    // Section alignment check

    var isSectionAlignmentIncorrect = false;

    if (!PE.isSectionAlignmentCorrect()) {
        isSectionAlignmentIncorrect = true;
    }

    if (isSectionAlignmentIncorrect) options = addOption(options, "Section alignment");




    // File alignment check

    var isFileAlignmentIncorrect = false;

    if (!PE.isFileAlignmentCorrect()) {
        isFileAlignmentIncorrect = true;
    }

    if (isFileAlignmentIncorrect) options = addOption(options, "File alignment");




    // Check for correct header

    var isHeaderIncorrect = false;

    if (!PE.isHeaderCorrect()) {
        isHeaderIncorrect = true;
    }

    if (isHeaderIncorrect) options = addOption(options, "Header");




    // Check for correct section headers

    var isRelocsTableIncorrect = false;

    if (!PE.isRelocsTableCorrect()) {
        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()) {
        isImportTableIncorrect = true;
    }

    if (isImportTableIncorrect) options = addOption(options, "IAT");




    // Check EAT (only for .dll)

    var isExportTableIncorrect = false;

    if (PE_Cached.isDynamicLinkLibrary && !PE.isExportTableCorrect()) {
        isExportTableIncorrect = true;
    }

    if (isExportTableIncorrect) options = addOption(options, "EAT");




    // Check for resources table

    var isResourcesTableIncorrect = false;

    if (!PE.isResourcesTableCorrect()) {
        isResourcesTableIncorrect = true;
    }

    if (isResourcesTableIncorrect) options = addOption(options, "Resources");




    // Check for corrupted DotNET data

    var isNetImportsCorrupted = false;

    if (!PE_Cached.isDotNet && isNetMetaDataPresent() && PE_Cached.numberOfUnmanagedImports == 0) {
        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())) {
        isNetClrVersionCorrupted = true;
    }

    if (isNetClrVersionCorrupted) options = addOption(options, "DotNET CLR version");




    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;

    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
            log(logType.any, "Debug section detected: \"" + clearSectionName(PE.getSectionName(debugSectionIndex)) + "\"");

            isDebugSectionPresent = true;
        }
    }

    if (isDebugSectionPresent) options = "Section #" + debugSectionIndex + " (\"" + clearSectionName(PE.getSectionName(debugSectionIndex)) + "\")";




    // .NET Native debug header, EAT function

    var isNetNativeDebugDataPresent = false;

    if (!PE_Cached.isDotNet && PE.isExportFunctionPresent("DotNetRuntimeDebugHeader")) {
        log(logType.any, ".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 pdbExtensionPatternOffset = PE.findSignature(
        PE.getDosStubOffset() + PE.getDosStubSize(),
        PE.getSize() - PE.getOverlaySize(),
        "%% %% %% '.pdb' 00"
    );

    var pdbPathBeginOffset = 0,
        pdbPath = String();

    if (pdbExtensionPatternOffset !== -1) {

        for (var i = pdbExtensionPatternOffset; i > 0; i--) {
            if (PE.readByte(i) === 0x00) {
                pdbPathBeginOffset = i + 1;
                break;
            }
        }

        pdbPath = PE.getString(pdbPathBeginOffset, 256);

        if (pdbPath.length > 255)
            pdbPath = pdbPath.substring(0, 255) + " . . .";

        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 = validateSignature("00 52 53 44 53") ? "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 if the current PE file is a .NET component.
 *
 * @returns {boolean} Returns true if the PE file is identified as a .NET component, 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;
}



/**
 * Validates the presence of a signature (pattern) within a specified range in a PE file.
 *
 * Searches for the given pattern starting from the DOS stub end until the PE size excluding the overlay.
 * Logs a message if the pattern is found and returns a boolean indicating the result.
 *
 * @param {string} pattern - The pattern to search for within the file.
 * @returns {boolean} True if the pattern is found, false otherwise.
 */
function validateSignature(pattern) {
    const
        offsetFound = PE.findSignature(PE.getDosStubOffset() + PE.getDosStubSize(), PE.getSize() - PE.getOverlaySize(), pattern),
        resultBool = offsetFound != -1;

    if (resultBool) {
        lastOffsetDetected = "0x" + Number(offsetFound).toString(16);
        log(logType.any, "Pattern found: " + pattern);
    }

    return resultBool;
}



/**
 * Searches all sections in the current PE file for the specified byte code.
 * If discovered, logs the offset at which it appears and returns true.
 * 
 * @param {string} byteCode - The byte code signature to locate.
 * @returns {boolean} True if the byte code is found in any section, otherwise false.
 */
function validateNetByteCode(byteCode) {
    for (var s = 0; s < PE_Cached.numberOfSections; s++) {
        const
            sectionOffset = PE.getSectionFileOffset(s),
            sectionSize = PE.getSectionFileSize(s);

        var offsetFound = PE.findSignature(sectionOffset, sectionOffset + sectionSize, byteCode);
        if (offsetFound != -1) {
            lastOffsetDetected = "0x" + Number(offsetFound).toString(16);
            log(logType.net, "ByteCode detected: " + byteCode);
            return true;
        }
    }

    return false;
}



/**
 * Validates if the given .NET object is present in the PE file.
 *
 * @param {Object} object - The object to be validated.
 * @returns {boolean} - Returns true if the object is a .NET object, otherwise false.
 */
function validateNetObject(object) {
    const result = PE.isNetObjectPresent(object);
    if (result) log(logType.net, "Object present: " + object);
    return result;
}



/**
 * Validates if a .NET Unicode string is present in the PE file.
 *
 * @param {string} ustring - The Unicode string to validate.
 * @returns {boolean} - Returns true if the Unicode string is present, otherwise false.
 */
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 searches for the specified Unicode string within the PE file's
 * memory range, excluding the DOS stub and overlay sections. If the string is
 * found, it logs a message indicating the presence of the string.
 *
 * @param {string} ustring - The Unicode string to search for within the PE file.
 * @returns {boolean} - Returns true if the Unicode string is found, otherwise false.
 */
function validateGlobalUnicodeString(ustring) {
    const result = PE.findSignature(PE.getDosStubOffset() + PE.getDosStubSize(), PE.getSize() - PE.getOverlaySize(), generateUnicodeSignatureMask(ustring)) != -1;
    if (result) log(logType.nothing, "Unicode string found: \"" + ustring + "\"");
    return result;
}



/**
 * Generates a Unicode signature mask for the given input string.
 *
 * This function iterates through each character in the input string and appends
 * its Unicode representation to the output string. The first character is appended
 * without a prefix, while subsequent characters are prefixed with "00".
 * 
 * "test" -> "'t'00'e'00's'00't'"
 *
 * @param {string} ustring - The input string for which to generate the Unicode signature mask.
 * @returns {string} The generated Unicode signature mask.
 */
function generateUnicodeSignatureMask(ustring) {
    var output = String();

    // Iterate through each character in the input string
    for (var c = 0; c < ustring.length; c++) {
        // Append the Unicode representation of the character to the output
        output += (c != 0 ? "00" : String()) + "'" + ustring[c] + "'";
    }

    // Return the generated Unicode signature mask
    return output;
}



/**
 * Checks if all .NET references in the provided array are missing.
 *
 * @param {Array} references - An array of .NET references to check.
 * @returns {boolean} - Returns true if all .NET references are missing, otherwise false.
 */
function isAllNetReferencesMissing(references) {
    // Iterate through the array of .NET references
    for (var i = 0; i < references.length; i++) {
        // Get the current reference
        const ref = references[i];

        // If the .NET object corresponding to the reference is present, return false
        if (PE.isNetObjectPresent(ref)) {
            return false;
        }
    }

    // If all .NET references are missing, return true
    return true;
}



/**
 * Checks if all .NET references are present.
 *
 * @param {Array} references - An array of .NET references to check.
 * @returns {boolean} - Returns true if all .NET references are present, otherwise false.
 */
function isAllNetReferencesPresent(references) {
    // Iterate through the array of .NET references
    for (var i = 0; i < references.length; i++) {
        // Get the current reference
        const ref = references[i];

        // If the .NET object corresponding to the reference is not present, return false
        if (!PE.isNetObjectPresent(ref)) {
            return false;
        }
    }

    // If all .NET references are present, return true
    return true;
}



/**
 * Searches for a signature in the first section of a PE file and marks it if found.
 *
 * @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.
 */
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.
 */
function isNameObfuscated(name) {
    if (!name || name.length === 0) 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,}/)) {
                return false;
            }

            if (isTokenObfuscated(currentToken)) {
                log(logType.nothing, "Obfuscated string segment: \"" + currentToken + "\" in \"" + 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.nothing, "Obfuscated string chunk: \"" + chunk + "\" in \"" + name + "\"");
            return true;
        }
    }

    return false;
}



/**
 * 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
 *
 * @param {string} token - The string token to analyze
 * @returns {boolean} True if token matches obfuscation patterns, false otherwise
 */
function isTokenObfuscated(token) {
    if (!token || token.length < 7) return false;

    // === WHITELIST: Common legitimate patterns ===

    // Date patterns: YYYY-YYYY, DD.MM.YYYY, YYYY-MM-DD
    if (token.match(/^\d{4}[-\/\.]\d{4}$/) ||
        token.match(/^\d{1,2}[-\/\.]\d{1,2}[-\/\.]\d{2,4}$/) ||
        token.match(/^\d{4}[-\/\.]\d{1,2}[-\/\.]\d{1,2}$/)) return false;

    // Version numbers: v1.2.3, 1.0.0.0, 10.15.7
    if (token.match(/^v?\d+(\.\d+){1,4}$/i)) return false;

    // UUIDs: 550e8400-e29b-41d4-a716-446655440000
    if (token.match(/^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i)) return false;

    // Common hex prefixes: 0x1234ABCD, 0X1A2B3C4D
    if (token.match(/^0x[0-9a-f]+$/i)) return false;

    // File sizes with units: 123KB, 45.6MB, 1.5GB
    if (token.match(/^\d+(\.\d+)?(bytes?|[kmgt]b)$/i)) return false;

    // IP addresses: 192.168.1.1
    if (token.match(/^\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3}$/)) return false;

    // Common abbreviations/acronyms (all caps with digits): DX11, OpenGL3, Win32
    if (token.match(/^[A-Z]{2,}[0-9]{1,3}$/)) return false;

    // Changelist/build numbers (pure digits 6-12 chars): CL 35398403, Build 123456
    // Todo: upgrade
    if (token.match(/^\d{6,12}$/)) return false;

    // Version control tags: v1.0, v21.9
    if (token.match(/^[Vv]\d{1,2}\.\d{1,4}/)) return false;

    // Build/CI server patterns: buildbot, jenkins, gitlab-runner, etc.
    // Contains common CI/build keywords with hyphens/underscores
    if (token.match(/(buildbot|builder|jenkins|gitlab|runner|relclient|steam|win32|win64|x64|x86)/i)) 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")
    // 6+ 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]{6,}/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 5+ times in strings shorter than 15 chars
    var consonantCounts = {};
    for (var i = 0; i < token.length; i++) {
        var ch = token[i].toLowerCase();
        if ('bcdfghjklmnpqrstvwxz'.indexOf(ch) !== -1) {
            consonantCounts[ch] = (consonantCounts[ch] || 0) + 1;
            if (consonantCounts[ch] >= 5 && token.length < 15) return true;
        }
    }

    // Keyboard walk patterns (e.g., "qwerty", "asdfgh", "zxcvbn")
    if (token.match(/qwerty|asdfgh|zxcvbn|yuiop|hjkl|cvbnm|poiuy|lkjh/i)) 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;

    // 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
    );
}



// #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) 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.getDosStubSize() === 0) {
        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++
            "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.getDosStubOffset(), PE.getDosStubSize(), "'" + messages[d] + "'")) {
                isCustomDosPresent = false;
            }
        }
    }

    // Add appropriate option based on DOS presence
    if (isDosMissing) options = addOption(options, "Missing DOS");
    else if (isCustomDosPresent) options = addOption(options, "Custom DOS");




    // 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] !== ".") {
                isContainsNoExtensionLibrary = true;
            }
        } else { /* if (libraryName.indexOf(".") === -1) */
            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.substr(libraryName.length - 4, 4) === ".exe") {
                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) {
            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)$/)) {
        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.isSectionNamePresent(".text") || PE.isSectionNamePresent(".textbss")) && PE.section[0] === ".code") {
        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
        isIatMissing = true;
    } else if (PE_Cached.indexOfImportsSection !== -1 && !PE_Cached.numberOfUnmanagedImports) {
        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.getAddressOfEntryPoint() && (PE_Cached.indexOfImportsSection === -1 && PE_Cached.indexOfExportsSection === -1)) { // DLLs without imports/exports and with EP (not a resource-only DLL)
        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.getAddressOfEntryPoint()) { // DLL without exports and with EP (not a resource-only DLL)
            isDllEatMissing = true;
        } else if (PE_Cached.indexOfExportsSection !== -1 && PE_Cached.numberOfUnmanagedExports === 0) { // DLL with empty exports
            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.getExportSection() !== -1) {
        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 {
        /*
         * NOP-equivalent instructions for ARM/ARM64:
         *     * Standard NOP instructions:
         * - 0x00 0xF0 0x20 0xE3               -> NOP (ARM32 explicit)
         * - 0x1F 0x20 0x03 0xD5               -> NOP (ARM64 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)
         * - 0xE0 0x03 0x00 0xAA               -> MOV X0, X0   (ARM64)
         * - 0xE1 0x03 0x01 0xAA               -> MOV X1, X1   (ARM64)
         * - 0xE2 0x03 0x02 0xAA               -> MOV X2, X2   (ARM64)
         * - 0xE3 0x03 0x03 0xAA               -> MOV X3, X3   (ARM64)
         * - 0xE4 0x03 0x04 0xAA               -> MOV X4, X4   (ARM64)
         * - 0xE5 0x03 0x05 0xAA               -> MOV X5, X5   (ARM64)
         * - 0xE6 0x03 0x06 0xAA               -> MOV X6, X6   (ARM64)
         * - 0xE7 0x03 0x07 0xAA               -> MOV X7, X7   (ARM64)
         * - 0xE0 0x03 0x00 0x2A               -> MOV W0, W0   (ARM64)
         * - 0xE1 0x03 0x01 0x2A               -> MOV W1, W1   (ARM64)
         * - 0xE2 0x03 0x02 0x2A               -> MOV W2, W2   (ARM64)
         * - 0xE3 0x03 0x03 0x2A               -> MOV W3, W3   (ARM64)
         */

        // ARM/ARM64 NOP equivalents
        const nopEquivalentsArm = [
            "NOP",
            "MOV R0, R0", "MOV R1, R1", "MOV R2, R2", "MOV R3, R3",
            "MOV R4, R4", "MOV R5, R5", "MOV R6, R6", "MOV R7, R7",
            "MOV R8, R8", "MOV R9, R9", "MOV R10, R10", "MOV R11, R11",
            "MOV R12, R12", "MOV R13, R13", "MOV R14, R14", "MOV R15, R15"
        ];

        const nopEquivalentsArm64 = [
            "NOP",
            "MOV X0, X0", "MOV X1, X1", "MOV X2, X2", "MOV X3, X3",
            "MOV X4, X4", "MOV X5, X5", "MOV X6, X6", "MOV X7, X7",
            "MOV W0, W0", "MOV W1, W1", "MOV W2, W2", "MOV W3, W3"
        ];

        const nopEquivalents = PE_Cached.is64bit ? nopEquivalentsArm64 : nopEquivalentsArm;

        // Check for standard NOP opcode
        if (getFirstEpAsmOpCode() === "NOP") {
            isStartsWithNop = true;
        } else {
            // Check for functional NOP equivalents
            for (var i = 0; i < nopEquivalents.length && !isStartsWithNop; i++) {
                if (PE_Cached.firstEpAsmInstruction === nopEquivalents[i]) {
                    isStartsWithNop = true;
                }
            }
        }
    }

    if (isStartsWithNop) options = addOption(options, "Nop at 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 asmInstruction = getAsmInstructionByIndex(i);

                if (!asmInstruction) break; // Safety check

                // Check if instruction is CALL
                if (getAsmOpCode(asmInstruction) === "CALL") {
                    // Get address and offset for this instruction
                    var disasmAddress = PE.getAddressOfEntryPoint();

                    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 (PE.compare("E8 00 00 00 00", currentOffset)) {
                        log(logType.any, "Stack-push address near EP found at offset: 0x" + currentOffset.toString(16));

                        isAddrToStackTrickPresent = true;
                    }
                }
            }
        }
    }

    if (isAddrToStackTrickPresent) options = addOption(options, "Stack-push address near EP");




    // Check for CPUID at entry point

    var isCpuidNearEpPresent = false;

    if (PE_Cached.isArchX86) {
        // Check first 15 instructions for CPUID instruction
        if (!PE_Cached.isDynamicLinkLibrary) {
            for (var i = 0; i < 15 && !isCpuidNearEpPresent; i++) {
                var asmInstruction = getAsmInstructionByIndex(i);

                if (!asmInstruction) break; // Safety check

                // Check if instruction is CPUID
                if (getAsmOpCode(asmInstruction) === "CPUID") {
                    log(logType.any, "CPUID near EP found at instruction index: " + i);

                    isCpuidNearEpPresent = true;
                }
            }
        }
    }

    if (isCpuidNearEpPresent) options = addOption(options, "Cpuid near EP");




    // Check for TLS (Thread Local Storage) with a bad entry point (like INT3 or RET). Trick to hide the real EP. (like BoxedApp)

    var isTlsWithBadEpPresent = false;

    if (PE.isTLSPresent()) {
        if (PE_Cached.isArchX86) {
            if (/^INT( )?3$/.test(getAsmOpCode(PE_Cached.firstEpAsmInstruction)) ||    // INT3 (0xCC) / INT 3 (0xCD 0x03)
                /^RET( \d+)?$/.test(getAsmOpCode(PE_Cached.firstEpAsmInstruction))) {  // All types of RET
                isTlsWithBadEpPresent = true;
            }
        } else {
            // ARM: Check for breakpoint or return instructions
            if (/^BKPT/.test(getAsmOpCode(PE_Cached.firstEpAsmInstruction)) ||    // BKPT  (breakpoint)
                /^BRK/.test(getAsmOpCode(PE_Cached.firstEpAsmInstruction)) ||     // BRK   (ARM64 breakpoint)
                /^RET/.test(getAsmOpCode(PE_Cached.firstEpAsmInstruction)) ||     // RET   (ARM64 return)
                /^BX( )?LR$/.test(PE_Cached.firstEpAsmInstruction)) {             // BX LR (ARM32 return)) {
                isTlsWithBadEpPresent = true;
            }
        }
    }

    if (isTlsWithBadEpPresent) 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")
    )) {
        proxyJumpsAtEpPresent = true;
    }

    if (proxyJumpsAtEpPresent) options = addOption(options, "Proxy jmp at EP");




    // Check if NOP padding is present at the entry point

    var isNopPaddingPresent = false;

    // Condition to check for NOP padding
    if (!isStartsWithNop && getEpAsmPattern(true, 5).indexOf(getInstructionsAsmPattern(["NOP", "NOP"])) !== -1) {
        isNopPaddingPresent = true;
    }

    if (isNopPaddingPresent) options = addOption(options, "Nop EP padding");




    // 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) {
            isRwxSectionPresent = true;
            rwxSectionIndex = i;
        }
    }

    if (isRwxSectionPresent) options = addOption(options, "Section #" + rwxSectionIndex + " (\"" + clearSectionName(PE.getSectionName(rwxSectionIndex)) + "\") has RWX");




    // Check for zero padding at the beginning of the entry point section

    var isEpSectionZeroPaddingPresent = false;

    var epSectionWithZeroPaddingIndex = -1;

    // Find the section that contains the entry point
    const entryPointRva = PE.getAddressOfEntryPoint() - PE.getImageBase();

    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) {
            epSectionWithZeroPaddingIndex = i;

            // Check for zero padding at the beginning of the section
            if (PE.compare("00 00 00", PE.getSectionFileOffset(epSectionWithZeroPaddingIndex))) {
                isEpSectionZeroPaddingPresent = true;
            }
        }
    }

    if (isEpSectionZeroPaddingPresent) options = addOption(options, "EP-section #" + epSectionWithZeroPaddingIndex + " (\"" + clearSectionName(PE.getSectionName(epSectionWithZeroPaddingIndex)) + "\") zero padding");




    // Check for a lot of sections (more than 20 -> this is a lot for native builds)

    var tooManySectionsPresent = false;

    if (PE_Cached.numberOfSections > 20) {
        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) {
            isIatDirEmpty = true;
        }
    }

    if (isIatDirEmpty) options = addOption(options, "IAT directory empty");




    // 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) {
            isUnusedExceptionSectionPresent = true;
        }
    }

    if (isUnusedExceptionSectionPresent) options = addOption(options, "Exceptions directory empty");




    // 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) || (
                    !isItemMangled(functionName) &&
                    isNameObfuscated(replaceAllInString(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 (!isAsciiString(libraryName) || libraryName.indexOf(badImportChars[l]) !== -1) {
                    isStrangeImportsPresent = true;
                }
            }
        } else if (libraryName.indexOf("MZ") !== 0) { // 'MZ' -> IAT corrupted
            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: " + 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) {
        isZeroLinkerVersionPresent = true;
    }

    if (isZeroLinkerVersionPresent) options = addOption(options, "Zero linker version");




    // Check for .NET metadata presence

    var isNetMetaDataInNativePresent = false;

    if (isNetMetaDataPresent() && PE_Cached.numberOfUnmanagedImports > 0) {
        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")) {
        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



/**
 * Determines if the PE file's operating system architecture is x86-based.
 *
 * @returns {boolean|undefined} Returns true if the architecture is x86_32 (I386) or x86_64 (AMD64),
 *                               false if the architecture is ARM-based (ARM, ARMNT, THUMB),
 *                               undefined if the architecture is unknown or not recognized.
 */
function isArchX86() {
    switch (PE.getOperationSystemOptions().split(',')[0]) {
        case "I386":  // x86
        case "AMD64": // x86_64
            return true;
        case "ARM":   // ARM
        case "ARMNT": // ARMv7
        case "THUMB": // ARM Thumb
            return false;
        default:
            return undefined;
    }
}



const _patternSplitter = "|";

/**
 * Generates a pattern of assembly 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} The generated assembly pattern.
 */
function getEpAsmPattern(onlyOpCodes, numberOf) {
    // Initialize the result with a pattern splitter
    var result = _patternSplitter;

    // Get the address of the entry point
    var disasmAddress = PE.getAddressOfEntryPoint();

    // Iterate through instructions up to the specified number
    for (var i = 0; i < numberOf; i++) {

        // Update the address to the next instruction if not the first iteration
        if (i >= 1) {
            disasmAddress = PE.getDisasmNextAddress(disasmAddress);
        }

        // Get the assembly instruction at the current address
        const asmInstruction = PE.getDisasmString(disasmAddress);

        // 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 = [];

/**
 * Retrieves the assembly instruction at the specified index from the entry point of a PE file.
 *
 * @param {number} index - The index of the assembly instruction to retrieve.
 * @returns {string} The assembly instruction at the specified index.
 */
function getAsmInstructionByIndex(index) {
    // Check if the instruction is already cached
    if (epAsmInstructions[index] !== undefined) {
        return epAsmInstructions[index];
    }

    // Get the address of the entry point
    var disasmAddress = PE.getAddressOfEntryPoint();

    // Iterate through instructions until the specified index is reached
    for (var i = 0; i <= index; i++) {

        // Update the address to the next instruction if not the first iteration
        if (i >= 1) {
            disasmAddress = PE.getDisasmNextAddress(disasmAddress);
        }

        // Cache the instruction if it hasn't been cached yet
        if (epAsmInstructions[i] === undefined) {
            epAsmInstructions[i] = PE.getDisasmString(disasmAddress);
        }

        // If the current iteration matches the specified index, retrieve the instruction
        if (i === index) {
            // Return the assembly instruction from cache
            return epAsmInstructions[index];
        }
    }
}



/**
 * 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 assembly instruction at the entry point of the PE file.
 *
 * @returns {string} The disassembled string of the first instruction at the entry point.
 */
function getFirstEpAsmInstruction() {
    return getAsmInstructionByIndex(0);
    // return PE.getDisasmString(PE.OffsetToVA(PE.getEntryPointOffset()));
}



/**
 * Extracts the opcode from an assembly instruction.
 *
 * @param {string} instruction - The assembly instruction.
 * @returns {string} - The opcode of the instruction.
 */
function getAsmOpCode(instruction) {
    return instruction.indexOf(" ") !== -1 ? instruction.split(" ")[0] : instruction;
}



/**
 * 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[0] === '$' && (itemPattern[1] === 's' || itemPattern[1] === '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[0] === '$' && (itemPattern[1] === 's' || itemPattern[1] === '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, return the full name
                    if (nameWithPrefix.length > 1 && /[EGR]/.test(nameWithPrefix[1])) {
                        return nameWithPrefix;
                    }

                    // 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) {
            var methodName = parts[2].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[0] === '$' && (itemPattern[1] === 's' || itemPattern[1] === '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;
}



/**
 * Validates import hashes within a database collection.
 * Iterates through each entry in the collection, checking if the import position hash is present.
 * If a matching hash is found, returns the corresponding entry; otherwise, returns null.
 *
 * @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.
 */
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;
}



/**
 * Validates section names within a database collection against known PE section names or regular expressions.
 * Iterates through the collection and checks if any section signature matches a known section name or passes a regular expression test.
 *
 * @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.
 */
function validateSectionNames(dbCollection) {
    for (var i = 0; i < dbCollection.length; i++) {
        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]))) {
                    return dbCollection[i];
                }
            }
        } else if ((typeof sectionSignature === "string" && PE.isSectionNamePresent(sectionSignature)) ||
            (sectionSignature instanceof RegExp && PE.isSectionNamePresentExp(sectionSignature))) {
            return dbCollection[i];
        }
    }

    return null;
}



/**
 * Validates resource names within a database collection against known PE resource names or regular expressions.
 * Iterates through the collection and checks if any resource signature matches a known resource name or passes a regular expression test.
 *
 * @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.
 */
function validateResourceNames(dbCollection) {
    for (var i = 0; i < dbCollection.length; i++) {
        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]))) {
                    return dbCollection[i];
                }
            }
        } else if ((typeof resourceSignature === "string" && PE.isResourceNamePresent(resourceSignature)) ||
            (resourceSignature instanceof RegExp && PE.isResourceNamePresentExp(resourceSignature))) {
            return dbCollection[i];
        }
    }

    return null;
}



/**
 * Calculates and returns the offset of the PE optional header within the file.
 * 
 * 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) to the optional header in the PE file.
 */
function getOptHeaderOffset() {
    return PE.read_int32(0x3C) + 4 + 20;
}



/**
 * Checks if the .NET CLR metadata directory is present in the PE file.
 *
 * This function determines the presence of the .NET CLR Runtime Header
 * by reading the appropriate Data Directory entry from the PE Optional Header.
 * It supports both PE32 and PE32+ formats.
 *
 * @returns {boolean} True if the .NET CLR metadata directory is present, false otherwise.
 */
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.getSize() ||
        numRvaAndSizesOffset + 4 > PE.getSize()) {
        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(0x00, PE.getSize(), "%% %% %% %% %% %% %% %% '." + langExtName + "' 00")) {
            log(logType.any, "Lines of ." + langExtName + " files (" + langName + ") detected");
            _setLangByHeur(langName);
        }
    }


    if (!_isLangDetected("C++") && PE.isSignaturePresent(0x00, PE.getSize(), "%% %% %% %% %% %% %% %% %% %% '.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) {
                _setResult("~compiler", "Microsoft Visual Basic", "6.0", String());
                _setLangByHeur("VB");
                break;
            } else if (richId === 0x0103) {
                _setResult("~compiler", "MASM", String(), String());
                break;
            }
        }
    }




    // Detect IL2CPP by sections (thanks to AyukiDev)

    if (!isCompilerDetected()) {
        const il2cppSections = [
            ".text$mn",
            ".rdata$zzzdbg",
            ".rtc$IAA",
            ".rtc$IZZ",
            ".rtc$TAA",
            ".rtc$TZZ",
            "il2cpp"
        ];

        for (var i = 0; i < il2cppSections.length; i++) {
            if (PE.isSectionNamePresent(il2cppSections[i])) {
                _setResult("~compiler", "IL2CPP Technology", String(), String());
                _setLangByHeur("Native MSIL/C#");
                break;
            }
        }
    }




    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")) {
                _setResult("~tool", "Microsoft Visual Studio", String(), String())
            }

            _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")
            )) {

            _setResult("~compiler", "MinGW", String(), String());

            if (!_isLangDetected() && isCLikeLibsNotFound) {
                _setLangByHeur("C/C++");
            }
        } else if (PE.isSectionNamePresent(".flat") && !isPpLibraryPresent) {
            _setResult("~compiler", "FASM", String(), String());
            _setLangByHeur("ASMx" + (PE_Cached.is64bit ? "64" : "86"));
        }
    }




    const rdataSection = PE.section[".rdata"];

    if (rdataSection) {
        if (c_cpp && // if C/C++ detected by DIE
            PE.isSignaturePresent(
                rdataSection.FileOffset,
                rdataSection.FileSize,
                generateUnicodeSignatureMask("Visual C++"))) {

            log(logType.any, "Embedded Visual C++ Runtime detected.");

            isPpLibraryPresent = true; // Visual C++ Runtime library in resources
        }
    }

    if (isPpLibraryPresent || (c_cpp && PE.isSignaturePresent(0x00, PE.getSize() - PE.getOverlaySize(), "' 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")) {
        _setLangByHeur("C/C++");
    } else if (!_isLangDetected() && !_getNumberOfResults("compiler") && !PE_Cached.isDotNet) {
        if (!_getNumberOfResults("protector") &&
            !_getNumberOfResults("cryptor") && !_getNumberOfResults("~cryptor") &&
            !_getNumberOfResults("packer") && !_getNumberOfResults("~packer")) {
            _setLangByHeur("ASMx" + (PE_Cached.is64bit ? "64" : "86"));
        }
    }
}
// #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.
 * @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 !== -2) {
        prefix = heurLabel;
    }

    if (messageTypeId > -2 && messageTypeId !== 0) {
        prefix += "/";
    }

    switch (messageTypeId) {
        case -2: prefix = "!"; break;
        case -1: prefix += "About"; break;
        case 1: prefix += "Any"; break;
        case 2: prefix += ".NET"; 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 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 (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;
            }
        }

        var separatorToSearch = generateUnicodeSignatureMask("|'|'|") + "00";
        separatorToSearch = "??" + separatorToSearch.substring(2, separatorToSearch.length);

        // NjRAT Generic: Detect NjRAT via requests-separator or assembly name
        if (verdicts.length === 0 && (
            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.isSignatureInSectionPresent(0, separatorToSearch))) {
            verdicts.push({
                type: "NjRAT",
                version: String(),
                details: String()
            });
        }


        if (verdicts.length === 0 && PE.isNetObjectPresent("avfucker")) {
            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")
        )) {
            verdicts.push({
                type: "AsyncRAT",
                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"))) {
            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()
            });
        }


        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()
            });
        }


        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")) {
            verdicts.push({
                type: "Imminent Monitor RAT",
                version: String(),
                details: String()
            });
        }


        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("attrib $h $s $r $i /D ") ||
            PE.isNetUStringPresent("OneDrive10293")
        )) {
            verdicts.push({
                type: "UX-Locker",
                version: String(),
                details: String()
            });
        }


        if (verdicts.length === 0 && (PE.isSignatureInSectionPresent(0, "00" + generateUnicodeSignatureMask(" RAT") + "00 **") ||
            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, generateUnicodeSignatureMask("AntivirusProduct")) || PE.isSignatureInSectionPresent(0, generateUnicodeSignatureMask("DisableTaskMgr"))
                )
            ) || (
                // GetForegroundWindow + GetWindowText + NtSetInformationProcess
                PE.isNetObjectPresent("GetForegroundWindow") && PE.isNetObjectPresent("GetWindowText") && PE.isNetObjectPresent("NtSetInformationProcess")
            ))) {
            verdicts.push({
                type: "RAT",
                version: String(),
                details: "General signs"
            });
        }
    }




    if (verdicts.length === 0 && (
        PE.isNetObjectPresent("potentiallyVulnerablePasswords") ||
        PE.isNetObjectPresent("ScanGeckoBrowsersPaths") ||
        PE.isNetObjectPresent("ScannedWallets") ||
        PE.isNetObjectPresent("GetPasswords") ||
        PE.isNetObjectPresent("ScanSteam") ||
        PE.isNetObjectPresent("GetCookies")
    )) {
        verdicts.push({
            type: "Stealer",
            version: String(),
            details: "General signs"
        });
    }




    // https://malpedia.caad.fkie.fraunhofer.de/details/win.ramnit

    if (verdicts.length === 0 && PE.isSectionNamePresent(".rmnet")) {
        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://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://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()
        });
    }




    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/

    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.

    const signaturesVariants = [
        "TVoAAAAAA", // MZ ~[00 00 00 00 00]
        "TVqQAA", // MZ ~[90 00 03]
        "TVpQAA", // MZ ~[50 00 02]
        "TVp4AA" // MZ ~[78 00 01]
    ];

    // Iterate through signature variants
    for (var s = 0; s < signaturesVariants.length; s++) {
        const trigger = signaturesVariants[s];

        // Check if the signature is valid using Unicode signature mask or the original signature
        if (
            validateGlobalUnicodeString(trigger) ||
            validateSignature("'" + trigger + "'")
        ) {
            verdicts.push({
                type: "Base64 payload",
                version: String(),
                details: mayBeInfected
            });

            break;
        }
    }




    // Check for obfuscated names and anomalous signs in .NET and native assemblies

    if (PE_Cached.isDotNet && (
        PE_Cached.nameOfNetAssemblyName.toLowerCase() === "stub" ||
        PE_Cached.nameOfNetModuleName.toLowerCase().indexOf("stub.") === 0 ||
        PE_Cached.nameOfNetAssemblyName.toLowerCase().indexOf("crypted") !== -1 ||
        PE_Cached.nameOfNetModuleName.toLowerCase().indexOf("crypted") !== -1 ||
        PE_Cached.nameOfNetAssemblyName.toLowerCase().indexOf("payload") !== -1 ||
        PE_Cached.nameOfNetModuleName.toLowerCase().indexOf("payload") !== -1 ||
        isNameObfuscated(PE_Cached.nameOfNetAssemblyName) ||
        isNameObfuscated(PE_Cached.nameOfNetModuleName) ||
        /(^tmp|\.tmp$)/.test(PE_Cached.nameOfNetModuleName)
    ) ||
        PE.getVersionStringInfo("OriginalFilename").toLowerCase().indexOf("stub.") === 0 ||
        PE.getVersionStringInfo("InternalName").toLowerCase().indexOf("stub.") === 0 ||
        (function () {
            var 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
        });
    }




    if (PE.isResourceNamePresentExp(/^(STUB|SERVER)(\.[A-Z]{3})?$/) ||
        PE.isResourceNamePresentExp(/(PAYLOAD|SHELLCODE|INJECT|CRYPTED)/)) {
        verdicts.push({
            type: "Anomalous resources",
            version: String(),
            details: mayBeInfected
        });
    }




    for (var v = 0; v < verdicts.length; v++) {
        var verdict = verdicts[v];

        _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();




    // 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") {
            if (PE.isSignaturePresent(
                PE.getResourceOffsetByNumber(i),
                PE.getResourceSizeByNumber(i), generateUnicodeSignatureMask("GameDefinitionFile"))) {
                isGdfSchemaPresent = true;
            }
        }
    }

    if (isGdfSchemaPresent) options = "GDF Schema";




    if (options.length != 0) isDetected = true;


    if (isDetected) {
        _setResult("~marker", "Contains", String(), PE.isVerbose() ? options : String());
    }
}
// #endregion

// Every time I start writing bad code I get hit with my head on the keybofewuihdsowefjfqodgsa79dowqhdsioefurogrwhuoguethuhofrwyioguqwehuf
