Skip to content

Commit

Permalink
This one simple trick unlocked the next level of obfuscation
Browse files Browse the repository at this point in the history
  • Loading branch information
pvdz committed Aug 18, 2024
1 parent 745cf51 commit 2772426
Show file tree
Hide file tree
Showing 2 changed files with 86 additions and 1 deletion.
25 changes: 24 additions & 1 deletion src/normalize/normalize.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -2826,6 +2826,29 @@ export function phaseNormalize(fdata, fname, { allowEval = true }) {
}
}
}

if (!callee.computed && AST.isPrimitive(callee.object) && AST.getPrimitiveValue(callee.object) === '') {
// Property access on empty string... Some silly low hanging fruit cases.
if (ASSUME_BUILTINS) {
// Targeting a specific obfuscation: ``.replace(/^/, String)
if (callee.property.name === 'replace' && node.arguments[1].type === 'Identifier' && node.arguments[1].name === 'String') {
// This will invariably return the empty string

riskyRule('Calling .replace on an empty string with irrelevant function always results in empty string');
example('"".replace(/$/, String)', '$coerce(/$/); ""');
before(body[i]);

const finalNode = AST.primitive('');
const finalParent = wrapExpressionAs(wrapKind, varInitAssignKind, varInitAssignId, wrapLhs, varOrAssignKind, finalNode);
body.splice(i, 1, AST.expressionStatement(AST.callExpression('$coerce', [node.arguments[1], AST.primitive('string')])), finalParent);

after(body[i]);
after(body[i + 1]);

return true;
}
}
}
}

// Simple member expression is atomic callee. Can't break down further since the object can change the context.
Expand Down Expand Up @@ -3621,7 +3644,7 @@ export function phaseNormalize(fdata, fname, { allowEval = true }) {
);

const finalParent = wrapExpressionAs(wrapKind, varInitAssignKind, varInitAssignId, wrapLhs, varOrAssignKind, finalNode);
body.splice(i, 1, finalParent);
body[i] = finalParent;

after(body[i]);
return true;
Expand Down
62 changes: 62 additions & 0 deletions tests/cases/builtins_cases/empty_string_replace.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,62 @@
# Preval test case

# empty_string_replace.md

> Builtins cases > Empty string replace
>
>
## Input

`````js filename=intro
$(''.replace(/^/, String));
`````

## Pre Normal


`````js filename=intro
$(``.replace(/^/, String));
`````

## Normalized


`````js filename=intro
const tmpCallCallee = $;
const tmpCalleeParam$1 = /^/;
const tmpCalleeParam$3 = String;
const tmpCalleeParam = ``.replace(tmpCalleeParam$1, tmpCalleeParam$3);
tmpCallCallee(tmpCalleeParam);
`````

## Output


`````js filename=intro
$(``);
`````

## PST Output

With rename=true

`````js filename=intro
$( "" );
`````

## Globals

None

## Result

Should call `$` with:
- 1: ''
- eval returned: undefined

Pre normalization calls: Same

Normalized calls: Same

Final output calls: Same

0 comments on commit 2772426

Please sign in to comment.