restringer
Advanced tools
Comparing version 1.6.0 to 1.6.1
{ | ||
"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; |
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
627621
11293