Module refinery.lib.scripts.au3.deobfuscation
AutoIt3 AST deobfuscation transforms.
Five groups, in the order the obfuscated shapes need them. normalize rewrites what a call
structurally is — an Execute call over a string literal becomes the expression that literal
names, a call through an alias variable becomes the function that variable provably names, and
a Call, Assign, or Eval over a string-literal name becomes the direct call, plain write,
or plain read that name spells — refinery.lib.scripts.au3.deobfuscation.fold replaces what is known — an expression whose operands
pin its value becomes the literal of that value, a variable read the propagation walk proves a
value for becomes that literal, and a dispatcher loop whose state a provable constant pins becomes
the one case body it ever runs — emulate replaces what a script-defined function returns:
the interpreter runs the call over the value domain and the call becomes the literal of its
result — cleanup removes what no live code reaches: a function no call names, a declarator
no occurrence binds, an effect-free statement, and a plain assignment whose target nothing reads
— each only when no dynamic-name channel could observe the removal — and canonicalize reprints
what survived: a call whose callee names a builtin, or whose Call argument does, takes the
official spelling of that name, a change no resolution reads.
refinery.lib.scripts.au3.deobfuscation.fold depends on normalize and re-triggers it: folding an Execute call's argument into the
string literal it always was is what makes the next normalize pass see a flattenable call, and
rewriting a callee through an alias is what makes the next refinery.lib.scripts.au3.deobfuscation.fold pass see the builtin the alias
named. emulate depends on refinery.lib.scripts.au3.deobfuscation.fold — only literal arguments reach a decoded call — and every
change it makes re-triggers the whole pipeline: a decoded "BinaryToString" is what the next
normalize pass turns into an alias rewrite, and a decoded argument is what the next refinery.lib.scripts.au3.deobfuscation.fold
pass folds. cleanup depends on emulate and takes no selective invalidation set: a removal
changes everything the other models know, the emulate stance. canonicalize depends on
cleanup and takes the empty one: it rewrites only spellings, every model keys lowercased
names, and no group depends on it, so its changes re-run nothing. The group's internal loop is
what iterates a sweep to its fixpoint — a function whose only callers an earlier sweep removed
keeps its call sites until the next sweep re-census.
Expand source code Browse git
"""
AutoIt3 AST deobfuscation transforms.
Five groups, in the order the obfuscated shapes need them. `normalize` rewrites what a call
structurally is — an `Execute` call over a string literal becomes the expression that literal
names, a call through an alias variable becomes the function that variable provably names, and
a `Call`, `Assign`, or `Eval` over a string-literal name becomes the direct call, plain write,
or plain read that name spells — `fold` replaces what is known — an expression whose operands
pin its value becomes the literal of that value, a variable read the propagation walk proves a
value for becomes that literal, and a dispatcher loop whose state a provable constant pins becomes
the one case body it ever runs — `emulate` replaces what a script-defined function returns:
the interpreter runs the call over the value domain and the call becomes the literal of its
result — `cleanup` removes what no live code reaches: a function no call names, a declarator
no occurrence binds, an effect-free statement, and a plain assignment whose target nothing reads
— each only when no dynamic-name channel could observe the removal — and `canonicalize` reprints
what survived: a call whose callee names a builtin, or whose `Call` argument does, takes the
official spelling of that name, a change no resolution reads.
`fold` depends on `normalize` and re-triggers it: folding an `Execute` call's argument into the
string literal it always was is what makes the next `normalize` pass see a flattenable call, and
rewriting a callee through an alias is what makes the next `fold` pass see the builtin the alias
named. `emulate` depends on `fold` — only literal arguments reach a decoded call — and every
change it makes re-triggers the whole pipeline: a decoded `"BinaryToString"` is what the next
`normalize` pass turns into an alias rewrite, and a decoded argument is what the next `fold`
pass folds. `cleanup` depends on `emulate` and takes no selective invalidation set: a removal
changes everything the other models know, the `emulate` stance. `canonicalize` depends on
`cleanup` and takes the empty one: it rewrites only spellings, every model keys lowercased
names, and no group depends on it, so its changes re-run nothing. The group's internal loop is
what iterates a sweep to its fixpoint — a function whose only callers an earlier sweep removed
keeps its call sites until the next sweep re-census.
"""
from __future__ import annotations
from refinery.lib.scripts.au3.analysis import Au3ModelCache
from refinery.lib.scripts.au3.deobfuscation.casing import Au3BuiltinCaseNormalization
from refinery.lib.scripts.au3.deobfuscation.eliminate import Au3DeadCodeElimination
from refinery.lib.scripts.au3.deobfuscation.evaluate import Au3FunctionEvaluation
from refinery.lib.scripts.au3.deobfuscation.execute import Au3ExecuteFlattening
from refinery.lib.scripts.au3.deobfuscation.fold import Au3ConstantFolding
from refinery.lib.scripts.au3.deobfuscation.indirect import Au3IndirectCallRewrite
from refinery.lib.scripts.au3.deobfuscation.names import Au3DynamicNameFlattening
from refinery.lib.scripts.au3.deobfuscation.propagate import Au3ConstantPropagation
from refinery.lib.scripts.au3.deobfuscation.unflatten import Au3ControlFlowDeflattening
from refinery.lib.scripts.au3.deobfuscation.unused import Au3UnusedVariableRemoval
from refinery.lib.scripts.au3.model import Au3Script
from refinery.lib.scripts.pipeline import DeobfuscationPipeline, TransformerGroup
_normalize = TransformerGroup(
'normalize',
Au3ExecuteFlattening,
Au3IndirectCallRewrite,
Au3DynamicNameFlattening,
)
_fold = TransformerGroup(
'fold',
Au3ConstantFolding,
Au3ConstantPropagation,
Au3ControlFlowDeflattening,
)
_emulate = TransformerGroup(
'emulate',
Au3FunctionEvaluation,
)
_cleanup = TransformerGroup(
'cleanup',
Au3DeadCodeElimination,
Au3UnusedVariableRemoval,
)
_canonicalize = TransformerGroup(
'canonicalize',
Au3BuiltinCaseNormalization,
)
_GROUP_DEPENDENCIES = {
'fold': {'normalize'},
'emulate': {'fold'},
'cleanup': {'emulate'},
'canonicalize': {'cleanup'},
}
_GROUP_INVALIDATORS = {
'fold': {'normalize', 'emulate', 'cleanup', 'canonicalize'},
'canonicalize': set(),
}
_pipeline = DeobfuscationPipeline(
[_normalize, _fold, _emulate, _cleanup, _canonicalize],
dependencies=_GROUP_DEPENDENCIES,
invalidators=_GROUP_INVALIDATORS,
)
def deobfuscate(ast: Au3Script, max_steps: int = 5000) -> 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 set of
transforms that fails to converge fails loudly instead of hanging. Pass `0` to disable the
bound entirely.
"""
return _pipeline.run(ast, max_steps=max_steps, models=Au3ModelCache(ast))
Sub-modules
refinery.lib.scripts.au3.deobfuscation.casing-
Builtin case normalization: printing every reference to a builtin function the official documentation spells it with …
refinery.lib.scripts.au3.deobfuscation.eliminate-
The Phase 4 transform: what no live code reaches leaves the tree. A function no call names and no string spells, a declarator no occurrence binds or …
refinery.lib.scripts.au3.deobfuscation.evaluate-
The Phase 3 transform: what a script-defined decoder function returns becomes the literal its call site stands for. The interpreter runs the call over …
refinery.lib.scripts.au3.deobfuscation.execute-
Executeflattening: splicing the one expression a string-literal argument provably names into the place of the call … refinery.lib.scripts.au3.deobfuscation.fold-
Constant folding: replacing an expression whose value every ingredient pins with the literal that spells it …
refinery.lib.scripts.au3.deobfuscation.helpers-
Shared utilities for AutoIt3 deobfuscation transforms.
refinery.lib.scripts.au3.deobfuscation.indirect-
Alias and indirection rewriting: replacing the callee of a call through an alias variable with the function that variable provably names …
refinery.lib.scripts.au3.deobfuscation.names-
Dynamic-name flattening: replacing a
Call,Assign, orEvalover a string-literal name with the direct call, plain write, or plain read that name … refinery.lib.scripts.au3.deobfuscation.propagate-
Constant propagation: replacing a variable read whose value the walk in
refinery.lib.scripts.au3.analysis.propagationproves with the literal that … refinery.lib.scripts.au3.deobfuscation.unflatten-
Control-flow deflattening, degenerate form: a truthy-constant
Whilewhose body is one state assignment and oneSwitchover that state splices down … refinery.lib.scripts.au3.deobfuscation.unused-
The write-only store sweep: a plain assignment whose target nothing reads leaves the tree. The read census of …
Functions
def deobfuscate(ast, max_steps=5000)-
Apply all available deobfuscators to the input. A non-zero
max_stepsbounds the total number of change-producing transformer passes and raisesDeobfuscationTimeoutonce it is exceeded, so a set of transforms that fails to converge fails loudly instead of hanging. Pass0to disable the bound entirely.Expand source code Browse git
def deobfuscate(ast: Au3Script, max_steps: int = 5000) -> 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 set of transforms that fails to converge fails loudly instead of hanging. Pass `0` to disable the bound entirely. """ return _pipeline.run(ast, max_steps=max_steps, models=Au3ModelCache(ast))