From e06593d619cfce3c8d8980ce2704c2d44292cb5c Mon Sep 17 00:00:00 2001 From: j4k0xb <55899582+j4k0xb@users.noreply.github.com> Date: Wed, 3 Jan 2024 18:08:18 +0100 Subject: [PATCH 1/2] fix: ignore possible dead branches for control flow object checks --- .../src/deobfuscate/control-flow-object.ts | 42 ++++++++++++++++--- 1 file changed, 37 insertions(+), 5 deletions(-) diff --git a/packages/webcrack/src/deobfuscate/control-flow-object.ts b/packages/webcrack/src/deobfuscate/control-flow-object.ts index 485312f0..983f0348 100644 --- a/packages/webcrack/src/deobfuscate/control-flow-object.ts +++ b/packages/webcrack/src/deobfuscate/control-flow-object.ts @@ -1,6 +1,6 @@ import type { Binding, NodePath } from '@babel/traverse'; -import * as t from '@babel/types'; import type { FunctionExpression } from '@babel/types'; +import * as t from '@babel/types'; import * as m from '@codemod/matchers'; import type { Transform } from '../ast-utils'; import { @@ -104,6 +104,30 @@ export default { m.capture(m.objectExpression(objectProperties)), ); + const anyMemberAccess = constMemberExpression(m.identifier(), propertyName); + const deadBranchMatcher = m.or( + m.ifStatement( + m.or( + m.callExpression(anyMemberAccess, [anyMemberAccess, anyMemberAccess]), + m.binaryExpression( + m.or('===', '!=='), + m.stringLiteral(), + m.stringLiteral(), + ), + ), + ), + m.conditionalExpression( + m.or( + m.callExpression(anyMemberAccess, [anyMemberAccess, anyMemberAccess]), + m.binaryExpression( + m.or('===', '!=='), + m.stringLiteral(), + m.stringLiteral(), + ), + ), + ), + ); + function isConstantBinding(binding: Binding) { // Workaround because sometimes babel treats the VariableDeclarator/binding itself as a violation return binding.constant || binding.constantViolations[0] === binding.path; @@ -116,9 +140,13 @@ export default { // would have generated the code (no reassignments, etc.) const binding = path.scope.getBinding(varId.current!.name); if (!binding) return changes; - if (!isConstantBinding(binding)) return changes; + const isInDeadBranchMaybe = binding.constantViolations.every((path) => + findParent(path, deadBranchMatcher), + ); + if (!isInDeadBranchMaybe && !isConstantBinding(binding)) return changes; if (!transformObjectKeys(binding)) return changes; - if (!isReadonlyObject(binding, memberAccess)) return changes; + if (!isInDeadBranchMaybe && !isReadonlyObject(binding, memberAccess)) + return changes; const props = new Map( objectProperties.current!.map((p) => [ @@ -133,13 +161,17 @@ export default { // Have to loop backwards because we might replace a node that // contains another reference to the binding (https://github.com/babel/babel/issues/12943) [...binding.referencePaths].reverse().forEach((ref) => { - const memberPath = ref.parentPath as NodePath; + const memberPath = ref.parentPath; + + // It should always be a MemberExpression unless when dead code injection is enabled + if (!t.isMemberExpression(memberPath?.node)) return; + const propName = getPropName(memberPath.node.property)!; const value = props.get(propName)!; if (t.isStringLiteral(value)) { memberPath.replaceWith(value); - } else { + } else if (t.isFunctionExpression(value)) { inlineFunction( value, memberPath.parentPath as NodePath, From dfe0c75f1434552d7be0db1dd0a91f276214f3b4 Mon Sep 17 00:00:00 2001 From: j4k0xb <55899582+j4k0xb@users.noreply.github.com> Date: Wed, 3 Jan 2024 18:24:09 +0100 Subject: [PATCH 2/2] refactor --- .../src/deobfuscate/control-flow-object.ts | 30 +++++++------------ 1 file changed, 10 insertions(+), 20 deletions(-) diff --git a/packages/webcrack/src/deobfuscate/control-flow-object.ts b/packages/webcrack/src/deobfuscate/control-flow-object.ts index 983f0348..11a5eb0f 100644 --- a/packages/webcrack/src/deobfuscate/control-flow-object.ts +++ b/packages/webcrack/src/deobfuscate/control-flow-object.ts @@ -105,28 +105,18 @@ export default { ); const anyMemberAccess = constMemberExpression(m.identifier(), propertyName); - const deadBranchMatcher = m.or( - m.ifStatement( - m.or( - m.callExpression(anyMemberAccess, [anyMemberAccess, anyMemberAccess]), - m.binaryExpression( - m.or('===', '!=='), - m.stringLiteral(), - m.stringLiteral(), - ), - ), - ), - m.conditionalExpression( - m.or( - m.callExpression(anyMemberAccess, [anyMemberAccess, anyMemberAccess]), - m.binaryExpression( - m.or('===', '!=='), - m.stringLiteral(), - m.stringLiteral(), - ), - ), + const deadBranchTest = m.or( + m.callExpression(anyMemberAccess, [anyMemberAccess, anyMemberAccess]), + m.binaryExpression( + m.or('===', '!=='), + m.stringLiteral(), + m.stringLiteral(), ), ); + const deadBranchMatcher = m.or( + m.ifStatement(deadBranchTest), + m.conditionalExpression(deadBranchTest), + ); function isConstantBinding(binding: Binding) { // Workaround because sometimes babel treats the VariableDeclarator/binding itself as a violation