From 394c221c0504db46d4201dca4e16100dcb0f3ecf Mon Sep 17 00:00:00 2001 From: Ben Baryo <60312583+BenBaryoPX@users.noreply.github.com> Date: Sun, 3 Nov 2024 16:59:27 +0200 Subject: [PATCH] Fix and improve unwrapSimpleOperations (#124) * Add missing operations * Handle update expressions as well * Verify refs are call expressions before replacement * Add tests for unary and update operations * Remove completed TODO --- src/modules/safe/unwrapSimpleOperations.js | 22 +++++++++---------- tests/modules.test.js | 25 +++++++++++++++++++++- tests/samples.test.js | 3 --- 3 files changed, 34 insertions(+), 16 deletions(-) diff --git a/src/modules/safe/unwrapSimpleOperations.js b/src/modules/safe/unwrapSimpleOperations.js index 8868246..29c505a 100644 --- a/src/modules/safe/unwrapSimpleOperations.js +++ b/src/modules/safe/unwrapSimpleOperations.js @@ -1,7 +1,6 @@ -const operators = ['+', '-', '*', '/', '%', '&', '|', '&&', '||', '**', '^', - '<=', '>=', '<', '>', '==', '===', '!=', +const operators = ['+', '-', '*', '/', '%', '&', '|', '&&', '||', '**', '^', '<=', '>=', '<', '>', '==', '===', '!=', '!==', '<<', '>>', '>>>', 'in', 'instanceof', '??']; -const fixes = ['!', '~', '-', '+', '--', '++', 'typeof']; +const fixes = ['!', '~', '-', '+', 'typeof', 'void', 'delete', '--', '++']; // as in prefix and postfix operators /** * @param {ASTNode} n @@ -21,10 +20,9 @@ function matchBinaryOrLogical(n) { * @param {Arborist} arb */ function handleBinaryOrLogical(c, arb) { - // noinspection JSUnresolvedVariable const refs = (c.scope.block?.id?.references || []).map(r => r.parentNode); for (const ref of refs) { - if (ref.arguments.length === 2) arb.markNode(ref, { + if (ref.type === 'CallExpression' && ref.arguments.length === 2) arb.markNode(ref, { type: c.type, operator: c.operator, left: ref.arguments[0], @@ -37,8 +35,8 @@ function handleBinaryOrLogical(c, arb) { * @param {ASTNode} n * @return {boolean} */ -function matchUnary(n) { - return n.type === 'UnaryExpression' && +function matchUnaryOrUpdate(n) { + return ['UnaryExpression', 'UpdateExpression'].includes(n.type) && fixes.includes(n.operator) && n.parentNode.type === 'ReturnStatement' && n.parentNode.parentNode?.body?.length === 1 && @@ -49,10 +47,10 @@ function matchUnary(n) { * @param {ASTNode} c * @param {Arborist} arb */ -function handleUnary(c, arb) { +function handleUnaryAndUpdate(c, arb) { const refs = (c.scope.block?.id?.references || []).map(r => r.parentNode); for (const ref of refs) { - if (ref.arguments.length === 1) arb.markNode(ref, { + if (ref.type === 'CallExpression' && ref.arguments.length === 1) arb.markNode(ref, { type: c.type, operator: c.operator, prefix: c.prefix, @@ -70,15 +68,15 @@ function handleUnary(c, arb) { function unwrapSimpleOperations(arb, candidateFilter = () => true) { for (let i = 0; i < arb.ast.length; i++) { const n = arb.ast[i]; - if ((matchBinaryOrLogical(n) || matchUnary(n)) && - candidateFilter(n)) { + if ((matchBinaryOrLogical(n) || matchUnaryOrUpdate(n)) && candidateFilter(n)) { switch (n.type) { case 'BinaryExpression': case 'LogicalExpression': handleBinaryOrLogical(n, arb); break; case 'UnaryExpression': - handleUnary(n, arb); + case 'UpdateExpression': + handleUnaryAndUpdate(n, arb); break; } } diff --git a/tests/modules.test.js b/tests/modules.test.js index 4195845..8a93b25 100644 --- a/tests/modules.test.js +++ b/tests/modules.test.js @@ -462,7 +462,7 @@ describe('SAFE: unwrapIIFEs', async () => { }); describe('SAFE: unwrapSimpleOperations', async () => { const targetModule = (await import('../src/modules/safe/unwrapSimpleOperations.js')).default; - it('TP-1', () => { + it('TP-1: Binary operations', () => { const code = `function add(b,c){return b + c;} function minus(b,c){return b - c;} function mul(b,c){return b * c;} @@ -623,6 +623,29 @@ typeof 1; const result = applyModuleToCode(code, targetModule); assert.strictEqual(result, expected); }); + it('TP-2 Unary operations', () => { + const code = `function unaryNegation(v) {return -v;} + function unaryPlus(v) {return +v;} + function logicalNot(v) {return !v;} + function bitwiseNot(v) {return ~v;} + function typeofOp(v) {return typeof v;} + function deleteOp(v) {return delete v;} + function voidOp(v) {return void v;} + (unaryNegation(1), unaryPlus(2), logicalNot(3), bitwiseNot(4), typeofOp(5), deleteOp(6), voidOp(7)); + `; + const expected = `function unaryNegation(v) {\n return -v;\n}\nfunction unaryPlus(v) {\n return +v;\n}\nfunction logicalNot(v) {\n return !v;\n}\nfunction bitwiseNot(v) {\n return ~v;\n}\nfunction typeofOp(v) {\n return typeof v;\n}\nfunction deleteOp(v) {\n return delete v;\n}\nfunction voidOp(v) {\n return void v;\n}\n-1, +2, !3, ~4, typeof 5, delete 6, void 7;`; + const result = applyModuleToCode(code, targetModule); + assert.strictEqual(result, expected); + }); + it('TP-3 Update operations', () => { + const code = `function incrementPre(v) {return ++v;} + function decrementPost(v) {return v--;} + (incrementPre(a), decrementPost(b)); + `; + const expected = `function incrementPre(v) {\n return ++v;\n}\nfunction decrementPost(v) {\n return v--;\n}\n++a, b--;`; + const result = applyModuleToCode(code, targetModule); + assert.strictEqual(result, expected); + }); }); describe('SAFE: separateChainedDeclarators', async () => { const targetModule = (await import('../src/modules/safe/separateChainedDeclarators.js')).default; diff --git a/tests/samples.test.js b/tests/samples.test.js index 9389a59..f14343a 100644 --- a/tests/samples.test.js +++ b/tests/samples.test.js @@ -5,8 +5,6 @@ import {fileURLToPath} from 'node:url'; import {join} from 'node:path'; import {REstringer} from '../src/restringer.js'; - - function getDeobfuscatedCode(code) { const restringer = new REstringer(code); restringer.logger.setLogLevel(restringer.logger.logLevels.NONE); @@ -105,5 +103,4 @@ describe('Samples tests', () => { const result = getDeobfuscatedCode(code); assert.strictEqual(result, expected); }); - it.todo('Fix issue with multiple comments in Local Proxies sample'); }); \ No newline at end of file