Huge News!Announcing our $40M Series B led by Abstract Ventures.Learn More
Socket
Sign inDemoInstall
Socket

restringer

Package Overview
Dependencies
Maintainers
1
Versions
45
Alerts
File Explorer

Advanced tools

Socket logo

Install Socket

Detect and block malicious and high-risk dependencies

Install

restringer - npm Package Compare versions

Comparing version 1.2.1 to 1.2.2

src/utils/parseArgs.js

2

package.json
{
"name": "restringer",
"version": "1.2.1",
"version": "1.2.2",
"description": "Deobfuscate Javascript with emphasis on reconstructing strings",

@@ -5,0 +5,0 @@ "main": "index.js",

@@ -18,4 +18,2 @@ # Restringer

* [Read More](#read-more)
* [TODO](#todo)
***

@@ -41,14 +39,17 @@

Output deobfuscated result to STDOUT (nothing will print if deobfuscation failed)
> node restringer.js script.js
```
Usage: restringer input_filename [-h] [-c] [-q|-v] [-o [output_filename]]
Show debug information and save deobfuscated script to `script.js-<obfuscation-type>-deob.js`
> export DEOBDEBUG=true && node restringer.js script.js
positional arguments:
input_filename The obfuscated JS file
Log level can be adusted via the `DEOBDEBUGLEVEL` environment variable for more or less granular
log output. The default level is an arbitrary 50, simply to leave space for other levels to be added when needed.
> export DEOBDEBUG=true && DEOBDEBUGLEVEL=50 && node restringer.js script.js
Level 1 is most verbose, level 2 is a good value to use for debugging.
optional arguments:
-h, --help Show this help message and exit.
-c, --clean Remove dead nodes from script after deobfuscation is complete (unsafe).
-q, --quiet Suppress output to stdout. Output result only to stdout if the -o option is not set.
Does not go with the -v option.
-v, --verbose Show more debug messages while deobfuscating. Does not go with the -q option.
-o, --output [output_filename] Write deobfuscated script to output_filename.
Use <input_filename>-deob.js if no filename is provided.
```
### Use as a Module

@@ -55,0 +56,0 @@

@@ -12,15 +12,21 @@ /**

n.parentNode.type === 'BlockStatement');
for (const c of candidates) {
if (c.parentNode.body?.length > 1) {
const parent = c.parentNode;
if (parent.body?.length > 1) {
if (c.body.length === 1) arb.markNode(c, c.body[0]);
else {
const currentIdx = c.parentNode.body.indexOf(c);
const currentIdx = parent.body.indexOf(c);
const replacementNode = {
type: 'BlockStatement',
body: [...c.parentNode.body.slice(0, currentIdx), ...c.body, ...c.parentNode.body.slice(currentIdx + 1)],
body: [
...parent.body.slice(0, currentIdx),
...c.body,
...parent.body.slice(currentIdx + 1)
],
};
arb.markNode(c.parentNode, replacementNode);
arb.markNode(parent, replacementNode);
}
}
else arb.markNode(c.parentNode, c);
else arb.markNode(parent, c);
}

@@ -27,0 +33,0 @@ return arb;

@@ -18,3 +18,3 @@ const {badIdentifierCharsRegex, validIdentifierBeginning} = require(__dirname + '/../config');

validIdentifierBeginning.test(n.property.value) &&
!badIdentifierCharsRegex.exec(n.property.value)) ||
!badIdentifierCharsRegex.test(n.property.value)) ||
/**

@@ -34,12 +34,14 @@ * Ignore the same cases for method names and object properties, for example

validIdentifierBeginning.test(n.key.value) &&
!badIdentifierCharsRegex.exec(n.key.value)));
!badIdentifierCharsRegex.test(n.key.value)));
for (const c of candidates) {
const relevantProperty = c.type === 'MemberExpression' ? 'property' : 'key';
const nonComputed = {...c};
nonComputed.computed = false;
nonComputed[relevantProperty] = {
type: 'Identifier',
name: c[relevantProperty].value,
};
arb.markNode(c, nonComputed);
arb.markNode(c, {
...c,
computed: false,
[relevantProperty]: {
type: 'Identifier',
name: c[relevantProperty].value,
},
});
}

@@ -46,0 +48,0 @@ return arb;

@@ -8,2 +8,3 @@ /**

const candidates = arb.ast.filter(n => n.type === 'EmptyStatement');
for (const c of candidates) {

@@ -10,0 +11,0 @@ // A for loop is sometimes used to assign variables without providing a loop body, just an empty statement.

@@ -1,2 +0,1 @@

const logger = require(__dirname + '/../utils/logger');
const createNewNode = require(__dirname + '/../utils/createNewNode');

@@ -13,3 +12,4 @@

n.type === 'TemplateLiteral' &&
!n.expressions.filter(exp => exp.type !== 'Literal').length);
!n.expressions.find(exp => exp.type !== 'Literal'));
for (const c of candidates) {

@@ -21,3 +21,3 @@ let newStringLiteral = '';

newStringLiteral += c.quasis.slice(-1)[0].value.raw;
arb.markNode(c, createNewNode(newStringLiteral, logger));
arb.markNode(c, createNewNode(newStringLiteral));
}

@@ -24,0 +24,0 @@ return arb;

@@ -0,1 +1,3 @@

const relevantParents = ['VariableDeclarator', 'AssignmentExpression', 'FunctionDeclaration', 'ClassDeclaration'];
/**

@@ -9,3 +11,2 @@ * Remove nodes code which is only declared but never used.

function removeDeadNodes(arb) {
const relevantParents = ['VariableDeclarator', 'AssignmentExpression', 'FunctionDeclaration', 'ClassDeclaration'];
const candidates = arb.ast.filter(n =>

@@ -16,4 +17,7 @@ n.type === 'Identifier' &&

.map(n => n.parentNode);
for (const c of candidates) {
arb.markNode(c);
// Do not remove root nodes as they might be referenced in another script
if (c.parentNode.type === 'Program') continue;
arb.markNode(c?.parentNode?.type === 'ExpressionStatement' ? c.parentNode : c);
}

@@ -20,0 +24,0 @@ return arb;

@@ -17,2 +17,3 @@ /**

n.callee.declNode.parentKey === 'id')));
for (const c of candidates) {

@@ -19,0 +20,0 @@ const declBody = c.callee.declNode.parentNode?.init?.body || c.callee.declNode.parentNode?.body;

@@ -10,2 +10,3 @@ const {generateFlatAST} = require('flast');

* eval('console.log("hello world")'); // <-- will be replaced with console.log("hello world");
* eval('a(); b();'); // <-- will be replaced with '{a(); b();}'
* @param {Arborist} arb

@@ -20,2 +21,3 @@ * @return {Arborist}

n.arguments[0]?.type === 'Literal');
for (const c of candidates) {

@@ -56,3 +58,3 @@ const cacheName = `replaceEval-${generateHash(c.src)}`;

} catch (e) {
logger.error(`[-] Unable to replace eval's body with call expression: ${e}`, 1);
logger.debug(`[-] Unable to replace eval's body with call expression: ${e}`);
}

@@ -59,0 +61,0 @@ }

@@ -9,5 +9,6 @@ /**

n.type === 'FunctionDeclaration' &&
n.body?.body?.length === 1 &&
n.body?.body?.length &&
n.body.body[0].type === 'ReturnStatement' &&
['Literal', 'Identifier'].includes(n.body.body[0].argument?.type));
for (const c of candidates) {

@@ -14,0 +15,0 @@ const replacementNode = c.body.body[0].argument;

@@ -11,9 +11,9 @@ /**

!n.parentNode.arguments.length &&
n.body?.body?.length === 1 &&
n.body?.body?.length &&
n.body.body[0].type === 'ReturnStatement' &&
['Literal', 'Identifier'].includes(n.body.body[0].argument?.type))
.map(n => n.parentNode);
for (const c of candidates) {
const replacementNode = c.callee.body.body[0].argument;
arb.markNode(c, replacementNode);
arb.markNode(c, c.callee.body.body[0].argument);
}

@@ -20,0 +20,0 @@ return arb;

@@ -12,2 +12,3 @@ const areReferencesModified = require(__dirname + '/../utils/areReferencesModified');

!(n.parentKey === 'property' && n.parentNode.type === 'ObjectExpression'));
for (const c of candidates) {

@@ -14,0 +15,0 @@ const valueNode = c.declNode.parentNode.init;

@@ -17,5 +17,5 @@ const areReferencesModified = require(__dirname + '/../utils/areReferencesModified');

r.parentNode.type === 'AssignmentExpression' &&
getMainDeclaredObjectOfMemberExpression(r.parentNode.left).nodeId === r.nodeId).length === 1 &&
!n.references.filter(r =>
(/For.*Statement/.exec(r.parentNode.type) &&
getMainDeclaredObjectOfMemberExpression(r.parentNode.left) === r).length === 1 &&
!n.references.find(r =>
(/For.*Statement/.test(r.parentNode.type) &&
r.parentKey === 'left') ||

@@ -28,10 +28,11 @@ // This covers cases like:

r.parentNode.parentNode?.parentNode?.parentNode?.type,
].includes('ConditionalExpression')).length);
].includes('ConditionalExpression')));
for (const c of candidates) {
const assignmentNode = c.references.filter(r =>
const assignmentNode = c.references.find(r =>
r.parentNode.type === 'AssignmentExpression' &&
getMainDeclaredObjectOfMemberExpression(r.parentNode.left).nodeId === r.nodeId)[0];
getMainDeclaredObjectOfMemberExpression(r.parentNode.left) === r);
const valueNode = assignmentNode.parentNode.right;
if (valueNode.type !== 'Literal') continue;
const refs = c.references.filter(r => r.nodeId !== assignmentNode.nodeId);
const refs = c.references.filter(r => r !== assignmentNode);
if (!areReferencesModified(arb.ast, refs)) {

@@ -38,0 +39,0 @@ for (const ref of refs) {

@@ -14,2 +14,3 @@ /**

n.test.type === 'Literal');
for (const c of candidates) {

@@ -16,0 +17,0 @@ if (c.test.value) {

@@ -16,8 +16,6 @@ const {generateFlatAST} = require('flast');

for (const c of candidates) {
// TODO: Without the next line we get an anonymous function. Is that bad?
if (!['VariableDeclarator', 'AssignmentExpression'].includes(c.parentNode.type)) continue;
let args = '';
if (c.arguments.length > 1) {
const originalArgs = c.arguments.slice(0, -1);
if (originalArgs.filter(n => n.type !== 'Literal').length) continue;
if (originalArgs.find(n => n.type !== 'Literal')) continue;
args = originalArgs.map(n => n.value).join(', ');

@@ -24,0 +22,0 @@ }

@@ -0,1 +1,5 @@

const logger = require(__dirname + '/../utils/logger');
const minArrayLength = 20;
/**

@@ -8,7 +12,5 @@ * Resolve member expressions to their targeted index in an array.

* @param {Arborist} arb
* @param {object} logger
* @return {Arborist}
*/
function resolveMemberExpressionReferencesToArrayIndex(arb, logger) {
const minArrayLength = 20;
function resolveMemberExpressionReferencesToArrayIndex(arb) {
const candidates = arb.ast.filter(n =>

@@ -19,2 +21,3 @@ n.type === 'VariableDeclarator' &&

n.init.elements.length > minArrayLength);
for (const c of candidates) {

@@ -24,7 +27,7 @@ const refs = c.id.references.map(n => n.parentNode);

if ((ref.parentNode.type === 'AssignmentExpression' && ref.parentKey === 'left') || ref.type !== 'MemberExpression') continue;
else if ((ref.property && ref.property.type !== 'Literal') || Number.isNaN(parseInt(ref.property?.value))) continue;
if ((ref.property && ref.property.type !== 'Literal') || Number.isNaN(parseInt(ref.property?.value))) continue;
try {
arb.markNode(ref, c.init.elements[parseInt(ref.property.value)]);
} catch (e) {
logger.error(`[-] Unable to mark node for replacement: ${e}`, 1);
logger.debug(`[-] Unable to mark node for replacement: ${e}`);
}

@@ -31,0 +34,0 @@ }

@@ -17,2 +17,3 @@ /**

n.parentNode.right.type === 'Literal');
for (const c of candidates) {

@@ -25,3 +26,3 @@ const prop = c.property?.value || c.property?.name;

n.parentNode.property?.name === prop) &&
n.parentNode.nodeId !== c.nodeId)
n.parentNode !== c)
.map(n => n.parentNode);

@@ -28,0 +29,0 @@ if (valueUses.length) {

@@ -25,6 +25,7 @@ /**

n.body.body[0].argument.callee.type === 'Identifier');
for (const c of candidates) {
const funcId = c.id;
const funcName = c.id;
const ret = c.body.body[0].argument;
let transitiveArguments = true;
let transitiveArguments = true;
try {

@@ -40,5 +41,6 @@ for (let i = 0; i < c.params.length; i++) {

}
if (!transitiveArguments) continue;
for (const ref of funcId.references || []) {
arb.markNode(ref, ret.callee);
if (transitiveArguments) {
for (const ref of funcName.references || []) {
arb.markNode(ref, ret.callee);
}
}

@@ -45,0 +47,0 @@ }

@@ -20,2 +20,3 @@ const getDescendants = require(__dirname + '/../utils/getDescendants');

!/For.*Statement/.test(n.parentNode?.parentNode?.type));
for (const c of candidates) {

@@ -26,5 +27,5 @@ const relevantIdentifier = getMainDeclaredObjectOfMemberExpression(c.id)?.declNode || c.id;

const replacementMainIdentifier = getMainDeclaredObjectOfMemberExpression(c.init)?.declNode;
if (replacementMainIdentifier && replacementMainIdentifier.nodeId === relevantIdentifier.nodeId) continue;
if (replacementMainIdentifier && replacementMainIdentifier === relevantIdentifier) continue;
// Exclude changes in the identifier's own init
if (getDescendants(c.init).find(n => n.declNode?.nodeId === relevantIdentifier.nodeId)) continue;
if (getDescendants(c.init).find(n => n.declNode === relevantIdentifier)) continue;
if (refs.length && !areReferencesModified(arb.ast, refs) && !areReferencesModified(arb.ast, [replacementNode])) {

@@ -31,0 +32,0 @@ for (const ref of refs) {

@@ -15,2 +15,3 @@ const areReferencesModified = require(__dirname + '/../utils/areReferencesModified');

n?.init?.type === 'Identifier'))];
for (const c of candidates) {

@@ -17,0 +18,0 @@ const refs = c.id.references || [];

@@ -13,2 +13,3 @@ /**

n.test.type === 'LogicalExpression');
for (const c of candidates) {

@@ -15,0 +16,0 @@ if (c.test.operator === '&&') {

@@ -21,2 +21,3 @@ /**

n.body.body[0].argument.callee.object.type === 'FunctionExpression');
for (const c of candidates) {

@@ -23,0 +24,0 @@ const replacementNode = c.body.body[0].argument.callee.object;

const {VM} = require('vm2');
const assert = require('node:assert');
const {badValue} = require(__dirname + '/../config');
const defaultLogger = require(__dirname + '/../utils/logger');
const logger = require(__dirname + '/../utils/logger');
const getObjType = require(__dirname + '/../utils/getObjType');

@@ -43,6 +44,5 @@ const generateHash = require(__dirname + '/../utils/generateHash');

* @param {string} stringToEval
* @param {object} logger (optional) logging functions.
* @return {string|ASTNode} A node based on the eval result if successful; badValue string otherwise.
*/
function evalInVm(stringToEval, logger = defaultLogger) {
function evalInVm(stringToEval) {
const cacheName = `eval-${generateHash(stringToEval)}`;

@@ -56,12 +56,10 @@ if (cache[cacheName] === undefined) {

const res = (new VM(vmOptions)).run(stringToEval);
// noinspection JSUnresolvedVariable
if (!res.VMError && !badTypes.includes(getObjType(res))) {
// To exclude results based on randomness or timing, eval again and compare results
const res2 = (new VM(vmOptions)).run(stringToEval);
if (JSON.stringify(res) === JSON.stringify(res2)) {
cache[cacheName] = createNewNode(res);
}
assert.deepEqual(res, res2);
cache[cacheName] = createNewNode(res);
}
} catch (e) {
logger.error(`[-] Error in _evalInVm: ${e}`, 1);
logger.debug(`[-] Error in _evalInVm: ${e}`);
}

@@ -68,0 +66,0 @@ }

// noinspection HtmlRequiredLangAttribute,HtmlRequiredTitleElement
const fs = require('fs');
const fs = require('node:fs');
const {NodeVM} = require('vm2');
const {JSDOM} = require('jsdom');
const defaultLogger = require(__dirname + '/../utils/logger');
const logger = require(__dirname + '/../utils/logger');
const generateHash = require(__dirname + '/../utils/generateHash');

@@ -18,6 +18,5 @@

* @param {boolean} injectjQuery Inject jQuery into the VM if true.
* @param {object} logger (optional) logging functions.
* @return {string} The output string if successful; empty string otherwise.
*/
function evalWithDom(stringToEval, injectjQuery = false, logger = defaultLogger) {
function evalWithDom(stringToEval, injectjQuery = false) {
const cacheName = `evalWithDom-${generateHash(stringToEval)}`;

@@ -54,3 +53,3 @@ if (!cache[cacheName]) {

} catch (e) {
logger.error(`[-] Error in evalWithDom: ${e}`, 1);
logger.debug(`[-] Error in evalWithDom: ${e}`);
}

@@ -57,0 +56,0 @@ cache[cacheName] = out;

@@ -5,17 +5,18 @@ const evalInVm = require(__dirname + '/evalInVm');

const relevantNodeTypes = ['Literal', 'ArrayExpression', 'ObjectExpression', 'UnaryExpression'];
/**
* Replace redundant not operators with actual value (e.g. !true -> false)
* @param {Arborist} arb
* @param {object?} logger (optional) logging functions.
* @return {Arborist}
*/
function normalizeRedundantNotOperator(arb, logger = {error: () => {}}) {
const relevantNodeTypes = ['Literal', 'ArrayExpression', 'ObjectExpression', 'UnaryExpression'];
function normalizeRedundantNotOperator(arb) {
const candidates = arb.ast.filter(n =>
n.operator === '!' &&
n.type === 'UnaryExpression' &&
relevantNodeTypes.includes(n.argument.type) &&
n.operator === '!');
relevantNodeTypes.includes(n.argument.type));
for (const c of candidates) {
if (canUnaryExpressionBeResolved(c.argument)) {
const newNode = evalInVm(c.src, logger);
const newNode = evalInVm(c.src);
if (newNode !== badValue) arb.markNode(c, newNode);

@@ -22,0 +23,0 @@ }

const evalInVm = require(__dirname + '/evalInVm');
const {badValue} = require(__dirname + '/../config');
const logger = require(__dirname + '/../utils/logger');
const getDescendants = require(__dirname + '/../utils/getDescendants');

@@ -16,7 +15,8 @@

n.type === 'FunctionDeclaration' && n.id);
for (const c of candidates) {
const descendants = getDescendants(c);
if (descendants.filter(d =>
if (descendants.find(d =>
d.type === 'AssignmentExpression' &&
d.left?.name === c.id?.name).length) {
d.left?.name === c.id?.name)) {
const arrDecryptor = c;

@@ -26,2 +26,3 @@ const arrCandidates = descendants.filter(n =>

.map(n => n.object);
for (const ac of arrCandidates) {

@@ -39,4 +40,3 @@ // If a direct reference to a global variable pointing at an array

if (arrRef) {
const arrRefId = ac.declNode.nodeId;
const iifes = arb.ast.filter(n =>
const iife = arb.ast.find(n =>
n.type === 'ExpressionStatement' &&

@@ -47,13 +47,12 @@ n.expression.type === 'CallExpression' &&

n.expression.arguments[0].type === 'Identifier' &&
n.expression.arguments[0].declNode.nodeId === arrRefId);
if (iifes.length) {
const iife = iifes[0];
n.expression.arguments[0].declNode === ac.declNode);
if (iife) {
const context = [arrRef.src, arrDecryptor.src, iife.src].join('\n');
const skipScopes = [arrRef.scope.scopeId, arrDecryptor.scope.scopeId, iife.expression.callee.scope.scopeId];
const skipScopes = [arrRef.scope, arrDecryptor.scope, iife.expression.callee.scope];
const replacementCandidates = arb.ast.filter(n =>
n?.callee?.name === arrDecryptor.id.name &&
!skipScopes.includes(n.scope.scopeId));
!skipScopes.includes(n.scope));
for (const rc of replacementCandidates) {
const src = `${context}\n${rc.src}`;
const newNode = evalInVm(src, logger);
const newNode = evalInVm(src);
if (newNode !== badValue) arb.markNode(rc, newNode);

@@ -60,0 +59,0 @@ }

const evalInVm = require(__dirname + '/evalInVm');
const {badValue} = require(__dirname + '/../config');
const logger = require(__dirname + '/../utils/logger');
const createNewNode = require(__dirname + '/../utils/createNewNode');

@@ -18,3 +17,4 @@ const safeImplementations = require(__dirname + '/../utils/safeImplementations');

n.type === 'CallExpression' &&
!n.arguments.filter(a => a.type !== 'Literal').length);
!n.arguments.find(a => a.type !== 'Literal'));
const candidates = callsWithOnlyLiteralArugments.filter(n =>

@@ -24,2 +24,3 @@ n.callee.type === 'Identifier' &&

!skipBuiltinFunctions.includes(n.callee.name));
candidates.push(...callsWithOnlyLiteralArugments.filter(n =>

@@ -30,5 +31,7 @@ n.callee.type === 'MemberExpression' &&

!skipProperties.includes(n.callee.property?.name || n.callee.property?.value)));
candidates.push(...arb.ast.filter(n =>
n.type === 'CallExpression' &&
availableSafeImplementations.includes((n.callee.name))));
for (const c of candidates) {

@@ -43,6 +46,6 @@ try {

if (tempValue) {
arb.markNode(c, createNewNode(tempValue, logger));
arb.markNode(c, createNewNode(tempValue));
}
} else {
const newNode = evalInVm(c.src, logger);
const newNode = evalInVm(c.src);
if (newNode !== badValue) arb.markNode(c, newNode);

@@ -49,0 +52,0 @@ }

const evalInVm = require(__dirname + '/evalInVm');
const {badValue} = require(__dirname + '/../config');
const logger = require(__dirname + '/../utils/logger');
const doesBinaryExpressionContainOnlyLiterals = require(__dirname + '/../utils/doesBinaryExpressionContainOnlyLiterals');
/**

@@ -17,4 +17,5 @@ * Resolve definite binary expressions.

doesBinaryExpressionContainOnlyLiterals(n));
for (const c of candidates) {
const newNode = evalInVm(c.src, logger);
const newNode = evalInVm(c.src);
if (newNode !== badValue) arb.markNode(c, newNode);

@@ -21,0 +22,0 @@ }

const evalInVm = require(__dirname + '/evalInVm');
const {badValue} = require(__dirname + '/../config');
const logger = require(__dirname + '/../utils/logger');

@@ -16,2 +15,3 @@ /**

n.type === 'MemberExpression' &&
n.parentNode.type !== 'UpdateExpression' && // Prevent replacing (++[[]][0]) with (++1)
(n.property.type === 'Literal' ||

@@ -21,5 +21,5 @@ (n.property.name && !n.computed)) &&

(n.object?.value?.length || n.object?.elements?.length));
for (const c of candidates) {
if (c.parentNode.type === 'UpdateExpression') continue; // Prevent replacing (++[[]][0]) with (++1)
const newNode = evalInVm(c.src, logger);
const newNode = evalInVm(c.src);
if (newNode !== badValue) arb.markNode(c, newNode);

@@ -26,0 +26,0 @@ }

const evalInVm = require(__dirname + '/evalInVm');
const logger = require(__dirname + '/../utils/logger');

@@ -15,4 +14,5 @@ /**

n.test.type === 'Literal');
for (const c of candidates) {
const newNode = evalInVm(`!!(${c.test.src});`, logger);
const newNode = evalInVm(`Boolean(${c.test.src});`);
if (newNode.type === 'Literal') {

@@ -19,0 +19,0 @@ arb.markNode(c, newNode.value ? c.consequent : c.alternate);

const {parseCode} = require('flast');
const evalInVm = require(__dirname + '/evalInVm');
const {badValue} = require(__dirname + '/../config');
const logger = require(__dirname + '/../utils/logger');

@@ -19,6 +18,7 @@ /**

n.arguments[0].type !== 'Literal');
for (const c of candidates) {
const argument = c.arguments[0];
const src = `var __a_ = ${argument.src}\n;__a_`;
const newNode = evalInVm(src, logger);
const newNode = evalInVm(src);
const targetNode = c.parentNode.type === 'ExpressionStatement' ? c.parentNode : c;

@@ -40,3 +40,3 @@ let replacementNode = newNode;

} catch {}
if (replacementNode !== badValue) {arb.markNode(targetNode, replacementNode);}
if (replacementNode !== badValue) arb.markNode(targetNode, replacementNode);
}

@@ -43,0 +43,0 @@ return arb;

const evalInVm = require(__dirname + '/evalInVm');
const {badValue} = require(__dirname + '/../config');
const logger = require(__dirname + '/../utils/logger');
const createOrderedSrc = require(__dirname + '/../utils/createOrderedSrc');

@@ -21,3 +20,4 @@ const getDeclarationWithContext = require(__dirname + '/../utils/getDeclarationWithContext');

n.operator === '=' &&
(/FunctionExpression/.test(n.right?.type) || n.right?.type === 'Identifier'));
(/FunctionExpression|Identifier/.test(n.right?.type)));
for (const c of candidates) {

@@ -30,2 +30,3 @@ const methodName = c.left.property?.name || c.left.property?.value;

(n.callee.property?.name || n.callee.property?.value) === methodName);
for (const ref of references) {

@@ -40,3 +41,3 @@ const refContext = [

const src = `${createOrderedSrc([...context, ...refContext])}\n${ref.src}`;
const newNode = evalInVm(src, logger);
const newNode = evalInVm(src);
if (newNode !== badValue) arb.markNode(ref, newNode);

@@ -43,0 +44,0 @@ }

const evalInVm = require(__dirname + '/evalInVm');
const logger = require(__dirname + '/../utils/logger');
const getCache = require(__dirname + '/../utils/getCache');
const getCalleeName = require(__dirname + '/../utils/getCalleeName');
const isNodeInRanges = require(__dirname + '/../utils/isNodeInRanges');
const createOrderedSrc = require(__dirname + '/../utils/createOrderedSrc');
const doesNodeContainRanges = require(__dirname + '/../utils/doesNodeContainRanges');
const getDeclarationWithContext = require(__dirname + '/../utils/getDeclarationWithContext');

@@ -38,4 +37,4 @@ const {badValue, badArgumentTypes, skipIdentifiers, skipProperties} = require(__dirname + '/../config');

for (const c of candidates.sort(sortByFrequency)) {
if (c.arguments.filter(a => badArgumentTypes.includes(a.type)).length) continue;
if (doesNodeContainRanges(c, modifiedRanges)) continue;
if (c.arguments.find(a => badArgumentTypes.includes(a.type))) continue;
if (isNodeInRanges(c, modifiedRanges)) continue;
const callee = c.callee?.object || c.callee;

@@ -46,3 +45,3 @@ const declNode = c.callee?.declNode || c.callee?.object?.declNode;

const returnArg = declNode.parentNode.body.body[0].argument;
if (['Literal', 'Identifier'].includes(returnArg.type) || /Function/.exec(returnArg.type)) continue; // Unwrap identifier
if (['Literal', 'Identifier'].includes(returnArg.type) || /Function/.test(returnArg.type)) continue; // Unwrap identifier
else if (returnArg.type === 'CallExpression' &&

@@ -57,3 +56,3 @@ returnArg.callee?.object?.type === 'FunctionExpression' &&

(callee.type === 'ArrayExpression' && !callee.elements.length) ||
!!(callee.arguments || []).filter(a => skipIdentifiers.includes(a) || a?.type === 'ThisExpression').length) continue;
!!(callee.arguments || []).find(a => skipIdentifiers.includes(a) || a?.type === 'ThisExpression')) continue;
if (declNode) {

@@ -69,3 +68,3 @@ // Verify the declNode isn't a simple wrapper for an identifier

const src = context ? `${context}\n${c.src}` : c.src;
const newNode = evalInVm(src, logger);
const newNode = evalInVm(src);
if (newNode !== badValue && newNode.type !== 'FunctionDeclaration') {

@@ -72,0 +71,0 @@ arb.markNode(c, newNode);

@@ -1,2 +0,1 @@

const logger = require(__dirname + '/../utils/logger');
const evalInVm = require(__dirname + '/evalInVm');

@@ -26,2 +25,3 @@ const {badValue, skipProperties} = require(__dirname + '/../config');

!skipProperties.includes(n.property?.name || n.property?.value));
for (const c of candidates) {

@@ -42,7 +42,7 @@ // If this member expression is the callee of a call expression - skip it

if (/Function/.test(declNode.parentNode.type) &&
(declNode.parentNode.params || []).filter(p => p.nodeId === declNode.nodeId).length) continue;
(declNode.parentNode.params || []).find(p => p === declNode)) continue;
const context = createOrderedSrc(getDeclarationWithContext(relevantIdentifier.declNode.parentNode));
if (context) {
const src = `${context}\n${c.src}`;
const newNode = evalInVm(src, logger);
const newNode = evalInVm(src);
if (newNode !== badValue) {

@@ -49,0 +49,0 @@ let isEmptyReplacement = false;

const evalInVm = require(__dirname + '/evalInVm');
const {badValue} = require(__dirname + '/../config');
const logger = require(__dirname + '/../utils/logger');
const getDescendants = require(__dirname + '/../utils/getDescendants');

@@ -16,3 +15,3 @@

(n.type === 'UnaryExpression' &&
((n.argument.type === 'Literal' && /^\D/.exec(n.argument.raw[0])) ||
((n.argument.type === 'Literal' && /^\D/.test(n.argument.raw[0])) ||
n.argument.type === 'ArrayExpression')) ||

@@ -23,5 +22,6 @@ (n.type === 'BinaryExpression' &&

![n.left?.type, n.right?.type].includes('ThisExpression')));
for (const c of candidates) {
if (getDescendants(c).find(n => n.type === 'ThisExpression')) continue;
const newNode = evalInVm(c.src, logger);
const newNode = evalInVm(c.src);
if (newNode !== badValue) {

@@ -28,0 +28,0 @@ arb.markNode(c, newNode);

@@ -10,10 +10,15 @@ const {propertiesThatModifyContent} = require(__dirname + '/../config');

// Verify no reference is on the left side of an assignment
return Boolean(refs.find(r => r.parentKey === 'left' &&
['AssignmentExpression', 'ForInStatement', 'ForOfStatement'].includes(r.parentNode.type)) ||
return Boolean(refs.find(r =>
(r.parentKey === 'left' && ['AssignmentExpression', 'ForInStatement', 'ForOfStatement'].includes(r.parentNode.type)) ||
// Verify no reference is part of an update expression
refs.find(r => r.parentNode.type === 'UpdateExpression') ||
r.parentNode.type === 'UpdateExpression' ||
// Verify no variable with the same name is declared in a subscope
refs.find(r => r.parentNode.type === 'VariableDeclarator' && r.parentKey === 'id') ||
(r.parentNode.type === 'VariableDeclarator' && r.parentKey === 'id') ||
// Verify no modifying calls are executed on any of the references
(r.parentNode.type === 'MemberExpression' &&
r.parentNode.parentNode.type === 'CallExpression' &&
r.parentNode.parentNode.callee?.object === r &&
propertiesThatModifyContent.includes(r.parentNode.property?.value || r.parentNode.property?.name)) ||
// Verify there are no member expressions among the references which are being assigned to
refs.find(r => r.type === 'MemberExpression' &&
(r.type === 'MemberExpression' &&
ast.find(n => n.type === 'AssignmentExpression' &&

@@ -23,10 +28,5 @@ n.left.type === 'MemberExpression' &&

(n.left.property?.name || n.left.property?.value === r.property?.name || r.property?.value) &&
(n.left.object.declNode?.nodeId && (r.object.declNode?.nodeId || r.object?.nodeId) === n.left.object.declNode.nodeId))) ||
// Verify no modifying calls are executed on any of the references
refs.find(r => r.parentNode.type === 'MemberExpression' &&
r.parentNode.parentNode.type === 'CallExpression' &&
r.parentNode.parentNode.callee?.object?.nodeId === r.nodeId &&
propertiesThatModifyContent.includes(r.parentNode.property?.value || r.parentNode.property?.name)));
(n.left.object.declNode && (r.object.declNode || r.object) === n.left.object.declNode)))));
}
module.exports = areReferencesModified;

@@ -0,1 +1,2 @@

const logger = require(__dirname + '/logger');
const {generateCode, parseCode} = require('flast');

@@ -8,6 +9,5 @@ const {badValue} = require(__dirname + '/../config');

* @param {*} value The value to be parsed into an ASTNode.
* @param {object?} logger (optional) logging functions.
* @returns {ASTNode|badValue} The newly created node if successful; badValue string otherwise.
*/
function createNewNode(value, logger = {debugErr: () => {}}) {
function createNewNode(value) {
let newNode = badValue;

@@ -99,3 +99,3 @@ try {

} catch (e) {
logger.debugErr(`[-] Unable to create a new node: ${e}`, 1);
logger.debug(`[-] Unable to create a new node: ${e}`);
}

@@ -102,0 +102,0 @@ return newNode;

@@ -1,2 +0,2 @@

const crypto = require('crypto');
const crypto = require('node:crypto');

@@ -3,0 +3,0 @@ function generateHash(script) {

@@ -27,12 +27,10 @@ const getCache = require(__dirname + '/getCache');

const collectedContext = [originNode];
const examinedNodes = [];
const examineStack = [originNode];
const collectedContextIds = [];
const collectedRanges = [];
const examinedIds = [];
while (examineStack.length) {
const relevantNode = examineStack.pop();
if (examinedIds.includes(relevantNode.nodeId)) continue;
else examinedIds.push(relevantNode.nodeId);
if (examinedNodes.includes(relevantNode)) continue;
else examinedNodes.push(relevantNode);
if (isNodeMarked(relevantNode)) continue;
collectedContextIds.push(relevantNode.nodeId);
collectedRanges.push(relevantNode.range);

@@ -91,5 +89,4 @@ let relevantScope = relevantNode.scope;

for (const rn of contextToCollect) {
if (rn && !collectedContextIds.includes(rn.nodeId) && !isNodeInRanges(rn, collectedRanges)) {
if (rn && !collectedContext.includes(rn) && !isNodeInRanges(rn, collectedRanges)) {
collectedRanges.push(rn.range);
collectedContextIds.push(rn.nodeId);
collectedContext.push(rn);

@@ -96,0 +93,0 @@ examineStack.push(rn);

@@ -7,3 +7,2 @@ module.exports = {

doesBinaryExpressionContainOnlyLiterals: require(__dirname + '/doesBinaryExpressionContainOnlyLiterals'),
doesNodeContainRanges: require(__dirname + '/doesNodeContainRanges'),
generateHash: require(__dirname + '/generateHash'),

@@ -10,0 +9,0 @@ getCache: require(__dirname + '/getCache'),

@@ -7,14 +7,39 @@ /**

*/
const logLevels = {
DEBUG: 1,
LOG: 2,
ERROR: 3,
NONE: 9e10,
};
let currentLogLevel = logLevels.NONE;
const isDebugModeOn = process.env.DEOBDEBUG === 'true' || false;
const defaultDebugLevel = 50;
let debugLevel = process.env.DEOBDEBUGLEVEL || defaultDebugLevel; // The lower the number the more verbose it is
const log = (msg, level = defaultDebugLevel) => isDebugModeOn && level >= debugLevel ? console.log(msg) : undefined;
const error = (msg, level = defaultDebugLevel) => isDebugModeOn && level >= debugLevel ? console.error(msg) : undefined;
function createLoggerForLevel(logLevel) {
if (!Object.values(logLevels).includes(logLevel)) throw new Error(`Unknown log level ${logLevel}.`);
return msg => logLevel >= currentLogLevel ? console.log(msg) : undefined;
}
const debug = createLoggerForLevel(logLevels.DEBUG);
const log = createLoggerForLevel(logLevels.LOG);
const error = createLoggerForLevel(logLevels.ERROR);
/**
* Set the current log level
* @param {number} newLogLevel
*/
function setLogLevel(newLogLevel) {
if (!Object.values(logLevels).includes(newLogLevel)) throw new Error(`Unknown log level ${newLogLevel}.`);
currentLogLevel = newLogLevel;
}
function isLogging() {
return currentLogLevel > 0;
}
module.exports = {
isDebugModeOn,
debugLevel,
currentLogLevel,
debug,
error,
isLogging,
log,
error,
logLevels,
setLogLevel,
};
const {Arborist} = require('flast');
const generateHash = require(__dirname + '/generateHash');
const defaultLogger = require(__dirname + '/../utils/logger');
const logger = require(__dirname + '/../utils/logger');
const {defaultMaxIterations} = require(__dirname + '/../config');

@@ -12,7 +12,6 @@

* @param {function[]} funcs
* @param {object?} logger (optional) logging functions.
* @param {number?} maxIterations (optional) Stop the loop after this many iterations at most.
* @return {string} The possibly modified script.
*/
function runLoop(script, funcs, maxIterations = defaultMaxIterations, logger = defaultLogger) {
function runLoop(script, funcs, maxIterations = defaultMaxIterations) {
let scriptSnapshot = '';

@@ -31,3 +30,3 @@ let currentIteration = 0;

try {
logger.log(`\t[!] Running ${func.name}...`, 1);
logger.debug(`\t[!] Running ${func.name}...`);
arborist = func(arborist);

@@ -48,4 +47,4 @@ if (!arborist.ast.length) break;

} finally {
logger.log(`\t\t[!] Running ${func.name} completed in ` +
`${((+new Date() - funcStartTime) / 1000).toFixed(3)} seconds`, 1);
logger.debug(`\t\t[!] Running ${func.name} completed in ` +
`${((+new Date() - funcStartTime) / 1000).toFixed(3)} seconds`);
}

@@ -57,3 +56,2 @@ }

` with ${changesCounter ? changesCounter : 'no'} changes (${arborist.ast.length} nodes)`);
if (maxIterations) break;
}

@@ -60,0 +58,0 @@ if (changesCounter) script = arborist.script;

@@ -1,14 +0,1 @@

const {
unsafe: {
evalInVm
},
config: {
badValue
},
utils: {
createOrderedSrc,
getDeclarationWithContext,
},
} = require(__dirname + '/../modules');
/**

@@ -30,2 +17,14 @@ * Augmented Array Replacements

*/
const {
unsafe: {
evalInVm
},
config: {
badValue
},
utils: {
createOrderedSrc,
getDeclarationWithContext,
},
} = require(__dirname + '/../modules');

@@ -45,4 +44,5 @@ /**

n.arguments[1].type === 'Literal' && !Number.isNaN(parseInt(n.arguments[1].value)));
for (const candidate of candidates) {
const relevantArrayIdentifier = candidate.arguments.filter(n => n.type === 'Identifier')[0];
const relevantArrayIdentifier = candidate.arguments.find(n => n.type === 'Identifier');
// The context for this eval is the relevant array and the IIFE augmenting it (the candidate).

@@ -49,0 +49,0 @@ const context = `var ${relevantArrayIdentifier.declNode.parentNode.src}\n!${createOrderedSrc(getDeclarationWithContext(candidate))}`;

const {unsafe: {evalWithDom}, safe: {removeDeadNodes}} = require(__dirname + '/../modules');
const {generateCode, Arborist} = require('flast');
const {Arborist} = require('flast');

@@ -37,3 +37,3 @@ const lineWithFinalAssignmentRegex = /(\w{3})\[.*]\s*=.*\((\w{3})\).*=\s*\1\s*\+\s*['"]/ms;

// We can catch the variable holding the code before it's injected and output it instead.
let script = generateCode(arb.ast[0]);
let script = arb.script;

@@ -40,0 +40,0 @@ const matches = lineWithFinalAssignmentRegex.exec(script);

@@ -0,1 +1,5 @@

/**
* Function To Array Replacements
* The obfuscated script dynamically generates an array which is referenced throughout the script.
*/
const {

@@ -15,7 +19,2 @@ unsafe: {

/**
* Function To Array Replacements
* The obfuscated script dynamically generates an array which is referenced throughout the script.
*/
/**
* Run the generating function and replace it with the actual array.

@@ -35,3 +34,4 @@ * Candidates are variables which are assigned a call expression, and every reference to them is a member expression.

n.id?.references &&
n.id?.references.filter(r => r.parentNode.type === 'MemberExpression').length === n.id?.references.length);
!n.id.references.find(r => r.parentNode.type !== 'MemberExpression'));
for (const c of candidates) {

@@ -38,0 +38,0 @@ const targetNode = c.init.callee?.declNode?.parentNode || c.init;

@@ -18,3 +18,5 @@ /**

const candidates = arb.ast.filter(n =>
n.type === 'Literal' && ['newState', 'removeCookie'].includes(n.value));
n.type === 'Literal' &&
['newState', 'removeCookie'].includes(n.value));
for (const c of candidates) {

@@ -21,0 +23,0 @@ let targetNode;

#!/usr/bin/env node
const fs = require('fs');
const processors = require(__dirname + '/processors');
const detectObfuscation = require('obfuscation-detector');
const version = require(__dirname + '/../package').version;
const detectObfuscation = require('obfuscation-detector');
const processors = require(__dirname + '/processors');
const {
utils: {
runLoop: staticRunLoop,
runLoop,
normalizeScript,

@@ -66,2 +64,4 @@ logger,

this._postprocessors = [];
this.logger = logger;
this.logger.setLogLevel(logger.logLevels.LOG); // Default log level
}

@@ -76,4 +76,4 @@

const relevantProcessors = processors[detectedObfuscationType]();
if (relevantProcessors?.preprocessors?.length) this._preprocessors = relevantProcessors.preprocessors;
if (relevantProcessors?.postprocessors?.length) this._postprocessors = relevantProcessors.postprocessors;
this._preprocessors = relevantProcessors?.preprocessors || [];
this._postprocessors = relevantProcessors?.postprocessors || [];
this.obfuscationName = detectedObfuscationType;

@@ -139,3 +139,4 @@ }

this.modified = false;
script = staticRunLoop(this.script, this._safeDeobfuscationMethods());
script = runLoop(this.script, this._safeDeobfuscationMethods());
script = runLoop(script, this._unsafeDeobfuscationMethods(), 1);
if (this.script !== script) {

@@ -145,7 +146,2 @@ this.modified = true;

}
script = staticRunLoop(this.script, this._unsafeDeobfuscationMethods(), 1);
if (this.script !== script) {
this.modified = true;
this.script = script;
}
if (this.modified) modified = true;

@@ -170,3 +166,3 @@ } while (this.modified); // Run this loop until the deobfuscation methods stop being effective.

if (this.normalize) this.script = normalizeScript(this.script);
if (clean) this.script = staticRunLoop(this.script, [removeDeadNodes]);
if (clean) this.script = runLoop(this.script, [removeDeadNodes]);
return this.modified;

@@ -181,3 +177,3 @@ }

_runProcessors(processors) {
processors.forEach(proc => this.script = staticRunLoop(this.script, [proc], 1));
processors.forEach(proc => this.script = runLoop(this.script, [proc], 1));
}

@@ -188,23 +184,26 @@ }

if (require.main === module) {
const {parseArgs, printHelp} = require(__dirname + '/utils/parseArgs');
try {
const argv = process.argv;
if (argv.length > 2) {
const inputFilename = argv[2];
let content = fs.readFileSync(inputFilename, 'utf-8');
const args = parseArgs(process.argv.slice(2));
if (Object.keys(args).length && !(args.verbose && args.quiet) && args.inputFilename) {
const fs = require('node:fs');
let content = fs.readFileSync(args.inputFilename, 'utf-8');
const startTime = Date.now();
const originalInputLength = content.length;
logger.log(`[!] Attempting to deobfuscate ${inputFilename} (length: ${originalInputLength})\n`);
logger.log(`[!] Deobfuscating ${args.inputFilename}...\n`);
const restringer = new REstringer(content);
restringer.deobfuscate(argv[3] === '--clean');
const outputFilename = `${inputFilename}-${restringer.obfuscationName}-deob.js`;
if (args.quiet) restringer.logger.setLogLevel(logger.logLevels.NONE);
else if (args.verbose) restringer.logger.setLogLevel(logger.logLevels.DEBUG);
restringer.deobfuscate();
if (restringer.modified) {
logger.log(`[+] Output saved to ${outputFilename}\n\tLength: ${restringer.script.length} ` +
`(difference is ${restringer.script.length - content.length})\n\tChanges: ${restringer.totalChangesCounter}`);
logger.log(`[!] Deobfuscation took ${(Date.now() - startTime) / 1000} seconds`);
if (logger.isDebugModeOn) fs.writeFileSync(outputFilename, restringer.script, {encoding: 'utf-8'});
logger.log(`[+] Saved ${args.outputFilename}`);
logger.log(`[!] Deobfuscation took ${(Date.now() - startTime) / 1000} seconds, with ${restringer.totalChangesCounter} changes.`);
if (args.outputToFile) fs.writeFileSync(args.outputFilename, restringer.script, {encoding: 'utf-8'});
else console.log(restringer.script);
} else logger.log(`[-] Nothing was deobfuscated ¯\\_(ツ)_/¯`);
} else console.log('Usage:\n\trestringer.js obfuscated.js \t\t# Print deobfuscated file to stdout\n\t' +
'restringer.js obfuscated.js --clean \t# Print deobfuscated file to stdout and remove dead nodes');
} else {
if (!args.inputFilename) console.log(`Input filename must be provided`);
else if (args.verbose && args.quiet) console.log(`Don't set both -q and -v at the same time *smh*`);
console.log(printHelp());
}
} catch (e) {

@@ -211,0 +210,0 @@ logger.error(`[-] Critical Error: ${e}`);

const {generateFlatAST} = require('flast');
const {badValue} = require(__dirname + '/../src/modules/config');

@@ -28,2 +29,17 @@ module.exports = [

enabled: true,
name: 'consolidateNestedBlockStatements - TP-3',
func: __dirname + '/../src/modules/safe/consolidateNestedBlockStatements',
source: `if (a) {{do_a();} do_b();}`,
expected: `if (a) {\n do_a();\n do_b();\n}`,
},
{
enabled: true,
looped: true,
name: 'consolidateNestedBlockStatements - TP-4',
func: __dirname + '/../src/modules/safe/consolidateNestedBlockStatements',
source: `if (a) {{{{{do_a();}}}} do_b();}`,
expected: `if (a) {\n do_a();\n do_b();\n}`,
},
{
enabled: true,
name: 'normalizeComputed - TP-1',

@@ -155,6 +171,6 @@ func: __dirname + '/../src/modules/safe/normalizeComputed',

enabled: true,
name: 'resolveFunctionConstructorCalls - TN-1',
name: 'resolveFunctionConstructorCalls - TP-2',
func: __dirname + '/../src/modules/safe/resolveFunctionConstructorCalls',
source: `a = Function.constructor('return /" + this + "/')().constructor('^([^ ]+( +[^ ]+)+)+[^ ]}');`,
expected: `a = Function.constructor('return /" + this + "/')().constructor('^([^ ]+( +[^ ]+)+)+[^ ]}');`,
expected: `a = function () {\n return /" + this + "/;\n}().constructor('^([^ ]+( +[^ ]+)+)+[^ ]}');`,
},

@@ -249,10 +265,10 @@ {

{
enabled: false,
reason: 'TODO: Consider proper tests for function',
name: 'evalInVm - TP-1',
enabled: true,
isUtil: true,
name: 'evalInVm - TN-1',
func: __dirname + '/../src/modules/unsafe/evalInVm',
prepareTest: () => {},
prepareResult: () => {},
source: ``,
expected: `function a(x) {\n return x + 3;\n}`,
prepareTest: a => [a],
prepareResult: b => b,
source: `Math.random();`,
expected: badValue,
},

@@ -442,2 +458,3 @@ {

enabled: true,
isUtil: true,
name: 'areReferencesModified - TP-1',

@@ -455,2 +472,3 @@ func: __dirname + '/../src/modules/utils/areReferencesModified',

enabled: true,
isUtil: true,
name: 'areReferencesModified - TP-2',

@@ -468,2 +486,3 @@ func: __dirname + '/../src/modules/utils/areReferencesModified',

enabled: true,
isUtil: true,
name: 'areReferencesModified - TN-1',

@@ -470,0 +489,0 @@ func: __dirname + '/../src/modules/utils/areReferencesModified',

@@ -7,7 +7,7 @@ module.exports = {

'Prototype Calls': 'prototypeCalls.js',
'Obfuscator.io': 'obfuscatorIo.js',
'Caesar+': 'caesar.js',
'eval(Ox$': 'evalOxd.js',
'$s': 'ds.js',
'Obfuscator.io': 'obfuscatorIo.js',
'Local Proxies': 'localProxies.js',
};

@@ -814,4 +814,4 @@ var _0x2d93 = [

var _0x5efb50 = function () {
var _0x5d91f6 = _0x5efb50.constructor('return /" + this + "/')().constructor('^([^ ]+( +[^ ]+)+)+[^ ]}');
return !_0x5d91f6.test(_0x5d8b17);
var _0x5d91f6 = /^([^ ]+( +[^ ]+)+)+[^ ]}/;
return !/^([^ ]+( +[^ ]+)+)+[^ ]}/.test(_0x5d8b17);
};

@@ -818,0 +818,0 @@ return _0x5efb50();

@@ -145,4 +145,4 @@ var _ya = [

var a = function () {
var b = a.constructor('return /" + this + "/')().compile('^([^ ]+( +[^ ]+)+)+[^ ]}');
return !b.test(_yh);
var b = /^([^ ]+( +[^ ]+)+)+[^ ]}/;
return !/^([^ ]+( +[^ ]+)+)+[^ ]}/.test(_yh);
};

@@ -203,13 +203,15 @@ return a();

if (typeof c === 'string') {
return function (e) {
}.constructor('while (true) {}').apply('counter');
return function () {
while (true) {
}
}.apply('counter');
} else {
if (('' + c / c).length !== 1 || c % 20 === 0) {
(function () {
return true;
}.constructor('debugge_').call('action'));
debugge_;
}.call('action'));
} else {
(function () {
return false;
}.constructor('debugge_').apply('stateObject'));
debugge_;
}.apply('stateObject'));
}

@@ -216,0 +218,0 @@ }

@@ -1,2 +0,2 @@

const assert = require('assert');
const assert = require('node:assert');
const {REstringer} = require(__dirname + '/..');

@@ -18,2 +18,3 @@

const restringer = new REstringer(source);
restringer.logger.setLogLevel(restringer.logger.logLevels.NONE);
restringer.deobfuscate();

@@ -20,0 +21,0 @@ assert((restringer.script === expected ||

@@ -1,3 +0,4 @@

const assert = require('assert');
const assert = require('node:assert');
const {Arborist} = require('flast');
const {runLoop, logger} = require(__dirname + '/../src/modules').utils;

@@ -20,3 +21,3 @@ const tests = {

*/
function testModule(testName, testFunc, source, expected, prepTest = defaultPrepTest, prepRes = defaultPrepRes) {
function testModuleOnce(testName, testFunc, source, expected, prepTest = defaultPrepTest, prepRes = defaultPrepRes) {
process.stdout.write(`Testing ${testName}... `);

@@ -27,9 +28,28 @@ console.time('PASS');

const result = prepRes(rawRes);
assert(result === expected,
`\n\tFAIL: deobfuscation result !== expected:\n-------------\n${result}\n\t!==\n${expected}\n-------------`);
assert.equal(result, expected);
console.timeEnd('PASS');
}
/**
* Generic function for verifying source code is deobfuscated as expected.
* @param testName {string} - The name of the test to be displayed.
* @param testFunc {function} - The tested function.
* @param source {string} - The source code to be deobfuscated.
* @param expected {string} - The expected output.
* @param prepTest {function} - (optional) Function for preparing the test input.
* @param prepRes {function} - (optional) Function for parsing the test output.
*/
function testModuleInLoop(testName, testFunc, source, expected, prepTest = null, prepRes = null) {
process.stdout.write(`Testing ${testName}... `);
console.time('PASS');
const testInput = prepTest ? prepTest(source) : source;
const rawResult = runLoop(testInput, [testFunc]);
const result = prepRes ? prepRes(rawResult) : rawResult;
assert.equal(result, expected);
console.timeEnd('PASS');
}
let allTests = 0;
let skippedTests = 0;
logger.setLogLevel(logger.logLevels.NONE);
console.time('tests in');

@@ -41,3 +61,6 @@ for (const [moduleName, moduleTests] of Object.entries(tests)) {

if (test.enabled) {
testModule(`[${moduleName}] ${test.name}`.padEnd(90, '.'), require(test.func), test.source, test.expected, test.prepareTest, test.prepareResult);
// Tests will have the `looped` flag if they only produce the desired result after consecutive runs
if (!test.looped) testModuleOnce(`[${moduleName}] ${test.name}`.padEnd(90, '.'), require(test.func), test.source, test.expected, test.prepareTest, test.prepareResult);
// Tests will have the `isUtil` flag if they do not return an Arborist instance (i.e. can't use runLoop)
if (!test.isUtil) testModuleInLoop(`[${moduleName}] ${test.name} (looped)`.padEnd(90, '.'), require(test.func), test.source, test.expected, test.prepareTest, test.prepareResult);
} else {

@@ -44,0 +67,0 @@ skippedTests++;

@@ -1,7 +0,6 @@

const fs = require('fs');
const assert = require('assert');
const fs = require('node:fs');
const assert = require('node:assert');
const {REstringer} = require(__dirname + '/..');
const obfuscatedSamples = require(__dirname + '/obfuscated-samples');
const {REstringer} = require(__dirname + '/..');
const resourcePath = __dirname + '/resources';

@@ -16,6 +15,6 @@

const restringer = new REstringer(obfuscatedSource);
restringer.logger.setLogLevel(restringer.logger.logLevels.NONE);
restringer.deobfuscate();
const deobfuscationResult = restringer.script.replace(/[\n\r]/g, ' ').replace(/\s{2,}/g, ' ');
assert(deobfuscationResult === deobfuscatedTarget,
`Deobfuscation result of '${testSampleName}' does not match the expected result!\nEXPECTED:\n${deobfuscatedTarget}\n\nOUTPUT:\n${deobfuscationResult}`);
assert.equal(deobfuscationResult, deobfuscatedTarget);
console.timeEnd(' PASS');

@@ -22,0 +21,0 @@ }

@@ -1,3 +0,3 @@

const assert = require('assert');
const {generateFlatAST, generateCode, Arborist} = require('flast');
const {Arborist} = require('flast');
const assert = require('node:assert');

@@ -23,9 +23,7 @@ const tests = {

console.time('PASS');
const testInput = prepTest(source);
let rawRes = testInput;
let rawRes = prepTest(source);
testProcs.preprocessors.forEach(proc => rawRes = proc(...(Array.isArray(rawRes) ? rawRes : [rawRes])));
testProcs.postprocessors.forEach(proc => rawRes = proc(...(Array.isArray(rawRes) ? rawRes : [rawRes])));
const result = prepRes(rawRes);
assert(result === expected,
`\n\tFAIL: deobfuscation result !== expected:\n-------------\n${result}\n\t!==\n${expected}\n-------------`);
assert.equal(result, expected);
console.timeEnd('PASS');

@@ -32,0 +30,0 @@ }

@@ -7,2 +7,3 @@ const availableTests = {

};
console.time('\nAll tests completed in');

@@ -9,0 +10,0 @@ let exception = '';

SocketSocket SOC 2 Logo

Product

  • Package Alerts
  • Integrations
  • Docs
  • Pricing
  • FAQ
  • Roadmap
  • Changelog

Packages

npm

Stay in touch

Get open source security insights delivered straight into your inbox.


  • Terms
  • Privacy
  • Security

Made with ⚡️ by Socket Inc