Module refinery.units.scripting.bat
Expand source code Browse git
from __future__ import annotations
import codecs
import ntpath
import uuid
from refinery.lib.meta import MV
from refinery.lib.scripts.bat import BatchEmulator, BatchState
from refinery.lib.scripts.bat.emulator import BatchEmulatorConfig
from refinery.lib.types import Param, buf
from refinery.units import Arg, Unit
class BatchEmulatorUnit(Unit, abstract=True):
def __init__(
self,
name: Param[buf | None, Arg.Binary(
help='The emulated file name, a random name is chosen by default.')] = None,
*args: Param[buf, Arg.Binary(
help='All remaining arguments are passed to the Batch emulation.')],
skip_goto: Param[bool, Arg.Switch('-G',
help='Do not trace into GOTO statements; emulation resumes at the next line instead.')] = False,
skip_call: Param[bool, Arg.Switch('-C',
help='Do not trace into CALL statements; emulation resumes at the next line instead.')] = False,
skip_exit: Param[bool, Arg.Switch('-E',
help='Do not respect EXIT commands; continue execution regardless.')] = False,
delayed_expand: Param[bool, Arg.Switch('-D',
help='Set delayed expansion of variables.')] = False,
cmdline: Param[bool, Arg.Switch('-m',
help='Use command-line mode where for-loop variables use single percent signs.')] = False,
**kwargs
):
super().__init__(
name=name,
args=args,
skip_goto=skip_goto,
skip_call=skip_call,
skip_exit=skip_exit,
delayed_expand=delayed_expand,
cmdline=cmdline,
**kwargs
)
def _emulator(self, data: buf | str):
state = BatchState()
cfg = BatchEmulatorConfig(
skip_goto=self.args.skip_goto,
skip_call=self.args.skip_call,
skip_exit=self.args.skip_exit,
)
if (name := self.args.name):
state.name = codecs.decode(name, self.codec)
else:
state.name = F'{uuid.uuid4()!s}.bat'
state.command_line = ' '.join(
codecs.decode(arg, self.codec) for arg in self.args.args)
emulator = BatchEmulator(data, state, cfg)
emulator.state.delayexpand = self.args.delayed_expand
emulator.state.cmdline = self.args.cmdline
return emulator
class bat(BatchEmulatorUnit):
"""
Emulate batch file execution and extract command lines.
Each command line that would be executed is emitted as an individual chunk. This can
remove simple obfuscation based on expansion of environment variables.
"""
def __init__(
self,
name=None,
*args,
skip_goto=False,
skip_call=False,
skip_exit=False,
delayed_expand=False,
cmdline=False,
show_junk: Param[bool, Arg.Switch('-j',
help='Synthesize emulated commands that look like junk; hidden by default.')] = False,
show_labels: Param[bool, Arg.Switch('-l', help=(
'Synthesize labels. These are shown at the time the emulator encounters them, which is '
'not necessarily where they occur in the file.'))] = False,
show_nops: Param[bool, Arg.Switch('-n',
help='Synthesize emulated commands that have no effect; hidden by default.')] = False,
show_comments: Param[bool, Arg.Switch('-r',
help='Synthesize REM and :: comments. These are hidden by default.')] = False,
show_sets: Param[bool, Arg.Switch('-s',
help='Synthesize environment variable assignments; hidden by default.')] = False,
):
super().__init__(
name, *args,
show_sets=show_sets,
skip_goto=skip_goto,
skip_call=skip_call,
skip_exit=skip_exit,
delayed_expand=delayed_expand,
cmdline=cmdline,
show_junk=show_junk,
show_labels=show_labels,
show_nops=show_nops,
show_comments=show_comments,
)
def process(self, data):
emulator = self._emulator(data)
for cmd in emulator.emulate():
yield cmd.encode(self.codec)
class batev(BatchEmulatorUnit):
"""
Extract environment variables created by an input batch script, based on emulation.
"""
def process(self, data):
emulator = self._emulator(data)
environment_variables: dict[str, str] = {}
for _ in emulator.emulate():
for name, value in emulator.state.environment.items():
try:
prev = environment_variables[name]
except KeyError:
prev = None
if prev == value:
continue
environment_variables[name] = value
yield self.labelled(value.encode(self.codec), **{MV.NAME: name})
class batfs(BatchEmulatorUnit):
"""
Extract file system artifacts that would be created by a batch script based on emulation.
A chunk is emitted for the content of a file when the script destroys it, either by deletion
or by overwriting it, and for the final content of every file the script has left behind.
The emulated script itself is never an artifact.
"""
def process(self, data):
emulator = self._emulator(data)
state = emulator.state
script = state.resolve_path(state.name)
ingested = None
previous: dict[str, str] = dict(state.file_system)
touched: set[str] = set()
def artifacts(path: str, content: str):
if not content or path == script and content == ingested:
return
relpath = ntpath.relpath(path, state.cwd)
yield self.labelled(content.encode(self.codec), **{MV.PATH: relpath})
def changes():
nonlocal ingested
for path in list(previous):
if path not in state.file_system:
yield from artifacts(path, previous.pop(path))
for path, new in state.file_system.items():
if (old := previous.get(path)) is None:
if path == script:
ingested = new
else:
touched.add(path)
previous[path] = new
elif new != old:
touched.add(path)
previous[path] = new
if not new.startswith(old):
yield from artifacts(path, old)
for _ in emulator.emulate():
yield from changes()
yield from changes()
for path in touched:
if (final := state.file_system.get(path)) is not None:
yield from artifacts(path, final)
Classes
class BatchEmulatorUnit (name=None, *args, skip_goto=False, skip_call=False, skip_exit=False, delayed_expand=False, cmdline=False, **kwargs)-
Expand source code Browse git
class BatchEmulatorUnit(Unit, abstract=True): def __init__( self, name: Param[buf | None, Arg.Binary( help='The emulated file name, a random name is chosen by default.')] = None, *args: Param[buf, Arg.Binary( help='All remaining arguments are passed to the Batch emulation.')], skip_goto: Param[bool, Arg.Switch('-G', help='Do not trace into GOTO statements; emulation resumes at the next line instead.')] = False, skip_call: Param[bool, Arg.Switch('-C', help='Do not trace into CALL statements; emulation resumes at the next line instead.')] = False, skip_exit: Param[bool, Arg.Switch('-E', help='Do not respect EXIT commands; continue execution regardless.')] = False, delayed_expand: Param[bool, Arg.Switch('-D', help='Set delayed expansion of variables.')] = False, cmdline: Param[bool, Arg.Switch('-m', help='Use command-line mode where for-loop variables use single percent signs.')] = False, **kwargs ): super().__init__( name=name, args=args, skip_goto=skip_goto, skip_call=skip_call, skip_exit=skip_exit, delayed_expand=delayed_expand, cmdline=cmdline, **kwargs ) def _emulator(self, data: buf | str): state = BatchState() cfg = BatchEmulatorConfig( skip_goto=self.args.skip_goto, skip_call=self.args.skip_call, skip_exit=self.args.skip_exit, ) if (name := self.args.name): state.name = codecs.decode(name, self.codec) else: state.name = F'{uuid.uuid4()!s}.bat' state.command_line = ' '.join( codecs.decode(arg, self.codec) for arg in self.args.args) emulator = BatchEmulator(data, state, cfg) emulator.state.delayexpand = self.args.delayed_expand emulator.state.cmdline = self.args.cmdline return emulatorAncestors
Subclasses
Inherited members
Unit:FilterEverythingRequiresactassemblecarvedcodecconsolefinishhandlesis_quietis_reversibleisattylabelledleniencylog_alwayslog_debuglog_detachlog_faillog_infolog_levellog_warnloggernamenozzleoptional_dependenciesreadread1required_dependenciesresetreverserunsourcesuperinit
UnitBase:
class bat (name=None, *args, skip_goto=False, skip_call=False, skip_exit=False, delayed_expand=False, cmdline=False, show_junk=False, show_labels=False, show_nops=False, show_comments=False, show_sets=False)-
Emulate batch file execution and extract command lines.
Each command line that would be executed is emitted as an individual chunk. This can remove simple obfuscation based on expansion of environment variables.
Expand source code Browse git
class bat(BatchEmulatorUnit): """ Emulate batch file execution and extract command lines. Each command line that would be executed is emitted as an individual chunk. This can remove simple obfuscation based on expansion of environment variables. """ def __init__( self, name=None, *args, skip_goto=False, skip_call=False, skip_exit=False, delayed_expand=False, cmdline=False, show_junk: Param[bool, Arg.Switch('-j', help='Synthesize emulated commands that look like junk; hidden by default.')] = False, show_labels: Param[bool, Arg.Switch('-l', help=( 'Synthesize labels. These are shown at the time the emulator encounters them, which is ' 'not necessarily where they occur in the file.'))] = False, show_nops: Param[bool, Arg.Switch('-n', help='Synthesize emulated commands that have no effect; hidden by default.')] = False, show_comments: Param[bool, Arg.Switch('-r', help='Synthesize REM and :: comments. These are hidden by default.')] = False, show_sets: Param[bool, Arg.Switch('-s', help='Synthesize environment variable assignments; hidden by default.')] = False, ): super().__init__( name, *args, show_sets=show_sets, skip_goto=skip_goto, skip_call=skip_call, skip_exit=skip_exit, delayed_expand=delayed_expand, cmdline=cmdline, show_junk=show_junk, show_labels=show_labels, show_nops=show_nops, show_comments=show_comments, ) def process(self, data): emulator = self._emulator(data) for cmd in emulator.emulate(): yield cmd.encode(self.codec)Ancestors
Subclasses
Class variables
var reverse-
The type of the None singleton.
Inherited members
BatchEmulatorUnit:FilterEverythingRequiresactassemblecarvedcodecconsolefilterfinishhandlesis_quietis_reversibleisattylabelledleniencylog_alwayslog_debuglog_detachlog_faillog_infolog_levellog_warnloggernamenozzleoptional_dependenciesprocessreadread1required_dependenciesresetrunsourcesuperinit
class batev (name=None, *args, skip_goto=False, skip_call=False, skip_exit=False, delayed_expand=False, cmdline=False)-
Extract environment variables created by an input batch script, based on emulation.
Expand source code Browse git
class batev(BatchEmulatorUnit): """ Extract environment variables created by an input batch script, based on emulation. """ def process(self, data): emulator = self._emulator(data) environment_variables: dict[str, str] = {} for _ in emulator.emulate(): for name, value in emulator.state.environment.items(): try: prev = environment_variables[name] except KeyError: prev = None if prev == value: continue environment_variables[name] = value yield self.labelled(value.encode(self.codec), **{MV.NAME: name})Ancestors
Subclasses
Class variables
var reverse-
The type of the None singleton.
Inherited members
BatchEmulatorUnit:FilterEverythingRequiresactassemblecarvedcodecconsolefilterfinishhandlesis_quietis_reversibleisattylabelledleniencylog_alwayslog_debuglog_detachlog_faillog_infolog_levellog_warnloggernamenozzleoptional_dependenciesprocessreadread1required_dependenciesresetrunsourcesuperinit
class batfs (name=None, *args, skip_goto=False, skip_call=False, skip_exit=False, delayed_expand=False, cmdline=False)-
Extract file system artifacts that would be created by a batch script based on emulation.
A chunk is emitted for the content of a file when the script destroys it, either by deletion or by overwriting it, and for the final content of every file the script has left behind. The emulated script itself is never an artifact.
Expand source code Browse git
class batfs(BatchEmulatorUnit): """ Extract file system artifacts that would be created by a batch script based on emulation. A chunk is emitted for the content of a file when the script destroys it, either by deletion or by overwriting it, and for the final content of every file the script has left behind. The emulated script itself is never an artifact. """ def process(self, data): emulator = self._emulator(data) state = emulator.state script = state.resolve_path(state.name) ingested = None previous: dict[str, str] = dict(state.file_system) touched: set[str] = set() def artifacts(path: str, content: str): if not content or path == script and content == ingested: return relpath = ntpath.relpath(path, state.cwd) yield self.labelled(content.encode(self.codec), **{MV.PATH: relpath}) def changes(): nonlocal ingested for path in list(previous): if path not in state.file_system: yield from artifacts(path, previous.pop(path)) for path, new in state.file_system.items(): if (old := previous.get(path)) is None: if path == script: ingested = new else: touched.add(path) previous[path] = new elif new != old: touched.add(path) previous[path] = new if not new.startswith(old): yield from artifacts(path, old) for _ in emulator.emulate(): yield from changes() yield from changes() for path in touched: if (final := state.file_system.get(path)) is not None: yield from artifacts(path, final)Ancestors
Subclasses
Class variables
var reverse-
The type of the None singleton.
Inherited members
BatchEmulatorUnit:FilterEverythingRequiresactassemblecarvedcodecconsolefilterfinishhandlesis_quietis_reversibleisattylabelledleniencylog_alwayslog_debuglog_detachlog_faillog_infolog_levellog_warnloggernamenozzleoptional_dependenciesprocessreadread1required_dependenciesresetrunsourcesuperinit