restringer
Advanced tools
Comparing version 1.2.3 to 1.2.4
{ | ||
"name": "restringer", | ||
"version": "1.2.3", | ||
"version": "1.2.4", | ||
"description": "Deobfuscate Javascript with emphasis on reconstructing strings", | ||
@@ -5,0 +5,0 @@ "main": "index.js", |
@@ -14,5 +14,8 @@ # Restringer | ||
* [Installation](#installation) | ||
* [npm](#npm) | ||
* [Clone The Repo](#clone-the-repo) | ||
* [Usage](#usage) | ||
* [Command-Line Usage](#command-line-usage) | ||
* [Use as a Module](#use-as-a-module) | ||
* [Create Custom Deobfuscators](#create-custom-deobfuscators) | ||
* [Read More](#read-more) | ||
@@ -22,2 +25,8 @@ *** | ||
## Installation | ||
### npm | ||
```bash | ||
npm install restringer | ||
``` | ||
### Clone The Repo | ||
Requires Node 16 or newer. | ||
@@ -41,6 +50,6 @@ ```bash | ||
``` | ||
Usage: restringer input_filename [-h] [-c] [-q|-v] [-o [output_filename]] | ||
Usage: restringer input_filename [-h] [-c] [-q | -v] [-m M] [-o [output_filename]] | ||
positional arguments: | ||
input_filename The obfuscated JS file | ||
input_filename The obfuscated JS file | ||
@@ -52,2 +61,3 @@ optional arguments: | ||
Does not go with the -v option. | ||
-m, --max-iterations M Run at most M iterations | ||
-v, --verbose Show more debug messages while deobfuscating. Does not go with the -q option. | ||
@@ -72,3 +82,41 @@ -o, --output [output_filename] Write deobfuscated script to output_filename. | ||
*** | ||
## Create Custom Deobfuscators | ||
REstringer is highly modularized. It exposes modules that allow creating custom deobfuscators | ||
that can solve specific problems. | ||
The basic structure of such a deobfuscator would be an array of deobfuscation modules | ||
(either [safe](src/modules/safe) or [unsafe](src/modules/unsafe)), run via the [runLoop](src/modules/utils/runLoop.js) util function. | ||
Unsafe modules run code through `eval` (using [vm2](https://www.npmjs.com/package/vm2) to be on the safe side) while safe modules do not. | ||
```javascript | ||
const { | ||
safe: {normalizeComputed}, | ||
unsafe: {resolveDefiniteBinaryExpressions, resolveLocalCalls} | ||
} = require('restringer').modules; | ||
let script = 'obfuscated JS here'; | ||
const deobModules = [ | ||
resolveDefiniteBinaryExpressions, | ||
resolveLocalCalls, | ||
normalizeComputed, | ||
]; | ||
script = runLoop(script, deobModules); | ||
console.log(script); // Deobfuscated script | ||
``` | ||
With the additional `candidateFilter` function argument, it's possible to narrow down the targeted nodes: | ||
```javascript | ||
const {unsafe: {resolveLocalCalls}} = require('restringer').modules; | ||
let script = 'obfuscated JS here'; | ||
// It's better to define a function with a name that can show up in the log (otherwise you'll get 'undefined') | ||
function resolveLocalCallsInGlobalScope(arb) { | ||
return resolveLocalCalls(arb, n => n.parentNode?.type === 'Program'); | ||
} | ||
script = runLoop(script, [resolveLocalCallsInGlobalScope]); | ||
console.log(script); // Deobfuscated script | ||
``` | ||
*** | ||
## Read More | ||
@@ -75,0 +123,0 @@ * [Processors](src/processors/README.md) |
@@ -6,8 +6,10 @@ /** | ||
* @param {Arborist} arb | ||
* @param {Function} candidateFilter (optional) a filter to apply on the candidates list | ||
* @return {Arborist} | ||
*/ | ||
function consolidateNestedBlockStatements(arb) { | ||
function consolidateNestedBlockStatements(arb, candidateFilter = () => true) { | ||
const candidates = arb.ast.filter(n => | ||
n.type === 'BlockStatement' && | ||
n.parentNode.type === 'BlockStatement'); | ||
n.parentNode.type === 'BlockStatement' && | ||
candidateFilter(n)); | ||
@@ -14,0 +16,0 @@ for (const c of candidates) { |
@@ -8,5 +8,6 @@ const {badIdentifierCharsRegex, validIdentifierBeginning} = require(__dirname + '/../config'); | ||
* @param {Arborist} arb | ||
* @param {Function} candidateFilter (optional) a filter to apply on the candidates list | ||
* @return {Arborist} | ||
*/ | ||
function normalizeComputed(arb) { | ||
function normalizeComputed(arb, candidateFilter = () => true) { | ||
const candidates = arb.ast.filter(n => | ||
@@ -34,3 +35,4 @@ n.computed && // Filter for only member expressions using bracket notation | ||
validIdentifierBeginning.test(n.key.value) && | ||
!badIdentifierCharsRegex.test(n.key.value))); | ||
!badIdentifierCharsRegex.test(n.key.value)) && | ||
candidateFilter(n)); | ||
@@ -37,0 +39,0 @@ for (const c of candidates) { |
/** | ||
* Remove unrequired empty statements. | ||
* @param {Arborist} arb | ||
* @param {Function} candidateFilter (optional) a filter to apply on the candidates list | ||
* @return {Arborist} | ||
*/ | ||
function normalizeEmptyStatements(arb) { | ||
const candidates = arb.ast.filter(n => n.type === 'EmptyStatement'); | ||
function normalizeEmptyStatements(arb, candidateFilter = () => true) { | ||
const candidates = arb.ast.filter(n => | ||
n.type === 'EmptyStatement' && | ||
candidateFilter(n)); | ||
@@ -9,0 +12,0 @@ for (const c of candidates) { |
@@ -7,8 +7,10 @@ const createNewNode = require(__dirname + '/../utils/createNewNode'); | ||
* @param {Arborist} arb | ||
* @param {Function} candidateFilter (optional) a filter to apply on the candidates list | ||
* @return {Arborist} | ||
*/ | ||
function parseTemplateLiteralsIntoStringLiterals(arb) { | ||
function parseTemplateLiteralsIntoStringLiterals(arb, candidateFilter = () => true) { | ||
const candidates = arb.ast.filter(n => | ||
n.type === 'TemplateLiteral' && | ||
!n.expressions.find(exp => exp.type !== 'Literal')); | ||
!n.expressions.find(exp => exp.type !== 'Literal') && | ||
candidateFilter(n)); | ||
@@ -15,0 +17,0 @@ for (const c of candidates) { |
@@ -8,9 +8,11 @@ const getDescendants = require(__dirname + '/../utils/getDescendants'); | ||
* @param {Arborist} arb | ||
* @param {Function} candidateFilter (optional) a filter to apply on the candidates list | ||
* @return {Arborist} | ||
*/ | ||
function rearrangeSwitches(arb) { | ||
function rearrangeSwitches(arb, candidateFilter = () => true) { | ||
const candidates = arb.ast.filter(n => | ||
n.type === 'SwitchStatement' && | ||
n.discriminant.type === 'Identifier' && | ||
n?.discriminant.declNode?.parentNode?.init?.type === 'Literal'); | ||
n?.discriminant.declNode?.parentNode?.init?.type === 'Literal' && | ||
candidateFilter(n)); | ||
@@ -17,0 +19,0 @@ for (const c of candidates) { |
@@ -8,9 +8,11 @@ const relevantParents = ['VariableDeclarator', 'AssignmentExpression', 'FunctionDeclaration', 'ClassDeclaration']; | ||
* @param {Arborist} arb | ||
* @param {Function} candidateFilter (optional) a filter to apply on the candidates list | ||
* @return {Arborist} | ||
*/ | ||
function removeDeadNodes(arb) { | ||
function removeDeadNodes(arb, candidateFilter = () => true) { | ||
const candidates = arb.ast.filter(n => | ||
n.type === 'Identifier' && | ||
relevantParents.includes(n.parentNode.type) && | ||
(!n?.declNode?.references?.length && !n?.references?.length)) | ||
(!n?.declNode?.references?.length && !n?.references?.length) && | ||
candidateFilter(n)) | ||
.map(n => n.parentNode); | ||
@@ -17,0 +19,0 @@ |
@@ -7,5 +7,6 @@ /** | ||
* @param {Arborist} arb | ||
* @param {Function} candidateFilter (optional) a filter to apply on the candidates list | ||
* @return {Arborist} | ||
*/ | ||
function replaceCallExpressionsWithUnwrappedIdentifier(arb) { | ||
function replaceCallExpressionsWithUnwrappedIdentifier(arb, candidateFilter = () => true) { | ||
const candidates = arb.ast.filter(n => | ||
@@ -17,3 +18,4 @@ n.type === 'CallExpression' && | ||
(n.callee.declNode.parentNode.type === 'FunctionDeclaration' && | ||
n.callee.declNode.parentKey === 'id'))); | ||
n.callee.declNode.parentKey === 'id')) && | ||
candidateFilter(n)); | ||
@@ -20,0 +22,0 @@ for (const c of candidates) { |
@@ -12,5 +12,6 @@ const {generateFlatAST} = require('flast'); | ||
* @param {Arborist} arb | ||
* @param {Function} candidateFilter (optional) a filter to apply on the candidates list | ||
* @return {Arborist} | ||
*/ | ||
function replaceEvalCallsWithLiteralContent(arb) { | ||
function replaceEvalCallsWithLiteralContent(arb, candidateFilter = () => true) { | ||
const cache = getCache(arb.ast[0].scriptHash); | ||
@@ -20,3 +21,4 @@ const candidates = arb.ast.filter(n => | ||
n.callee?.name === 'eval' && | ||
n.arguments[0]?.type === 'Literal'); | ||
n.arguments[0]?.type === 'Literal' && | ||
candidateFilter(n)); | ||
@@ -23,0 +25,0 @@ for (const c of candidates) { |
/** | ||
* Functions which only return a single literal or identifier will have their references replaced with the actual return value. | ||
* @param {Arborist} arb | ||
* @param {Function} candidateFilter (optional) a filter to apply on the candidates list | ||
* @return {Arborist} | ||
*/ | ||
function replaceFunctionShellsWithWrappedValue(arb) { | ||
function replaceFunctionShellsWithWrappedValue(arb, candidateFilter = () => true) { | ||
const candidates = arb.ast.filter(n => | ||
@@ -11,3 +12,4 @@ n.type === 'FunctionDeclaration' && | ||
n.body.body[0].type === 'ReturnStatement' && | ||
['Literal', 'Identifier'].includes(n.body.body[0].argument?.type)); | ||
['Literal', 'Identifier'].includes(n.body.body[0].argument?.type) && | ||
candidateFilter(n)); | ||
@@ -14,0 +16,0 @@ for (const c of candidates) { |
/** | ||
* Functions which only return a single literal or identifier will have their references replaced with the actual return value. | ||
* @param {Arborist} arb | ||
* @param {Function} candidateFilter (optional) a filter to apply on the candidates list | ||
* @return {Arborist} | ||
*/ | ||
function replaceFunctionShellsWithWrappedValueIIFE(arb) { | ||
function replaceFunctionShellsWithWrappedValueIIFE(arb, candidateFilter = () => true) { | ||
const candidates = arb.ast.filter(n => | ||
@@ -13,3 +14,4 @@ n.type === 'FunctionExpression' && | ||
n.body.body[0].type === 'ReturnStatement' && | ||
['Literal', 'Identifier'].includes(n.body.body[0].argument?.type)) | ||
['Literal', 'Identifier'].includes(n.body.body[0].argument?.type) && | ||
candidateFilter(n)) | ||
.map(n => n.parentNode); | ||
@@ -16,0 +18,0 @@ |
@@ -6,8 +6,10 @@ const areReferencesModified = require(__dirname + '/../utils/areReferencesModified'); | ||
* @param {Arborist} arb | ||
* @param {Function} candidateFilter (optional) a filter to apply on the candidates list | ||
* @return {Arborist} | ||
*/ | ||
function replaceIdentifierWithFixedAssignedValue(arb) { | ||
function replaceIdentifierWithFixedAssignedValue(arb, candidateFilter = () => true) { | ||
const candidates = arb.ast.filter(n => | ||
n?.declNode?.parentNode?.init?.type === 'Literal' && | ||
!(n.parentKey === 'property' && n.parentNode.type === 'ObjectExpression')); | ||
!(n.parentKey === 'property' && n.parentNode.type === 'ObjectExpression') && | ||
candidateFilter(n)); | ||
@@ -14,0 +16,0 @@ for (const c of candidates) { |
@@ -8,5 +8,6 @@ const areReferencesModified = require(__dirname + '/../utils/areReferencesModified'); | ||
* @param {Arborist} arb | ||
* @param {Function} candidateFilter (optional) a filter to apply on the candidates list | ||
* @return {Arborist} | ||
*/ | ||
function replaceIdentifierWithFixedValueNotAssignedAtDeclaration(arb) { | ||
function replaceIdentifierWithFixedValueNotAssignedAtDeclaration(arb, candidateFilter = () => true) { | ||
const candidates = arb.ast.filter(n => | ||
@@ -28,3 +29,4 @@ n.parentNode?.type === 'VariableDeclarator' && | ||
r.parentNode.parentNode?.parentNode?.parentNode?.type, | ||
].includes('ConditionalExpression'))); | ||
].includes('ConditionalExpression')) && | ||
candidateFilter(n)); | ||
@@ -31,0 +33,0 @@ for (const c of candidates) { |
@@ -8,8 +8,10 @@ /** | ||
* @param {Arborist} arb | ||
* @param {Function} candidateFilter (optional) a filter to apply on the candidates list | ||
* @return {Arborist} | ||
*/ | ||
function resolveDeterministicIfStatements(arb) { | ||
function resolveDeterministicIfStatements(arb, candidateFilter = () => true) { | ||
const candidates = arb.ast.filter(n => | ||
n.type === 'IfStatement' && | ||
n.test.type === 'Literal'); | ||
n.test.type === 'Literal' && | ||
candidateFilter(n)); | ||
@@ -16,0 +18,0 @@ for (const c of candidates) { |
@@ -7,5 +7,6 @@ const {generateFlatAST} = require('flast'); | ||
* @param {Arborist} arb | ||
* @param {Function} candidateFilter (optional) a filter to apply on the candidates list | ||
* @return {Arborist} | ||
*/ | ||
function resolveFunctionConstructorCalls(arb) { | ||
function resolveFunctionConstructorCalls(arb, candidateFilter = () => true) { | ||
const candidates = arb.ast.filter(n => | ||
@@ -15,3 +16,5 @@ n.type === 'CallExpression' && | ||
(n.callee.property?.name || n.callee.property?.value) === 'constructor' && | ||
n.arguments.length && n.arguments.slice(-1)[0].type === 'Literal'); | ||
n.arguments.length && n.arguments.slice(-1)[0].type === 'Literal' && | ||
candidateFilter(n)); | ||
for (const c of candidates) { | ||
@@ -18,0 +21,0 @@ let args = ''; |
@@ -12,5 +12,6 @@ const logger = require(__dirname + '/../utils/logger'); | ||
* @param {Arborist} arb | ||
* @param {Function} candidateFilter (optional) a filter to apply on the candidates list | ||
* @return {Arborist} | ||
*/ | ||
function resolveMemberExpressionReferencesToArrayIndex(arb) { | ||
function resolveMemberExpressionReferencesToArrayIndex(arb, candidateFilter = () => true) { | ||
const candidates = arb.ast.filter(n => | ||
@@ -20,3 +21,4 @@ n.type === 'VariableDeclarator' && | ||
n.id?.references && | ||
n.init.elements.length > minArrayLength); | ||
n.init.elements.length > minArrayLength && | ||
candidateFilter(n)); | ||
@@ -23,0 +25,0 @@ for (const c of candidates) { |
@@ -9,5 +9,6 @@ /** | ||
* @param {Arborist} arb | ||
* @param {Function} candidateFilter (optional) a filter to apply on the candidates list | ||
* @return {Arborist} | ||
*/ | ||
function resolveMemberExpressionsWithDirectAssignment(arb) { | ||
function resolveMemberExpressionsWithDirectAssignment(arb, candidateFilter = () => true) { | ||
const candidates = arb.ast.filter(n => | ||
@@ -17,3 +18,4 @@ n.type === 'MemberExpression' && | ||
n.parentNode.type === 'AssignmentExpression' && | ||
n.parentNode.right.type === 'Literal'); | ||
n.parentNode.right.type === 'Literal' && | ||
candidateFilter(n)); | ||
@@ -20,0 +22,0 @@ for (const c of candidates) { |
@@ -15,5 +15,6 @@ /** | ||
* @param {Arborist} arb | ||
* @param {Function} candidateFilter (optional) a filter to apply on the candidates list | ||
* @return {Arborist} | ||
*/ | ||
function resolveProxyCalls(arb) { | ||
function resolveProxyCalls(arb, candidateFilter = () => true) { | ||
const candidates = arb.ast.filter(n => | ||
@@ -25,3 +26,4 @@ n.type === 'FunctionDeclaration' && | ||
n.body.body[0].argument.arguments.length === n.params.length && | ||
n.body.body[0].argument.callee.type === 'Identifier'); | ||
n.body.body[0].argument.callee.type === 'Identifier' && | ||
candidateFilter(n)); | ||
@@ -28,0 +30,0 @@ for (const c of candidates) { |
@@ -12,5 +12,6 @@ const getDescendants = require(__dirname + '/../utils/getDescendants'); | ||
* @param {Arborist} arb | ||
* @param {Function} candidateFilter (optional) a filter to apply on the candidates list | ||
* @return {Arborist} | ||
*/ | ||
function resolveProxyReferences(arb) { | ||
function resolveProxyReferences(arb, candidateFilter = () => true) { | ||
const candidates = arb.ast.filter(n => | ||
@@ -20,3 +21,4 @@ (n.type === 'VariableDeclarator' && | ||
['Identifier', 'MemberExpression'].includes(n.init?.type)) && | ||
!/For.*Statement/.test(n.parentNode?.parentNode?.type)); | ||
!/For.*Statement/.test(n.parentNode?.parentNode?.type) && | ||
candidateFilter(n)); | ||
@@ -23,0 +25,0 @@ for (const c of candidates) { |
@@ -9,8 +9,12 @@ const areReferencesModified = require(__dirname + '/../utils/areReferencesModified'); | ||
* @param {Arborist} arb | ||
* @param {Function} candidateFilter (optional) a filter to apply on the candidates list | ||
* @return {Arborist} | ||
*/ | ||
function resolveProxyVariables(arb) { | ||
const candidates = [...new Set(arb.ast.filter(n => | ||
n.type === 'VariableDeclarator' && | ||
n?.init?.type === 'Identifier'))]; | ||
function resolveProxyVariables(arb, candidateFilter = () => true) { | ||
const candidates = [ | ||
...new Set(arb.ast.filter(n => | ||
n.type === 'VariableDeclarator' && | ||
n?.init?.type === 'Identifier' && | ||
candidateFilter(n))) | ||
]; | ||
@@ -17,0 +21,0 @@ for (const c of candidates) { |
@@ -7,8 +7,10 @@ /** | ||
* @param {Arborist} arb | ||
* @param {Function} candidateFilter (optional) a filter to apply on the candidates list | ||
* @return {Arborist} | ||
*/ | ||
function resolveRedundantLogicalExpressions(arb) { | ||
function resolveRedundantLogicalExpressions(arb, candidateFilter = () => true) { | ||
const candidates = arb.ast.filter(n => | ||
n.type === 'IfStatement' && | ||
n.test.type === 'LogicalExpression'); | ||
n.test.type === 'LogicalExpression' && | ||
candidateFilter(n)); | ||
@@ -15,0 +17,0 @@ for (const c of candidates) { |
@@ -11,5 +11,6 @@ /** | ||
* @param {Arborist} arb | ||
* @param {Function} candidateFilter (optional) a filter to apply on the candidates list | ||
* @return {Arborist} | ||
*/ | ||
function unwrapFunctionShells(arb) { | ||
function unwrapFunctionShells(arb, candidateFilter = () => true) { | ||
const candidates = arb.ast.filter(n => | ||
@@ -21,3 +22,4 @@ ['FunctionDeclaration', 'FunctionExpression'].includes(n.type) && | ||
n.body.body[0].argument.arguments?.length === 2 && | ||
n.body.body[0].argument.callee.object.type === 'FunctionExpression'); | ||
n.body.body[0].argument.callee.object.type === 'FunctionExpression' && | ||
candidateFilter(n)); | ||
@@ -24,0 +26,0 @@ for (const c of candidates) { |
@@ -10,9 +10,11 @@ const evalInVm = require(__dirname + '/evalInVm'); | ||
* @param {Arborist} arb | ||
* @param {Function} candidateFilter (optional) a filter to apply on the candidates list | ||
* @return {Arborist} | ||
*/ | ||
function normalizeRedundantNotOperator(arb) { | ||
function normalizeRedundantNotOperator(arb, candidateFilter = () => true) { | ||
const candidates = arb.ast.filter(n => | ||
n.operator === '!' && | ||
n.type === 'UnaryExpression' && | ||
relevantNodeTypes.includes(n.argument.type)); | ||
relevantNodeTypes.includes(n.argument.type) && | ||
candidateFilter(n)); | ||
@@ -19,0 +21,0 @@ for (const c of candidates) { |
@@ -10,7 +10,9 @@ const evalInVm = require(__dirname + '/evalInVm'); | ||
* @param {Arborist} arb | ||
* @param {Function} candidateFilter (optional) a filter to apply on the candidates list | ||
* @return {Arborist} | ||
*/ | ||
function resolveAugmentedFunctionWrappedArrayReplacements(arb) { | ||
function resolveAugmentedFunctionWrappedArrayReplacements(arb, candidateFilter = () => true) { | ||
const candidates = arb.ast.filter(n => | ||
n.type === 'FunctionDeclaration' && n.id); | ||
n.type === 'FunctionDeclaration' && n.id && | ||
candidateFilter(n)); | ||
@@ -17,0 +19,0 @@ for (const c of candidates) { |
@@ -11,9 +11,11 @@ const evalInVm = require(__dirname + '/evalInVm'); | ||
* @param {Arborist} arb | ||
* @param {Function} candidateFilter (optional) a filter to apply on the candidates list | ||
* @return {Arborist} | ||
*/ | ||
function resolveBuiltinCalls(arb) { | ||
function resolveBuiltinCalls(arb, candidateFilter = () => true) { | ||
const availableSafeImplementations = Object.keys(safeImplementations); | ||
const callsWithOnlyLiteralArugments = arb.ast.filter(n => | ||
n.type === 'CallExpression' && | ||
!n.arguments.find(a => a.type !== 'Literal')); | ||
!n.arguments.find(a => a.type !== 'Literal') && | ||
candidateFilter(n)); | ||
@@ -20,0 +22,0 @@ const candidates = callsWithOnlyLiteralArugments.filter(n => |
@@ -11,8 +11,10 @@ const evalInVm = require(__dirname + '/evalInVm'); | ||
* @param {Arborist} arb | ||
* @param {Function} candidateFilter (optional) a filter to apply on the candidates list | ||
* @return {Arborist} | ||
*/ | ||
function resolveDefiniteBinaryExpressions(arb) { | ||
function resolveDefiniteBinaryExpressions(arb, candidateFilter = () => true) { | ||
const candidates = arb.ast.filter(n => | ||
n.type === 'BinaryExpression' && | ||
doesBinaryExpressionContainOnlyLiterals(n)); | ||
doesBinaryExpressionContainOnlyLiterals(n) && | ||
candidateFilter(n)); | ||
@@ -19,0 +21,0 @@ for (const c of candidates) { |
@@ -10,5 +10,6 @@ const evalInVm = require(__dirname + '/evalInVm'); | ||
* @param {Arborist} arb | ||
* @param {Function} candidateFilter (optional) a filter to apply on the candidates list | ||
* @return {Arborist} | ||
*/ | ||
function resolveDefiniteMemberExpressions(arb) { | ||
function resolveDefiniteMemberExpressions(arb, candidateFilter = () => true) { | ||
const candidates = arb.ast.filter(n => | ||
@@ -21,3 +22,4 @@ n.type === 'MemberExpression' && | ||
['ArrayExpression', 'Literal'].includes(n.object.type) && | ||
(n.object?.value?.length || n.object?.elements?.length)); | ||
(n.object?.value?.length || n.object?.elements?.length) && | ||
candidateFilter(n)); | ||
@@ -24,0 +26,0 @@ for (const c of candidates) { |
@@ -8,8 +8,10 @@ const evalInVm = require(__dirname + '/evalInVm'); | ||
* @param {Arborist} arb | ||
* @param {Function} candidateFilter (optional) a filter to apply on the candidates list | ||
* @return {Arborist} | ||
*/ | ||
function resolveDeterministicConditionalExpressions(arb) { | ||
function resolveDeterministicConditionalExpressions(arb, candidateFilter = () => true) { | ||
const candidates = arb.ast.filter(n => | ||
n.type === 'ConditionalExpression' && | ||
n.test.type === 'Literal'); | ||
n.test.type === 'Literal' && | ||
candidateFilter(n)); | ||
@@ -16,0 +18,0 @@ for (const c of candidates) { |
@@ -10,5 +10,6 @@ const {parseCode} = require('flast'); | ||
* @param {Arborist} arb | ||
* @param {Function} candidateFilter (optional) a filter to apply on the candidates list | ||
* @return {Arborist} | ||
*/ | ||
function resolveEvalCallsOnNonLiterals(arb) { | ||
function resolveEvalCallsOnNonLiterals(arb, candidateFilter = () => true) { | ||
const candidates = arb.ast.filter(n => | ||
@@ -18,3 +19,4 @@ n.type === 'CallExpression' && | ||
n.arguments.length === 1 && | ||
n.arguments[0].type !== 'Literal'); | ||
n.arguments[0].type !== 'Literal' && | ||
candidateFilter(n)); | ||
@@ -21,0 +23,0 @@ for (const c of candidates) { |
@@ -12,5 +12,6 @@ const evalInVm = require(__dirname + '/evalInVm'); | ||
* @param {Arborist} arb | ||
* @param {Function} candidateFilter (optional) a filter to apply on the candidates list | ||
* @return {Arborist} | ||
*/ | ||
function resolveInjectedPrototypeMethodCalls(arb) { | ||
function resolveInjectedPrototypeMethodCalls(arb, candidateFilter = () => true) { | ||
const candidates = arb.ast.filter(n => | ||
@@ -21,3 +22,4 @@ n.type === 'AssignmentExpression' && | ||
n.operator === '=' && | ||
(/FunctionExpression|Identifier/.test(n.right?.type))); | ||
(/FunctionExpression|Identifier/.test(n.right?.type)) && | ||
candidateFilter(n)); | ||
@@ -24,0 +26,0 @@ for (const c of candidates) { |
@@ -13,5 +13,6 @@ const evalInVm = require(__dirname + '/evalInVm'); | ||
* @param {Arborist} arb | ||
* @param {Function} candidateFilter (optional) a filter to apply on the candidates list | ||
* @return {Arborist} | ||
*/ | ||
function resolveLocalCalls(arb) { | ||
function resolveLocalCalls(arb, candidateFilter = () => true) { | ||
const cache = getCache(arb.ast[0].scriptHash); | ||
@@ -23,3 +24,4 @@ const candidates = arb.ast.filter(n => | ||
!skipProperties.includes(n.callee.property?.value || n.callee.property?.name)) || | ||
n.callee?.object?.type === 'Literal')); | ||
n.callee?.object?.type === 'Literal') && | ||
candidateFilter(n)); | ||
@@ -26,0 +28,0 @@ const frequency = {}; |
@@ -19,9 +19,11 @@ const evalInVm = require(__dirname + '/evalInVm'); | ||
* @param {Arborist} arb | ||
* @param {Function} candidateFilter (optional) a filter to apply on the candidates list | ||
* @return {Arborist} | ||
*/ | ||
function resolveMemberExpressionsLocalReferences(arb) { | ||
function resolveMemberExpressionsLocalReferences(arb, candidateFilter = () => true) { | ||
const candidates = arb.ast.filter(n => | ||
n.type === 'MemberExpression' && | ||
['Identifier', 'Literal'].includes(n.property.type) && | ||
!skipProperties.includes(n.property?.name || n.property?.value)); | ||
!skipProperties.includes(n.property?.name || n.property?.value) && | ||
candidateFilter(n)); | ||
@@ -28,0 +30,0 @@ for (const c of candidates) { |
@@ -10,5 +10,6 @@ const evalInVm = require(__dirname + '/evalInVm'); | ||
* @param {Arborist} arb | ||
* @param {Function} candidateFilter (optional) a filter to apply on the candidates list | ||
* @return {Arborist} | ||
*/ | ||
function resolveMinimalAlphabet(arb) { | ||
function resolveMinimalAlphabet(arb, candidateFilter = () => true) { | ||
const candidates = arb.ast.filter(n => | ||
@@ -21,3 +22,4 @@ (n.type === 'UnaryExpression' && | ||
(n.left.type !== 'MemberExpression' && Number.isNaN(parseFloat(n.left?.value))) && | ||
![n.left?.type, n.right?.type].includes('ThisExpression'))); | ||
![n.left?.type, n.right?.type].includes('ThisExpression')) && | ||
candidateFilter(n)); | ||
@@ -24,0 +26,0 @@ for (const c of candidates) { |
@@ -30,3 +30,3 @@ const {Arborist} = require('flast'); | ||
let arborist = new Arborist(script, logger.log); | ||
while (arborist.ast.length && scriptSnapshot !== script && currentIteration < maxIterations && !hasGlobalMaxIterationBeenReached()) { | ||
while (arborist.ast?.length && scriptSnapshot !== script && currentIteration < maxIterations && !hasGlobalMaxIterationBeenReached()) { | ||
const cycleStartTime = Date.now(); | ||
@@ -40,3 +40,3 @@ scriptSnapshot = script; | ||
arborist = func(arborist); | ||
if (!arborist.ast.length) break; | ||
if (!arborist.ast?.length) break; | ||
// If the hash doesn't exist it means the Arborist was replaced | ||
@@ -62,3 +62,3 @@ const numberOfNewChanges = arborist.getNumberOfChanges() + +!arborist.ast[0].scriptHash; | ||
logger.log(`[+] ==> Cycle ${globalIterationsCounter} completed in ${(Date.now() - cycleStartTime) / 1000} seconds` + | ||
` with ${changesCounter ? changesCounter : 'no'} changes (${arborist.ast.length} nodes)`); | ||
` with ${changesCounter ? changesCounter : 'no'} changes (${arborist.ast?.length || '???'} nodes)`); | ||
} | ||
@@ -65,0 +65,0 @@ if (changesCounter) script = arborist.script; |
@@ -11,3 +11,3 @@ /** | ||
'augmented_array_function_replacements': () => require(__dirname + '/augmentedArray.js'), | ||
'proxied_augmented_array_function_replacements': () => require(__dirname + '/augmentedArray.js'), | ||
'augmented_proxied_array_function_replacements': () => require(__dirname + '/augmentedArray.js'), | ||
}; |
@@ -198,4 +198,3 @@ #!/usr/bin/env node | ||
} | ||
restringer.deobfuscate(); | ||
if (restringer.modified) { | ||
if (restringer.deobfuscate()) { | ||
logger.log(`[+] Saved ${args.outputFilename}`); | ||
@@ -202,0 +201,0 @@ logger.log(`[!] Deobfuscation took ${(Date.now() - startTime) / 1000} seconds.`); |
@@ -5,6 +5,6 @@ function printHelp() { | ||
Usage: restringer input_filename [-h] [-c] [-q|-v] [-m M] [-o [output_filename]] | ||
Usage: restringer input_filename [-h] [-c] [-q | -v] [-m M] [-o [output_filename]] | ||
positional arguments: | ||
input_filename The obfuscated JS file | ||
input_filename The obfuscated JS file | ||
@@ -11,0 +11,0 @@ optional arguments: |
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
599496
107
10005
123
167