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

restringer

Package Overview
Dependencies
Maintainers
2
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.6.0 to 1.6.1

2

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

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

@@ -10,3 +10,2 @@ const operators = ['+', '-', '*', '/', '%', '&', '|', '&&', '||', '**', '^'];

function matchBinaryOrLogical(n) {
// noinspection JSUnresolvedVariable
return ['LogicalExpression', 'BinaryExpression'].includes(n.type) &&

@@ -44,3 +43,2 @@ operators.includes(n.operator) &&

function matchUnary(n) {
// noinspection JSUnresolvedVariable
return n.type === 'UnaryExpression' &&

@@ -47,0 +45,0 @@ fixes.includes(n.operator) &&

@@ -44,3 +44,3 @@ const {VM} = require('vm2');

* @param {string} stringToEval
* @return {string|ASTNode} A node based on the eval result if successful; badValue string otherwise.
* @return {ASTNode|badValue} A node based on the eval result if successful; badValue string otherwise.
*/

@@ -56,2 +56,3 @@ function evalInVm(stringToEval) {

const res = (new VM(vmOptions)).run(stringToEval);
// noinspection JSUnresolvedVariable
if (!res?.VMError && !badTypes.includes(getObjType(res))) {

@@ -58,0 +59,0 @@ // To exclude results based on randomness or timing, eval again and compare results

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

function resolveFunctionToArray(arb) {
// noinspection DuplicatedCode
const candidates = arb.ast.filter(n =>

@@ -33,3 +32,4 @@ n.type === 'VariableDeclarator' &&

const targetNode = c.init.callee?.declNode?.parentNode || c.init;
const src = createOrderedSrc(getDeclarationWithContext(targetNode)) + `\n${createOrderedSrc([c.init])}`;
const isContained = [c.init, c.init?.parentNode].includes(targetNode);
const src = createOrderedSrc(getDeclarationWithContext(targetNode, isContained)) + `\n${createOrderedSrc([c.init])}`;
const newNode = evalInVm(src);

@@ -36,0 +36,0 @@ if (newNode !== badValue) {

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

if (n.parentNode.type === 'ExpressionStatement') {
// noinspection JSValidateTypes
nodes[idx] = n.parentNode;

@@ -36,2 +35,12 @@ if (!preserveOrder && n.callee.type === 'FunctionExpression') {

}
} else if (n.type === 'FunctionExpression' && !n.id) {
if (n.parentNode.type === 'VariableDeclarator') {
const funcStartRegexp = new RegExp('function[^(]*');
const funcSrc = n.src.replace(funcStartRegexp, 'function ' + n.parentNode.id.name);
const newNode = generateFlatAST(`(${funcSrc});`)[1];
if (newNode) {
newNode.nodeId = n.nodeId;
nodes[idx] = newNode;
}
}
}

@@ -38,0 +47,0 @@ });

const getCache = require(__dirname + '/getCache');
const generateHash = require(__dirname + '/generateHash');
const isNodeMarked = require(__dirname + '/isNodeMarked');
const isNodeInRanges = require(__dirname + '/isNodeInRanges');
const getDescendants = require(__dirname + '/../utils/getDescendants');
const {propertiesThatModifyContent} = require(__dirname + '/../config');
const skipCollectionTypes = [
// Types that give no context by themselves
const irrelevantTypesToBeFilteredOut = [
'Literal',

@@ -13,9 +14,63 @@ 'Identifier',

// Relevant types for giving context
const typesToCollect = [
'CallExpression',
'ArrowFunctionExpression',
'AssignmentExpression',
'FunctionDeclaration',
'FunctionExpression',
'VariableDeclarator',
];
// Child nodes that can be skipped as they give no context
const irrelevantTypesToAvoidIteratingOver = [
'Literal',
'ThisExpression',
];
// Direct child nodes of an if statement
const ifKeys = ['consequent', 'alternate'];
/**
*
* @param {ASTNode} targetNode
* @return {boolean} True if any of the descendants are marked for modification; false otherwise.
*/
function areDescendantsModified(targetNode) {
for (const n of getDescendants(targetNode)) if (n.isMarked) return true;
return false;
}
/**
* @param {ASTNode} targetNode
* @return {boolean} True if the target node is directly under an if statement; false otherwise
*/
function isConsequentOrAlternate(targetNode) {
return targetNode.parentNode.type === 'IfStatement' ||
ifKeys.includes(targetNode.parentKey) ||
ifKeys.includes(targetNode.parentNode.parentKey) ||
(targetNode.parentNode.parentNode.type === 'BlockStatement' && ifKeys.includes(targetNode.parentNode.parentNode.parentKey));
}
/**
* @param {ASTNode} n
* @return {boolean} True if the target node is the object of a member expression
* and its property is being assigned to; false otherwise.
*/
function isNodeAnAssignmentToProperty(n) {
return n.parentNode.type === 'MemberExpression' &&
!isConsequentOrAlternate(n.parentNode) &&
((n.parentNode.parentNode.type === 'AssignmentExpression' && // e.g. targetNode.prop = value
n.parentNode.parentKey === 'left') ||
(n.parentKey === 'object' && // e.g. targetNode.push(value) <-- this changes the value of targetNode
(propertiesThatModifyContent.includes(n.parentNode.property?.value || n.parentNode.property.name) ||
n.parentNode.property.isMarked))); // Collect references which are marked, so they will prevent the context from collecting
}
/**
* @param {ASTNode} originNode
* @param {boolean} [excludeOriginNode] (optional) Do not return the originNode. Defaults to false.
* @return {ASTNode[]} A flat array of all available declarations and call expressions relevant to
* the context of the origin node.
*/
function getDeclarationWithContext(originNode) {
function getDeclarationWithContext(originNode, excludeOriginNode = false) {
const cache = getCache(originNode.scriptHash);

@@ -27,92 +82,61 @@ const srcHash = generateHash(originNode.src);

if (!cached) {
const collectedContext = [originNode];
const examinedNodes = [];
const examineStack = [originNode];
const collectedRanges = [];
while (examineStack.length) {
const relevantNode = examineStack.pop();
if (examinedNodes.includes(relevantNode)) continue;
else examinedNodes.push(relevantNode);
if (isNodeMarked(relevantNode)) continue;
collectedRanges.push(relevantNode.range);
let relevantScope = relevantNode.scope;
const assignments = [];
const references = [];
switch (relevantNode.type) {
case 'VariableDeclarator':
relevantScope = relevantNode.init?.scope || relevantNode.id.scope;
// Collect direct assignments
assignments.push(...relevantNode.id.references.filter(r =>
const stack = [originNode]; // The working stack for nodes to be reviewed
const collected = []; // These will be our context
const seenNodes = []; // Collected to avoid re-iterating over the same nodes
const collectedRanges = []; // Collected to prevent collecting nodes from within collected nodes.
while (stack.length) {
const node = stack.shift();
if (seenNodes.includes(node)) continue;
seenNodes.push(node);
// Do not collect any context if one of the relevant nodes is marked to be replaced or deleted
if (node.isMarked || areDescendantsModified(node)) {
collected.length = 0;
break;
}
if (typesToCollect.includes(node.type) && !isNodeInRanges(node, collectedRanges)) {
collected.push(node);
collectedRanges.push(node.range);
}
// For each node, whether collected or not, target relevant relative nodes for further review.
const targetNodes = [node];
switch (node.type) {
case 'Identifier': {
const refs = node.references || [];
// Review the declaration of an identifier
if (node.declNode && node.declNode.parentNode) targetNodes.push(node.declNode.parentNode);
else if (refs.length && node.parentNode) targetNodes.push(node.parentNode);
// Review call expression that receive the identifier as an argument for possible augmenting functions
targetNodes.push(...refs.filter(r =>
r.parentNode.type === 'CallExpression' &&
r.parentKey === 'arguments')
.map(r => r.parentNode));
// Review direct assignments to the identifier
targetNodes.push(...refs.filter(r =>
r.parentNode.type === 'AssignmentExpression' &&
r.parentKey === 'left')
r.parentKey === 'left' &&
!isConsequentOrAlternate(r))
.map(r => r.parentNode));
// Collect assignments to variable properties
assignments.push(...relevantNode.id.references.filter(r =>
r.parentNode.type === 'MemberExpression' &&
((r.parentNode.parentNode.type === 'AssignmentExpression' &&
r.parentNode.parentKey === 'left') ||
(r.parentKey === 'object' &&
propertiesThatModifyContent.includes(r.parentNode.property?.value || r.parentNode.property.name))))
// Review assignments to property
targetNodes.push(...refs.filter(isNodeAnAssignmentToProperty)
.map(r => r.parentNode.parentNode));
// Find augmenting functions
references.push(...relevantNode.id.references.filter(r =>
r.parentNode.type === 'CallExpression' &&
r.parentKey === 'arguments')
.map(r => r.parentNode));
break;
case 'AssignmentExpression':
relevantScope = relevantNode.right?.scope;
examineStack.push(relevantNode.right);
break;
case 'CallExpression':
relevantScope = relevantNode.callee.scope;
references.push(...relevantNode.arguments.filter(a => a.type === 'Identifier'));
examineStack.push(relevantNode.callee);
break;
}
case 'MemberExpression':
relevantScope = relevantNode.object.scope;
examineStack.push(relevantNode.object, relevantNode.property);
if (node.property?.declNode) targetNodes.push(node.property.declNode);
break;
case 'Identifier': {
let actualNode;
if (relevantNode.declNode) actualNode = relevantNode.declNode;
else if (relevantNode.parentKey === 'id') {
switch (relevantNode.parentNode.type) {
case 'FunctionDeclaration':
actualNode = relevantNode;
break;
case 'VariableDeclarator':
if (/Function/.exec(relevantNode.parentNode?.init?.type)) actualNode = relevantNode;
break;
}
}
if (actualNode) {
relevantScope = actualNode.scope;
references.push(actualNode.parentNode);
}
break;
}
case 'FunctionExpression':
// Review the parent node of anonymous functions
if (!node.id) targetNodes.push(node.parentNode);
}
// noinspection JSUnresolvedVariable
const contextToCollect = [
...new Set(
relevantScope.through.map(ref => ref.identifier?.declNode?.parentNode)
.concat(assignments)
.concat(references)
)].map(ref => ref?.declNode ? ref.declNode : ref);
for (const rn of contextToCollect) {
if (rn && !collectedContext.includes(rn)) {
if (/Function/.exec(rn.type) || (!isNodeInRanges(rn, collectedRanges) || (rn.declNode && !isNodeInRanges(rn.declNode, collectedRanges)))) {
if (rn.scope.scopeId > 0 && rn.scope.block !== rn && rn.scope !== relevantScope) {
examineStack.push(rn.scope.block);
collectedContext.push(rn);
} else {
collectedRanges.push(rn.range);
collectedContext.push(rn);
examineStack.push(rn);
for (const cn of (rn.childNodes || [])) {
examineStack.push(cn);
}
}
for (const targetNode of targetNodes) {
if (!seenNodes.includes(targetNode)) stack.push(targetNode);
for (const childNode of targetNode.childNodes) {
if (
!seenNodes.includes(childNode) &&
!stack.includes(childNode) &&
!irrelevantTypesToAvoidIteratingOver.includes(childNode.type)
) {
stack.push(childNode);
}

@@ -122,3 +146,12 @@ }

}
cached = [...new Set(collectedContext.filter(n => !skipCollectionTypes.includes(n.type)))];
cached = [...new Set(collected.filter(n => !irrelevantTypesToBeFilteredOut.includes(n.type)))];
if (excludeOriginNode) cached = cached.filter(n => !isNodeInRanges(n, [originNode.range]));
// A fix to ignore reassignments in cases where functions are overwritten as part of an anti-debugging mechanism
const functionNameReassignment = [];
cached.filter(n =>
n.type === 'FunctionDeclaration' &&
n.id && (n.id.references || []).filter(r =>
r.parentNode.type === 'AssignmentExpression' &&
r.parentKey === 'left').forEach(ref => functionNameReassignment.push(ref.parentNode)));
if (functionNameReassignment.length) cached = cached.filter(n => !functionNameReassignment.includes(n));
cache[cacheNameId] = cached; // Caching context for the same node

@@ -125,0 +158,0 @@ cache[cacheNameSrc] = cached; // Caching context for a different node with similar content

@@ -46,2 +46,6 @@ /**

for (const c of candidates) {
let targetNode = c;
while (targetNode && targetNode.type !== 'ExpressionStatement') {
targetNode = targetNode?.parentNode;
}
const relevantArrayIdentifier = c.arguments.find(n => n.type === 'Identifier');

@@ -51,12 +55,9 @@ const declKind = /function/i.test(relevantArrayIdentifier.declNode.parentNode.type) ? '' : 'var ';

// The context for this eval is the relevant array and the IIFE augmenting it (the candidate).
const context = `${declKind}${relevantArrayIdentifier.declNode.parentNode.src}\n!${createOrderedSrc(getDeclarationWithContext(c))}`;
const contextNodes = getDeclarationWithContext(c, true);
const context = `${contextNodes.length ? createOrderedSrc(contextNodes) : ''}`;
// By adding the name of the array after the context, the un-shuffled array is procured.
const src = `${context};\n${ref};`;
const src = `${context};\n${targetNode.src}\n${ref};`;
const newNode = evalInVm(src); // The new node will hold the un-shuffled array's assignment
if (newNode !== badValue) {
let candidateExpression = c;
while (candidateExpression && candidateExpression.type !== 'ExpressionStatement') {
candidateExpression = candidateExpression?.parentNode;
}
arb.markNode(candidateExpression ? candidateExpression : c);
arb.markNode(targetNode || c);
if (relevantArrayIdentifier.declNode.parentNode.type === 'FunctionDeclaration') {

@@ -63,0 +64,0 @@ arb.markNode(relevantArrayIdentifier.declNode.parentNode.body, {

@@ -51,3 +51,2 @@ #!/usr/bin/env node

resolveEvalCallsOnNonLiterals,
resolveFunctionToArray,
},

@@ -134,3 +133,2 @@ config: {

return [
resolveFunctionToArray,
resolveMinimalAlphabet,

@@ -183,3 +181,3 @@ resolveDefiniteBinaryExpressions,

this._runProcessors(this._postprocessors);
if (this.normalize) this.script = normalizeScript(this.script);
if (this.modified && this.normalize) this.script = normalizeScript(this.script);
if (clean) this.script = runLoop(this.script, [removeDeadNodes]);

@@ -186,0 +184,0 @@ return this.modified;

@@ -0,0 +0,0 @@ /*

@@ -14,13 +14,19 @@ module.exports = [

})(arr, 3);
console.log(arr.join(' '));`,
expected: `const arr = [\n 1,\n 2,\n 3,\n 4,\n 5,\n 6,\n 7,\n 8,\n 9,\n 10,\n 'a',\n 'b',\n 'c'\n];
(function (targetArray, numberOfShifts) {
var augmentArray = function (counter) {
while (--counter) {
targetArray.push(targetArray.shift());
}
};
augmentArray(++numberOfShifts);
}(arr, 3));
console.log('4 5 6 7 8 9 10 a b c 1 2 3');`,
console.log(arr[7], arr[8]);`,
expected: `const arr = [
4,
5,
6,
7,
8,
9,
10,
'a',
'b',
'c',
1,
2,
3
];
console.log('a', 'b');`,
},

@@ -342,8 +348,2 @@ {

enabled: true,
name: 'Uncompute Member Expressions: Literal -> Identifier',
source: `console['log']`,
expected: `console.log;`,
},
{
enabled: true,
name: 'Unwrap Function Shells',

@@ -350,0 +350,0 @@ source: `function O() {return function () {return clearInterval;}.apply(this, arguments);}`,

@@ -554,3 +554,3 @@ var _0x3378 = [

if (!_0x56b1e2('json')) {
var _0x39b9bf = false;
var _0x39b9bf = _0x56b1e2('bug-string-char-index');
if (!_0x3b41d8) {

@@ -660,3 +660,3 @@ var _0x47a41c = _0x5002c2.floor;

var _0x33aa4c = function (_0x363abc) {
for (var _0x2143a1 = '"', _0x18c7f2 = 0, _0x7f9ff0 = _0x363abc.length, _0x1fc035 = true || 10 < _0x7f9ff0, _0x4ca1ea = _0x1fc035 && _0x363abc; _0x18c7f2 < _0x7f9ff0; _0x18c7f2++) {
for (var _0x2143a1 = '"', _0x18c7f2 = 0, _0x7f9ff0 = _0x363abc.length, _0x1fc035 = !_0x39b9bf || 10 < _0x7f9ff0, _0x4ca1ea = _0x1fc035 && (_0x39b9bf ? _0x363abc.split('') : _0x363abc); _0x18c7f2 < _0x7f9ff0; _0x18c7f2++) {
var _0x3f4acc = _0x363abc.charCodeAt(_0x18c7f2);

@@ -821,3 +821,3 @@ switch (_0x3f4acc) {

case 44: {
_0x58335a = _0x5e15d0[_0x16aa5b];
_0x58335a = _0x39b9bf ? _0x5e15d0.charAt(_0x16aa5b) : _0x5e15d0[_0x16aa5b];
_0x16aa5b++;

@@ -927,3 +927,3 @@ return _0x58335a;

if ('string' == typeof _0x4c848e) {
if ('@' == _0x4c848e[0])
if ('@' == (_0x39b9bf ? _0x4c848e.charAt(0) : _0x4c848e[0]))
return _0x4c848e.slice(1);

@@ -953,3 +953,3 @@ if ('[' == _0x4c848e) {

}
if (!(',' != _0x4c848e && 'string' == typeof _0x4c848e && '@' == _0x4c848e[0] && ':' == _0x2868d4())) {
if (!(',' != _0x4c848e && 'string' == typeof _0x4c848e && '@' == (_0x39b9bf ? _0x4c848e.charAt(0) : _0x4c848e[0]) && ':' == _0x2868d4())) {
_0x2614a6();

@@ -956,0 +956,0 @@ }

@@ -152,3 +152,3 @@ var _ya = [

});
_yh();
true;
var _yi = function () {

@@ -155,0 +155,0 @@ var a = true;

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