/*
             ██████╗ ███████╗███╗   ██╗███████╗██████╗ ██╗ ██████╗
            ██╔════╝ ██╔════╝████╗  ██║██╔════╝██╔══██╗██║██╔════╝
            ██║  ███╗█████╗  ██╔██╗ ██║█████╗  ██████╔╝██║██║
            ██║   ██║██╔══╝  ██║╚██╗██║██╔══╝  ██╔══██╗██║██║
            ╚██████╔╝███████╗██║ ╚████║███████╗██║  ██║██║╚██████╗
             ╚═════╝ ╚══════╝╚═╝  ╚═══╝╚══════╝╚═╝  ╚═╝╚═╝ ╚═════╝
  ██╗    ██╗  ██╗███████╗██╗   ██╗██████╗ ██╗███████╗████████╗██╗ ██████╗    ██╗
 ██╔╝    ██║  ██║██╔════╝██║   ██║██╔══██╗██║██╔════╝╚══██╔══╝██║██╔════╝    ╚██╗
██╔╝     ███████║█████╗  ██║   ██║██████╔╝██║███████╗   ██║   ██║██║          ╚██╗
╚██╗     ██╔══██║██╔══╝  ██║   ██║██╔══██╗██║╚════██║   ██║   ██║██║          ██╔╝
 ╚██╗    ██║  ██║███████╗╚██████╔╝██║  ██║██║███████║   ██║   ██║╚██████╗    ██╔╝
  ╚═╝    ╚═╝  ╚═╝╚══════╝ ╚═════╝ ╚═╝  ╚═╝╚═╝╚══════╝   ╚═╝   ╚═╝ ╚═════╝    ╚═╝
           █████╗ ███╗   ██╗ █████╗ ██╗  ██╗   ██╗███████╗██╗███████╗
          ██╔══██╗████╗  ██║██╔══██╗██║  ╚██╗ ██╔╝██╔════╝██║██╔════╝
          ███████║██╔██╗ ██║███████║██║   ╚████╔╝ ███████╗██║███████╗
          ██╔══██║██║╚██╗██║██╔══██║██║    ╚██╔╝  ╚════██║██║╚════██║
          ██║  ██║██║ ╚████║██║  ██║███████╗██║   ███████║██║███████║
          ╚═╝  ╚═╝╚═╝  ╚═══╝╚═╝  ╚═╝╚══════╝╚═╝   ╚══════╝╚═╝╚══════╝
 */

// Detect It Easy: detection rule file format

// =================== [ ABOUT ] ===================
// This heuristic analysis will help identify
// presence of obfuscation the or wrapper
// tricks through advanced complex checks.
// Static emulation, signature analysis,
// smart checks and much more!
// ================================================

// ================== [ DONATE ] ==================
// Did you like my work? :D Thank you! But what
// about donation? I'll be very grateful <3
//
// >> Payeer: P1066822521 [Recommended]
//
// >> BTC:  37uRiHBqK3QiJ2jamqmmk1Q3sCmAmWngcC
//                        0,0005 BTC minimum
//
// >> LTC:  MCwRK1Z7K4GYHt9ZrbTR2SMCEqzqQaTbRF
//                        0,001 LTC minimum
//
// >> USDT: TUVH7QkcZws78QMC3XyAwfuzxUbaeLnfAC
//    TRC-20              5 USDT minimum
// ================================================

// ================= [ CONTACTS ] =================
// Author: 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
// ================================================

// For the script to work correctly, the following
// official Detect It Easy components are required:
// "language", "FASM", "RosASM", "SpASM", "FPC"
// "PE\linker.6.sg", "Microsoft.6.sg"

// Please do not read the code out loud unless you have exorcism skills

const logType = {
        warning: -2,
        about: -1,
        nothing: 0,
        any: 1,
        net: 2
    },
    heurLabel = "HEUR";

var lastOffsetDetected = "0x00";

const detect = main;

function main() {

    if (stubForWrongEnvironment()) return null; // 'PE' is undefined
    if (stubForLegacyEngines()) return null; // old DIE version

    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.isNET()) {
            scanForObfuscations_NET();
            scanForAntiAnalysis_NET();
        } else {
            scanForObfuscations_Native();
        }

        scanForPackersAndCryptors_NET_and_Native();
        scanForLicensingSystems_NET_and_Native();

        if (PE.isVerbose()) {
            scanForLanguages_NET_and_Native();
        }

        // >> Happy end <<
        log(logType.nothing, "Scan completed.");


        // scanForMaciliousCode_NET_and_Native();
    } else {
        log(logType.warning, "Heuristic scan is disabled. Use '--heuristicscan' to enable");
    }
}


function stubForLegacyEngines() {
    if (typeof PE.isNetGlobalCctorPresent === 'undefined') {
        stdout(">>> Update DIE Engine to 3.10 and higher for using Heuristic-analyser by DosX <<<");
        return true;
    }

    return false;
}



function stubForWrongEnvironment() {
    if (typeof PE === 'undefined') {
        stdout(">>> Wrong environment! 'PE' is undefined. Check DIE-engine for correct installation <<<");
        return true;
    }

    return false;
}



function stdout(stringToOut) {
    if (typeof console === 'object') {
        console.warn(stringToOut);
    } else if (typeof File === 'object') {
        _setResult("~Warning", stringToOut, String(), String());
    } else {
        throw stringToOut;
    }
}



