diff --git a/README.md b/README.md index d8caf42..ef158f3 100644 --- a/README.md +++ b/README.md @@ -116,6 +116,29 @@ script = runLoop(script, [resolveLocalCallsInGlobalScope]); console.log(script); // Deobfuscated script ``` +You can also customize any deobfuscation method while still using REstringer without running the loop yourself: +```javascript +const fs = require('node:fs'); +const {REstringer} = require('restringer'); + +const inputFilename = process.argv[2]; +const code = fs.readFileSync(inputFilename, 'utf-8'); +const res = new REstringer(code); + +// res.logger.setLogLevel(res.logger.logLevels.DEBUG); +res.detectObfuscationType = false; // Skip obfuscation type detection, including any pre and post processors + +const targetFunc = res.unsafeMethods.find(m => m.name === 'resolveLocalCalls'); +let changes = 0; // Resolve only the first 5 calls +res.safeMethods[res.unsafeMethods.indexOf(targetFunc)] = function customResolveLocalCalls(n) {return targetFunc(n, () => changes++ < 5)} + +res.deobfuscate(); + +if (res.script !== code) { + console.log('[+] Deob successful'); + fs.writeFileSync(`${inputFilename}-deob.js`, res.script, 'utf-8'); +} else console.log('[-] Nothing deobfuscated :/'); +``` *** ## Read More diff --git a/package-lock.json b/package-lock.json index 0554a03..0278a06 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { "name": "restringer", - "version": "1.10.0", + "version": "1.10.1", "lockfileVersion": 2, "requires": true, "packages": { "": { "name": "restringer", - "version": "1.10.0", + "version": "1.10.1", "license": "MIT", "dependencies": { "flast": "^1.5.2", diff --git a/package.json b/package.json index a460b4d..4ede4d4 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "restringer", - "version": "1.10.0", + "version": "1.10.1", "description": "Deobfuscate Javascript with emphasis on reconstructing strings", "main": "index.js", "bin": { diff --git a/src/restringer.js b/src/restringer.js index e7909d8..ec2b97a 100755 --- a/src/restringer.js +++ b/src/restringer.js @@ -8,49 +8,8 @@ const { normalizeScript, logger, }, - safe: { - resolveProxyCalls, - normalizeEmptyStatements, - removeRedundantBlockStatements, - removeDeadNodes, - resolveRedundantLogicalExpressions, - resolveMemberExpressionReferencesToArrayIndex, - resolveMemberExpressionsWithDirectAssignment, - parseTemplateLiteralsIntoStringLiterals, - resolveDeterministicIfStatements, - unwrapFunctionShells, - replaceFunctionShellsWithWrappedValue, - replaceFunctionShellsWithWrappedValueIIFE, - replaceCallExpressionsWithUnwrappedIdentifier, - replaceEvalCallsWithLiteralContent, - replaceIdentifierWithFixedAssignedValue, - replaceIdentifierWithFixedValueNotAssignedAtDeclaration, - replaceNewFuncCallsWithLiteralContent, - replaceBooleanExpressionsWithIf, - replaceSequencesWithExpressions, - resolveFunctionConstructorCalls, - resolveProxyVariables, - resolveProxyReferences, - rearrangeSequences, - simplifyCalls, - simplifyIfStatements, - rearrangeSwitches, - unwrapIIFEs, - unwrapSimpleOperations, - separateChainedDeclarators, - }, - unsafe: { - resolveMinimalAlphabet, - resolveDefiniteBinaryExpressions, - resolveAugmentedFunctionWrappedArrayReplacements, - resolveMemberExpressionsLocalReferences, - resolveDefiniteMemberExpressions, - resolveLocalCalls, - resolveBuiltinCalls, - resolveDeterministicConditionalExpressions, - resolveInjectedPrototypeMethodCalls, - resolveEvalCallsOnNonLiterals, - }, + safe, + unsafe, config: { setGlobalMaxIterations, } @@ -75,6 +34,51 @@ class REstringer { this._postprocessors = []; this.logger = logger; this.logger.setLogLevel(logger.logLevels.LOG); // Default log level + this.detectObfuscationType = true; + // Deobfuscation methods that don't use eval + this.safeMethods = [ + safe.rearrangeSequences, + safe.separateChainedDeclarators, + safe.rearrangeSwitches, + safe.normalizeEmptyStatements, + safe.removeRedundantBlockStatements, + safe.resolveRedundantLogicalExpressions, + safe.unwrapSimpleOperations, + safe.resolveProxyCalls, + safe.resolveProxyVariables, + safe.resolveProxyReferences, + safe.resolveMemberExpressionReferencesToArrayIndex, + safe.resolveMemberExpressionsWithDirectAssignment, + safe.parseTemplateLiteralsIntoStringLiterals, + safe.resolveDeterministicIfStatements, + safe.replaceCallExpressionsWithUnwrappedIdentifier, + safe.replaceEvalCallsWithLiteralContent, + safe.replaceIdentifierWithFixedAssignedValue, + safe.replaceIdentifierWithFixedValueNotAssignedAtDeclaration, + safe.replaceNewFuncCallsWithLiteralContent, + safe.replaceBooleanExpressionsWithIf, + safe.replaceSequencesWithExpressions, + safe.resolveFunctionConstructorCalls, + safe.replaceFunctionShellsWithWrappedValue, + safe.replaceFunctionShellsWithWrappedValueIIFE, + safe.simplifyCalls, + safe.unwrapFunctionShells, + safe.unwrapIIFEs, + safe.simplifyIfStatements, + ]; + // Deobfuscation methods that use eval + this.unsafeMethods = [ + unsafe.resolveMinimalAlphabet, + unsafe.resolveDefiniteBinaryExpressions, + unsafe.resolveAugmentedFunctionWrappedArrayReplacements, + unsafe.resolveMemberExpressionsLocalReferences, + unsafe.resolveDefiniteMemberExpressions, + unsafe.resolveBuiltinCalls, + unsafe.resolveDeterministicConditionalExpressions, + unsafe.resolveInjectedPrototypeMethodCalls, + unsafe.resolveLocalCalls, + unsafe.resolveEvalCallsOnNonLiterals, + ]; } /** @@ -92,60 +96,6 @@ class REstringer { return this.obfuscationName; } - /** - * @return {Function[]} Deobfuscation methods that don't use eval - */ - _safeDeobfuscationMethods() { - return [ - rearrangeSequences, - separateChainedDeclarators, - rearrangeSwitches, - normalizeEmptyStatements, - removeRedundantBlockStatements, - resolveRedundantLogicalExpressions, - unwrapSimpleOperations, - resolveProxyCalls, - resolveProxyVariables, - resolveProxyReferences, - resolveMemberExpressionReferencesToArrayIndex, - resolveMemberExpressionsWithDirectAssignment, - parseTemplateLiteralsIntoStringLiterals, - resolveDeterministicIfStatements, - replaceCallExpressionsWithUnwrappedIdentifier, - replaceEvalCallsWithLiteralContent, - replaceIdentifierWithFixedAssignedValue, - replaceIdentifierWithFixedValueNotAssignedAtDeclaration, - replaceNewFuncCallsWithLiteralContent, - replaceBooleanExpressionsWithIf, - replaceSequencesWithExpressions, - resolveFunctionConstructorCalls, - replaceFunctionShellsWithWrappedValue, - replaceFunctionShellsWithWrappedValueIIFE, - simplifyCalls, - unwrapFunctionShells, - unwrapIIFEs, - simplifyIfStatements, - ]; - } - - /** - * @return {Function[]} Deobfuscation methods that use eval - */ - _unsafeDeobfuscationMethods() { - return [ - resolveMinimalAlphabet, - resolveDefiniteBinaryExpressions, - resolveAugmentedFunctionWrappedArrayReplacements, - resolveMemberExpressionsLocalReferences, - resolveDefiniteMemberExpressions, - resolveBuiltinCalls, - resolveDeterministicConditionalExpressions, - resolveInjectedPrototypeMethodCalls, - resolveLocalCalls, - resolveEvalCallsOnNonLiterals, - ]; - } - /** * Make all changes which don't involve eval first in order to avoid running eval on probelmatic values * which can only be detected once part of the script is deobfuscated. Once all the safe changes are made, @@ -156,8 +106,8 @@ class REstringer { let modified, script; do { this.modified = false; - script = runLoop(this.script, this._safeDeobfuscationMethods()); - script = runLoop(script, this._unsafeDeobfuscationMethods(), 1); + script = runLoop(this.script, this.safeMethods); + script = runLoop(script, this.unsafeMethods, 1); if (this.script !== script) { this.modified = true; this.script = script; @@ -176,12 +126,12 @@ class REstringer { * @return {boolean} true if the script was modified during deobfuscation; false otherwise. */ deobfuscate(clean = false) { - this.determineObfuscationType(); + if (this.detectObfuscationType) this.determineObfuscationType(); this._runProcessors(this._preprocessors); this._loopSafeAndUnsafeDeobfuscationMethods(); this._runProcessors(this._postprocessors); if (this.modified && this.normalize) this.script = normalizeScript(this.script); - if (clean) this.script = runLoop(this.script, [removeDeadNodes]); + if (clean) this.script = runLoop(this.script, [unsafe.removeDeadNodes]); return this.modified; }