From 9aca65a9c6cf97c089f09334d096add453874f4a Mon Sep 17 00:00:00 2001 From: j4k0xb <55899582+j4k0xb@users.noreply.github.com> Date: Sun, 31 Dec 2023 23:40:51 +0100 Subject: [PATCH 1/2] fix: babel-plugin-minify-mangle-names default parameter error (#41) --- packages/webcrack/src/transforms/mangle.ts | 37 ++++++++++++++++++++-- packages/webcrack/test/mangle.test.ts | 28 ++++++++++++++++ 2 files changed, 63 insertions(+), 2 deletions(-) create mode 100644 packages/webcrack/test/mangle.test.ts diff --git a/packages/webcrack/src/transforms/mangle.ts b/packages/webcrack/src/transforms/mangle.ts index 3f4c8e11..ebb29229 100644 --- a/packages/webcrack/src/transforms/mangle.ts +++ b/packages/webcrack/src/transforms/mangle.ts @@ -1,8 +1,37 @@ -import traverse, { NodePath } from '@babel/traverse'; +import { statement } from '@babel/template'; +import traverse, { NodePath, Visitor, visitors } from '@babel/traverse'; import * as t from '@babel/types'; import mangle from 'babel-plugin-minify-mangle-names'; import { Transform } from '../ast-utils'; +// See https://github.com/j4k0xb/webcrack/issues/41 and https://github.com/babel/minify/issues/1023 +const fixDefaultParamError: Visitor = { + Function(path) { + const { params } = path.node; + for (let i = params.length - 1; i >= 0; i--) { + const param = params[i]; + if ( + !t.isAssignmentPattern(param) || + !t.isIdentifier(param.left) || + t.isLiteral(param.right) + ) + continue; + + if ( + path.isArrowFunctionExpression() && + !t.isBlockStatement(path.node.body) + ) { + path.node.body = t.blockStatement([t.returnStatement(path.node.body)]); + } + (path.get('body') as NodePath).unshiftContainer( + 'body', + statement`if (${param.left} !== undefined) ${param.left} = ${param.right}`(), + ); + param.right = t.identifier('undefined'); + } + }, +}; + export default { name: 'mangle', tags: ['safe'], @@ -12,8 +41,12 @@ export default { // eslint-disable-next-line @typescript-eslint/unbound-method const { getSource } = NodePath.prototype; NodePath.prototype.getSource = () => ''; + const visitor = visitors.merge([ + fixDefaultParamError, + mangle({ types: t, traverse }).visitor, + ]); - traverse(ast, mangle({ types: t, traverse }).visitor, undefined, { + traverse(ast, visitor, undefined, { opts: { eval: true, topLevel: true, diff --git a/packages/webcrack/test/mangle.test.ts b/packages/webcrack/test/mangle.test.ts new file mode 100644 index 00000000..fb896671 --- /dev/null +++ b/packages/webcrack/test/mangle.test.ts @@ -0,0 +1,28 @@ +import { test } from 'vitest'; +import { testTransform } from '.'; +import mangle from '../src/transforms/mangle'; + +const expectJS = testTransform(mangle); + +// https://github.com/j4k0xb/webcrack/issues/41 +test('rename and extract non-literal default parameters', () => { + expectJS(` + function func(arg1, arg2 = 0, arg3 = arg1, arg4 = 30) { + return arg1; + } + `).toMatchInlineSnapshot(` + function a(a, b = 0, c = undefined, d = 30) { + if (c !== undefined) c = a; + return a; + } + `); + + expectJS(` + const func = (arg1, arg2 = 0, arg3 = arg1, arg4 = 30) => arg1; + `).toMatchInlineSnapshot(` + const a = (a, b = 0, c = undefined, d = 30) => { + if (c !== undefined) c = a; + return a; + }; + `); +}); From d1033cae02bdf8377204a2459de6e5e24ecae4ae Mon Sep 17 00:00:00 2001 From: j4k0xb <55899582+j4k0xb@users.noreply.github.com> Date: Mon, 1 Jan 2024 00:48:52 +0100 Subject: [PATCH 2/2] fix: flip default condition, mangle destructuring parameter --- packages/webcrack/src/ast-utils/matcher.ts | 5 ++- .../deobfuscate/merge-object-assignments.ts | 4 +-- packages/webcrack/src/transforms/mangle.ts | 33 +++++++++++-------- packages/webcrack/test/mangle.test.ts | 31 +++++++++++++---- 4 files changed, 49 insertions(+), 24 deletions(-) diff --git a/packages/webcrack/src/ast-utils/matcher.ts b/packages/webcrack/src/ast-utils/matcher.ts index 2798b664..1736cb1a 100644 --- a/packages/webcrack/src/ast-utils/matcher.ts +++ b/packages/webcrack/src/ast-utils/matcher.ts @@ -2,7 +2,10 @@ import { Binding, NodePath } from '@babel/traverse'; import * as t from '@babel/types'; import * as m from '@codemod/matchers'; -export const anyLiteral: m.Matcher = m.matcher( +/** + * Matches any literal except for template literals with expressions (that could have side effects) + */ +export const safeLiteral: m.Matcher = m.matcher( (node) => t.isLiteral(node) && (!t.isTemplateLiteral(node) || node.expressions.length === 0), diff --git a/packages/webcrack/src/deobfuscate/merge-object-assignments.ts b/packages/webcrack/src/deobfuscate/merge-object-assignments.ts index 6c0773d8..36fc21de 100644 --- a/packages/webcrack/src/deobfuscate/merge-object-assignments.ts +++ b/packages/webcrack/src/deobfuscate/merge-object-assignments.ts @@ -1,7 +1,7 @@ import { Binding } from '@babel/traverse'; import * as t from '@babel/types'; import * as m from '@codemod/matchers'; -import { Transform, anyLiteral, constObjectProperty } from '../ast-utils'; +import { Transform, constObjectProperty, safeLiteral } from '../ast-utils'; /** * Merges object assignments into the object expression. @@ -109,7 +109,7 @@ function hasCircularReference(node: t.Node, binding: Binding) { const inlineableObject: m.Matcher = m.matcher((node) => m .or( - anyLiteral, + safeLiteral, m.arrayExpression(m.arrayOf(inlineableObject)), m.objectExpression(m.arrayOf(constObjectProperty(inlineableObject))), ) diff --git a/packages/webcrack/src/transforms/mangle.ts b/packages/webcrack/src/transforms/mangle.ts index ebb29229..44ede12a 100644 --- a/packages/webcrack/src/transforms/mangle.ts +++ b/packages/webcrack/src/transforms/mangle.ts @@ -2,31 +2,36 @@ import { statement } from '@babel/template'; import traverse, { NodePath, Visitor, visitors } from '@babel/traverse'; import * as t from '@babel/types'; import mangle from 'babel-plugin-minify-mangle-names'; -import { Transform } from '../ast-utils'; +import { Transform, safeLiteral } from '../ast-utils'; // See https://github.com/j4k0xb/webcrack/issues/41 and https://github.com/babel/minify/issues/1023 const fixDefaultParamError: Visitor = { Function(path) { const { params } = path.node; + for (let i = params.length - 1; i >= 0; i--) { const param = params[i]; - if ( - !t.isAssignmentPattern(param) || - !t.isIdentifier(param.left) || - t.isLiteral(param.right) - ) + if (!t.isAssignmentPattern(param) || safeLiteral.match(param.right)) continue; - if ( - path.isArrowFunctionExpression() && - !t.isBlockStatement(path.node.body) - ) { + if (!t.isBlockStatement(path.node.body)) { path.node.body = t.blockStatement([t.returnStatement(path.node.body)]); } - (path.get('body') as NodePath).unshiftContainer( - 'body', - statement`if (${param.left} !== undefined) ${param.left} = ${param.right}`(), - ); + + const body = path.get('body') as NodePath; + if (t.isIdentifier(param.left)) { + body.unshiftContainer( + 'body', + statement`if (${param.left} === undefined) ${param.left} = ${param.right}`(), + ); + } else { + const tempId = path.scope.generateUidIdentifier(); + body.unshiftContainer( + 'body', + statement`var ${param.left} = ${tempId} === undefined ? ${param.right} : ${tempId}`(), + ); + param.left = tempId; + } param.right = t.identifier('undefined'); } }, diff --git a/packages/webcrack/test/mangle.test.ts b/packages/webcrack/test/mangle.test.ts index fb896671..e18df845 100644 --- a/packages/webcrack/test/mangle.test.ts +++ b/packages/webcrack/test/mangle.test.ts @@ -5,24 +5,41 @@ import mangle from '../src/transforms/mangle'; const expectJS = testTransform(mangle); // https://github.com/j4k0xb/webcrack/issues/41 -test('rename and extract non-literal default parameters', () => { +test('rename default parameters of function', () => { expectJS(` - function func(arg1, arg2 = 0, arg3 = arg1, arg4 = 30) { + function func(arg1, arg2 = 0, arg3 = arg1, arg4 = arg1) { return arg1; } `).toMatchInlineSnapshot(` - function a(a, b = 0, c = undefined, d = 30) { - if (c !== undefined) c = a; + function a(a, b = 0, c = undefined, d = undefined) { + if (c === undefined) c = a; + if (d === undefined) d = a; return a; } `); +}); +test('rename default parameters of arrow function', () => { expectJS(` - const func = (arg1, arg2 = 0, arg3 = arg1, arg4 = 30) => arg1; + const func = (arg1, arg2 = 0, arg3 = arg1, arg4 = arg1) => arg1; `).toMatchInlineSnapshot(` - const a = (a, b = 0, c = undefined, d = 30) => { - if (c !== undefined) c = a; + const a = (a, b = 0, c = undefined, d = undefined) => { + if (c === undefined) c = a; + if (d === undefined) d = a; return a; }; `); }); + +test('rename default destructuring parameters', () => { + expectJS(` + function func(arg1, [arg2] = arg1) { + return arg2; + } + `).toMatchInlineSnapshot(` + function a(a, b = undefined) { + var [c] = b === undefined ? a : b; + return c; + } + `); +});