// Detect It Easy: detection rule file // Author: DosX // E-Mail: collab@kay-software.ru // GitHub: https://github.com/DosX-dev // Telegram: @DosX_dev function detect() { if (Binary.isHeuristicScan()) { main(); } } function main() { switch (Binary.getFileSuffix().toLowerCase()) { case "js": // JavaScript case "jse": // JScript Encoded case "jsc": // JavaScript Compiled case "jsx": // JavaScript XML case "mjs": // JavaScript Module case "cjs": // JavaScript CommonJS Module case "sg": // Detect It Easy module var options = String(); if (!Binary.isPlainText()) { options = "bytecode"; } else { if (Binary.getSize() > 0x400) { var scriptContent = Binary.getString(0x00, Binary.getSize()); // Split the code into segments that are outside of string literals. // Handle escaping (\", \\', \`) — escaped quotes are not treated as string delimiters. // Also include expressions inside template literals `${...}` as code (they will be scanned). var segments = [], currentSegment = String(), insideString = false, stringDelimiter = String(), isEscaped = false; for (var i = 0; i < scriptContent.length; i++) { var currChar = scriptContent[i]; if (insideString) { if (isEscaped) { isEscaped = false; continue; } if (currChar === '\\') { isEscaped = true; continue; } // For template literals: when encountering `${` include nested expression into current segment if (stringDelimiter === '`' && currChar === '$' && i + 1 < scriptContent.length && scriptContent[i + 1] === '{') { // skip '{' and start accumulating the expression content i++; // now scriptContent[i] === '{' var braceDepth = 1; // Inside the expression, handle escaping as well while (i + 1 < scriptContent.length && braceDepth > 0) { i++; var innerChar = scriptContent[i]; if (innerChar === '\\') { i++; // skip escaped char continue; } // Skip string literals inside the expression to avoid false positives if (innerChar === '"' || innerChar === "'") { var innerDelim = innerChar; while (i + 1 < scriptContent.length) { i++; if (scriptContent[i] === '\\') { i++; continue; } if (scriptContent[i] === innerDelim) break; } continue; } // Nested template literal — skip until matching backtick (any depth) if (innerChar === '`') { i = skipNestedTemplate(scriptContent, i + 1); continue; } if (innerChar === '{') { braceDepth++; currentSegment += innerChar; continue; } if (innerChar === '}') { braceDepth--; if (braceDepth === 0) break; currentSegment += innerChar; continue; } currentSegment += innerChar; } continue; } if (currChar === stringDelimiter) { insideString = false; stringDelimiter = String(); } // do not copy characters that are inside strings } else { // not inside a string if (currChar === '"' || currChar === "'" || currChar === '`') { // start of string — finish current segment if (currentSegment.length > 0) { segments.push(currentSegment); currentSegment = String(); } insideString = true; stringDelimiter = currChar; } else { currentSegment += currChar; } } } if (currentSegment.length > 0) segments.push(currentSegment); for (var segIndex = 0; segIndex < segments.length; segIndex++) { var tokenToProcess = segments[segIndex]; if (!/(?: |\t)/.test(tokenToProcess) && ( /(?:(?:(?:var|let|const)[\t ]|\())\b[a-zA-Z](?:,[a-zA-Z]){3,}\b/.test(tokenToProcess) || /[a-zA-Z][!=]?=?=![01][;,\}\)]/.test(tokenToProcess) )) { options = "minified/compiled"; break; } } } } _setResult("~language", "JavaScript", String(), Binary.isVerbose() ? options : String()); break; } } // Skips a template literal from position i (the char after the opening backtick). // Handles nested ${} and inner strings/templates of any depth recursively. // Returns the index of the closing backtick. function skipNestedTemplate(s, i) { while (i < s.length) { var c = s[i]; if (c === '\\') { i += 2; continue; } // escaped char — skip both if (c === '`') return i; // closing backtick found if (c === '$' && i + 1 < s.length && s[i + 1] === '{') { i += 2; // skip '${' // Scan the expression body until matching '}' while (i < s.length) { var d = s[i]; if (d === '\\') { i += 2; continue; } if (d === '"' || d === "'") { // Skip string literal, respecting escape sequences var q = d; i++; while (i < s.length) { if (s[i] === '\\') { i += 2; continue; } if (s[i] === q) { i++; break; } i++; } continue; } if (d === '`') { i = skipNestedTemplate(s, i + 1) + 1; continue; } // recurse if (d === '}') { i++; break; } // end of ${} i++; } continue; } i++; } return i; }