Module 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 the value domain; the globals it may read are the ones the propagation walk proved at that call site, and the gate that admits the function is the walk's may-write summary, so a call this transform rewrites changed nothing the walk learned and the models of the pass stay sound.
A call qualifies when its direct callee is a script function, every argument is a literal the
value domain reads, and the call stands where an expression belongs — a call that is itself the
whole statement produces no value, and a bare literal is not a statement, so that call stays for
the dead-code phase to remove. The visit is bottom-up, so a decoder fed by another decoder —
nvb(sjw("…"), 8) — resolves in one pass: the inner call is replaced before the outer one is
asked.
The literal it installs faces the same render gates the propagation pass obeys, including the depth bound: a decoder whose result spells as a chain deeper than the recursive emitter prints leaves the call standing.
Expand source code Browse git
"""
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 the value domain; the globals it may
read are the ones the propagation walk proved at that call site, and the gate that admits the
function is the walk's may-write summary, so a call this transform rewrites changed nothing the
walk learned and the models of the pass stay sound.
A call qualifies when its direct callee is a script function, every argument is a literal the
value domain reads, and the call stands where an expression belongs — a call that is itself the
whole statement produces no value, and a bare literal is not a statement, so that call stays for
the dead-code phase to remove. The visit is bottom-up, so a decoder fed by another decoder —
`nvb(sjw("…"), 8)` — resolves in one pass: the inner call is replaced before the outer one is
asked.
The literal it installs faces the same render gates the propagation pass obeys, including the
depth bound: a decoder whose result spells as a chain deeper than the recursive emitter prints
leaves the call standing.
"""
from __future__ import annotations
from refinery.lib.scripts import Node, Transformer
from refinery.lib.scripts.au3 import ast
from refinery.lib.scripts.au3.analysis.cache import model_cache
from refinery.lib.scripts.au3.analysis.interpreter import Au3Interpreter
from refinery.lib.scripts.au3.analysis.propagation import Au3PropagationModel
from refinery.lib.scripts.au3.analysis.scopes import Au3ScopeModel
from refinery.lib.scripts.au3.analysis.values import literal_value, render_at
from refinery.lib.scripts.au3.model import (
Au3Call,
Au3ExpressionStatement,
Au3Script,
)
class Au3FunctionEvaluation(Transformer):
"""
Replaces a call to a script function the interpreter runs with the literal of its result,
wherever the position of the call accepts that literal.
"""
def __init__(self):
super().__init__()
self._scopes: Au3ScopeModel | None = None
self._propagation: Au3PropagationModel | None = None
self._interpreter: Au3Interpreter | None = None
def visit(self, node: Node) -> Node | None:
if isinstance(node, Au3Script):
cache = model_cache(self, node)
self._scopes = cache.scopes
self._propagation = cache.propagation
self._interpreter = Au3Interpreter(cache.scopes, cache.propagation.summaries)
self.generic_visit(node)
if not isinstance(node, Au3Call):
return None
if self._scopes is None or self._propagation is None or self._interpreter is None:
return None
statement = ast.owning_statement(node)
if (
isinstance(statement, Au3ExpressionStatement)
and ast.unwrap_parens(statement.expression) is node
):
return None
callee = ast.direct_callee(node)
if callee is None:
return None
function = self._scopes.functions.get(callee.name)
if function is None:
return None
arguments = []
for argument in node.arguments:
value = literal_value(argument, self._scopes.function_names)
if value is None:
return None
arguments.append(value)
result = self._interpreter.call(
function, arguments, self._propagation.entry_globals(node))
if result is None:
return None
return render_at(node, result, self._scopes.function_names)
Classes
class Au3FunctionEvaluation-
Replaces a call to a script function the interpreter runs with the literal of its result, wherever the position of the call accepts that literal.
Expand source code Browse git
class Au3FunctionEvaluation(Transformer): """ Replaces a call to a script function the interpreter runs with the literal of its result, wherever the position of the call accepts that literal. """ def __init__(self): super().__init__() self._scopes: Au3ScopeModel | None = None self._propagation: Au3PropagationModel | None = None self._interpreter: Au3Interpreter | None = None def visit(self, node: Node) -> Node | None: if isinstance(node, Au3Script): cache = model_cache(self, node) self._scopes = cache.scopes self._propagation = cache.propagation self._interpreter = Au3Interpreter(cache.scopes, cache.propagation.summaries) self.generic_visit(node) if not isinstance(node, Au3Call): return None if self._scopes is None or self._propagation is None or self._interpreter is None: return None statement = ast.owning_statement(node) if ( isinstance(statement, Au3ExpressionStatement) and ast.unwrap_parens(statement.expression) is node ): return None callee = ast.direct_callee(node) if callee is None: return None function = self._scopes.functions.get(callee.name) if function is None: return None arguments = [] for argument in node.arguments: value = literal_value(argument, self._scopes.function_names) if value is None: return None arguments.append(value) result = self._interpreter.call( function, arguments, self._propagation.entry_globals(node)) if result is None: return None return render_at(node, result, self._scopes.function_names)Ancestors
Methods
def visit(self, node)-
Expand source code Browse git
def visit(self, node: Node) -> Node | None: if isinstance(node, Au3Script): cache = model_cache(self, node) self._scopes = cache.scopes self._propagation = cache.propagation self._interpreter = Au3Interpreter(cache.scopes, cache.propagation.summaries) self.generic_visit(node) if not isinstance(node, Au3Call): return None if self._scopes is None or self._propagation is None or self._interpreter is None: return None statement = ast.owning_statement(node) if ( isinstance(statement, Au3ExpressionStatement) and ast.unwrap_parens(statement.expression) is node ): return None callee = ast.direct_callee(node) if callee is None: return None function = self._scopes.functions.get(callee.name) if function is None: return None arguments = [] for argument in node.arguments: value = literal_value(argument, self._scopes.function_names) if value is None: return None arguments.append(value) result = self._interpreter.call( function, arguments, self._propagation.entry_globals(node)) if result is None: return None return render_at(node, result, self._scopes.function_names)
Inherited members