Module refinery.lib.scripts.ps1.deobfuscation
PowerShell AST deobfuscation transforms.
Expand source code Browse git
"""
PowerShell AST deobfuscation transforms.
"""
from __future__ import annotations
from refinery.lib.scripts.pipeline import DeobfuscationPipeline, TransformerGroup
from refinery.lib.scripts.ps1.analysis import Ps1ModelCache
from refinery.lib.scripts.ps1.deobfuscation.aliases import Ps1AliasInlining
from refinery.lib.scripts.ps1.deobfuscation.constants import (
Ps1ConstantInlining,
Ps1NullVariableInlining,
)
from refinery.lib.scripts.ps1.deobfuscation.deadcode import Ps1DeadCodeElimination
from refinery.lib.scripts.ps1.deobfuscation.emulator import Ps1ForEachPipeline, Ps1FunctionEvaluator
from refinery.lib.scripts.ps1.deobfuscation.expandable import Ps1ExpandableStringHoist
from refinery.lib.scripts.ps1.deobfuscation.folding import Ps1ConstantFolding
from refinery.lib.scripts.ps1.deobfuscation.iexinline import Ps1IexInlining
from refinery.lib.scripts.ps1.deobfuscation.options import Ps1DeobfuscationOptions
from refinery.lib.scripts.ps1.deobfuscation.rename import Ps1VariableRenaming
from refinery.lib.scripts.ps1.deobfuscation.securestring import Ps1SecureStringDecryptor
from refinery.lib.scripts.ps1.deobfuscation.simplify import Ps1Simplifications
from refinery.lib.scripts.ps1.deobfuscation.typecast import Ps1TypeCasts
from refinery.lib.scripts.ps1.deobfuscation.typenames import Ps1TypeSystemSimplifications
from refinery.lib.scripts.ps1.deobfuscation.unflatten import Ps1ControlFlowDeflattening
from refinery.lib.scripts.ps1.deobfuscation.unused import (
Ps1DeadStoreElimination,
Ps1JunkStatementRemoval,
Ps1UnusedVariableRemoval,
)
from refinery.lib.scripts.ps1.deobfuscation.wildcards import Ps1WildcardResolution
from refinery.lib.scripts.ps1.model import Ps1Script
_folds = (
Ps1ConstantFolding,
Ps1DeadCodeElimination,
Ps1ControlFlowDeflattening,
Ps1ConstantInlining,
Ps1ExpandableStringHoist,
Ps1TypeCasts,
)
_cleanup = (
Ps1NullVariableInlining,
Ps1UnusedVariableRemoval,
Ps1DeadStoreElimination,
Ps1JunkStatementRemoval,
)
_fold_base = TransformerGroup('fold', *_folds)
_fold_full = TransformerGroup('fold', *_folds, *_cleanup)
_emulate = TransformerGroup(
'emulate',
Ps1ForEachPipeline,
Ps1FunctionEvaluator,
)
_normalize = TransformerGroup(
'normalize',
Ps1Simplifications,
Ps1AliasInlining,
Ps1WildcardResolution,
Ps1TypeSystemSimplifications,
)
_finalize = TransformerGroup(
'finalize',
Ps1SecureStringDecryptor,
Ps1IexInlining,
)
_cosmetic = TransformerGroup(
'cosmetic',
Ps1VariableRenaming,
)
_DEPENDENCIES = {
'fold' : {'normalize'},
'emulate' : {'fold'},
'finalize' : {'emulate'},
}
_INVALIDATORS = {
'fold': {'normalize', 'emulate', 'finalize'},
}
_phase1 = DeobfuscationPipeline(
[_normalize, _fold_base, _emulate, _finalize],
dependencies=_DEPENDENCIES,
invalidators=_INVALIDATORS,
)
_phase2 = DeobfuscationPipeline(
[_normalize, _fold_full, _emulate, _finalize, _cosmetic],
dependencies={**_DEPENDENCIES, 'cosmetic': {'finalize'}},
invalidators=_INVALIDATORS,
)
def deobfuscate(
ast: Ps1Script,
max_steps: int = 0,
remove_junk: bool = True,
preserve_bare_output: bool = False,
) -> int:
"""
Apply all available deobfuscators to the input. When `remove_junk` is `True`, a second pass
removes unused variable assignments, uncalled function definitions, and side-effect-free
expression statements.
The two switches are not the same knob at different strengths. `remove_junk` decides whether
that second pass runs at all, so turning it off also keeps every dead store and uncalled
function, `preserve_bare_output` decides one question inside it — whether a statement whose only
effect is to write a value to the success output stream may be deleted — and leaves the rest of
the pass working. See `refinery.lib.scripts.ps1.deobfuscation.options.Ps1DeobfuscationOptions`
for what that costs and the assumption it rests on.
"""
# One analysis cache is built over the script and shared across both phases; the now-honored
# `tree_version` counter keeps it consistent even across the two pipeline runs, so a transform in
# either phase queries models built on the current tree instead of rebuilding them per pass.
cache = Ps1ModelCache(ast)
# Both phases are handed the same options, because a configuration two pipelines disagree about
# is a configuration neither of them states. Only phase 2 holds the pass that reads this one
# today, and building the phase-1 call that way would encode which pass that is.
options = Ps1DeobfuscationOptions(preserve_bare_output=preserve_bare_output)
steps = _phase1.run(ast, max_steps=max_steps, models=cache, options=options)
if not remove_junk:
return steps
# Carry the phase-1 step count into phase 2 so a single `max_steps` budget is enforced across
# both phases. Splitting the budget instead lets a phase-1 result of exactly `max_steps` leave a
# remaining budget of 0, which the pipeline would read as "unlimited" and run phase 2 unbounded.
return _phase2.run(
ast,
max_steps=max_steps,
initial_steps=steps,
models=cache,
options=options,
)
Sub-modules
refinery.lib.scripts.ps1.deobfuscation.aliases-
Inline command aliases defined via Set-Alias / New-Alias.
refinery.lib.scripts.ps1.deobfuscation.constants-
Inline constant variable references in PowerShell scripts.
refinery.lib.scripts.ps1.deobfuscation.deadcode-
Eliminate dead code from PowerShell scripts after constant folding.
refinery.lib.scripts.ps1.deobfuscation.emulator-
Evaluate user-defined PowerShell functions called with constant arguments.
refinery.lib.scripts.ps1.deobfuscation.expandable-
Hoist void subexpressions out of expandable strings, replacing the expandable string with a plain string literal of its text parts. The hoisted …
refinery.lib.scripts.ps1.deobfuscation.folding-
PowerShell constant folding transforms.
refinery.lib.scripts.ps1.deobfuscation.helpers-
Shared utilities for PowerShell deobfuscation transforms.
refinery.lib.scripts.ps1.deobfuscation.iexinline-
Inline constant IEX (Invoke-Expression) and [scriptblock]::Create() calls by parsing the string argument …
refinery.lib.scripts.ps1.deobfuscation.options-
Caller-supplied options controlling PowerShell deobfuscation.
refinery.lib.scripts.ps1.deobfuscation.removal-
The single route by which a PowerShell cleanup pass removes or rewrites statements in a body.
refinery.lib.scripts.ps1.deobfuscation.rename-
Rename obfuscated variable names to short sequential identifiers.
refinery.lib.scripts.ps1.deobfuscation.securestring-
PowerShell SecureString decryption transformer.
refinery.lib.scripts.ps1.deobfuscation.simplify-
PowerShell syntax normalization transforms.
refinery.lib.scripts.ps1.deobfuscation.substitution-
The single route by which a PowerShell cleanup pass puts one part of the tree in another's place …
refinery.lib.scripts.ps1.deobfuscation.typecast-
PowerShell type cast simplification transforms.
refinery.lib.scripts.ps1.deobfuscation.typenames-
.NET type system utilities for PowerShell deobfuscation.
refinery.lib.scripts.ps1.deobfuscation.unflatten-
Recover original control flow from control-flow-flattened PowerShell scripts …
refinery.lib.scripts.ps1.deobfuscation.unused-
Remove unused variable assignments and junk expression statements.
refinery.lib.scripts.ps1.deobfuscation.wildcards-
Resolve wildcard-based obfuscation patterns in PowerShell scripts …
Functions
def deobfuscate(ast, max_steps=0, remove_junk=True, preserve_bare_output=False)-
Apply all available deobfuscators to the input. When
remove_junkisTrue, a second pass removes unused variable assignments, uncalled function definitions, and side-effect-free expression statements.The two switches are not the same knob at different strengths.
remove_junkdecides whether that second pass runs at all, so turning it off also keeps every dead store and uncalled function,preserve_bare_outputdecides one question inside it — whether a statement whose only effect is to write a value to the success output stream may be deleted — and leaves the rest of the pass working. SeePs1DeobfuscationOptionsfor what that costs and the assumption it rests on.Expand source code Browse git
def deobfuscate( ast: Ps1Script, max_steps: int = 0, remove_junk: bool = True, preserve_bare_output: bool = False, ) -> int: """ Apply all available deobfuscators to the input. When `remove_junk` is `True`, a second pass removes unused variable assignments, uncalled function definitions, and side-effect-free expression statements. The two switches are not the same knob at different strengths. `remove_junk` decides whether that second pass runs at all, so turning it off also keeps every dead store and uncalled function, `preserve_bare_output` decides one question inside it — whether a statement whose only effect is to write a value to the success output stream may be deleted — and leaves the rest of the pass working. See `refinery.lib.scripts.ps1.deobfuscation.options.Ps1DeobfuscationOptions` for what that costs and the assumption it rests on. """ # One analysis cache is built over the script and shared across both phases; the now-honored # `tree_version` counter keeps it consistent even across the two pipeline runs, so a transform in # either phase queries models built on the current tree instead of rebuilding them per pass. cache = Ps1ModelCache(ast) # Both phases are handed the same options, because a configuration two pipelines disagree about # is a configuration neither of them states. Only phase 2 holds the pass that reads this one # today, and building the phase-1 call that way would encode which pass that is. options = Ps1DeobfuscationOptions(preserve_bare_output=preserve_bare_output) steps = _phase1.run(ast, max_steps=max_steps, models=cache, options=options) if not remove_junk: return steps # Carry the phase-1 step count into phase 2 so a single `max_steps` budget is enforced across # both phases. Splitting the budget instead lets a phase-1 result of exactly `max_steps` leave a # remaining budget of 0, which the pipeline would read as "unlimited" and run phase 2 unbounded. return _phase2.run( ast, max_steps=max_steps, initial_steps=steps, models=cache, options=options, )