Module refinery.lib.scripts.js.deobfuscation

JavaScript AST deobfuscation transforms.

Expand source code Browse git
"""
JavaScript AST deobfuscation transforms.
"""
from __future__ import annotations

from refinery.lib.scripts.js.deobfuscation.antidbg import JsRemoveReDoS
from refinery.lib.scripts.js.deobfuscation.argwrap import JsAssignmentsAsFunctionArgs
from refinery.lib.scripts.js.deobfuscation.b91strings import JsBase91StringDecoder
from refinery.lib.scripts.js.deobfuscation.cff import (
    JsControlFlowUnflattening,
    JsGeneratorCFFUnflattening,
)
from refinery.lib.scripts.js.deobfuscation.constants import JsConstantInlining
from refinery.lib.scripts.js.deobfuscation.deadcode import JsDeadCodeElimination
from refinery.lib.scripts.js.deobfuscation.dispatcher import JsDispatcherUnwrapper
from refinery.lib.scripts.js.deobfuscation.evaluator import JsFunctionEvaluator
from refinery.lib.scripts.js.deobfuscation.globalfinder import JsGlobalFinderInlining
from refinery.lib.scripts.js.deobfuscation.iifeaccessor import JsIIFEAccessorPromoter
from refinery.lib.scripts.js.deobfuscation.namespaces import JsNamespaceFlattening
from refinery.lib.scripts.js.deobfuscation.objectfold import JsObjectFold
from refinery.lib.scripts.js.deobfuscation.reflection import JsReflectionInlining
from refinery.lib.scripts.js.deobfuscation.restunpack import JsRestArrayUnpacking
from refinery.lib.scripts.js.deobfuscation.scramble import JsScrambleStringDecoder
from refinery.lib.scripts.js.deobfuscation.simplify import JsSimplifications
from refinery.lib.scripts.js.deobfuscation.stringarray import JsStringArrayResolver
from refinery.lib.scripts.js.deobfuscation.unshuffle import JsArrayUnshuffle
from refinery.lib.scripts.js.deobfuscation.unused import JsUnusedCodeRemoval
from refinery.lib.scripts.js.deobfuscation.wrappers import JsCallWrapperInliner
from refinery.lib.scripts.js.deobfuscation.options import DeobfuscationOptions
from refinery.lib.scripts.js.analysis.cache import ModelCache
from refinery.lib.scripts.js.model import JsScript
from refinery.lib.scripts.pipeline import DeobfuscationPipeline, TransformerGroup

_pipeline = DeobfuscationPipeline(
    groups=[
        TransformerGroup(
            'unpack',
            JsReflectionInlining,
        ),
        TransformerGroup(
            'normalize',
            JsAssignmentsAsFunctionArgs,
            JsSimplifications,
            JsDeadCodeElimination,
        ),
        TransformerGroup(
            'fold',
            JsNamespaceFlattening,
            JsCallWrapperInliner,
            JsDispatcherUnwrapper,
            JsIIFEAccessorPromoter,
            JsFunctionEvaluator,
            JsObjectFold,
            JsControlFlowUnflattening,
            JsGeneratorCFFUnflattening,
            JsRestArrayUnpacking,
            JsArrayUnshuffle,
            JsConstantInlining,
            JsGlobalFinderInlining,
        ),
        TransformerGroup(
            'resolve',
            JsStringArrayResolver,
            JsBase91StringDecoder,
            JsScrambleStringDecoder,
        ),
        TransformerGroup(
            'cleanup',
            JsRemoveReDoS,
            JsUnusedCodeRemoval,
        ),
    ],
    dependencies={
        'normalize': {'unpack'},
        'fold': {'normalize'},
        'resolve': {'fold'},
        'cleanup': {'fold'},
    },
    invalidators={
        'unpack': {'normalize', 'fold', 'resolve', 'cleanup'},
        'normalize': {'fold', 'resolve'},
        'fold': {'normalize', 'resolve'},
        'resolve': {'normalize', 'fold'},
        'cleanup': {'fold'},
    },
)


def deobfuscate(ast: JsScript, max_steps: int = 5000, *, module: bool = False) -> int:
    """
    Apply all available deobfuscators to the input. A non-zero *max_steps* bounds the total number of
    change-producing transformer passes and raises
    `refinery.lib.scripts.pipeline.DeobfuscationTimeout` once it is exceeded, so a transform that fails
    to converge fails loudly instead of hanging. The default is generous — real inputs settle in a few
    to a few dozen passes (the differential corpus peaks in the low tens) — so it never bounds a
    legitimate deobfuscation, only a runaway loop. Pass `0` to disable the bound entirely. *module*
    selects the execution model the input is assumed to run under; see
    `refinery.lib.scripts.js.deobfuscation.options.DeobfuscationOptions`.
    """
    options = DeobfuscationOptions(module=module)
    return _pipeline.run(ast, max_steps=max_steps, models=ModelCache(ast), options=options)