function scanForObfuscations_NET() {
    var options = String();

    var isDetected = Boolean();




    const isVbNet = isVbNetStandartLibraryPresent();


    var isEntryPointModified = false;

    const vbNetEntries = [
            "Main",
            "main",
            "MAIN",
            "MyApplication"
        ],
        defaultEntries = [ // like MSIL, C#, C++ NET etc
            "Main",
            "main", // F# entry
            "<Main>$", // For programs with top-level operators (C#)
            "mainCRTStartup", // C++ CLR .NET (CLI)
            "wWinMainCRTStartup" // C++ CLR .NET (GUI)
        ]

    if (!PE.isDll()) {
        if (isVbNet) {
            if (isAllNetReferencesMissing(vbNetEntries)) {
                isEntryPointModified = true;
            }
        } else if (isAllNetReferencesMissing(defaultEntries)) {
            isEntryPointModified = true;
        }

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

    if (isEntryPointModified) options = "Modified EP";




    var isNetCctorPresent = false;

    if (PE.isNetGlobalCctorPresent() && !isClrNET()) {
        isNetCctorPresent = true;
    }

    if (isNetCctorPresent) options += (options.length != 0 ? " + " : String()) + "CLR constructor";




    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.isDll() && PE.getNumberOfSections() > 1 && !isClrNET()) {
        // 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 += (options.length != 0 ? " + " : String()) + "Strange EP position";




    var isNativeEntryPointModified = false;

    if (!PE.isDll() && !isClrNET()) { // not for CLR apps
        if (!PE.is64()) {
            // 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(onlyOpCodes = true, numberOf = 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 += (options.length != 0 ? " + " : String()) + "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 += (options.length != 0 ? " + " : String()) + "Modified DOS";




    // Check PE image for strange sections

    var strangeSections = false;

    const badChars = '_-=+~!@#$%^&*()"№;%:?*():;,/\\|\'`<>.0123456789'; // Very very bad!

    if (PE.getNumberOfSections() > (!isClrNET() ? 6 : 10) || !PE.isSectionNamePresent(".text")) { strangeSections = true; } else {
        // Iterate through each section
        for (var i = 0; i < PE.getNumberOfSections() && !strangeSections; 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) {
                strangeSections = true;
                break;
            }

            // Iterate through characters after "."
            // Check if the character is in the badChars list
            for (var d = 0; d < badChars.length && !strangeSections; d++) {
                if (sectionName.substr(1, sectionName.length).indexOf(badChars[d]) !== -1) {
                    strangeSections = true;
                }
            }
        }
    }

    if (strangeSections) options += (options.length != 0 ? " + " : String()) + "Strange 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.bne_un_s + 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 += (options.length != 0 ? " + " : String()) + "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(
            references = [
                "System.Reflection", // System.Reflection.dll
                "GetILGenerator", // MSIL: 'System.Reflection.Emit.DynamicMethod::GetILGenerator()'
                "BeginInvoke",
                "EndInvoke",
                "OpCode" // MSIL: 'System.Reflection.Emit.OpCode'
            ]
        ) &&
        (
            PE.isNetObjectPresent("Ldarg_0") || // MSIL: 'System.Reflection.Emit.OpCodes.Ldarg_0'
            PE.isNetObjectPresent("CreateDelegate") // MSIL: 'System.Delegate.CreateDelegate'
        ) && !isFrameworkComponent()
    ) {
        isVirtualizationPresent = true;
    }

    if (isVirtualizationPresent) options += (options.length != 0 ? " + " : String()) + "Virtualization";




    // Hiding calls using delegate tricks

    var callsEncrypt = false;

    if (
        isAllNetReferencesPresent(
            references = [
                "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 += (options.length != 0 ? " + " : String()) + "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 += (options.length != 0 ? " + " : String()) + "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
    ) {
        isAntiDe4dotPresent = true;
    }

    if (isAntiDe4dotPresent) options += (options.length != 0 ? " + " : String()) + "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.idelem_i +
            opCodes.setStrict(opCodes.calli, "** 00 00 11")
        )) {
        log(logType.net, "Calli invokes detected! Offset: " + lastOffsetDetected);
        isCalliInvokesPresent = true;
    }

    if (isCalliInvokesPresent) options += (options.length != 0 ? " + " : String()) + "Calli invokes";




    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 += (options.length != 0 ? " + " : String()) + "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.setStrict(opCodes.switch__nobody, "** ** ?? ?? ?? ?? ?? ?? ?? ?? ?? ?? ?? ?? ?? ?? ?? ?? ?? ?? ?? ?? 00 00") +
            opCodes.ldc_i4 + opCodes.br
        ) ||
        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'
            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 += (options.length != 0 ? " + " : String()) + "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)) { // second way :D
        var shortNamesFound = 0;

        const chars = "QWERTYUIOPASDFGHJKLZXCVBNMqwertyuiopasdfghjklzxcvbnm";
        for (var i = 1; i < chars.length && !isShortNamesPresent; i++) {
            if (PE.isNetObjectPresent(chars[i])) {
                shortNamesFound++;
                log(logType.net, "Short name found: '" + chars[i] + "' (" + shortNamesFound + "/20)");
            }

            if (shortNamesFound === 20) {
                isShortNamesPresent = true;
            }
        }
    }

    if (isShortNamesPresent) options += (options.length != 0 ? " + " : String()) + "Short names";




    var badNamings = false;

    var buffer = "";

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

    for (var i = 0; i < patternsToExplore.length && !badNamings; i++) {

        const currentStringPattern = patternsToExplore[i];

        if (currentStringPattern.indexOf("<") === -1 && currentStringPattern.match(/^(?=\d|[a-z])(?=.*[a-z]\d[a-z])(?=(?:.*\d){4,})/i))
            strangePatternsCounter++;

        if (strangePatternsCounter > 4)
            badNamings = true;

    }

    if (badNamings) options += (options.length != 0 ? " + " : String()) + "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 += (options.length != 0 ? " + " : String()) + "Anti-tamper";




    // If in the assembly you can find a 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 += (options.length != 0 ? " + " : String()) + "Fake .cctor name";




    var isBadCctor = false;

    if (currentCtorOffset === -1) {
        isBadCctor = true;
    }

    if (isBadCctor) options += (options.length != 0 ? " + " : String()) + "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.ldc_i4 + opCodes.add, // samples by: ConfuserEx (Beds mod)
        opCodes.ldloc_1 + opCodes.ldc_i4 + opCodes.ldc_i4 + "%s" + opCodes.ldc_i4 + opCodes.ldc_i4, // samples by: SkiDzEX
        opCodes.ldloc + opCodes.ldc_i4 + opCodes.ldc_i4 + opCodes.ldc_i4 + "%s" + opCodes.stelem_i1, // samples by: .NET Reactor
        opCodes.ldc_i4 + opCodes.ldc_i4 + "%s" + opCodes.br_s // samples by: [Unknown protector, only samples]
    ];

    for (var y = 0; y < mathTemplates.length && !isMutationsPresent; y++) {
        const template = mathTemplates[y];

        for (var e = 0; e < mathOpCodes.length && !isMutationsPresent; e++) {

            if (e == 0 && !validateNetByteCode(template.replace("%s", opCodes._unknown))) break; // No math mutations

            const pattern = template.replace("%s", mathOpCodes[e]);

            if (validateNetByteCode(pattern)) {
                log(logType.net, "Math mutations detected! Offset: " + lastOffsetDetected);
                isMutationsPresent = true;
            }
        }
    }

    if (isMutationsPresent) options += (options.length != 0 ? " + " : String()) + "Math mutations";




    // VB NET apps with resources only

    var isStringsEncryptionPresent = false;

    if (isVbNet) {
        if (PE.isNetObjectPresent("Resources") && !validateGlobalUnicodeString(".Resources")) {
            isStringsEncryptionPresent = true;
        }
    }

    if (isStringsEncryptionPresent) options += (options.length != 0 ? " + " : String()) + "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 += (options.length != 0 ? " + " : String()) + "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, "Invalid OpCodes detected, offset " + lastOffsetDetected);
        invalidOpCodes = true;
    }

    if (invalidOpCodes) options += (options.length != 0 ? " + " : String()) + "Invalid OpCodes";




    var isProtectionRuntimePresent = false;

    var runtimeFound = String();

    const protectionsRuntime = [ // Need more
        { lib: "AgileDotNet.VMRuntime.dll", name: "Agile" },
        { lib: "Xerin.Runtime.dll", name: "XerinFuscator" },
        { lib: "OneVM.Runtime.dll", name: "OneVM" },
        { lib: "HVMRuntm.dll", name: "DNGuard" }
    ];

    for (var i = 0; i < protectionsRuntime.length; i++) {
        const
            runtimeInfo = protectionsRuntime[i],
            runtimeLibraryName = runtimeInfo.lib,
            protectorName = runtimeInfo.name;

        if (PE.isNetObjectPresent(runtimeLibraryName) || // "runtime.dll"
            PE.isNetObjectPresent(runtimeLibraryName.substring(0, runtimeLibraryName.length - 4))) { // "runtime"
            isProtectionRuntimePresent = true;
            runtimeFound = protectorName;
            break;
        }
    }

    if (isProtectionRuntimePresent) options += (options.length != 0 ? " + " : String()) + runtimeFound + " runtime";




    const obfuscatorsAttributes = [
        "Xenocode.Client.Attributes.AssemblyAttributes.ProcessedByXenocode",
        "CryptoObfuscator.ProtectedWithCryptoObfuscatorAttribute",
        "SecureTeam.Attributes.ObfuscatedByAgileDotNetAttribute",
        "Xenocode.Client.Attributes.AssemblyAttributes",
        "SmartAssembly.Attributes.PoweredByAttribute",
        "ObfuscatedByAgileDotNetAttribute",
        "NineRays.Obfuscator.Evaluation",
        "ObfuscatedByCliSecureAttribute",
        "BabelObfuscatorAttribute",
        "AsStrongAsFuckAttribute",
        "ProtectedByDotnetsafer",
        "Macrobject.Obfuscator",
        "DotfuscatorAttribute",
        "CodeWallTrialVersion",
        "ConfusedByAttribute",
        "ObfuscatedByGoliath",
        "NETSpider.Attribute",
        "NineRays.Obfuscator",
        "PoweredByAttribute", // Smart Assembly
        "RustemSoft.Skater",
        "BabelAttribute",
        "MRuntime3.dll", // Maxtocode
        "YanoAttribute",
        "EMyPID_8234_",
        "ZYXDNGuarder",
        "SkiDzEX", // ConfuserEx based
        "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 && !isFakeSignaturesPresent; t++) {

        if (validateNetObject(obfuscatorsAttributes[t])) {
            obfuscatorAttributeFound = obfuscatorsAttributes[t];
            signaturesCounter++;
        }

        // Check if the number of detected signatures exceeds 1
        if (signaturesCounter > 1) {

            // Set flag indicating the presence of fake signatures
            isFakeSignaturesPresent = true;
        }
    }

    if (isFakeSignaturesPresent) {
        options += (options.length != 0 ? " + " : String()) + "Fake signatures";
    } else {
        // "Watermark" is only possible in the absence of fake signatures

        if (signaturesCounter === 1) {
            log(logType.nothing, "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 += (options.length != 0 ? " + " : String()) + "Watermark";




    const protectorsLabelsToRemove = [ // Protectors with these names will be removed from results
            "SafeNet Sentinel LDK .NET",
            "Xenocode Postbuild",
            "Smart Assembly",
            "XerinFuscator",
            "Dotfuscator",
            "Babel .NET",
            "Spices.Net",
            "Maxtocode",
            "FISH .NET",
            "CliSecure",
            "CodeWall",
            "CodeVeil",
            "Sixxpack",
            "DNGuard",
            "Goliath",
            "Agile",
            "Yano"
        ],
        packersLabelsToRemove = [
            "ChainskiCrypter",
            "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 d = 0; d < protectorsLabelsToRemove.length; d++) {
            _removeResult("protector", protectorsLabelsToRemove[d]);
        }

        for (var d = 0; d < packersLabelsToRemove.length; d++) {
            _removeResult("cryptor", packersLabelsToRemove[d]);
            _removeResult("packer", packersLabelsToRemove[d]);
        }

        for (var d = 0; d < protectionsLabelsToRemove.length; d++) {
            _removeResult("protection", protectionsLabelsToRemove[d]);
        }
    }




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


    if (isDetected) {
        _setResult("~protection", "Obfuscation", String(), PE.isVerbose() ? options : String());
    }
}



function scanForAntiAnalysis_NET() {
    var options = String();


    // 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")
            )) && !isFrameworkComponent()
    ) {
        isAntiDebugPresent = true;
    }

    if (isAntiDebugPresent) options += (options.length != 0 ? " + " : String()) + "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 (
        validateNetObject("VirtualProtect") && ( // from 'kernel32.dll', WinAPI
            // Need to check
            isAllNetReferencesPresent( // samples by: ConfuserEx, SkiDzEX
                references = [
                    "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
                references = [
                    "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
                references = [
                    "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 += (options.length != 0 ? " + " : String()) + "Anti-dump";




    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 + "'")
        ) {
            // Set flag indicating the presence of anti-DnSpy behavior
            isAntiDnSpyPresent = true;
        }
    }

    if (isAntiDnSpyPresent) options += (options.length != 0 ? " + " : String()) + "Anti-dnSpy";




    const antiIlSpyTriggers = [
        "ilspy",
        "ilSpy",
        "ILSpy",
        "ILSPY"
    ];

    var isAntiIlSpyPresent = false;

    for (var l = 0; l < antiIlSpyTriggers.length && !isAntiIlSpyPresent; l++) {
        const ilSpyName = antiIlSpyTriggers[l];

        if (
            validateGlobalUnicodeString(ilSpyName) ||
            validateSignature("'" + ilSpyName + "'")
        ) {
            isAntiIlSpyPresent = true;
        }
    }

    if (isAntiIlSpyPresent) options += (options.length != 0 ? " + " : String()) + "Anti-ILSpy";




    const sbieVariants = [
        "sbiedll.",
        "SbieDll.",
        "SBIEDLL."
    ];

    var isAntiSbiePresent = false;

    // Check if the signature for 'GetModuleHandle' is present
    if (validateSignature("'GetModuleHandle'")) { // from 'kernel32.dll') {
        // 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 (
                validateGlobalUnicodeString(sbieLib) ||
                validateSignature("'" + sbieLib + "'")
            ) {
                // Set flag indicating the presence of anti-Sandboxie behavior
                isAntiSbiePresent = true;
            }
        }
    }

    if (isAntiSbiePresent) options += (options.length != 0 ? " + " : String()) + "Anti-SandBoxie";




    var isAntiVmPresent = false;

    if (validateNetUnicodeString("vmware") || validateNetUnicodeString("VirtualBox")) {
        isAntiVmPresent = true;
    }

    if (isAntiVmPresent) options += (options.length != 0 ? " + " : String()) + "Anti-VM";




    if (options.length != 0) {
        _setResult("~protection", "Anti analysis", String(), PE.isVerbose() ? options : String());
    }
}



