From 3587cc9b88701d3a1cc1292774af3509a5dd9a6a Mon Sep 17 00:00:00 2001 From: Ben Baryo Date: Tue, 12 Nov 2024 16:51:50 +0200 Subject: [PATCH 1/4] Small code improvements --- .../replaceFunctionShellsWithWrappedValue.js | 5 ++--- ...eplaceFunctionShellsWithWrappedValueIIFE.js | 3 +-- ...erWithFixedValueNotAssignedAtDeclaration.js | 4 ++-- .../replaceNewFuncCallsWithLiteralContent.js | 2 +- src/modules/safe/resolveProxyCalls.js | 2 +- src/modules/safe/unwrapIIFEs.js | 2 +- src/modules/unsafe/resolveLocalCalls.js | 18 ++++++++---------- 7 files changed, 16 insertions(+), 20 deletions(-) diff --git a/src/modules/safe/replaceFunctionShellsWithWrappedValue.js b/src/modules/safe/replaceFunctionShellsWithWrappedValue.js index 009f16b..833d5f4 100644 --- a/src/modules/safe/replaceFunctionShellsWithWrappedValue.js +++ b/src/modules/safe/replaceFunctionShellsWithWrappedValue.js @@ -8,9 +8,8 @@ function replaceFunctionShellsWithWrappedValue(arb, candidateFilter = () => true for (let i = 0; i < arb.ast.length; i++) { const n = arb.ast[i]; if (n.type === 'FunctionDeclaration' && - n.body?.body?.length && - n.body.body[0].type === 'ReturnStatement' && - ['Literal', 'Identifier'].includes(n.body.body[0].argument?.type) && + n.body.body?.[0]?.type === 'ReturnStatement' && + ['Literal', 'Identifier'].includes(n.body.body[0]?.argument?.type) && candidateFilter(n)) { const replacementNode = n.body.body[0].argument; for (const ref of (n.id?.references || [])) { diff --git a/src/modules/safe/replaceFunctionShellsWithWrappedValueIIFE.js b/src/modules/safe/replaceFunctionShellsWithWrappedValueIIFE.js index aa7ed00..179f645 100644 --- a/src/modules/safe/replaceFunctionShellsWithWrappedValueIIFE.js +++ b/src/modules/safe/replaceFunctionShellsWithWrappedValueIIFE.js @@ -10,8 +10,7 @@ function replaceFunctionShellsWithWrappedValueIIFE(arb, candidateFilter = () => if (n.type === 'FunctionExpression' && n.parentKey === 'callee' && !n.parentNode.arguments.length && - n.body?.body?.length && - n.body.body[0].type === 'ReturnStatement' && + n.body.body?.[0]?.type === 'ReturnStatement' && ['Literal', 'Identifier'].includes(n.body.body[0].argument?.type) && candidateFilter(n)) { arb.markNode(n.parentNode, n.parentNode.callee.body.body[0].argument); diff --git a/src/modules/safe/replaceIdentifierWithFixedValueNotAssignedAtDeclaration.js b/src/modules/safe/replaceIdentifierWithFixedValueNotAssignedAtDeclaration.js index 3c901b2..d8f7d79 100644 --- a/src/modules/safe/replaceIdentifierWithFixedValueNotAssignedAtDeclaration.js +++ b/src/modules/safe/replaceIdentifierWithFixedValueNotAssignedAtDeclaration.js @@ -13,8 +13,8 @@ function replaceIdentifierWithFixedValueNotAssignedAtDeclaration(arb, candidateF const n = arb.ast[i]; if (n.parentNode?.type === 'VariableDeclarator' && !n.parentNode.init && - n?.references?.length && - n.references.filter(r => + // n?.references?.length && + n.references?.filter(r => r.parentNode.type === 'AssignmentExpression' && getMainDeclaredObjectOfMemberExpression(r.parentNode.left) === r).length === 1 && !n.references.some(r => diff --git a/src/modules/safe/replaceNewFuncCallsWithLiteralContent.js b/src/modules/safe/replaceNewFuncCallsWithLiteralContent.js index 4396ebb..3166477 100644 --- a/src/modules/safe/replaceNewFuncCallsWithLiteralContent.js +++ b/src/modules/safe/replaceNewFuncCallsWithLiteralContent.js @@ -18,7 +18,7 @@ function replaceNewFuncCallsWithLiteralContent(arb, candidateFilter = () => true const n = arb.ast[i]; if (n.type === 'NewExpression' && n.parentKey === 'callee' && - n.parentNode?.arguments?.length === 0 && + !n.parentNode?.arguments?.length && n.callee?.name === 'Function' && n.arguments?.length === 1 && n.arguments[0].type === 'Literal' && diff --git a/src/modules/safe/resolveProxyCalls.js b/src/modules/safe/resolveProxyCalls.js index fa77ea6..9581d08 100644 --- a/src/modules/safe/resolveProxyCalls.js +++ b/src/modules/safe/resolveProxyCalls.js @@ -19,7 +19,7 @@ function resolveProxyCalls(arb, candidateFilter = () => true) { for (let i = 0; i < arb.ast.length; i++) { const n = arb.ast[i]; if (n.type === 'FunctionDeclaration' && - n.body?.body?.length === 1 && + // n.body?.body?.length === 1 && n.body.body[0].type === 'ReturnStatement' && n.body.body[0].argument?.type === 'CallExpression' && n.body.body[0].argument.arguments?.length === n.params?.length && diff --git a/src/modules/safe/unwrapIIFEs.js b/src/modules/safe/unwrapIIFEs.js index 690884d..aad19c5 100644 --- a/src/modules/safe/unwrapIIFEs.js +++ b/src/modules/safe/unwrapIIFEs.js @@ -29,7 +29,7 @@ function unwrapIIFEs(arb, candidateFilter = () => true) { if (replacementNode.type === 'BlockStatement') { let targetChild = replacementNode; // IIFEs with a single return statement - if (replacementNode.body?.length === 1 && replacementNode.body[0].argument) replacementNode = replacementNode.body[0].argument; + if (replacementNode.body?.[0]?.argument) replacementNode = replacementNode.body[0].argument; // IIFEs with multiple statements or expressions else while (targetNode && !targetNode.body) { // Skip cases where IIFE is used to initialize or set a value diff --git a/src/modules/unsafe/resolveLocalCalls.js b/src/modules/unsafe/resolveLocalCalls.js index dde37a8..2d5de79 100644 --- a/src/modules/unsafe/resolveLocalCalls.js +++ b/src/modules/unsafe/resolveLocalCalls.js @@ -7,7 +7,7 @@ import {createOrderedSrc} from '../utils/createOrderedSrc.js'; import {getDeclarationWithContext} from '../utils/getDeclarationWithContext.js'; import {badValue, badArgumentTypes, skipIdentifiers, skipProperties} from '../config.js'; -let appearances = {}; +let appearances = new Map(); const cacheLimit = 100; /** @@ -15,9 +15,7 @@ const cacheLimit = 100; * @param {ASTNode} b */ function sortByApperanceFrequency(a, b) { - a = getCalleeName(a); - b = getCalleeName(b); - return appearances[a] < appearances[b] ? 1 : appearances[b] < appearances[a] ? -1 : 0; + return appearances.get(getCalleeName(b)) - appearances.get(getCalleeName(a)); } /** @@ -26,8 +24,9 @@ function sortByApperanceFrequency(a, b) { */ function countAppearances(node) { const callee = getCalleeName(node); - if (!appearances[callee]) appearances[callee] = 0; - return ++appearances[callee]; + const count = (appearances.get(callee) || 0) + 1; + appearances.set(callee, count); + return count; } /** @@ -38,7 +37,7 @@ function countAppearances(node) { * @return {Arborist} */ export default function resolveLocalCalls(arb, candidateFilter = () => true) { - appearances = {}; + appearances = new Map(); const cache = getCache(arb.ast[0].scriptHash); const candidates = []; for (let i = 0; i < arb.ast.length; i++) { @@ -61,7 +60,7 @@ export default function resolveLocalCalls(arb, candidateFilter = () => true) { if (c.arguments.some(a => badArgumentTypes.includes(a.type)) || isNodeInRanges(c, modifiedRanges)) continue; const callee = c.callee?.object || c.callee; const declNode = c.callee?.declNode || c.callee?.object?.declNode; - if (declNode?.parentNode?.body?.body?.length && declNode.parentNode?.body?.body[0].type === 'ReturnStatement') { + if (declNode?.parentNode?.body?.body?.[0]?.type === 'ReturnStatement') { // Leave this replacement to a safe function const returnArg = declNode.parentNode.body.body[0].argument; if (['Literal', 'Identifier'].includes(returnArg.type) || /Function/.test(returnArg.type)) continue; // Unwrap identifier @@ -79,8 +78,7 @@ export default function resolveLocalCalls(arb, candidateFilter = () => true) { if (declNode) { // Verify the declNode isn't a simple wrapper for an identifier if (declNode.parentNode.type === 'FunctionDeclaration' && - declNode.parentNode?.body?.body?.length && - ['Identifier', 'Literal'].includes(declNode.parentNode.body.body[0]?.argument?.type)) continue; + ['Identifier', 'Literal'].includes(declNode.parentNode?.body?.body?.[0]?.argument?.type)) continue; const contextSb = new Sandbox(); try { contextSb.run(createOrderedSrc(getDeclarationWithContext(declNode.parentNode))); From cc43636608c90db15ffbd396fefc2fb79b7373ca Mon Sep 17 00:00:00 2001 From: Ben Baryo Date: Tue, 12 Nov 2024 18:06:52 +0200 Subject: [PATCH 2/4] Update dependencies --- package-lock.json | 58 +++++++++++++++++++++++------------------------ package.json | 10 ++++---- 2 files changed, 34 insertions(+), 34 deletions(-) diff --git a/package-lock.json b/package-lock.json index 9e5d923..53c86dd 100644 --- a/package-lock.json +++ b/package-lock.json @@ -9,18 +9,18 @@ "version": "2.0.3", "license": "MIT", "dependencies": { - "flast": "^2.0.3", + "flast": "^2.1.0", "isolated-vm": "^5.0.1", "jsdom": "^25.0.1", - "obfuscation-detector": "^2.0.2" + "obfuscation-detector": "^2.0.3" }, "bin": { "restringer": "bin/deobfuscate.js" }, "devDependencies": { - "@babel/eslint-parser": "^7.25.8", - "@babel/plugin-syntax-import-assertions": "^7.25.7", - "eslint": "^9.12.0", + "@babel/eslint-parser": "^7.25.9", + "@babel/plugin-syntax-import-assertions": "^7.26.0", + "eslint": "^9.14.0", "globals": "^15.12.0", "husky": "^9.1.6" } @@ -832,9 +832,9 @@ } }, "node_modules/caniuse-lite": { - "version": "1.0.30001677", - "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001677.tgz", - "integrity": "sha512-fmfjsOlJUpMWu+mAAtZZZHz7UEwsUxIIvu1TJfO1HqFQvB/B+ii0xr9B5HpbZY/mC4XZ8SvjHJqtAY6pDPQEog==", + "version": "1.0.30001680", + "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001680.tgz", + "integrity": "sha512-rPQy70G6AGUMnbwS1z6Xg+RkHYPAi18ihs47GH0jcxIG7wArmPgY3XbS2sRdBbxJljp3thdT8BIqv9ccCypiPA==", "dev": true, "funding": [ { @@ -924,9 +924,9 @@ "peer": true }, "node_modules/cross-spawn": { - "version": "7.0.3", - "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.3.tgz", - "integrity": "sha512-iRDPJKUPVEND7dHPO8rkbOnPpyDygcDFtWjpeWNCgy8WP2rXcxXL8TskReQl6OrB2G7+UJrags1q15Fudc7G6w==", + "version": "7.0.5", + "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.5.tgz", + "integrity": "sha512-ZVJrKKYunU38/76t0RMOulHOnUcbU9GbpWKAOZ0mhjr7CX6FVrH+4FrAapSOekrgFQ3f/8gwMEuIft0aKq6Hug==", "dev": true, "license": "MIT", "dependencies": { @@ -1035,9 +1035,9 @@ } }, "node_modules/electron-to-chromium": { - "version": "1.5.52", - "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.5.52.tgz", - "integrity": "sha512-xtoijJTZ+qeucLBDNztDOuQBE1ksqjvNjvqFoST3nGC7fSpqJ+X6BdTBaY5BHG+IhWWmpc6b/KfpeuEDupEPOQ==", + "version": "1.5.56", + "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.5.56.tgz", + "integrity": "sha512-7lXb9dAvimCFdvUMTyucD4mnIndt/xhRKFAlky0CyFogdnNmdPQNoHI23msF/2V4mpTxMzgMdjK4+YRlFlRQZw==", "dev": true, "license": "ISC", "peer": true @@ -1446,13 +1446,13 @@ } }, "node_modules/flast": { - "version": "2.0.3", - "resolved": "https://registry.npmjs.org/flast/-/flast-2.0.3.tgz", - "integrity": "sha512-T44oiMvVTd9rGVxIBfJ7jYVL2gtKr39pNP8IRm+AkLaxekZH0LzHcyJKsvoyzA5Ebl2/qWWNyhrofFlPlt+UYw==", + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/flast/-/flast-2.1.0.tgz", + "integrity": "sha512-d8tcIuV8BZFe4oRVFd4KwcKNKUJ4iY4MNmKfLd2wHGsMYWC+ipP/Aq5Cz1m18lhzoXlfDldwmaNYfBeyi7oaiw==", "license": "MIT", "dependencies": { "escodegen": "npm:@javascript-obfuscator/escodegen", - "eslint-scope": "^8.1.0", + "eslint-scope": "^8.2.0", "espree": "^10.3.0", "estraverse": "^5.3.0" } @@ -2047,12 +2047,12 @@ "license": "MIT" }, "node_modules/obfuscation-detector": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/obfuscation-detector/-/obfuscation-detector-2.0.2.tgz", - "integrity": "sha512-x3+3vN1uq0Bo+jvr3ZjD/8Q/iKWc4hE6hZSdq6bvakn1nxmLQ1yXinBS1jl+3BFxYNUIsPD61ZQX4IsDJgcPuw==", + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/obfuscation-detector/-/obfuscation-detector-2.0.3.tgz", + "integrity": "sha512-CEIWkJjTHpRQLVMPq4BDT43dG9g1UgHxGBJfRwmm4shbQPL6X9rV/7NCl0fJfVTQ4B6z7DzC8zW+HYpyct9KOg==", "license": "MIT", "dependencies": { - "flast": "^2.0.3" + "flast": "^2.1.0" }, "bin": { "obfuscation-detector": "bin/obfuscation-detector.js" @@ -2482,21 +2482,21 @@ "license": "MIT" }, "node_modules/tldts": { - "version": "6.1.58", - "resolved": "https://registry.npmjs.org/tldts/-/tldts-6.1.58.tgz", - "integrity": "sha512-MQJrJhjHOYGYb8DobR6Y4AdDbd4TYkyQ+KBDVc5ODzs1cbrvPpfN1IemYi9jfipJ/vR1YWvrDli0hg1y19VRoA==", + "version": "6.1.60", + "resolved": "https://registry.npmjs.org/tldts/-/tldts-6.1.60.tgz", + "integrity": "sha512-TYVHm7G9NCnhgqOsFalbX6MG1Po5F4efF+tLfoeiOGQq48Oqgwcgz8upY2R1BHWa4aDrj28RYx0dkYJ63qCFMg==", "license": "MIT", "dependencies": { - "tldts-core": "^6.1.58" + "tldts-core": "^6.1.60" }, "bin": { "tldts": "bin/cli.js" } }, "node_modules/tldts-core": { - "version": "6.1.58", - "resolved": "https://registry.npmjs.org/tldts-core/-/tldts-core-6.1.58.tgz", - "integrity": "sha512-dR936xmhBm7AeqHIhCWwK765gZ7dFyL+IqLSFAjJbFlUXGMLCb8i2PzlzaOuWBuplBTaBYseSb565nk/ZEM0Bg==", + "version": "6.1.60", + "resolved": "https://registry.npmjs.org/tldts-core/-/tldts-core-6.1.60.tgz", + "integrity": "sha512-XHjoxak8SFQnHnmYHb3PcnW5TZ+9ErLZemZei3azuIRhQLw4IExsVbL3VZJdHcLeNaXq6NqawgpDPpjBOg4B5g==", "license": "MIT" }, "node_modules/tough-cookie": { diff --git a/package.json b/package.json index 559d296..f9b48ce 100644 --- a/package.json +++ b/package.json @@ -12,10 +12,10 @@ "test": "tests" }, "dependencies": { - "flast": "^2.0.3", + "flast": "^2.1.0", "isolated-vm": "^5.0.1", "jsdom": "^25.0.1", - "obfuscation-detector": "^2.0.2" + "obfuscation-detector": "^2.0.3" }, "scripts": { "test": "node --test --trace-warnings --no-node-snapshot", @@ -40,9 +40,9 @@ }, "homepage": "https://github.com/PerimeterX/Restringer#readme", "devDependencies": { - "@babel/eslint-parser": "^7.25.8", - "@babel/plugin-syntax-import-assertions": "^7.25.7", - "eslint": "^9.12.0", + "@babel/eslint-parser": "^7.25.9", + "@babel/plugin-syntax-import-assertions": "^7.26.0", + "eslint": "^9.14.0", "globals": "^15.12.0", "husky": "^9.1.6" } From 9c37f4b4666816c95722a7b52c957be44cf131e7 Mon Sep 17 00:00:00 2001 From: Ben Baryo Date: Tue, 12 Nov 2024 18:30:48 +0200 Subject: [PATCH 3/4] Use typeMap to target specific node types instead of iterating over the entire tree each time --- README.md | 44 ++++++++++++++----- src/modules/safe/normalizeComputed.js | 9 +++- src/modules/safe/normalizeEmptyStatements.js | 9 ++-- ...parseTemplateLiteralsIntoStringLiterals.js | 10 +++-- src/modules/safe/rearrangeSequences.js | 8 +++- src/modules/safe/rearrangeSwitches.js | 10 +++-- src/modules/safe/removeDeadNodes.js | 10 +++-- .../safe/removeRedundantBlockStatements.js | 10 +++-- .../safe/replaceBooleanExpressionsWithIf.js | 10 +++-- ...eCallExpressionsWithUnwrappedIdentifier.js | 10 +++-- .../replaceEvalCallsWithLiteralContent.js | 10 +++-- .../replaceFunctionShellsWithWrappedValue.js | 10 +++-- ...placeFunctionShellsWithWrappedValueIIFE.js | 10 +++-- ...replaceIdentifierWithFixedAssignedValue.js | 7 ++- ...rWithFixedValueNotAssignedAtDeclaration.js | 8 ++-- .../replaceNewFuncCallsWithLiteralContent.js | 10 +++-- .../safe/replaceSequencesWithExpressions.js | 10 +++-- .../safe/resolveDeterministicIfStatements.js | 11 ++--- .../safe/resolveFunctionConstructorCalls.js | 10 +++-- ...eMemberExpressionReferencesToArrayIndex.js | 10 +++-- ...veMemberExpressionsWithDirectAssignment.js | 10 +++-- src/modules/safe/resolveProxyCalls.js | 11 ++--- src/modules/safe/resolveProxyReferences.js | 12 ++--- src/modules/safe/resolveProxyVariables.js | 9 ++-- .../resolveRedundantLogicalExpressions.js | 10 +++-- .../safe/separateChainedDeclarators.js | 10 +++-- src/modules/safe/simplifyCalls.js | 10 +++-- src/modules/safe/simplifyIfStatements.js | 9 ++-- src/modules/safe/unwrapFunctionShells.js | 8 +++- src/modules/safe/unwrapIIFEs.js | 10 +++-- src/modules/safe/unwrapSimpleOperations.js | 10 ++++- .../unsafe/normalizeRedundantNotOperator.js | 8 ++-- ...gmentedFunctionWrappedArrayReplacements.js | 14 +++--- src/modules/unsafe/resolveBuiltinCalls.js | 9 +++- .../resolveDefiniteBinaryExpressions.js | 9 ++-- .../resolveDefiniteMemberExpressions.js | 10 +++-- ...olveDeterministicConditionalExpressions.js | 11 ++--- .../unsafe/resolveEvalCallsOnNonLiterals.js | 10 +++-- src/modules/unsafe/resolveFunctionToArray.js | 9 ++-- .../resolveInjectedPrototypeMethodCalls.js | 15 ++++--- src/modules/unsafe/resolveLocalCalls.js | 10 +++-- ...resolveMemberExpressionsLocalReferences.js | 10 +++-- src/modules/unsafe/resolveMinimalAlphabet.js | 8 +++- src/modules/utils/areReferencesModified.js | 2 +- src/processors/augmentedArray.js | 10 +++-- src/processors/obfuscatorIo.js | 10 +++-- 46 files changed, 304 insertions(+), 176 deletions(-) diff --git a/README.md b/README.md index 69efc1f..3375dd2 100644 --- a/README.md +++ b/README.md @@ -161,7 +161,7 @@ if (res.script !== code) { ### Boilerplate code for starting from scratch ```javascript -import {applyIteratively, treeModifier, logger} from 'flast'; +import {applyIteratively, logger} from 'flast'; // Optional loading from file // import fs from 'node:fs'; // const inputFilename = process.argv[2] || 'target.js'; @@ -173,22 +173,42 @@ const code = `(function() { })();`; logger.setLogLevelDebug(); + +/** + * Replace specific strings with other strings + * @param {Arborist} arb + * @return {Arborist} + */ +function replaceSpecificLiterals(arb) { + const replacements = { + 'Hello': 'General', + 'there!': 'Kenobi!', + }; + // Iterate over only the relevant nodes by targeting specific types using the typeMap property on the root node + const relevantNodes = [ + ...(arb.ast[0].typeMap.Literal || []), + // ...(arb.ast.typeMap.TemplateLiteral || []), // unnecessary for this example, but this is how to add more types + ]; + for (const n of relevantNodes) { + if (replacements[n.value]) { + // dynamically define a replacement node by creating an object with a type and value properties + // markNode(n) would delete the node, while markNode(n, {...}) would replace the node with the supplied node. + arb.markNode(n, {type: 'Literal', value: replacements[n.value]}); + } + } + return arb; +} + let script = code; -// Use this function to target the relevant nodes -const f = n => n.type === 'Literal' && replacements[n.value]; -// Use this function to modify the nodes according to your needs. -// markNode(n) would delete the node, while markNode(n, {...}) would replace the node with the supplied node. -const m = (n, arb) => arb.markNode(n, { - type: 'Literal', - value: replacements[n.value], -}); -const swc = treeModifier(f, m, 'StarWarsChanger'); -script = applyIteratively(script, [swc]); + +script = applyIteratively(script, [ + replaceSpecificLiterals, +]); + if (code !== script) { console.log(script); // fs.writeFileSync(inputFilename + '-deob.js', script, 'utf-8'); } else console.log(`No changes`); - ``` *** diff --git a/src/modules/safe/normalizeComputed.js b/src/modules/safe/normalizeComputed.js index 3d951de..2360665 100644 --- a/src/modules/safe/normalizeComputed.js +++ b/src/modules/safe/normalizeComputed.js @@ -9,8 +9,13 @@ import {badIdentifierCharsRegex, validIdentifierBeginning} from '../config.js'; * @return {Arborist} */ function normalizeComputed(arb, candidateFilter = () => true) { - for (let i = 0; i < arb.ast.length; i++) { - const n = arb.ast[i]; + const relevantNodes = [ + ...(arb.ast[0].typeMap.MemberExpression || []), + ...(arb.ast[0].typeMap.MethodDefinition || []), + ...(arb.ast[0].typeMap.Property || []), + ]; + for (let i = 0; i < relevantNodes.length; i++) { + const n = relevantNodes[i]; if (n.computed && // Filter for only member expressions using bracket notation // Ignore member expressions with properties which can't be non-computed, like arr[2] or window['!obj'] // or those having another variable reference as their property like window[varHoldingFuncName] diff --git a/src/modules/safe/normalizeEmptyStatements.js b/src/modules/safe/normalizeEmptyStatements.js index d41e20d..34c84e5 100644 --- a/src/modules/safe/normalizeEmptyStatements.js +++ b/src/modules/safe/normalizeEmptyStatements.js @@ -5,9 +5,12 @@ * @return {Arborist} */ function normalizeEmptyStatements(arb, candidateFilter = () => true) { - for (let i = 0; i < arb.ast.length; i++) { - const n = arb.ast[i]; - if (n.type === 'EmptyStatement' && candidateFilter(n)) { + const relevantNodes = [ + ...(arb.ast[0].typeMap.EmptyStatement || []), + ]; + for (let i = 0; i < relevantNodes.length; i++) { + const n = relevantNodes[i]; + if (candidateFilter(n)) { // A for loop is sometimes used to assign variables without providing a loop body, just an empty statement. // If we delete that empty statement the syntax breaks // e.g. for (var i = 0, b = 8;;); - this is a valid for statement. diff --git a/src/modules/safe/parseTemplateLiteralsIntoStringLiterals.js b/src/modules/safe/parseTemplateLiteralsIntoStringLiterals.js index 7c4c4c4..8ff7a1e 100644 --- a/src/modules/safe/parseTemplateLiteralsIntoStringLiterals.js +++ b/src/modules/safe/parseTemplateLiteralsIntoStringLiterals.js @@ -8,10 +8,12 @@ import {createNewNode} from '../utils/createNewNode.js'; * @return {Arborist} */ function parseTemplateLiteralsIntoStringLiterals(arb, candidateFilter = () => true) { - for (let i = 0; i < arb.ast.length; i++) { - const n = arb.ast[i]; - if (n.type === 'TemplateLiteral' && - !n.expressions.some(exp => exp.type !== 'Literal') && + const relevantNodes = [ + ...(arb.ast[0].typeMap.TemplateLiteral || []), + ]; + for (let i = 0; i < relevantNodes.length; i++) { + const n = relevantNodes[i]; + if (!n.expressions.some(exp => exp.type !== 'Literal') && candidateFilter(n)) { let newStringLiteral = ''; for (let j = 0; j < n.expressions.length; j++) { diff --git a/src/modules/safe/rearrangeSequences.js b/src/modules/safe/rearrangeSequences.js index ce4396f..231471f 100644 --- a/src/modules/safe/rearrangeSequences.js +++ b/src/modules/safe/rearrangeSequences.js @@ -7,8 +7,12 @@ * @return {Arborist} */ function rearrangeSequences(arb, candidateFilter = () => true) { - for (let i = 0; i < arb.ast.length; i++) { - const n = arb.ast[i]; + const relevantNodes = [ + ...(arb.ast[0].typeMap.ReturnStatement || []), + ...(arb.ast[0].typeMap.IfStatement || []), + ]; + for (let i = 0; i < relevantNodes.length; i++) { + const n = relevantNodes[i]; if (( n.type === 'ReturnStatement' && n.argument?.type === 'SequenceExpression' || n.type === 'IfStatement' && n.test.type === 'SequenceExpression' diff --git a/src/modules/safe/rearrangeSwitches.js b/src/modules/safe/rearrangeSwitches.js index 5ec98d2..3688bfa 100644 --- a/src/modules/safe/rearrangeSwitches.js +++ b/src/modules/safe/rearrangeSwitches.js @@ -9,10 +9,12 @@ const maxRepetition = 50; * @return {Arborist} */ function rearrangeSwitches(arb, candidateFilter = () => true) { - for (let i = 0; i < arb.ast.length; i++) { - const n = arb.ast[i]; - if (n.type === 'SwitchStatement' && - n.discriminant.type === 'Identifier' && + const relevantNodes = [ + ...(arb.ast[0].typeMap.SwitchStatement || []), + ]; + for (let i = 0; i < relevantNodes.length; i++) { + const n = relevantNodes[i]; + if (n.discriminant.type === 'Identifier' && n?.discriminant.declNode?.parentNode?.init?.type === 'Literal' && candidateFilter(n)) { let ordered = []; diff --git a/src/modules/safe/removeDeadNodes.js b/src/modules/safe/removeDeadNodes.js index 3ea57d4..aefbc84 100644 --- a/src/modules/safe/removeDeadNodes.js +++ b/src/modules/safe/removeDeadNodes.js @@ -14,10 +14,12 @@ const relevantParents = [ * @return {Arborist} */ function removeDeadNodes(arb, candidateFilter = () => true) { - for (let i = 0; i < arb.ast.length; i++) { - const n = arb.ast[i]; - if (n.type === 'Identifier' && - relevantParents.includes(n.parentNode.type) && + const relevantNodes = [ + ...(arb.ast[0].typeMap.Identifier || []), + ]; + for (let i = 0; i < relevantNodes.length; i++) { + const n = relevantNodes[i]; + if (relevantParents.includes(n.parentNode.type) && (!n?.declNode?.references?.length && !n?.references?.length) && candidateFilter(n)) { const parent = n.parentNode; diff --git a/src/modules/safe/removeRedundantBlockStatements.js b/src/modules/safe/removeRedundantBlockStatements.js index 91345d7..0e90a0b 100644 --- a/src/modules/safe/removeRedundantBlockStatements.js +++ b/src/modules/safe/removeRedundantBlockStatements.js @@ -8,10 +8,12 @@ * @return {Arborist} */ function removeRedundantBlockStatements(arb, candidateFilter = () => true) { - for (let i = 0; i < arb.ast.length; i++) { - const n = arb.ast[i]; - if (n.type === 'BlockStatement' && - ['BlockStatement', 'Program'].includes(n.parentNode.type) && + const relevantNodes = [ + ...(arb.ast[0].typeMap.BlockStatement || []), + ]; + for (let i = 0; i < relevantNodes.length; i++) { + const n = relevantNodes[i]; + if (['BlockStatement', 'Program'].includes(n.parentNode.type) && candidateFilter(n)) { const parent = n.parentNode; if (parent.body?.length > 1) { diff --git a/src/modules/safe/replaceBooleanExpressionsWithIf.js b/src/modules/safe/replaceBooleanExpressionsWithIf.js index 23b6b95..773e67f 100644 --- a/src/modules/safe/replaceBooleanExpressionsWithIf.js +++ b/src/modules/safe/replaceBooleanExpressionsWithIf.js @@ -7,10 +7,12 @@ * @return {Arborist} */ function replaceBooleanExpressionsWithIf(arb, candidateFilter = () => true) { - for (let i = 0; i < arb.ast.length; i++) { - const n = arb.ast[i]; - if (n.type === 'ExpressionStatement' && - (n.expression.operator === '&&' || n.expression.operator === '||') && candidateFilter(n)) { + const relevantNodes = [ + ...(arb.ast[0].typeMap.ExpressionStatement || []), + ]; + for (let i = 0; i < relevantNodes.length; i++) { + const n = relevantNodes[i]; + if (['&&', '||'].includes(n.expression.operator) && candidateFilter(n)) { // || requires inverted logic (only execute the consequent if all operands are false) const testExpression = n.expression.operator === '||' diff --git a/src/modules/safe/replaceCallExpressionsWithUnwrappedIdentifier.js b/src/modules/safe/replaceCallExpressionsWithUnwrappedIdentifier.js index 160b673..a242e91 100644 --- a/src/modules/safe/replaceCallExpressionsWithUnwrappedIdentifier.js +++ b/src/modules/safe/replaceCallExpressionsWithUnwrappedIdentifier.js @@ -8,10 +8,12 @@ * @return {Arborist} */ function replaceCallExpressionsWithUnwrappedIdentifier(arb, candidateFilter = () => true) { - for (let i = 0; i < arb.ast.length; i++) { - const n = arb.ast[i]; - if (n.type === 'CallExpression' && - ((n.callee?.declNode?.parentNode?.type === 'VariableDeclarator' && + const relevantNodes = [ + ...(arb.ast[0].typeMap.CallExpression || []), + ]; + for (let i = 0; i < relevantNodes.length; i++) { + const n = relevantNodes[i]; + if (((n.callee?.declNode?.parentNode?.type === 'VariableDeclarator' && /FunctionExpression/.test(n.callee.declNode.parentNode?.init?.type)) || (n.callee?.declNode?.parentNode?.type === 'FunctionDeclaration' && n.callee.declNode.parentKey === 'id')) && diff --git a/src/modules/safe/replaceEvalCallsWithLiteralContent.js b/src/modules/safe/replaceEvalCallsWithLiteralContent.js index 0e59559..d841bf6 100644 --- a/src/modules/safe/replaceEvalCallsWithLiteralContent.js +++ b/src/modules/safe/replaceEvalCallsWithLiteralContent.js @@ -13,10 +13,12 @@ import {generateHash} from '../utils/generateHash.js'; */ function replaceEvalCallsWithLiteralContent(arb, candidateFilter = () => true) { const cache = getCache(arb.ast[0].scriptHash); - for (let i = 0; i < arb.ast.length; i++) { - const n = arb.ast[i]; - if (n.type === 'CallExpression' && - n.callee?.name === 'eval' && + const relevantNodes = [ + ...(arb.ast[0].typeMap.CallExpression || []), + ]; + for (let i = 0; i < relevantNodes.length; i++) { + const n = relevantNodes[i]; + if (n.callee?.name === 'eval' && n.arguments[0]?.type === 'Literal' && candidateFilter(n)) { const cacheName = `replaceEval-${generateHash(n.src)}`; diff --git a/src/modules/safe/replaceFunctionShellsWithWrappedValue.js b/src/modules/safe/replaceFunctionShellsWithWrappedValue.js index 833d5f4..81bd1e1 100644 --- a/src/modules/safe/replaceFunctionShellsWithWrappedValue.js +++ b/src/modules/safe/replaceFunctionShellsWithWrappedValue.js @@ -5,10 +5,12 @@ * @return {Arborist} */ function replaceFunctionShellsWithWrappedValue(arb, candidateFilter = () => true) { - for (let i = 0; i < arb.ast.length; i++) { - const n = arb.ast[i]; - if (n.type === 'FunctionDeclaration' && - n.body.body?.[0]?.type === 'ReturnStatement' && + const relevantNodes = [ + ...(arb.ast[0].typeMap.FunctionDeclaration || []), + ]; + for (let i = 0; i < relevantNodes.length; i++) { + const n = relevantNodes[i]; + if (n.body.body?.[0]?.type === 'ReturnStatement' && ['Literal', 'Identifier'].includes(n.body.body[0]?.argument?.type) && candidateFilter(n)) { const replacementNode = n.body.body[0].argument; diff --git a/src/modules/safe/replaceFunctionShellsWithWrappedValueIIFE.js b/src/modules/safe/replaceFunctionShellsWithWrappedValueIIFE.js index 179f645..e1a967f 100644 --- a/src/modules/safe/replaceFunctionShellsWithWrappedValueIIFE.js +++ b/src/modules/safe/replaceFunctionShellsWithWrappedValueIIFE.js @@ -5,10 +5,12 @@ * @return {Arborist} */ function replaceFunctionShellsWithWrappedValueIIFE(arb, candidateFilter = () => true) { - for (let i = 0; i < arb.ast.length; i++) { - const n = arb.ast[i]; - if (n.type === 'FunctionExpression' && - n.parentKey === 'callee' && + const relevantNodes = [ + ...(arb.ast[0].typeMap.FunctionExpression || []), + ]; + for (let i = 0; i < relevantNodes.length; i++) { + const n = relevantNodes[i]; + if (n.parentKey === 'callee' && !n.parentNode.arguments.length && n.body.body?.[0]?.type === 'ReturnStatement' && ['Literal', 'Identifier'].includes(n.body.body[0].argument?.type) && diff --git a/src/modules/safe/replaceIdentifierWithFixedAssignedValue.js b/src/modules/safe/replaceIdentifierWithFixedAssignedValue.js index 8f1ea4b..fe1316d 100644 --- a/src/modules/safe/replaceIdentifierWithFixedAssignedValue.js +++ b/src/modules/safe/replaceIdentifierWithFixedAssignedValue.js @@ -7,8 +7,11 @@ import {areReferencesModified} from '../utils/areReferencesModified.js'; * @return {Arborist} */ function replaceIdentifierWithFixedAssignedValue(arb, candidateFilter = () => true) { - for (let i = 0; i < arb.ast.length; i++) { - const n = arb.ast[i]; + const relevantNodes = [ + ...(arb.ast[0].typeMap.Identifier || []), + ]; + for (let i = 0; i < relevantNodes.length; i++) { + const n = relevantNodes[i]; if (n?.declNode?.parentNode?.init?.type === 'Literal' && !(n.parentKey === 'property' && n.parentNode.type === 'ObjectExpression') && candidateFilter(n)) { diff --git a/src/modules/safe/replaceIdentifierWithFixedValueNotAssignedAtDeclaration.js b/src/modules/safe/replaceIdentifierWithFixedValueNotAssignedAtDeclaration.js index d8f7d79..3f3fe68 100644 --- a/src/modules/safe/replaceIdentifierWithFixedValueNotAssignedAtDeclaration.js +++ b/src/modules/safe/replaceIdentifierWithFixedValueNotAssignedAtDeclaration.js @@ -9,11 +9,13 @@ import {getMainDeclaredObjectOfMemberExpression} from '../utils/getMainDeclaredO * @return {Arborist} */ function replaceIdentifierWithFixedValueNotAssignedAtDeclaration(arb, candidateFilter = () => true) { - for (let i = 0; i < arb.ast.length; i++) { - const n = arb.ast[i]; + const relevantNodes = [ + ...(arb.ast[0].typeMap.Identifier || []), + ]; + for (let i = 0; i < relevantNodes.length; i++) { + const n = relevantNodes[i]; if (n.parentNode?.type === 'VariableDeclarator' && !n.parentNode.init && - // n?.references?.length && n.references?.filter(r => r.parentNode.type === 'AssignmentExpression' && getMainDeclaredObjectOfMemberExpression(r.parentNode.left) === r).length === 1 && diff --git a/src/modules/safe/replaceNewFuncCallsWithLiteralContent.js b/src/modules/safe/replaceNewFuncCallsWithLiteralContent.js index 3166477..f9b8c16 100644 --- a/src/modules/safe/replaceNewFuncCallsWithLiteralContent.js +++ b/src/modules/safe/replaceNewFuncCallsWithLiteralContent.js @@ -14,10 +14,12 @@ import {generateFlatAST, logger} from 'flast'; */ function replaceNewFuncCallsWithLiteralContent(arb, candidateFilter = () => true) { const cache = getCache(arb.ast[0].scriptHash); - for (let i = 0; i < arb.ast.length; i++) { - const n = arb.ast[i]; - if (n.type === 'NewExpression' && - n.parentKey === 'callee' && + const relevantNodes = [ + ...(arb.ast[0].typeMap.NewExpression || []), + ]; + for (let i = 0; i < relevantNodes.length; i++) { + const n = relevantNodes[i]; + if (n.parentKey === 'callee' && !n.parentNode?.arguments?.length && n.callee?.name === 'Function' && n.arguments?.length === 1 && diff --git a/src/modules/safe/replaceSequencesWithExpressions.js b/src/modules/safe/replaceSequencesWithExpressions.js index ea22035..41ce784 100644 --- a/src/modules/safe/replaceSequencesWithExpressions.js +++ b/src/modules/safe/replaceSequencesWithExpressions.js @@ -6,10 +6,12 @@ * @return {Arborist} */ function replaceSequencesWithExpressions(arb, candidateFilter = () => true) { - for (let i = 0; i < arb.ast.length; i++) { - const n = arb.ast[i]; - if (n.type === 'ExpressionStatement' && - n.expression.type === 'SequenceExpression' && + const relevantNodes = [ + ...(arb.ast[0].typeMap.ExpressionStatement || []), + ]; + for (let i = 0; i < relevantNodes.length; i++) { + const n = relevantNodes[i]; + if (n.expression.type === 'SequenceExpression' && candidateFilter(n)) { const parent = n.parentNode; const statements = n.expression.expressions.map(e => ({ diff --git a/src/modules/safe/resolveDeterministicIfStatements.js b/src/modules/safe/resolveDeterministicIfStatements.js index 36c0ed1..2c63bec 100644 --- a/src/modules/safe/resolveDeterministicIfStatements.js +++ b/src/modules/safe/resolveDeterministicIfStatements.js @@ -9,11 +9,12 @@ * @return {Arborist} */ function resolveDeterministicIfStatements(arb, candidateFilter = () => true) { - for (let i = 0; i < arb.ast.length; i++) { - const n = arb.ast[i]; - if (n.type === 'IfStatement' && - n.test.type === 'Literal' && - candidateFilter(n)) { + const relevantNodes = [ + ...(arb.ast[0].typeMap.IfStatement || []), + ]; + for (let i = 0; i < relevantNodes.length; i++) { + const n = relevantNodes[i]; + if (n.test.type === 'Literal' && candidateFilter(n)) { if (n.test.value) { if (n.consequent) arb.markNode(n, n.consequent); else arb.markNode(n); diff --git a/src/modules/safe/resolveFunctionConstructorCalls.js b/src/modules/safe/resolveFunctionConstructorCalls.js index d71d9f4..84084e1 100644 --- a/src/modules/safe/resolveFunctionConstructorCalls.js +++ b/src/modules/safe/resolveFunctionConstructorCalls.js @@ -8,10 +8,12 @@ import {generateFlatAST} from 'flast'; * @return {Arborist} */ function resolveFunctionConstructorCalls(arb, candidateFilter = () => true) { - for (let i = 0; i < arb.ast.length; i++) { - const n = arb.ast[i]; - if (n.type === 'CallExpression' && - n.callee?.type === 'MemberExpression' && + const relevantNodes = [ + ...(arb.ast[0].typeMap.CallExpression || []), + ]; + for (let i = 0; i < relevantNodes.length; i++) { + const n = relevantNodes[i]; + if (n.callee?.type === 'MemberExpression' && (n.callee.property?.name || n.callee.property?.value) === 'constructor' && n.arguments.length && n.arguments.slice(-1)[0].type === 'Literal' && candidateFilter(n)) {let args = ''; diff --git a/src/modules/safe/resolveMemberExpressionReferencesToArrayIndex.js b/src/modules/safe/resolveMemberExpressionReferencesToArrayIndex.js index 04a50f4..6aa189b 100644 --- a/src/modules/safe/resolveMemberExpressionReferencesToArrayIndex.js +++ b/src/modules/safe/resolveMemberExpressionReferencesToArrayIndex.js @@ -13,10 +13,12 @@ const minArrayLength = 20; * @return {Arborist} */ function resolveMemberExpressionReferencesToArrayIndex(arb, candidateFilter = () => true) { - for (let i = 0; i < arb.ast.length; i++) { - const n = arb.ast[i]; - if (n.type === 'VariableDeclarator' && - n.init?.type === 'ArrayExpression' && + const relevantNodes = [ + ...(arb.ast[0].typeMap.VariableDeclarator || []), + ]; + for (let i = 0; i < relevantNodes.length; i++) { + const n = relevantNodes[i]; + if (n.init?.type === 'ArrayExpression' && n.id?.references && n.init.elements.length > minArrayLength && candidateFilter(n)) { diff --git a/src/modules/safe/resolveMemberExpressionsWithDirectAssignment.js b/src/modules/safe/resolveMemberExpressionsWithDirectAssignment.js index ead70d7..6a61bb1 100644 --- a/src/modules/safe/resolveMemberExpressionsWithDirectAssignment.js +++ b/src/modules/safe/resolveMemberExpressionsWithDirectAssignment.js @@ -10,10 +10,12 @@ * @return {Arborist} */ function resolveMemberExpressionsWithDirectAssignment(arb, candidateFilter = () => true) { - for (let i = 0; i < arb.ast.length; i++) { - const n = arb.ast[i]; - if (n.type === 'MemberExpression' && - n.object.declNode && + const relevantNodes = [ + ...(arb.ast[0].typeMap.MemberExpression || []), + ]; + for (let i = 0; i < relevantNodes.length; i++) { + const n = relevantNodes[i]; + if (n.object.declNode && n.parentNode.type === 'AssignmentExpression' && n.parentNode.right.type === 'Literal' && candidateFilter(n)) { diff --git a/src/modules/safe/resolveProxyCalls.js b/src/modules/safe/resolveProxyCalls.js index 9581d08..f0e51eb 100644 --- a/src/modules/safe/resolveProxyCalls.js +++ b/src/modules/safe/resolveProxyCalls.js @@ -16,11 +16,12 @@ * @return {Arborist} */ function resolveProxyCalls(arb, candidateFilter = () => true) { - for (let i = 0; i < arb.ast.length; i++) { - const n = arb.ast[i]; - if (n.type === 'FunctionDeclaration' && - // n.body?.body?.length === 1 && - n.body.body[0].type === 'ReturnStatement' && + const relevantNodes = [ + ...(arb.ast[0].typeMap.FunctionDeclaration || []), + ]; + for (let i = 0; i < relevantNodes.length; i++) { + const n = relevantNodes[i]; + if (n?.body?.body?.[0].type === 'ReturnStatement' && n.body.body[0].argument?.type === 'CallExpression' && n.body.body[0].argument.arguments?.length === n.params?.length && n.body.body[0].argument.callee.type === 'Identifier' && diff --git a/src/modules/safe/resolveProxyReferences.js b/src/modules/safe/resolveProxyReferences.js index 666e191..71aa567 100644 --- a/src/modules/safe/resolveProxyReferences.js +++ b/src/modules/safe/resolveProxyReferences.js @@ -13,11 +13,13 @@ import {getMainDeclaredObjectOfMemberExpression} from '../utils/getMainDeclaredO * @return {Arborist} */ function resolveProxyReferences(arb, candidateFilter = () => true) { - for (let i = 0; i < arb.ast.length; i++) { - const n = arb.ast[i]; - if ((n.type === 'VariableDeclarator' && - ['Identifier', 'MemberExpression'].includes(n.id.type) && - ['Identifier', 'MemberExpression'].includes(n.init?.type)) && + const relevantNodes = [ + ...(arb.ast[0].typeMap.VariableDeclarator || []), + ]; + for (let i = 0; i < relevantNodes.length; i++) { + const n = relevantNodes[i]; + if (['Identifier', 'MemberExpression'].includes(n.id.type) && + ['Identifier', 'MemberExpression'].includes(n.init?.type) && !/For.*Statement/.test(n.parentNode?.parentNode?.type) && candidateFilter(n)) { const relevantIdentifier = getMainDeclaredObjectOfMemberExpression(n.id)?.declNode || n.id; diff --git a/src/modules/safe/resolveProxyVariables.js b/src/modules/safe/resolveProxyVariables.js index 9ee6962..a5e7c63 100644 --- a/src/modules/safe/resolveProxyVariables.js +++ b/src/modules/safe/resolveProxyVariables.js @@ -10,9 +10,12 @@ import {areReferencesModified} from '../utils/areReferencesModified.js'; * @return {Arborist} */ function resolveProxyVariables(arb, candidateFilter = () => true) { - for (let i = 0; i < arb.ast.length; i++) { - const n = arb.ast[i]; - if (n.type === 'VariableDeclarator' && n?.init?.type === 'Identifier' && candidateFilter(n)) { + const relevantNodes = [ + ...(arb.ast[0].typeMap.VariableDeclarator || []), + ]; + for (let i = 0; i < relevantNodes.length; i++) { + const n = relevantNodes[i]; + if (n?.init?.type === 'Identifier' && candidateFilter(n)) { const refs = n.id.references || []; // Remove proxy assignments if there are no more references if (!refs.length) arb.markNode(n); diff --git a/src/modules/safe/resolveRedundantLogicalExpressions.js b/src/modules/safe/resolveRedundantLogicalExpressions.js index b5ba019..9be5b40 100644 --- a/src/modules/safe/resolveRedundantLogicalExpressions.js +++ b/src/modules/safe/resolveRedundantLogicalExpressions.js @@ -8,10 +8,12 @@ * @return {Arborist} */ function resolveRedundantLogicalExpressions(arb, candidateFilter = () => true) { - for (let i = 0; i < arb.ast.length; i++) { - const n = arb.ast[i]; - if (n.type === 'IfStatement' && - n.test.type === 'LogicalExpression' && + const relevantNodes = [ + ...(arb.ast[0].typeMap.IfStatement || []), + ]; + for (let i = 0; i < relevantNodes.length; i++) { + const n = relevantNodes[i]; + if (n.test.type === 'LogicalExpression' && candidateFilter(n)) { if (n.test.operator === '&&') { if (n.test.left.type === 'Literal') { diff --git a/src/modules/safe/separateChainedDeclarators.js b/src/modules/safe/separateChainedDeclarators.js index 5498d2c..47dbd30 100644 --- a/src/modules/safe/separateChainedDeclarators.js +++ b/src/modules/safe/separateChainedDeclarators.js @@ -9,10 +9,12 @@ * @return {Arborist} */ function separateChainedDeclarators(arb, candidateFilter = () => true) { - for (let i = 0; i < arb.ast.length; i++) { - const n = arb.ast[i]; - if (n.type === 'VariableDeclaration' && - n.declarations.length > 1 && + const relevantNodes = [ + ...(arb.ast[0].typeMap.VariableDeclaration || []), + ]; + for (let i = 0; i < relevantNodes.length; i++) { + const n = relevantNodes[i]; + if (n.declarations.length > 1 && !n.parentNode.type.match(/For.*Statement/) && candidateFilter(n)) { const decls = []; diff --git a/src/modules/safe/simplifyCalls.js b/src/modules/safe/simplifyCalls.js index 5451872..5b7e5ab 100644 --- a/src/modules/safe/simplifyCalls.js +++ b/src/modules/safe/simplifyCalls.js @@ -5,10 +5,12 @@ * @return {Arborist} */ function simplifyCalls(arb, candidateFilter = () => true) { - for (let i = 0; i < arb.ast.length; i++) { - const n = arb.ast[i]; - if (n.type === 'CallExpression' && - n.arguments?.[0]?.type === 'ThisExpression' && + const relevantNodes = [ + ...(arb.ast[0].typeMap.CallExpression || []), + ]; + for (let i = 0; i < relevantNodes.length; i++) { + const n = relevantNodes[i]; + if (n.arguments?.[0]?.type === 'ThisExpression' && n.callee.type === 'MemberExpression' && ['apply', 'call'].includes(n.callee.property?.name || n.callee.property?.value) && (n.callee.object?.name || n.callee?.value) !== 'Function' && diff --git a/src/modules/safe/simplifyIfStatements.js b/src/modules/safe/simplifyIfStatements.js index 80155bb..fbe32f3 100644 --- a/src/modules/safe/simplifyIfStatements.js +++ b/src/modules/safe/simplifyIfStatements.js @@ -5,9 +5,12 @@ * @return {Arborist} */ function simplifyIfStatements(arb, candidateFilter = () => true) { - for (let i = 0; i < arb.ast.length; i++) { - const n = arb.ast[i]; - if (n.type === 'IfStatement' && candidateFilter(n)) { + const relevantNodes = [ + ...(arb.ast[0].typeMap.IfStatement || []), + ]; + for (let i = 0; i < relevantNodes.length; i++) { + const n = relevantNodes[i]; + if (candidateFilter(n)) { // Empty consequent if (n.consequent.type === 'EmptyStatement' || (n.consequent.type === 'BlockStatement' && !n.consequent.body.length)) { // Populated alternate diff --git a/src/modules/safe/unwrapFunctionShells.js b/src/modules/safe/unwrapFunctionShells.js index 3a078e3..56f4fec 100644 --- a/src/modules/safe/unwrapFunctionShells.js +++ b/src/modules/safe/unwrapFunctionShells.js @@ -12,8 +12,12 @@ * @return {Arborist} */ function unwrapFunctionShells(arb, candidateFilter = () => true) { - for (let i = 0; i < arb.ast.length; i++) { - const n = arb.ast[i]; + const relevantNodes = [ + ...(arb.ast[0].typeMap.FunctionExpression || []), + ...(arb.ast[0].typeMap.FunctionDeclaration || []), + ]; + for (let i = 0; i < relevantNodes.length; i++) { + const n = relevantNodes[i]; if (['FunctionDeclaration', 'FunctionExpression'].includes(n.type) && n.body?.body?.[0]?.type === 'ReturnStatement' && (n.body.body[0].argument?.callee?.property?.name || n.body.body[0].argument?.callee?.property?.value) === 'apply' && diff --git a/src/modules/safe/unwrapIIFEs.js b/src/modules/safe/unwrapIIFEs.js index aad19c5..dbd0983 100644 --- a/src/modules/safe/unwrapIIFEs.js +++ b/src/modules/safe/unwrapIIFEs.js @@ -5,10 +5,12 @@ * @return {Arborist} */ function unwrapIIFEs(arb, candidateFilter = () => true) { - candidatesLoop: for (let i = 0; i < arb.ast.length; i++) { - const n = arb.ast[i]; - if (n.type === 'CallExpression' && - !n.arguments.length && + const relevantNodes = [ + ...(arb.ast[0].typeMap.CallExpression || []), + ]; + candidatesLoop: for (let i = 0; i < relevantNodes.length; i++) { + const n = relevantNodes[i]; + if (!n.arguments.length && ['ArrowFunctionExpression', 'FunctionExpression'].includes(n.callee.type) && !n.callee.id && // IIFEs with a single return statement diff --git a/src/modules/safe/unwrapSimpleOperations.js b/src/modules/safe/unwrapSimpleOperations.js index 29c505a..fc6eb01 100644 --- a/src/modules/safe/unwrapSimpleOperations.js +++ b/src/modules/safe/unwrapSimpleOperations.js @@ -66,8 +66,14 @@ function handleUnaryAndUpdate(c, arb) { * @return {Arborist} */ function unwrapSimpleOperations(arb, candidateFilter = () => true) { - for (let i = 0; i < arb.ast.length; i++) { - const n = arb.ast[i]; + const relevantNodes = [ + ...(arb.ast[0].typeMap.BinaryExpression || []), + ...(arb.ast[0].typeMap.LogicalExpression || []), + ...(arb.ast[0].typeMap.UnaryExpression || []), + ...(arb.ast[0].typeMap.UpdateExpression || []), + ]; + for (let i = 0; i < relevantNodes.length; i++) { + const n = relevantNodes[i]; if ((matchBinaryOrLogical(n) || matchUnaryOrUpdate(n)) && candidateFilter(n)) { switch (n.type) { case 'BinaryExpression': diff --git a/src/modules/unsafe/normalizeRedundantNotOperator.js b/src/modules/unsafe/normalizeRedundantNotOperator.js index 24eb60e..63228c4 100644 --- a/src/modules/unsafe/normalizeRedundantNotOperator.js +++ b/src/modules/unsafe/normalizeRedundantNotOperator.js @@ -13,10 +13,12 @@ const relevantNodeTypes = ['Literal', 'ArrayExpression', 'ObjectExpression', 'Un */ function normalizeRedundantNotOperator(arb, candidateFilter = () => true) { let sharedSB; - for (let i = 0; i < arb.ast.length; i++) { - const n = arb.ast[i]; + const relevantNodes = [ + ...(arb.ast[0].typeMap.UnaryExpression || []), + ]; + for (let i = 0; i < relevantNodes.length; i++) { + const n = relevantNodes[i]; if (n.operator === '!' && - n.type === 'UnaryExpression' && relevantNodeTypes.includes(n.argument.type) && candidateFilter(n)) { if (canUnaryExpressionBeResolved(n.argument)) { diff --git a/src/modules/unsafe/resolveAugmentedFunctionWrappedArrayReplacements.js b/src/modules/unsafe/resolveAugmentedFunctionWrappedArrayReplacements.js index 25ab935..bea7e0d 100644 --- a/src/modules/unsafe/resolveAugmentedFunctionWrappedArrayReplacements.js +++ b/src/modules/unsafe/resolveAugmentedFunctionWrappedArrayReplacements.js @@ -12,10 +12,12 @@ import {getDescendants} from '../utils/getDescendants.js'; * @return {Arborist} */ export default function resolveAugmentedFunctionWrappedArrayReplacements(arb, candidateFilter = () => true) { - for (let i = 0; i < arb.ast.length; i++) { - const n = arb.ast[i]; - if (n.type === 'FunctionDeclaration' && n.id && - candidateFilter(n)) { + const relevantNodes = [ + ...(arb.ast[0].typeMap.FunctionDeclaration || []), + ]; + for (let i = 0; i < relevantNodes.length; i++) { + const n = relevantNodes[i]; + if (n.id && candidateFilter(n)) { const descendants = getDescendants(n); if (descendants.find(d => d.type === 'AssignmentExpression' && @@ -38,7 +40,7 @@ export default function resolveAugmentedFunctionWrappedArrayReplacements(arb, ca arrRef = ac.declNode.parentNode.init.callee?.declNode?.parentNode; } if (arrRef) { - const iife = arb.ast.find(c => + const iife = (arb.ast[0].typeMap.ExpressionStatement || []).find(c => c.type === 'ExpressionStatement' && c.expression.type === 'CallExpression' && c.expression.callee.type === 'FunctionExpression' && @@ -48,7 +50,7 @@ export default function resolveAugmentedFunctionWrappedArrayReplacements(arb, ca if (iife) { const context = [arrRef.src, arrDecryptor.src, iife.src].join('\n'); const skipScopes = [arrRef.scope, arrDecryptor.scope, iife.expression.callee.scope]; - const replacementCandidates = arb.ast.filter(c => + const replacementCandidates = (arb.ast[0].typeMap.CallExpression || []).filter(c => c?.callee?.name === arrDecryptor.id.name && !skipScopes.includes(c.scope)); const sb = new Sandbox(); diff --git a/src/modules/unsafe/resolveBuiltinCalls.js b/src/modules/unsafe/resolveBuiltinCalls.js index 550f734..2c91d59 100644 --- a/src/modules/unsafe/resolveBuiltinCalls.js +++ b/src/modules/unsafe/resolveBuiltinCalls.js @@ -43,8 +43,13 @@ function isUnwantedNode(node) { */ function resolveBuiltinCalls(arb, candidateFilter = () => true) { let sharedSb; - for (let i = 0; i < arb.ast.length; i++) { - const n = arb.ast[i]; + const relevantNodes = [ + ...(arb.ast[0].typeMap.MemberExpression || []), + ...(arb.ast[0].typeMap.CallExpression || []), + ...(arb.ast[0].typeMap.Identifier || []), + ]; + for (let i = 0; i < relevantNodes.length; i++) { + const n = relevantNodes[i]; if (!isUnwantedNode(n) && candidateFilter(n) && (isSafeCall(n) || (isCallWithOnlyLiteralArguments(n) && (isBuiltinIdentifier(n.callee) || isBuiltinMemberExpression(n.callee))) )) { diff --git a/src/modules/unsafe/resolveDefiniteBinaryExpressions.js b/src/modules/unsafe/resolveDefiniteBinaryExpressions.js index c992a07..32cd95f 100644 --- a/src/modules/unsafe/resolveDefiniteBinaryExpressions.js +++ b/src/modules/unsafe/resolveDefiniteBinaryExpressions.js @@ -14,9 +14,12 @@ import {doesBinaryExpressionContainOnlyLiterals} from '../utils/doesBinaryExpres */ function resolveDefiniteBinaryExpressions(arb, candidateFilter = () => true) { let sharedSb; - for (let i = 0; i < arb.ast.length; i++) { - const n = arb.ast[i]; - if (n.type === 'BinaryExpression' && doesBinaryExpressionContainOnlyLiterals(n) && candidateFilter(n)) { + const relevantNodes = [ + ...(arb.ast[0].typeMap.BinaryExpression || []), + ]; + for (let i = 0; i < relevantNodes.length; i++) { + const n = relevantNodes[i]; + if (doesBinaryExpressionContainOnlyLiterals(n) && candidateFilter(n)) { sharedSb = sharedSb || new Sandbox(); const replacementNode = evalInVm(n.src, sharedSb); if (replacementNode !== badValue) { diff --git a/src/modules/unsafe/resolveDefiniteMemberExpressions.js b/src/modules/unsafe/resolveDefiniteMemberExpressions.js index 32e22a5..2a487ea 100644 --- a/src/modules/unsafe/resolveDefiniteMemberExpressions.js +++ b/src/modules/unsafe/resolveDefiniteMemberExpressions.js @@ -13,10 +13,12 @@ import {evalInVm} from '../utils/evalInVm.js'; */ function resolveDefiniteMemberExpressions(arb, candidateFilter = () => true) { let sharedSb; - for (let i = 0; i < arb.ast.length; i++) { - const n = arb.ast[i]; - if (n.type === 'MemberExpression' && - !['UpdateExpression'].includes(n.parentNode.type) && // Prevent replacing (++[[]][0]) with (++1) + const relevantNodes = [ + ...(arb.ast[0].typeMap.MemberExpression || []), + ]; + for (let i = 0; i < relevantNodes.length; i++) { + const n = relevantNodes[i]; + if (!['UpdateExpression'].includes(n.parentNode.type) && // Prevent replacing (++[[]][0]) with (++1) !(n.parentKey === 'callee') && // Prevent replacing obj.method() with undefined() (n.property.type === 'Literal' || (n.property.name && !n.computed)) && diff --git a/src/modules/unsafe/resolveDeterministicConditionalExpressions.js b/src/modules/unsafe/resolveDeterministicConditionalExpressions.js index 7026b5a..8e4bc5d 100644 --- a/src/modules/unsafe/resolveDeterministicConditionalExpressions.js +++ b/src/modules/unsafe/resolveDeterministicConditionalExpressions.js @@ -11,11 +11,12 @@ import {evalInVm} from '../utils/evalInVm.js'; */ function resolveDeterministicConditionalExpressions(arb, candidateFilter = () => true) { let sharedSb; - for (let i = 0; i < arb.ast.length; i++) { - const n = arb.ast[i]; - if (n.type === 'ConditionalExpression' && - n.test.type === 'Literal' && - candidateFilter(n)) { + const relevantNodes = [ + ...(arb.ast[0].typeMap.ConditionalExpression || []), + ]; + for (let i = 0; i < relevantNodes.length; i++) { + const n = relevantNodes[i]; + if (n.test.type === 'Literal' && candidateFilter(n)) { sharedSb = sharedSb || new Sandbox(); const replacementNode = evalInVm(`Boolean(${n.test.src});`, sharedSb); if (replacementNode.type === 'Literal') { diff --git a/src/modules/unsafe/resolveEvalCallsOnNonLiterals.js b/src/modules/unsafe/resolveEvalCallsOnNonLiterals.js index a1a8eb2..4da1611 100644 --- a/src/modules/unsafe/resolveEvalCallsOnNonLiterals.js +++ b/src/modules/unsafe/resolveEvalCallsOnNonLiterals.js @@ -15,10 +15,12 @@ import {getDeclarationWithContext} from '../utils/getDeclarationWithContext.js'; */ function resolveEvalCallsOnNonLiterals(arb, candidateFilter = () => true) { let sharedSb; - for (let i = 0; i < arb.ast.length; i++) { - const n = arb.ast[i]; - if (n.type === 'CallExpression' && - n.callee.name === 'eval' && + const relevantNodes = [ + ...(arb.ast[0].typeMap.CallExpression || []), + ]; + for (let i = 0; i < relevantNodes.length; i++) { + const n = relevantNodes[i]; + if (n.callee.name === 'eval' && n.arguments.length === 1 && n.arguments[0].type !== 'Literal' && candidateFilter(n)) { diff --git a/src/modules/unsafe/resolveFunctionToArray.js b/src/modules/unsafe/resolveFunctionToArray.js index 6c41490..92dda50 100644 --- a/src/modules/unsafe/resolveFunctionToArray.js +++ b/src/modules/unsafe/resolveFunctionToArray.js @@ -21,9 +21,12 @@ import {badValue} from '../config.js'; */ export default function resolveFunctionToArray(arb, candidateFilter = () => true) { let sharedSb; - for (let i = 0; i < arb.ast.length; i++) { - const n = arb.ast[i]; - if (n.type === 'VariableDeclarator' && n.init?.type === 'CallExpression' && n.id?.references && + const relevantNodes = [ + ...(arb.ast[0].typeMap.VariableDeclarator || []), + ]; + for (let i = 0; i < relevantNodes.length; i++) { + const n = relevantNodes[i]; + if (n.init?.type === 'CallExpression' && n.id?.references && !n.id.references.some(r => r.parentNode.type !== 'MemberExpression') && candidateFilter(n)) { const targetNode = n.init.callee?.declNode?.parentNode || n.init; diff --git a/src/modules/unsafe/resolveInjectedPrototypeMethodCalls.js b/src/modules/unsafe/resolveInjectedPrototypeMethodCalls.js index b962867..0acf308 100644 --- a/src/modules/unsafe/resolveInjectedPrototypeMethodCalls.js +++ b/src/modules/unsafe/resolveInjectedPrototypeMethodCalls.js @@ -15,10 +15,12 @@ import {getDeclarationWithContext} from '../utils/getDeclarationWithContext.js'; * @return {Arborist} */ export default function resolveInjectedPrototypeMethodCalls(arb, candidateFilter = () => true) { - for (let i = 0; i < arb.ast.length; i++) { - const n = arb.ast[i]; - if (n.type === 'AssignmentExpression' && - n.left.type === 'MemberExpression' && + const relevantNodes = [ + ...(arb.ast[0].typeMap.AssignmentExpression || []), + ]; + for (let i = 0; i < relevantNodes.length; i++) { + const n = relevantNodes[i]; + if (n.left.type === 'MemberExpression' && (n.left.object.property?.name || n.left.object.property?.value) === 'prototype' && n.operator === '=' && (/FunctionExpression|Identifier/.test(n.right?.type)) && @@ -28,8 +30,9 @@ export default function resolveInjectedPrototypeMethodCalls(arb, candidateFilter const context = getDeclarationWithContext(n); const contextSb = new Sandbox(); contextSb.run(createOrderedSrc(context)); - for (let j = 0; j < arb.ast.length; j++) { - const ref = arb.ast[j]; + const rlvntNodes = arb.ast[0].typeMap.CallExpression || []; + for (let j = 0; j < rlvntNodes.length; j++) { + const ref = rlvntNodes[j]; if (ref.type === 'CallExpression' && ref.callee.type === 'MemberExpression' && (ref.callee.property?.name || ref.callee.property?.value) === methodName) { diff --git a/src/modules/unsafe/resolveLocalCalls.js b/src/modules/unsafe/resolveLocalCalls.js index 2d5de79..6ac7694 100644 --- a/src/modules/unsafe/resolveLocalCalls.js +++ b/src/modules/unsafe/resolveLocalCalls.js @@ -40,10 +40,12 @@ export default function resolveLocalCalls(arb, candidateFilter = () => true) { appearances = new Map(); const cache = getCache(arb.ast[0].scriptHash); const candidates = []; - for (let i = 0; i < arb.ast.length; i++) { - const n = arb.ast[i]; - if (n.type === 'CallExpression' && - (n.callee?.declNode || + const relevantNodes = [ + ...(arb.ast[0].typeMap.CallExpression || []), + ]; + for (let i = 0; i < relevantNodes.length; i++) { + const n = relevantNodes[i]; + if ((n.callee?.declNode || (n.callee?.object?.declNode && !skipProperties.includes(n.callee.property?.value || n.callee.property?.name)) || n.callee?.object?.type === 'Literal') && diff --git a/src/modules/unsafe/resolveMemberExpressionsLocalReferences.js b/src/modules/unsafe/resolveMemberExpressionsLocalReferences.js index c6f5487..ba9dd33 100644 --- a/src/modules/unsafe/resolveMemberExpressionsLocalReferences.js +++ b/src/modules/unsafe/resolveMemberExpressionsLocalReferences.js @@ -20,10 +20,12 @@ import {getMainDeclaredObjectOfMemberExpression} from '../utils/getMainDeclaredO * @return {Arborist} */ export default function resolveMemberExpressionsLocalReferences(arb, candidateFilter = () => true) { - for (let i = 0; i < arb.ast.length; i++) { - const n = arb.ast[i]; - if (n.type === 'MemberExpression' && - ['Identifier', 'Literal'].includes(n.property.type) && + const relevantNodes = [ + ...(arb.ast[0].typeMap.MemberExpression || []), + ]; + for (let i = 0; i < relevantNodes.length; i++) { + const n = relevantNodes[i]; + if (['Identifier', 'Literal'].includes(n.property.type) && !skipProperties.includes(n.property?.name || n.property?.value) && (!(n.parentKey === 'left' && n.parentNode.type === 'AssignmentExpression')) && candidateFilter(n)) { diff --git a/src/modules/unsafe/resolveMinimalAlphabet.js b/src/modules/unsafe/resolveMinimalAlphabet.js index 9de4608..f63902c 100644 --- a/src/modules/unsafe/resolveMinimalAlphabet.js +++ b/src/modules/unsafe/resolveMinimalAlphabet.js @@ -11,8 +11,12 @@ import {getDescendants} from '../utils/getDescendants.js'; * @return {Arborist} */ export default function resolveMinimalAlphabet(arb, candidateFilter = () => true) { - for (let i = 0; i < arb.ast.length; i++) { - const n = arb.ast[i]; + const relevantNodes = [ + ...(arb.ast[0].typeMap.UnaryExpression || []), + ...(arb.ast[0].typeMap.BinaryExpression || []), + ]; + for (let i = 0; i < relevantNodes.length; i++) { + const n = relevantNodes[i]; if ((n.type === 'UnaryExpression' && ((n.argument.type === 'Literal' && /^\D/.test(n.argument.raw[0])) || n.argument.type === 'ArrayExpression')) || diff --git a/src/modules/utils/areReferencesModified.js b/src/modules/utils/areReferencesModified.js index a88da09..cd84d07 100644 --- a/src/modules/utils/areReferencesModified.js +++ b/src/modules/utils/areReferencesModified.js @@ -23,7 +23,7 @@ function areReferencesModified(ast, refs) { r.parentNode.parentKey === 'left')) || // Verify there are no member expressions among the references which are being assigned to (r.type === 'MemberExpression' && - ast.some(n => n.type === 'AssignmentExpression' && + (ast[0].typeMap.AssignmentExpression || []).some(n => n.left.type === 'MemberExpression' && n.left.object?.name === r.object?.name && (n.left.property?.name || n.left.property?.value === r.property?.name || r.property?.value) && diff --git a/src/processors/augmentedArray.js b/src/processors/augmentedArray.js index 346a325..ab6271a 100644 --- a/src/processors/augmentedArray.js +++ b/src/processors/augmentedArray.js @@ -27,10 +27,12 @@ const {createOrderedSrc, evalInVm, getDeclarationWithContext} = utils.default; * @return {Arborist} */ function replaceArrayWithStaticAugmentedVersion(arb) { - for (let i = 0; i < arb.ast.length; i++) { - const n = arb.ast[i]; - if (n.type === 'CallExpression' && - n.callee.type === 'FunctionExpression' && + const relevantNodes = [ + ...(arb.ast[0].typeMap.CallExpression || []), + ]; + for (let i = 0; i < relevantNodes.length; i++) { + const n = relevantNodes[i]; + if (n.callee.type === 'FunctionExpression' && n.arguments.length > 1 && n.arguments[0].type === 'Identifier' && n.arguments[1].type === 'Literal' && !Number.isNaN(parseInt(n.arguments[1].value))) { let targetNode = n; diff --git a/src/processors/obfuscatorIo.js b/src/processors/obfuscatorIo.js index 955e124..e6a1b0c 100644 --- a/src/processors/obfuscatorIo.js +++ b/src/processors/obfuscatorIo.js @@ -14,10 +14,12 @@ const freezeReplacementString = 'function () {return "bypassed!"}'; * @return {Arborist} */ function freezeUnbeautifiedValues(arb) { - for (let i = 0; i < arb.ast.length; i++) { - const n = arb.ast[i]; - if (n.type === 'Literal' && - ['newState', 'removeCookie'].includes(n.value)) { + const relevantNodes = [ + ...(arb.ast[0].typeMap.Literal || []), + ]; + for (let i = 0; i < relevantNodes.length; i++) { + const n = relevantNodes[i]; + if (['newState', 'removeCookie'].includes(n.value)) { let targetNode; switch (n.value) { case 'newState': From 7d739be14592fc59659d9822704392c7cde0f296 Mon Sep 17 00:00:00 2001 From: Ben Baryo Date: Tue, 12 Nov 2024 18:31:54 +0200 Subject: [PATCH 4/4] 2.0.4 --- package-lock.json | 4 ++-- package.json | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/package-lock.json b/package-lock.json index 53c86dd..f3e4266 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { "name": "restringer", - "version": "2.0.3", + "version": "2.0.4", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "restringer", - "version": "2.0.3", + "version": "2.0.4", "license": "MIT", "dependencies": { "flast": "^2.1.0", diff --git a/package.json b/package.json index f9b48ce..19040a5 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "restringer", - "version": "2.0.3", + "version": "2.0.4", "description": "Deobfuscate Javascript with emphasis on reconstructing strings", "main": "index.js", "type": "module",