diff --git a/packages/webcrack/src/deobfuscate/control-flow-object.ts b/packages/webcrack/src/deobfuscate/control-flow-object.ts index 485312f0..11a5eb0f 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,20 @@ export default { m.capture(m.objectExpression(objectProperties)), ); + const anyMemberAccess = constMemberExpression(m.identifier(), propertyName); + 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 return binding.constant || binding.constantViolations[0] === binding.path; @@ -116,9 +130,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 +151,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,