// Determines whether the application is a C++ CLR

function isClrNET() {
    return PE.isNET() && PE.isLibraryPresent("KERNEL32.DLL") && PE.isNetGlobalCctorPresent();
}



// .NET OpCodes for static emulations
function NetOpCodes() {
    this.add = "58"; // MSIL: 'add'
    this.sub = "59"; // MSIL: 'sub'
    this.mul = "5A"; // MSIL: 'mul'
    this.and = "5F"; // MSIL: 'and'
    this.bne_un = "40????????"; // MSIL: 'bne.un'
    this.bne_un_s = "3309"; // MSIL: 'bne.un.s'
    this.br = "38????????"; // MSIL: 'br'
    this.br_s = "2B??"; // MSIL: 'br.s'
    this.brfalse = "39????????"; // MSIL: 'brfalse'
    this.brfalse_s = "2C??"; // MSIL: 'brfalse.s'
    this.brtrue = "3A????????"
    this.call = "28????????"; // MSIL: 'call'
    this.calli = "29????????"; // MSIL: 'calli'
    this.ceq = "FE01"; // MSIL: 'ceq'
    this.div = "5B"; // MSIL: 'div'
    this.dup = "25"; // MSIL: 'dup'
    this.idelem_i = "97"; // MSIL: 'idelem.i'
    this.ldc_i4 = "20????????"; // MSIL: 'ldc.i4'
    this.ldc_i4_0 = "16"; // MSIL: 'ldc.i4.0'
    this.ldc_i4_1 = "17"; // MSIL: 'ldc.i4.1'
    this.ldc_i4_2 = "18"; // MSIL: 'ldc.i4.2'
    this.ldc_i4_3 = "19"; // MSIL: 'ldc.i4.2'
    this.ldc_i4_4 = "1A"; // MSIL: 'ldc.i4.4'
    this.ldc_i4_s = "1F??"; // MSIL: 'ldc.i4.s'
    this.ldftn = "FE06????????"; // MSIL: 'ldftn'
    this.ldloc = "FE??????"; // MSIL: 'ldloc'
    this.ldloc_0 = "06"; // MSIL: 'ldloc.0'
    this.ldloc_1 = "07"; // MSIL: 'ldloc.1'
    this.ldloc_2 = "08"; // MSIL: 'ldloc.2'
    this.ldloc_3 = "09"; // MSIL: 'ldloc.3'
    this.ldloc_s = "11??"; // MSIL: 'ldloc.s'
    this.ldsfld = "7E????????"; // MSIL: 'ldsfld'
    this.ldstr = "72????????"; // MSIL: 'ldstr'
    this.ldelem_u4 = "95"; // MSIL: 'ldelem.u4'
    this.nop = "00"; // MSIL: 'nop'
    this.not = "66"; // MSIL: 'not'
    this.neg = "65"; // MSIL: 'neg'
    this.or = "60"; // MSIL: 'or'
    this.pop = "26"; // MSIL: 'pop'
    this.ret = "2A"; // MSIL: 'ret'
    this.rem_un = "5E"; // MSIL: 'rem.un'
    this.shl = "62"; // MSIL: 'shl'
    this.shr = "63"; // MSIL: 'shr'
    this.sizeof = "FE1C????????"; // MSIL: 'sizeof'
    this.stloc = "FE0E????"; // MSIL: 'stloc'
    this.stloc_0 = "0A"; // MSIL: 'stloc.0'
    this.stloc_1 = "0B"; // MSIL: 'stloc.1'
    this.stloc_2 = "0C"; // MSIL: 'stloc.2'
    this.stloc_3 = "0D"; // MSIL: 'stloc.3'
    this.stloc_s = "13??"; // MSIL: 'stloc.s'
    this.shr_un = "64"; // MSIL: 'shr.un'
    this.xor = "61"; // MSIL: 'xor'
    this.stelem_i = "9B"; // MSIL: 'stelem.i'
    this.stelem_i1 = "9C"; // MSIL: 'stelem.i1'
    this.stelem_i4 = "9E"; // MSIL: 'stelem.i4'
    this.box = "8C????????"; // MSIL: 'box'

    this.switch__nobody = "45"; // MSIL: 'switch'
    this._unknown = "**"; // Unknown opcode
    this._any = "??"; // Any opcode



    // setStrict sets the strict value of the opcode for substitution
    // btw I like what I do
    this.setStrict = function(opCodeMask, value) {
        // Remove spaces from opcode mask and value
        opCodeMask = removeSpaces(opCodeMask);
        value = removeSpaces(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.substr(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.substr(opCodeInHex.length).length != value.length) {
            throw "The size of the input values does not match.";
        }

        // Combine the opcode in hexadecimal with the value
        return opCodeInHex + value;
    }

    // Sets the mask value to zero for the specified opcode
    this.setNullValue = function(opCodeMask) {

        if (opCodeMask.indexOf("??") === -1) {
            throw "Instruction does not have a body to overwrite the value.";
        }

        return replaceAllInString(opCodeMask, "??", "00");
    }

}


function removeSpaces(inputString) {
    return inputString.split(" ").join("");
}


function replaceAllInString(inputString, search, replacement) {
    while (inputString.indexOf(search) !== -1) {
        inputString = inputString.replace(search, replacement)
    }

    return inputString;
}



// This feature was originally intended only for .NET, but
// now partially works with Native files.

function scanForPackersAndCryptors_NET_and_Native() { // For .NET and Native apps
    var options = String();

    var isDetected = Boolean(),
        isCryptor = Boolean();




    if (PE.isNET()) {

        var isAssemblyInvokeFound = false;

        if (isAllNetReferencesPresent( // TODO: update [!!!]
                references = [
                    "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", isFullName = false) != String()) {

            // Specify cryptography classes to look for
            const cryptoClasses = [
                "TripleDESCryptoServiceProvider",
                "RSACryptoServiceProvider",
                "DSACryptoServiceProvider",
                "DESCryptoServiceProvider",
                "AesCryptoServiceProvider",
                "Rfc2898DeriveBytes",
                "SHA256Managed",
                "TripleDES",
                "Rijndael",
                "ECDsaCng",
                "AesAEAD",
                "Aes192Cbc",
                "Aes256Cbc",
                "Aes128Cbc",
                "AesManaged",
                "AesCng",
                "SHA256",
                "SHA512",
                "SHA1CryptoServiceProvider",
                "SHA512CryptoServiceProvider",
                "RC2CryptoServiceProvider",
                "SHA384CryptoServiceProvider",
                // "MD5CryptoServiceProvider",
                "SHA256CryptoServiceProvider",
                "RNGCryptoServiceProvider"
            ];

            // Iterate through cryptography classes
            for (var i = 0; i < cryptoClasses.length && !isCryptor; i++) {
                var cryptoClassSign = cryptoClasses[i],
                    result = findAndMark(
                        sign = cryptoClassSign,
                        isFullName = true
                    );

                // Check if assembly invoke is found and the cryptography class is present
                if (isAssemblyInvokeFound && result.length != 0) {
                    log(logType.net, "Crypto class present: " + cryptoClassSign);

                    isCryptor = true;

                    // Add the cryptography class to options
                    options += (options.length != 0 ? " + " : String()) + cryptoClassSign;
                }
            }
        }




        // Check if any class from System.IO.Compression namespace is used (non-full name)
        if ((findAndMark("System.IO.Compression", isFullName = false).length != 0)) {

            // 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, isFullName = true);

                // Check if assembly invoke is found and the compression class is present
                if (isAssemblyInvokeFound && result.length != 0) {
                    log(logType.net, "Compression class present: " + compressionClassSign);

                    // If it's a cryptor, add the compression class to options
                    if (isCryptor) options += (options.length != 0 ? " + " : String()) + compressionClassSign;

                    // Break the loop if a match is found
                    break;
                }
            }
        }
    }


    // Self-Extracting archives
    // TODO: Upgrade
    var isSfx = false;

    if (!PE.isDll() && 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.isNET()) {
            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;
            }

            for (var k = 0; k < sfxEntries.length; k++) {
                if (PE.compareEP(sfxEntries[k])) {
                    log(logType.nothing, "SFX entry pattern: " + sfxEntries[k]);

                    isSfx = true;
                }
            }
        }
    }

    if (isSfx) options += (options.length != 0 ? " + " : String()) + "SFX";




    var entryLikePacker = false;

    if (!PE.isDll()) {
        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)
            "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)
            "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)
            "6068" // samples by: BeRo, ExE Pack, AHPacker (x32)
        ];

        // Iterate through the entries to check against the entry point
        for (var e = 0; e < entries.length && !entryLikePacker; e++) {
            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 += (options.length != 0 ? " + " : String()) + "EntryPoint";




    // Check if the entry point is in the last section
    var isLastSectionEP = false;

    if (!PE.isDll()) {
        if (PE.getNumberOfSections() > 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.getNumberOfSections() - i));
            }
            entryPointAddress = PE.getAddressOfEntryPoint();


            // Check if the entry point is greater than or equal to the last section address
            if (entryPointAddress >= lastSectionAddress) {
                isLastSectionEP = true;
                log(logType.nothing, "EP address (" + entryPointAddress + ") more than last section address (" + lastSectionAddress + ")");
            }
        }
    }

    if (isLastSectionEP) options = "Last section EP";




    // Check for strange calls if entry point is in the last section
    var isStrangeCalls = false;

    if (!PE.isDll()) {
        if (isLastSectionEP && getAsmOpCode(getAsmInstructionByIndex(1)) === "CALL") {
            log(logType.nothing, "Strange call to address: " + getAsmInstructionByIndex(1).split(" ")[1]);
            isStrangeCalls = true;
        }
    }

    if (isStrangeCalls) options += (options.length != 0 ? " + " : String()) + "Strange call";




    var isImportsLikePacker = false;

    // ["Name", "Version", ImportLibraryIndex, Hash]
    // If {ImportLibraryIndex} is -1, it means the hash has no index

    var dbCollectionOfHashesDictionary = [
        // packers
        ["UPX", "0.59-0.93", 0, 0xd4fdcab1],
        ["UPX", "0.94-1.93", 0, 0x1d51299a],
        ["UPX", "1.94-2.03", 0, 0xb3318086],
        ["UPX", "1.94-2.03", 0, 0x3778aab9],
        ["UPX", "2.90-3.XX", 0, 0xf375ee03],
        ["UPX", "2.90-3.XX", 0, 0xf737d853],
        ["UPX", "3.91+", 0, 0xf737d853],
        ["UPX", "3.91+", -1, 0x82a048fc],
        ["UPX", "3.91+", -1, 0x554a1748],
        ["NSPACK", null, 0, 0xf375ee03],
        ["ASPack", "1.XX-2.XX", 0, 0x1272f45b],
        ["MKFPACK", null, 0, 0x42b3e7f9],
        ["MPRESS", null, 0, 0x174efb84],
        ["PACKMAN", "0.0.0.1", 0, 0x174efb84],
        ["PACKMAN", "1.0", 0, 0x69076a83],
        ["PECompact", "0.90-0.91", -1, 0xbea416d1],
        ["PECompact", "0.92-0.94", -1, 0x93312c2e],
        ["PECompact", "0.97-0.971b", -1, 0xe6aa8495],
        ["PECompact", "0.975-1.10b3", -1, 0x29188619],
        ["PECompact", "1.10b7-1.34", -1, 0xe4c11305],
        ["PECompact", "1.30-1.40", 0, 0x9b3305ed],
        ["PECompact", "1.40-1.84", 0, 0xcc5b2a3c],
        ["PECompact", "2.40-3.XX", 0, 0x2652ce4f],
        ["PECompact", "2.40-3.XX", -1, 0xdb8fbb75],
        ["EXE32PACK", "1.3X-1.4X", 0, 0x174efb84],
        ["tElock", "1.0", -1, 0x051946f7],
        ["JDPACK", "2.00", 0, 0xc002db0e],
        ["CRINKLER", null, 0, 0x0b0e1fbf],
        ["WinUpack", null, -1, 0x29188619],
        ["YodasCrypter", "1.X", -1, 0x1303a51b],
        ["XComp", "0.97-0.98", -1, 0xea1e66e4],
        ["XPack", "0.97-0.98", -1, 0x2ac44dd2],
        ["kkrunchy", null, -1, 0x29188619],
        ["ANDPakk2", "0.18", -1, 0x29188619],
        // protectors
        ["ASProtect", "1.XX-2.XX", 0, 0x1272f45b],
        ["SHRINKER", "3.2", 0, 0xb2a64858],
        ["SHRINKER", "3.2", 0, 0x158af2d0],
        ["SHRINKER", "3.2", 0, 0x49e8aa1f],
        ["SHRINKER", "3.5", 0, 0xe9ea0851],
        ["SHRINKER", "3.5", 0, 0x3344b95d],
        ["SHRINKER", "3.5", 0, 0x586088f3],
        ["Enigma", "1.00-3.60", 0, 0xc002db0e],
        ["Enigma", "2.XX", 0, 0xdd92de10],
        ["Enigma", "3.70+", 0, 0xd04c7a50],
        ["PCGUARD", "5.04-5.05", 0, 0x5a169c7a],
        ["PCGUARD", "5.04-5.05", 0, 0x0b0b2965],
        ["eXPressor", "1.2", -1, 0x66b35c6e],
        ["eXPressor", "1.2", -1, 0x32f4466c],
        ["eXPressor", "1.3", -1, 0x921d0280],
        ["eXPressor", "1.3", -1, 0xf51eba68],
        ["eXPressor", "1.3", -1, 0xbc84ce09],
        ["eXPressor", "1.4.5.X", 0, 0x427816ab],
        ["eXPressor", "1.4.5.X", -1, 0x3c705cae],
        ["eXPressor", "1.4.5.X", -1, 0x4d02e093],
        ["eXPressor", "1.4.5.X", -1, 0x958a9ea2], // VB6
        ["eXPressor", "1.5.0.X", -1, 0x7ababb5a],
        ["eXPressor", "1.5.0.X", -1, 0x95ca15e4],
        ["eXPressor", "1.5.0.X", -1, 0xbd41da20],
        ["eXPressor", "1.6", -1, 0xca58fa0c],
        ["eXPressor", "1.6.1", -1, 0x48ffd359],
        ["VMProtect", "1.70", -1, 0x1ff3103f],
        // ["VMProtect", "1.70", -1, 0x0c16df2d],
        ["VMProtect", "2.0.3-2.13", -1, 0x9d12b153],
        ["VMProtect", "3.0.0", -1, 0x1e5500c1],
        ["VMProtect", "3.0.9", -1, 0xc5fb6a4b],
        ["VMProtect", "3.2.0-3.5.0", -1, 0x5caa99c7],
        ["YodasProtector", "1.0b", -1, 0x1303a51b],
        ["ASM Guard", "2.XX+", -1, 0xf1e0d63b],
        ["Themida", "2.XX-3.XX", 0, 0x3ffccc8a],
        ["Amber", null, -1, 0x97c72051],
        [".NET Reactor", null, 0, 0x96be8e26],
        [".NET Reactor", null, 1, 0xb4cda32f],
        ["Bat To Exe Converter", null, 0, 0x72a2ca64],
        ["Vbs To Exe Converter", null, 0, 0x182aac68],
        ["DNGuard", null, 0, 0x38432571]
    ];

    const importValidatingResult = validateImportHashes(dbCollectionOfHashesDictionary);

    var versionByImportsDetected;

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

        log(logType.nothing, "Imports hash like " + importValidatingResult[0] + (versionByImportsDetected ? " (version " + versionByImportsDetected + ")" : String()) + " (" + importValidatingResult[3] + ")")
        isImportsLikePacker = true;
    }

    // Clean up: release the dictionary
    dbCollectionOfHashesDictionary = undefined;

    if (isImportsLikePacker) options += (options.length != 0 ? " + " : String()) + "Imports like " + importValidatingResult[0] + (versionByImportsDetected ? " (v" + importValidatingResult[1] + ")" : String());




    var isSectionNameLikePacker = false;

    var dbCollectionOfSectionNamesDictionary = [
        ["UPX", null, "UPX0"],
        ["UPX", null, "UPX1"],
        ["UPX", null, "UPX2"],
        ["UPX", null, "UPX3"],
        ["VMProtect", null, ".vmp"],
        ["VMProtect", null, ".vmp0"],
        ["VMProtect", null, ".vmp1"],
        ["VMProtect", null, ".vmp2"],
        ["VMProtect", null, ".vmp3"],
        ["ASPack", "1.08-2.XX", ".adata"],
        ["ASPack", "2.XX", ".aspack"],
        ["Petite", null, ".petite"],
        ["Petite", null, "petite"],
        ["Enigma", null, ".enigma1"],
        ["Enigma", null, ".enigma2"],
        [".NET Reactor", "2.XX", ".reacto"],
        ["Themida", "3.X", ".imports"],
        ["Themida", "3.X", ".themida"],
        ["Themida", "3.X", ".winlice"],
        ["Themida", "3.X", ".loadcon"],
        ["ASM Guard", "2.XX", "ASMGUARD"],
        ["ASM Guard", "2.XX", ".asmg"],
        ["tElock", null, "UPX!"], // ???
        ["YodasProtector", "1.0b", ".yP"],
        ["YodasCrypter", "1.X", "yC"],
        ["MPRESS", null, ".MPRESS1"],
        ["MPRESS", null, ".MPRESS2"],
        ["DxPack", "1.0", "coderpub"],
        ["SafeNet", null, ".AKS1"],
        ["SafeNet", null, ".AKS2"],
        ["SafeNet", null, ".AKS3"],
        ["Alienyze", null, ".alien"],
        ["PECompact", null, "pec"],
        ["PECompact", null, "pec1"],
        ["RLP", null, ".rlp"],
        [".NET Reactor", null, ".reacto"],
        ["StarForce", "4.X-5.X", ".ps4"],
        ["StarForce", "3.X", ".sforce3"],
        ["Safengine Shielden", null, ".sedat"],
        ["VirtualizeProtect", null, "VProtect"],
        ["Krypton", null, "YADO"],
        ["NsPack", null, "nsp0"],
        ["NsPack", null, "nsp1"],
        ["nPack", null, ".nPack"],
        ["JDPack", null, ".jdpack"],
        ["SC Pack", null, ".scpack"],
        ["Simple Pack", null, ".spack"],
        ["Eronana", null, ".packer"],
        ["PE-SHiELD", null, "PESHiELD"],
        ["SVK Protector", null, "SVKP"],
        ["obfus.h", null, ".obfh"],
        ["Warbird", null, "?g_Encry"],
        ["ACProtect", null, ".perplex"],
        ["Software Compress", null, "SoftComp"],
        ["RLPack", null, ".RLPack"],
        ["CodeVirtualizer", null, ".vlizer"],
        ["DYAMAR", "1.3.5", ".dyamarC"],
        ["hmimys", "1.3", "hmimys"],
        ["Morphnah", "1.0.X", ".nah"]
    ];

    const sectionNamesValidatingResult = validateSectionNames(dbCollectionOfSectionNamesDictionary);

    var versionBySectionDetected;

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

        log(logType.nothing, "Sections like " + sectionNamesValidatingResult[0] + (versionBySectionDetected ? " (v" + versionBySectionDetected + ")" : String()));
        isSectionNameLikePacker = true;
    }

    // Clean up: release the dictionary
    dbCollectionOfSectionNamesDictionary = undefined;

    if (isSectionNameLikePacker) options += (options.length != 0 ? " + " : String()) + "Sections like " + sectionNamesValidatingResult[0] + (versionBySectionDetected ? " (v" + sectionNamesValidatingResult[1] + ")" : 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 != 0) {
        log(logType.nothing, "Section names collision: '" + sectionNameCollision + "'");
        isCollisionInSectionsPresent = true;
    }

    if (isCollisionInSectionsPresent) options += (options.length != 0 ? " + " : String()) + "Sections collision (\"" + sectionNameCollision + "\")";




    // Check if there are repeating section names
    var isSectionNamesRepeatingPresent = false;

    // Dictionary to track encountered section names
    var sectionNamesDictionary = {};

    // Iterate through sections to check for collisions
    for (var i = 0; i < PE.getNumberOfSections() && !isSectionNamesRepeatingPresent; i++) {
        const sectionName = PE.getSectionName(i);

        // If section name is already encountered, set collision flag and break
        if (sectionNamesDictionary[sectionName]) {
            log(logType.nothing, "Section names repeating: '" + sectionName + "'");
            isSectionNamesRepeatingPresent = true;
        } else {
            sectionNamesDictionary[sectionName] = true;
        }
    }

    // Clean up: release the dictionary
    sectionNamesDictionary = undefined;

    if (isSectionNamesRepeatingPresent) options += (options.length != 0 ? " + " : String()) + "Section names repeating";




    // Check if the first instruction at entry point starts with a stack operation
    var isStartsWithStackOperation = false;

    // Get the opcode of the first instruction at entry point
    const firstEpAsmOpCode = getFirstEpAsmOpCode();

    // Switch statement to check for specific stack operation opcodes
    switch (firstEpAsmOpCode) {
        case "PUSHAL":
        case "PUSHA":
        case "PUSHF":
        case "POPA":
            log(logType.nothing, "'" + firstEpAsmOpCode + "' at EP");
            isStartsWithStackOperation = true;
    }

    if (isStartsWithStackOperation) options += (options.length != 0 ? " + " : String()) + "\"" + firstEpAsmOpCode.toLowerCase() + "\" at EP";




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

    var isEncodedPeDetected = false;

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

        // Check if the signature is valid using Unicode signature mask or the original signature
        if (
            validateGlobalUnicodeString(trigger) || validateSignature("'" + trigger + "'")
        ) {
            log(logType.nothing, "Encoded PE detected! (with Base64)");
            isCryptor = true;
            isEncodedPeDetected = true;
        }
    }

    if (isEncodedPeDetected) options += (options.length != 0 ? " + " : String()) + "Base64 payload";




    var isMzSignatureDetected = false;

    if (PE.isOverlayPresent() &&
        PE.getOverlaySize() >= 100 &&
        PE.compareOverlay("'MZ'")) {
        log(logType.any, "PE signature at overlay");
        isMzSignatureDetected = true;
    }

    if (isMzSignatureDetected) options += (options.length != 0 ? " + " : String()) + "PE in overlay";




    // Check for a strange overlay in the PE file
    var hasStrangeOverlay = false;

    // Conditions to check for a strange overlay
    if (!isMzSignatureDetected && !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 += (options.length != 0 ? " + " : String()) + "Strange overlay";




    // Flag to indicate high entropy
    var isHighEntropy = false;

    // Checks for high entropy (ignore overlay)
    if (!(PE.isDll() && (PE.isSectionNamePresent(".rdata") || PE.isSectionNamePresent(".rsrc"))) && // .dll with resources
        PE.calculateEntropy(0x00, PE.getSize() - PE.getOverlaySize()) > 7.3) {
        isHighEntropy = true;
    }

    if (isHighEntropy) options += (options.length != 0 ? " + " : String()) + "High entropy";




    var isCompressedSectionPresent = false;

    var sectionNumber = 0;

    for (var t = 0; t < PE.getNumberOfSections() && !isCompressedSectionPresent; t++) {
        sectionNumber = t;
        if (PE.calculateEntropy(PE.getSectionFileOffset(sectionNumber), PE.getSectionFileSize(sectionNumber)) > 7.4) {
            isCompressedSectionPresent = true;
        }
    }

    if (isCompressedSectionPresent) options += (options.length != 0 ? " + " : String()) + "Section " + sectionNumber + " (\"" + PE.getSectionName(sectionNumber) + "\") compressed";




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


    if (isDetected) {
        var detectedType = isCryptor ? "cryptor" : "packer";

        _setResult("~" + detectedType, (isCryptor ? "Encrypted" : "Compressed") + " or packed data", String(), PE.isVerbose() ? options : String());
    }
}




