Skip to content

Commit

Permalink
v1.1.3 Refactor and Optimize (#6)
Browse files Browse the repository at this point in the history
* Refactor test code to a function.

* Add test timing

* Cosmetics

* Refactor and optimize

* 1.1.3
  • Loading branch information
BenBaryoPX committed Jun 20, 2023
1 parent 4a2bba9 commit 13a1812
Show file tree
Hide file tree
Showing 13 changed files with 147 additions and 149 deletions.
4 changes: 2 additions & 2 deletions package-lock.json

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

2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "obfuscation-detector",
"version": "1.1.2",
"version": "1.1.3",
"description": "Javascript obfuscation detector",
"main": "src/index.js",
"directories": {
Expand Down
6 changes: 1 addition & 5 deletions src/detectors/arrayFunctionReplacements.js
Original file line number Diff line number Diff line change
@@ -1,5 +1,3 @@
// noinspection JSValidateJSDoc

const {
findArrayDeclarationCandidates,
functionHasMinimumRequiredReferences,
Expand Down Expand Up @@ -30,6 +28,4 @@ function detectArrayFunctionReplacements(flatTree) {
return '';
}

try {
module.exports = detectArrayFunctionReplacements;
} catch {}
module.exports = detectArrayFunctionReplacements;
13 changes: 6 additions & 7 deletions src/detectors/arrayReplacements.js
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
// noinspection JSValidateJSDoc

const {findArrayDeclarationCandidates, arrayHasMinimumRequiredReferences} = require(__dirname + '/sharedDetectionMethods');
const {
arrayHasMinimumRequiredReferences,
findArrayDeclarationCandidates,
} = require(__dirname + '/sharedDetectionMethods');

const obfuscationName = 'array_replacements';

Expand All @@ -17,11 +18,9 @@ function detectArrayReplacements(flatTree) {

for (const c of candidates) {
const refs = c.id.references.map(n => n.parentNode);
if (arrayHasMinimumRequiredReferences(refs, c, flatTree)) return obfuscationName;
if (arrayHasMinimumRequiredReferences(refs, c.id.name, flatTree)) return obfuscationName;
}
return '';
}

try {
module.exports = detectArrayReplacements;
} catch {}
module.exports = detectArrayReplacements;
13 changes: 4 additions & 9 deletions src/detectors/augmentedArrayFunctionReplacements.js
Original file line number Diff line number Diff line change
@@ -1,12 +1,9 @@
// noinspection JSValidateJSDoc

const {
findArrayDeclarationCandidates,
arrayIsProvidedAsArgumentToIIFE,
functionHasMinimumRequiredReferences
findArrayDeclarationCandidates,
functionHasMinimumRequiredReferences,
} = require(__dirname + '/sharedDetectionMethods');


const obfuscationName = 'augmented_array_function_replacements';

/**
Expand All @@ -22,14 +19,12 @@ function detectAugmentedArrayFunctionReplacements(flatTree) {
for (const c of candidates) {
if (c.id.references.length > 2) continue;
const refs = c.id.references.map(n => n.parentNode);
if (!arrayIsProvidedAsArgumentToIIFE(refs, c)) continue;
if (!arrayIsProvidedAsArgumentToIIFE(refs, c.id.name)) continue;
for (const ref of c.id.references) {
if (functionHasMinimumRequiredReferences(ref, flatTree)) return obfuscationName;
}
}
return '';
}

try {
module.exports = detectAugmentedArrayFunctionReplacements;
} catch {}
module.exports = detectAugmentedArrayFunctionReplacements;
15 changes: 5 additions & 10 deletions src/detectors/augmentedArrayReplacements.js
Original file line number Diff line number Diff line change
@@ -1,12 +1,9 @@
// noinspection JSValidateJSDoc

const {
findArrayDeclarationCandidates,
arrayHasMinimumRequiredReferences,
arrayIsProvidedAsArgumentToIIFE,
arrayHasMinimumRequiredReferences
findArrayDeclarationCandidates,
} = require(__dirname + '/sharedDetectionMethods');


const obfuscationName = 'augmented_array_replacements';

/**
Expand All @@ -22,12 +19,10 @@ function detectAugmentedArrayReplacements(flatTree) {
for (const c of candidates) {
const refs = c.id.references.map(n => n.parentNode);
// Verify the IIFE exists and has the candidate array as one of its arguments.
if (arrayIsProvidedAsArgumentToIIFE(refs, c) &&
arrayHasMinimumRequiredReferences(refs, c, flatTree)) return obfuscationName;
if (arrayIsProvidedAsArgumentToIIFE(refs, c.id.name) &&
arrayHasMinimumRequiredReferences(refs, c.id.name, flatTree)) return obfuscationName;
}
return '';
}

try {
module.exports = detectAugmentedArrayReplacements;
} catch {}
module.exports = detectAugmentedArrayReplacements;
37 changes: 21 additions & 16 deletions src/detectors/augmentedProxiedArrayFunctionReplacements.js
Original file line number Diff line number Diff line change
@@ -1,5 +1,14 @@
const obfuscationName = 'augmented_proxied_array_function_replacements';

/**
* @param {ASTNode} node
* @param {string} refName
* @return {boolean}
*/
function isCallExpressionWithNamedReferenceArgument(node, refName) {
return node?.type === 'CallExpression' && (node.arguments|| []).some(a => a?.name === refName);
}

/**
* Augmented Proxied Array-Function Replacements obfuscation type has the following characteristics:
* - Has at least 3 root nodes - the last one containing the actual obfuscated code and the rest are obfuscation code.
Expand All @@ -9,28 +18,24 @@ const obfuscationName = 'augmented_proxied_array_function_replacements';
* @return {string} The obfuscation name if detected; empty string otherwise.
*/
function detectAugmentedProxiedArrayFunctionReplacements(flatTree) {
const roots = flatTree.filter(n => n.parentNode?.type === 'Program');
const roots = flatTree[0].childNodes;
if (roots.length >= 3) {
const arrFunc = roots.find(n => n.type === 'FunctionDeclaration' &&
const arrFunc = roots.find(n =>
n.body?.body?.length &&
n.body.body.slice(-1)[0]?.argument?.callee?.name === n?.id?.name);
n.body.body.slice(-1)[0]?.argument?.callee?.name === n?.id?.name &&
n.type === 'FunctionDeclaration');

if (arrFunc) {
const augFunc = roots.find(n => n.type === 'ExpressionStatement' &&
((
n.expression.type === 'CallExpression' &&
n.expression.arguments.find(a => a?.name === arrFunc.id.name)) ||
const arrFuncName = arrFunc.id.name;
if (roots.some(n =>
n.type === 'ExpressionStatement' &&
(isCallExpressionWithNamedReferenceArgument(n.expression, arrFuncName) ||
n.expression.type === 'SequenceExpression' &&
n.expression.expressions[0].type === 'CallExpression' &&
n.expression.expressions[0].arguments.find(a => a?.name === arrFunc.id.name)
));
if (augFunc) {
return obfuscationName;
}
isCallExpressionWithNamedReferenceArgument(n.expression.expressions[0], arrFuncName))
)) return obfuscationName;
}
}
return '';
}

try {
module.exports = detectAugmentedProxiedArrayFunctionReplacements;
} catch {}
module.exports = detectAugmentedProxiedArrayFunctionReplacements;
29 changes: 13 additions & 16 deletions src/detectors/caesarp.js
Original file line number Diff line number Diff line change
@@ -1,17 +1,15 @@
// noinspection JSValidateJSDoc

const obfuscationName = 'caesar_plus';

/**
* @param {ASTNode} targetNode
* @param {Scope} targetScope
* @param {ASTScope} targetScope
* @return {boolean} true if the target node is found in the targetScope; false otherwise.
*/
function isNodeInScope(targetNode, targetScope) {
if (!targetScope) return true;
let currentScope = targetNode.scope;
while (currentScope.scopeId) {
if (targetScope === currentScope.scopeId) return true;
if (targetScope === currentScope) return true;
currentScope = currentScope.upper;
}
return false;
Expand All @@ -33,25 +31,24 @@ function detectCaesarPlus(flatTree) {
// Verify the main function's name is 3 letters long and has maximum 1 reference;
const candidates = flatTree.filter(n =>
n.type === 'FunctionExpression' &&
n.id && n.id.name.length === 3 &&
n.parentNode && n.parentNode.type === 'CallExpression' && !n.parentNode.arguments.length);
n?.id?.name?.length === 3 &&
n?.parentNode?.type === 'CallExpression' && !n.parentNode.arguments.length);

for (const c of candidates) {
const funcTree = flatTree.filter(n => isNodeInScope(n, c.scope.scopeId));
const funcTree = flatTree.filter(n => isNodeInScope(n, c.scope));
// Verify all variables are 3 letters long
if (!funcTree.filter(n => n.type === 'VariableDeclarator' &&
n.id.name.length !== 3).length) {
if (!funcTree.some(n => n.type === 'VariableDeclarator' &&
n.id.name.length !== 3)) {
// Verify that inside the function there are references to window, document and String.fromCharCode;
if (funcTree.filter(n => n.type === 'Identifier' && n.name === 'window').length &&
funcTree.filter(n => n.type === 'Identifier' && n.name === 'document').length &&
funcTree.filter(n => n.type === 'MemberExpression' &&
if (funcTree.some(n => n.type === 'Identifier' && n.name === 'window') &&
funcTree.some(n => n.type === 'Identifier' && n.name === 'document') &&
funcTree.some(n => n.type === 'MemberExpression' &&
n.object.type === 'Identifier' &&
n.object.name === 'String' && [n.property.name, n.property.value].includes('fromCharCode')).length) return obfuscationName;
n.object.name === 'String' &&
'fromCharCode' === (n.property.name || n.property.value))) return obfuscationName;
}
}
return '';
}

try {
module.exports = detectCaesarPlus;
} catch {}
module.exports = detectCaesarPlus;
20 changes: 7 additions & 13 deletions src/detectors/functionToArrayReplacements.js
Original file line number Diff line number Diff line change
@@ -1,5 +1,3 @@
// noinspection JSValidateJSDoc

const obfuscationName = 'function_to_array_replacements';

/**
Expand All @@ -11,17 +9,13 @@ const obfuscationName = 'function_to_array_replacements';
* @return {string} The obfuscation name if detected; empty string otherwise.
*/
function detectFunctionToArrayReplacemets(flatTree) {
return flatTree.filter(n =>
return flatTree.some(n =>
n.type === 'VariableDeclarator' &&
n.init && n.init.type === 'CallExpression' &&
/function/i.test(n.init.callee.type) &&
n.id && n.id.references &&
n.id.references.filter(r =>
r.parentNode.type === 'MemberExpression' &&
r.parentKey === 'object').length === n.id.references.length)
.length ? obfuscationName : '';
n?.init?.callee?.type?.indexOf('unction') > -1 &&
n?.id?.references?.length &&
!n.id.references.some(r =>
!(r.parentNode.type === 'MemberExpression' &&
r.parentKey === 'object'))) ? obfuscationName : '';
}

try {
module.exports = detectFunctionToArrayReplacemets;
} catch {}
module.exports = detectFunctionToArrayReplacemets;
38 changes: 18 additions & 20 deletions src/detectors/obfuscator-io.js
Original file line number Diff line number Diff line change
@@ -1,21 +1,19 @@
// noinspection JSValidateJSDoc

const obfuscationName = 'obfuscator.io';

function setCookieIndicator(flatTree) {
const candidates = flatTree.filter(n =>
const candidate = flatTree.find(n =>
n.type === 'ObjectExpression' &&
n.properties.length &&
n.properties.filter(p =>
n.properties.some(p =>
p.key.type === 'Literal' &&
p.key.value === 'setCookie').length);
p.key.value === 'setCookie'));

if (candidates.length === 1) {
const setCookieFunc = candidates[0].properties.filter(p =>
if (candidate) {
const setCookieFunc = candidate.properties.find(p =>
p.key.type === 'Literal' &&
p.key.value === 'setCookie')[0].value;
if (setCookieFunc.type === 'FunctionExpression' &&
setCookieFunc.body.body.filter(b => b.type === 'ForStatement').length) return true;
p.key.value === 'setCookie')?.value;
if (setCookieFunc?.type === 'FunctionExpression' &&
setCookieFunc.body.body.some(b => b.type === 'ForStatement')) return true;
}
return false;
}
Expand All @@ -27,10 +25,15 @@ function notBooleanTilde(flatTree) {
n.body[0].type === 'IfStatement' &&
n.body[0].test?.type === 'UnaryExpression' &&
n.body[1].type === 'ReturnStatement');

for (const c of candidates) {
/** @type {ASTNode} */
const t = c.body[0].test;
if (t.operator === '!' && t.argument?.callee?.name === 'Boolean' && t.argument.arguments?.length === 1 &&
t.argument.arguments[0].type === 'UnaryExpression' && t.argument.arguments[0].operator === '~') return true;
if (t.operator === '!' &&
t.argument?.callee?.name === 'Boolean' &&
t.argument.arguments?.length === 1 &&
t.argument.arguments[0].type === 'UnaryExpression' &&
t.argument.arguments[0].operator === '~') return true;
}
return false;
}
Expand All @@ -46,13 +49,8 @@ function notBooleanTilde(flatTree) {
* @return {string} The obfuscation name if detected; empty string otherwise.
*/
function detectObfuscatorIo(flatTree, pdo = []) {
// Older version
if (pdo.includes('augmented_array_function_replacements') && setCookieIndicator(flatTree)) return obfuscationName;
// Newer version
else if (notBooleanTilde(flatTree)) return obfuscationName;
return '';
return (pdo.includes('augmented_array_function_replacements') && setCookieIndicator(flatTree)) ||
notBooleanTilde(flatTree) ? obfuscationName : '';
}

try {
module.exports = detectObfuscatorIo;
} catch {}
module.exports = detectObfuscatorIo;
Loading

0 comments on commit 13a1812

Please sign in to comment.