Sub-modules

refinery.lib.scripts.js.deobfuscation.antidbg

Remove the self-defending ReDoS anti-tamper pattern injected by javascript-obfuscator …

refinery.lib.scripts.js.deobfuscation.argwrap

The obfuscator converts statement sequences into calls to a self-disabling no-op function whose arguments carry all side effects. This transformer …

refinery.lib.scripts.js.deobfuscation.b91strings

The obfuscator replaces string literals with calls to per-scope caching accessor functions. Each accessor lazily decodes an encoded string from a …

refinery.lib.scripts.js.deobfuscation.cff

Control-flow flattening recovery transforms.

refinery.lib.scripts.js.deobfuscation.constants

Inline constant variable references in JavaScript.

refinery.lib.scripts.js.deobfuscation.deadcode

Eliminate dead code branches guarded by constant conditions …

refinery.lib.scripts.js.deobfuscation.dispatcher

The dispatcher obfuscation wraps function bodies into a central routing function that uses a string keyed lookup table and a global payload array for …

refinery.lib.scripts.js.deobfuscation.evaluator

Evaluate pure JavaScript functions called with constant arguments and replace call sites with computed results.

refinery.lib.scripts.js.deobfuscation.globalfinder

Resolve global-object-finder functions to globalThis

refinery.lib.scripts.js.deobfuscation.helpers

Shared utilities for JavaScript deobfuscation transforms.

refinery.lib.scripts.js.deobfuscation.iifeaccessor

Promote IIFE-bound function accessors to plain function declarations …

refinery.lib.scripts.js.deobfuscation.interpreter

Mini-interpreter for executing pure JavaScript functions with concrete arguments.

refinery.lib.scripts.js.deobfuscation.namespaces

Flatten empty namespace objects into bare variable declarations.

refinery.lib.scripts.js.deobfuscation.objectfold

Inline properties of locally-defined constant object literals …

refinery.lib.scripts.js.deobfuscation.options

Caller-supplied options controlling JavaScript deobfuscation.

refinery.lib.scripts.js.deobfuscation.reflection

Inline reflectively executed JavaScript code: eval, Function constructor, constructor chains, and setTimeout/setInterval with string arguments. An …

refinery.lib.scripts.js.deobfuscation.restunpack

Unpack rest-parameter arrays that pack multiple variables into a single parameter …

refinery.lib.scripts.js.deobfuscation.scramble

Resolves string concealment using the Scramble cipher. Scramble uses PBKDF2 key derivation followed by multiple rounds of a permutation-based …

refinery.lib.scripts.js.deobfuscation.simplify

JavaScript syntax normalization transforms.

refinery.lib.scripts.js.deobfuscation.strict_divergence

Runtime strict-vs-sloppy divergence detection for reflection inlining. The reflection transform inlines payloads from always-sloppy surfaces …

refinery.lib.scripts.js.deobfuscation.stringarray

Resolve the string-array rotation pattern produced by popular JavaScript obfuscators …

refinery.lib.scripts.js.deobfuscation.unshuffle

Resolve statically-evaluable array rotation calls …

refinery.lib.scripts.js.deobfuscation.unused

Remove unreachable function declarations and unused variable assignments …

refinery.lib.scripts.js.deobfuscation.wrappers

Inline trivial function call wrappers …

Functions

def deobfuscate(ast, max_steps=5000, *, module=False)

Apply all available deobfuscators to the input. A non-zero max_steps bounds the total number of change-producing transformer passes and raises DeobfuscationTimeout once it is exceeded, so a transform that fails to converge fails loudly instead of hanging. The default is generous — real inputs settle in a few to a few dozen passes (the differential corpus peaks in the low tens) — so it never bounds a legitimate deobfuscation, only a runaway loop. Pass 0 to disable the bound entirely. module selects the execution model the input is assumed to run under; see DeobfuscationOptions.

Expand source code Browse git
def deobfuscate(ast: JsScript, max_steps: int = 5000, *, module: bool = False) -> int:
    """
    Apply all available deobfuscators to the input. A non-zero *max_steps* bounds the total number of
    change-producing transformer passes and raises
    `refinery.lib.scripts.pipeline.DeobfuscationTimeout` once it is exceeded, so a transform that fails
    to converge fails loudly instead of hanging. The default is generous — real inputs settle in a few
    to a few dozen passes (the differential corpus peaks in the low tens) — so it never bounds a
    legitimate deobfuscation, only a runaway loop. Pass `0` to disable the bound entirely. *module*
    selects the execution model the input is assumed to run under; see
    `refinery.lib.scripts.js.deobfuscation.options.DeobfuscationOptions`.
    """
    options = DeobfuscationOptions(module=module)
    return _pipeline.run(ast, max_steps=max_steps, models=ModelCache(ast), options=options)