function scanForLicensingSystems_NET_and_Native() { // For .NET and Native apps
    var options = String();

    var isDetected = Boolean();




    if (PE.isNET()) {

        var isLicenseProviderPresent = false;

        if (PE.isNetObjectPresent("LicenseProviderAttribute")) {
            isLicenseProviderPresent = true;
        }

        if (isLicenseProviderPresent) options = "Provider attribute";



        var isLicenseManagerPresent = false;

        if (PE.isNetObjectPresent("LicenseManager")) {
            isLicenseManagerPresent = true;
        }

        if (isLicenseManagerPresent) options += (options.length != 0 ? " + " : String()) + "License manager";

    }




    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"];

    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 += (options.length != 0 ? " + " : String()) + "Strings";




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


    if (isDetected) {
        _setResult("~licensing", "Licensing", String(), PE.isVerbose() ? options : String());
    }
}




function isVbNetStandartLibraryPresent() {
    return PE.isNetObjectPresent("Microsoft.VisualBasic");
}


// Check if the file is a .NET Framework component
function isFrameworkComponent() {
    return PE.isNET() && PE.isDll() && PE.isSigned() && PE.findSignature(PE.getOverlayOffset(), 300, "'Microsoft Corporation'") != -1;
}

// Validate the presence of a signature in the file
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;
}


