Skip to content

Commit

Permalink
Externalize Deob Methods on REstringer Instance (#93)
Browse files Browse the repository at this point in the history
* Externalize safe and unsafe methods on the REstringer instance

* Allow automatic detection of obfuscation type to be disabled on instance.

* Add an example of method overriding and turning off obfuscation detection

* 1.10.1
  • Loading branch information
BenBaryoPX authored Sep 11, 2023
1 parent 4c47043 commit 8d5876e
Show file tree
Hide file tree
Showing 4 changed files with 77 additions and 104 deletions.
23 changes: 23 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
4 changes: 2 additions & 2 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
@@ -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": {
Expand Down
152 changes: 51 additions & 101 deletions src/restringer.js
Original file line number Diff line number Diff line change
Expand Up @@ -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,
}
Expand All @@ -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,
];
}

/**
Expand All @@ -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,
Expand All @@ -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;
Expand All @@ -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;
}

Expand Down

0 comments on commit 8d5876e

Please sign in to comment.