Module refinery.lib.scripts.js.deobfuscation.antidbg
Remove the self-defending ReDoS anti-tamper pattern injected by javascript-obfuscator.
The obfuscator inserts a guard function that calls toString().search('(((.+)+)+)+$') on itself.
The catastrophic-backtracking regex causes the JS engine to hang when the code has been reformatted
(e.g. by a pretty-printer), acting as an anti-tamper check. This transformer detects the signature
regex string after all other deobfuscation has completed and surgically removes the guard call, its
variable declarator, and the associated factory function.
Expand source code Browse git
"""
Remove the self-defending ReDoS anti-tamper pattern injected by javascript-obfuscator.
The obfuscator inserts a guard function that calls ``toString().search('(((.+)+)+)+$')`` on itself.
The catastrophic-backtracking regex causes the JS engine to hang when the code has been reformatted
(e.g. by a pretty-printer), acting as an anti-tamper check. This transformer detects the signature
regex string after all other deobfuscation has completed and surgically removes the guard call, its
variable declarator, and the associated factory function.
"""
from __future__ import annotations
from refinery.lib.scripts import (
Node,
Transformer,
_remove_from_parent,
)
from refinery.lib.scripts.js.deobfuscation.helpers import remove_declarator
from refinery.lib.scripts.js.model import (
JsBlockStatement,
JsCallExpression,
JsExpressionStatement,
JsIdentifier,
JsScript,
JsStringLiteral,
JsVariableDeclaration,
JsVariableDeclarator,
)
_REDOS_SIGNATURE = '(((.+)+)+)+$'
class JsRemoveReDoS(Transformer):
"""
Detect and remove the self-defending ReDoS pattern by its signature regex string.
"""
def visit_JsScript(self, node: JsScript):
for literal in list(node.walk()):
if (
isinstance(literal, JsStringLiteral)
and _REDOS_SIGNATURE in literal.value
):
self._remove_pattern(literal)
return None
def _remove_pattern(self, redos_literal: JsStringLiteral) -> None:
guard_decl = redos_literal.parent
while guard_decl is not None and not isinstance(guard_decl, JsVariableDeclarator):
guard_decl = guard_decl.parent
if guard_decl is None or not isinstance(guard_decl.id, JsIdentifier):
return
if not isinstance(guard_decl.init, JsCallExpression):
return
if not isinstance(guard_decl.init.callee, JsIdentifier):
return
guard_name = guard_decl.id.name
factory_name = guard_decl.init.callee.name
var_decl = guard_decl.parent
if not isinstance(var_decl, JsVariableDeclaration):
return
body_parent = var_decl.parent
if isinstance(body_parent, JsScript):
body = body_parent.body
elif isinstance(body_parent, JsBlockStatement):
body = body_parent.body
else:
return
for stmt in list(body):
if (
isinstance(stmt, JsExpressionStatement)
and isinstance(stmt.expression, JsCallExpression)
and isinstance(stmt.expression.callee, JsIdentifier)
and stmt.expression.callee.name == guard_name
and not stmt.expression.arguments
):
_remove_from_parent(stmt)
remove_declarator(guard_decl)
referenced = False
for n in body_parent.walk():
if isinstance(n, JsIdentifier) and n.name == factory_name:
if isinstance(n.parent, JsVariableDeclarator) and n.parent.id is n:
continue
referenced = True
break
if not referenced:
for stmt in list(body):
if not isinstance(stmt, JsVariableDeclaration):
continue
for d in list(stmt.declarations):
if (
isinstance(d, JsVariableDeclarator)
and isinstance(d.id, JsIdentifier)
and d.id.name == factory_name
):
remove_declarator(d)
self.mark_changed()
def generic_visit(self, node: Node):
pass
Classes
class JsRemoveReDoS-
Detect and remove the self-defending ReDoS pattern by its signature regex string.
Expand source code Browse git
class JsRemoveReDoS(Transformer): """ Detect and remove the self-defending ReDoS pattern by its signature regex string. """ def visit_JsScript(self, node: JsScript): for literal in list(node.walk()): if ( isinstance(literal, JsStringLiteral) and _REDOS_SIGNATURE in literal.value ): self._remove_pattern(literal) return None def _remove_pattern(self, redos_literal: JsStringLiteral) -> None: guard_decl = redos_literal.parent while guard_decl is not None and not isinstance(guard_decl, JsVariableDeclarator): guard_decl = guard_decl.parent if guard_decl is None or not isinstance(guard_decl.id, JsIdentifier): return if not isinstance(guard_decl.init, JsCallExpression): return if not isinstance(guard_decl.init.callee, JsIdentifier): return guard_name = guard_decl.id.name factory_name = guard_decl.init.callee.name var_decl = guard_decl.parent if not isinstance(var_decl, JsVariableDeclaration): return body_parent = var_decl.parent if isinstance(body_parent, JsScript): body = body_parent.body elif isinstance(body_parent, JsBlockStatement): body = body_parent.body else: return for stmt in list(body): if ( isinstance(stmt, JsExpressionStatement) and isinstance(stmt.expression, JsCallExpression) and isinstance(stmt.expression.callee, JsIdentifier) and stmt.expression.callee.name == guard_name and not stmt.expression.arguments ): _remove_from_parent(stmt) remove_declarator(guard_decl) referenced = False for n in body_parent.walk(): if isinstance(n, JsIdentifier) and n.name == factory_name: if isinstance(n.parent, JsVariableDeclarator) and n.parent.id is n: continue referenced = True break if not referenced: for stmt in list(body): if not isinstance(stmt, JsVariableDeclaration): continue for d in list(stmt.declarations): if ( isinstance(d, JsVariableDeclarator) and isinstance(d.id, JsIdentifier) and d.id.name == factory_name ): remove_declarator(d) self.mark_changed() def generic_visit(self, node: Node): passAncestors
Methods
def visit_JsScript(self, node)-
Expand source code Browse git
def visit_JsScript(self, node: JsScript): for literal in list(node.walk()): if ( isinstance(literal, JsStringLiteral) and _REDOS_SIGNATURE in literal.value ): self._remove_pattern(literal) return None def generic_visit(self, node)-
Expand source code Browse git
def generic_visit(self, node: Node): pass