function validateNetByteCode(byteCode) {
    for (var s = 0; s < PE.getNumberOfSections(); 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;
}


function validateNetObject(object) {
    const result = PE.isNetObjectPresent(object);
    if (result) log(logType.net, "Object present: " + object);
    return result;
}


function validateNetUnicodeString(ustring) {
    const result = PE.isNetUStringPresent(ustring);
    if (result) log(logType.net, "String present: \"" + ustring + "\"");
    return result;
}


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


// Function to generate Unicode signature mask from an input string
// "test" -> "'t'00'e'00's'00't'"

function generateUnicodeSignatureMask(inputString) {
    var output = String();

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

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


// Function to check if all specified .NET references are missing

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


// Function to check if all specified .NET references are present

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


// "isFullName = true"  = 00'sign'00
// "isFullName = false" = 00'sign'

function findAndMark(sign, isFullName) {
    if (PE.isSignatureInSectionPresent(0,
            ("00'" + sign + "'") + // 00'string
            (isFullName ? "00" : String()))) { // ... '00
        return sign;
    }
    return String();
}



function scanForObfuscations_Native() {
    var options = String();

    var isDetected = Boolean();




    // Check for section names containing forbidden characters

    var strangeSections = false;

    // Define forbidden characters
    const badSectionChars = '-=+~!@#$%^&*()"№;%:?*():;,/\\|\'`<> ';

    // Iterate through sections and characters to check for forbidden characters
    for (var i = 0; i < PE.getNumberOfSections() && !strangeSections; i++) {
        var sectionName = PE.getSectionName(i);

        if (sectionName.length === 0 || sectionName[0] === " ") {
            strangeSections = 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) {
            strangeSections = false;
            break;
        }


        for (var d = 0; d < badSectionChars.length && !strangeSections; d++) {
            // If forbidden character is found, set flag and break
            if (sectionName.indexOf(badSectionChars[d]) !== -1) {
                strangeSections = true;
            }
        }


    }

    if (strangeSections) options += (options.length != 0 ? " + " : String()) + "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 (standart)
            "This program must be run under Win32",
            "This program must be run under Win64",
            "This program requires Win32",
            "This is a Windows NT character-mode executable" // Watcom C/C++
        ];

        isCustomDosPresent = true;

        // Iterate through messages to check for custom DOS
        for (var d = 0; d < messages.length && isCustomDosPresent; d++) {
            if (PE.findSignature(PE.getDosStubOffset(), PE.getDosStubSize(), "'" + messages[d] + "'") != -1) {
                isCustomDosPresent = false;
            }
        }
    }

    // Add appropriate option based on DOS presence
    if (isDosMissing) options += (options.length != 0 ? " + " : String()) + "Missing DOS";
    else if (isCustomDosPresent) options += (options.length != 0 ? " + " : String()) + "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.getNumberOfImports() && !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 += (options.length != 0 ? " + " : String()) + "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.getNumberOfImports() && !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 += (options.length != 0 ? " + " : String()) + "EXE in imports";




    // Looks for sections whose names contain strange (or invalid) characters

    var isInvalidImportsPresent = false;

    const badImportChars = '=~!@#$%^&*()"№;%:?*():;,|\'`<> ';

    for (var i = 0; i < PE.getNumberOfImports() && !isInvalidImportsPresent; i++) {
        const libraryName = PE.getImportLibraryName(i).toLowerCase();

        for (var l = 0; l < badImportChars.length && !isInvalidImportsPresent; l++) {
            if (libraryName.indexOf(badImportChars[l]) !== -1) {
                isInvalidImportsPresent = true;
            }
        }
    }

    if (isInvalidImportsPresent) options += (options.length != 0 ? " + " : String()) + "Invalid imports";




    // Checks if application resources can be read or if they are compressed/encrypted

    var isUnreadableResourcesPresent = false;

    for (var i = 0; i < PE.getNumberOfResources() && !isUnreadableResourcesPresent; i++) {
        if (PE.getResourceOffsetByNumber(i) === -1)
            isUnreadableResourcesPresent = true;
    }

    if (isUnreadableResourcesPresent) options += (options.length != 0 ? " + " : String()) + "Unreadable resources";



    /*
        var isCheckSumEmpty = false;

        if (PE.getImageOptionalHeader("CheckSum") == 0) {
            log(logType.any, "IMAGE_OPTIONAL_HEADER : CheckSum == 0");
            isCheckSumEmpty = true;
        }

        if (isCheckSumEmpty) options += (options.length != 0 ? " + " : String()) + "No checksum";




        // False-positive detections; Todo: fix
        var aLotOfBreaks = false;

        const codeSection = PE.section[".text"];

        if (codeSection && PE.isSignaturePresent(codeSection.FileOffset, codeSection.FileSize, "CC CC CC CC CC CC CC CC CC CC CC CC CC")) { // ret (c3); int 3 (cc)
            aLotOfBreaks = true;
        }

        if (aLotOfBreaks) options += (options.length != 0 ? " + " : String()) + "A lot of \"__debugbreak()\"";
        */




    // 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.isDll() && (
            PE.isExportFunctionPresent("Start") ||
            PE.isExportFunctionPresent("main") ||
            PE.isExportFunctionPresent("_start"))) {
        exeAsDll = true;
    }

    if (exeAsDll) options += (options.length != 0 ? " + " : String()) + "EXE as DLL";




    // The .text section should always come first

    var isTextSectionNotFirst = false;

    if ((PE.section[0].Name != ".text" && PE.section[0].Name != ".textbss") && (PE.section[".text"] && PE.section[".textbss"])) {
        isTextSectionNotFirst = true;
    }

    if (isTextSectionNotFirst) options += (options.length != 0 ? " + " : String()) + "\".text\" section is not first";




    // If IAT (Import Address Table) is missing

    var isIatMissing = false;

    if (PE.getNumberOfImports() == 0 &&
        (!PE.isDll() && PE.section[".text"])) {
        isIatMissing = true;
    }

    if (isIatMissing) options += (options.length != 0 ? " + " : String()) + "No IAT";




    // Check if the entry point starts with NOP

    var isStartsWithNop = false;

    // Condition to check if the first instruction is NOP
    if (getFirstEpAsmInstruction() === "NOP") // nop (90)
        isStartsWithNop = true;

    if (isStartsWithNop) options += (options.length != 0 ? " + " : String()) + "Nop at EP";



    // A lot of bugs! Todo.
    /*
        // Example:
        //  xor eax, eax
        //  je $+1
        var isBreakerDetected = false;

       //
       // 0:  31 c0  |  xor eax, eax
       // 1:  31 db  |  xor ebx, ebx
       // 2:  31 c9  |  xor ecx, ecx
       // 3:  31 d2  |  xor edx, edx
       // 4:  31 f6  |  xor esi, esi
       // 5:  31 ff  |  xor edi, edi
       // 6:  31 ed  |  xor ebp, ebp
       // 7:  31 e4  |  xor esp, esp
       //
        const xorPatterns_x86 = [
            "c0", "db", "c9", "d2", "f6", "ff", "ed", "e4"
        ];

        for (var i = 0; i < xorPatterns_x86.length && !isBreakerDetected; i++) {
            const foundOffset = PE.findSignature(0x00, PE.getSize() - PE.getOverlaySize(), "31 " + xorPatterns_x86[i] + "0F84......00"); // ... je $+[*] ...

            if (foundOffset !== -1 && !PE.compare("%% %% %% %% %% %% %%", foundOffset - 7)) {
                isBreakerDetected = true;
            }
        }

        if (isBreakerDetected) options += (options.length != 0 ? " + " : String()) + "Anti-Decompile";
        */




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

    var isNopPaddingPresent = false;

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

    if (isNopPaddingPresent) options += (options.length != 0 ? " + " : String()) + "Nop EP padding";




    // ASM Guard fake signatures

    if (PE.isSectionNamePresent(".asmg") || PE.isSectionNamePresent("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());
    }
}



const _patternSplitter = "|";


// Makes it possible to disassemble the entry point code and output
// a specified number of instructions through a splitter (_patternSplitter)

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



// Function to get assembly instruction by index

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

        // If the current iteration matches the specified index, retrieve the instruction
        if (i === index) {
            const asmInstruction = PE.getDisasmString(disasmAddress);

            // Return the assembly instruction
            return asmInstruction;
        }
    }
}



// Makes it possible to get a subpattern to search for instructions in a
// pattern divided through a separator (_patternSplitter)
//
// like "|OPCODE1|OPCODE2|OPCODE3|".indexOf("|OPCODE2|")
// but  "|OPCODE1|OPCODE2|OPCODE3|".indexOf(getInstructionsAsmPattern("OPCODE2"))
//  or
// like "|OPCODE1|OPCODE2|OPCODE3|".indexOf("|OPCODE2|OPCODE3|")
// but  "|OPCODE1|OPCODE2|OPCODE3|".indexOf(getInstructionsAsmPattern(["OPCODE2", "OPCODE3"]))

function getInstructionsAsmPattern(instruction) {
    return _patternSplitter +
        (
            Array.isArray(instruction) ?
            instruction.join(_patternSplitter) :
            instruction
        ) +
        _patternSplitter;
}


function getFirstEpAsmInstruction() {
    return PE.getDisasmString(PE.OffsetToVA(PE.getEntryPointOffset()));
}



// Gets an opcode from an instruction

function getAsmOpCode(instruction) {
    return instruction.indexOf(" ") !== -1 ? instruction.split(" ")[0] : instruction;
}



// Returns only the name of the opcode used, without arguments

function getFirstEpAsmOpCode() {
    return getAsmOpCode(getFirstEpAsmInstruction());
}



// VC ?warp_size@cuda@at@@YAHXZ
// GNU _ZSt16__ostream_insertIcSt11char_traitsIcEERSt13basic_ostreamIT_T0_ES6_PKS3_i

function isFunctionMangled(functionPattern) {
    return functionPattern.length > 5 && (
        (functionPattern[0] == '?' && functionPattern.indexOf("@@") !== -1) || // MSVCPP mangler
        (functionPattern.substring(0, 4) == "_ZSt") // GNUCPP mangler
    );
}



function getNameOfMangledFunction(functionPattern) {
    if (isFunctionMangled(functionPattern)) {
        if (functionPattern[0] === '?' && functionPattern[1] !== '?') {

            // MSVCPP mangler (e.g., ??0_Lockit@std@@QEAA@H@Z)
            return functionPattern.split("?")[1].split("@")[0];

        } else if (functionPattern[0] === '?' && functionPattern[1] === '?' && functionPattern[3] === '?' && functionPattern[4] === '$') {

            // MSVCPP mangler (e.g., ??1?$basic_streambuf@DU?$char_traits@D@std@@@std@@UEAA@XZ)
            return functionPattern.split("$")[1].split("@")[0];

        } else if (functionPattern[0] === '?' && functionPattern[1] === '?' && functionPattern[2] === '_') {
            // MSVCPP mangler (e.g., ??_7_Facet_base@std@@6B@)
            var functionName = functionPattern.split("?")[2].split("@")[0];

            functionName = functionName.substring(2, functionName.length);
            return functionName;

        } else if (functionPattern[0] === '?' && functionPattern[1] === '?') {

            // MSVCPP mangler
            var functionName = functionPattern.split("?")[2].split("@")[0];
            functionName = functionName.substring(1, functionName.length); // first char is a number
            return functionName;

        } else if (functionPattern[0] === '_') {

            // GNUCPP mangler (e.g., _ZSt12functionName)
            var match = functionPattern.match(/_ZSt(\d+)(\w+)/);
            if (match) {
                return match[2].substring(0, parseInt(match[1], 10));
            } else {
                return functionPattern;
            }

        }
    } else {
        return functionPattern;
    }
}



function validateImportHashes(dbCollection) {
    for (var i = 0; i < dbCollection.length; i++) {
        const
            currentIndex = i,
            currentArrayPattern = dbCollection[currentIndex];

        const
            name = currentArrayPattern[0],
            version = currentArrayPattern[1],
            position = currentArrayPattern[2],
            hash = currentArrayPattern[3];

        if (PE.isImportPositionHashPresent(position, hash)) {
            return currentArrayPattern;
        }
    }

    return null;
}



function validateSectionNames(dbCollection) {
    for (var i = 0; i < dbCollection.length; i++) {
        const
            currentIndex = i,
            currentArrayPattern = dbCollection[currentIndex];

        const
            name = currentArrayPattern[0],
            version = currentArrayPattern[1],
            sectionName = currentArrayPattern[2];

        if (PE.isSectionNamePresent(sectionName)) {
            return currentArrayPattern;
        }
    }

    return null;
}



function scanForLanguages_NET_and_Native() {
    log(logType.nothing, "Scanning to programming language has started!");

    var c_cpp = _isLangPresent("C/C++"); // Unknown; C or C++

    const extdb = [
        ["C++", "cpp"],
        ["Rust", "rs"],
        ["Java", "class"],
        ["JavaScript", "js"],
        ["Python", "pyd"],
        ["PureBasic", "pb"]
    ];


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


    if (!_getNumberOfResults("protector") &&
        !_getNumberOfResults("cryptor") &&
        !_getNumberOfResults("~cryptor")
    ) {

        var isPpLibraryPresent = false,
            isCLibraryPresent = false;

        for (var i = 0; i < PE.getNumberOfImports(); i++) {
            const libraryName = PE.getImportLibraryName(i).toLowerCase();

            // Detect mangler
            for (var k = 0; k < PE.getNumberOfImportThunks(i); k++) {
                const functionName = PE.getImportFunctionName(i, k); // import, thunk

                if (!isPpLibraryPresent && isFunctionMangled(functionName)) {
                    log(logType.any, "Mangler detected -> \"" + libraryName + "\", at function \"" + getNameOfMangledFunction(functionName) + "\"");


                    if (!_getNumberOfResults("compiler") && !_getNumberOfResults("~compiler")) {
                        if (functionName[0] == '_') {
                            _setResult("~compiler", "MinGW", String(), String());
                        } else if (functionName[0] == '?') {
                            _setResult("~compiler", "Microsoft Visual C/C++", String(), String());
                        }
                    }

                    // if (!_isLangDetected()) 
                    isPpLibraryPresent = true; // if language is unknown
                }
            }



            if (libraryName.indexOf("msvcr") !== -1) {
                log(logType.any, "C library present -> \"" + libraryName + "\"");
                isCLibraryPresent = true;
            }

            if (
                libraryName.indexOf("++") !== -1 ||
                libraryName.indexOf("cpp") !== -1 ||
                libraryName.indexOf("msvcp") !== -1
            ) {
                log(logType.any, "C++ library present -> \"" + libraryName + "\"");
                isPpLibraryPresent = true;
            }
        }




        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.section[".msvcjmc"]) {
            _setLangByHeur("C/C++");
        } else if (!_isLangDetected() && !_getNumberOfResults("compiler") && !PE.isNET()) {
            _setLangByHeur("ASMx" + (PE.is64() ? "64" : "86"));
        }
    }
}



function _setLangByHeur(languageName) {
    log(logType.any, languageName + " language detected!");
    _setLang(languageName, true, heurLabel);
}



function log(messageTypeId, messageText) {
    // if (PE.isProfiling()) return null;

    if (messageText.indexOf("\n") != -1) {
        throw "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;
    }

    _log("[" + prefix + "] " + messageText);
}



// ALPHA v0.01
// The module is disabled and does not work
// You can write this yourself if you want.

function scanForMaciliousCode_NET_and_Native() {

    var _CriticalProc_ntdll = false;

    if (validateSignature("'RtlSetProcessIsCritical'")) {
        _CriticalProc_ntdll = true;
    }

    if (_CriticalProc_ntdll) heurAvSetResult("CriticalProc_ntdll", 8);




    var _TakeScreenshot = false;

    if (PE.isNET()) {
        if (validateNetObject("BitBlt") || validateNetObject("GetDC")) {
            _TakeScreenshot = true;
        }
    } else { // Global scan
        if (validateSignature("00'BitBlt'00") || validateSignature("00'GetDC'00")) {
            _TakeScreenshot = true;
        }
    }

    if (_TakeScreenshot) heurAvSetResult("TakeScreenshot", 3);
}



function heurAvSetResult(label, scores) {
    if (scores <= 10 && scores >= 0) {
        _setResult("macilious", ("Win" + (PE.is64() ? "64" : "32") + ".") + label, "Heuristic AV", scores + "/10");
    } else {
        throw "Incorrect scores value for '" + label + "'";
    }
}


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