Source code for bashfuscator.core.engine.obfuscation_handler

"""
Defines ObufscationHandler, which manages the obfuscation process.
"""
from bashfuscator.common.messages import printError, printWarning
from bashfuscator.core.engine.mangler import Mangler
from bashfuscator.core.engine.random import RandomGen
from bashfuscator.core.utils import import_mutators


[docs]class ObfuscationHandler(object): """ Manages command and script obfuscation, taking into account all user options and preferences. This class is the heart of the framework. :param cmdObfuscators: CommandObfuscators useable during execution :type cmdObfuscators: list of :class:`bashfuscator.lib.command_mutators.CommandObfuscator` :param strObfuscators: StringObfuscators useable during execution :type strObfuscators: list of :class:`bashfuscator.lib.string_mutators.StringObfuscator` :param tokObfuscators: TokenObfuscators useable during execution :type tokObfuscators: list of :class:`bashfuscator.lib.token_mutators.TokenObfuscator` :param encoders: Encoders useable during execution :type encoders: list of :class:`bashfuscator.lib.encoders.Encoder` :param compressors: Compressors useable during execution :type compressors: list of :class:`bashfuscator.lib.compressors.Compressor` :param args: arguments specified on the command line. If this parameter is not supplied, default values will be set for ObfuscationHandler's attributes. :type args: arguments parsed from :py:meth:`argparse.ArgumentParser.parse_args` in :mod:`bashfuscator.bin.bashfuscator` .. note:: If not set, the cmdObfuscators, cmdObfuscators, tokObfuscators, encoders, and compressors arguments will default to all of the respective Mutator Types contained by the framework. """ def __init__(self, cmdObfuscators=None, strObfuscators=None, tokObfuscators=None, encoders=None, compressors=None, args=None): if cmdObfuscators and strObfuscators and tokObfuscators and encoders and compressors: self.cmdObfuscators = cmdObfuscators self.strObfuscators = strObfuscators self.tokObfuscators = tokObfuscators self.encoders = encoders self.compressors = compressors else: self.cmdObfuscators, self.strObfuscators, self.tokObfuscators, self.encoders, self.compressors = import_mutators() if args: self.layers = args.layers self.sizePref = args.payload_size self.timePref = args.execution_time self.binaryPref = args.binaryPref self.filePref = args.no_file_write self.writeDir = args.write_dir self.full_ascii_strings = args.full_ascii_strings self.debug = args.debug self.clip = args.clip self.originalCmd = args.command if args.choose_mutators: self.userMutators = args.choose_mutators elif args.choose_all: self.userMutators = args.choose_all else: self.userMutators = None if args.no_mangling is not None: self.enableMangling = args.no_mangling else: self.enableMangling = None if args.no_binary_mangling is not None: self.mangleBinaries = args.no_binary_mangling else: self.mangleBinaries = None if args.binary_mangle_percent: self.binaryManglePercent = args.binary_mangle_percent else: self.binaryManglePercent = None if args.no_random_whitespace is not None: self.randWhitespace = args.no_random_whitespace else: self.randWhitespace = None if args.random_whitespace_range: self.randWhitespaceRange = args.random_whitespace_range else: self.randWhitespaceRange = None if args.no_insert_chars is not None: self.insertChars = args.no_insert_chars else: self.insertChars = None if args.insert_chars_range: self.insertCharsRange = args.insert_chars_range else: self.insertCharsRange = None if args.no_misleading_commands is not None: self.misleadingCmds = args.no_misleading_commands else: self.misleadingCmds = None if args.misleading_commands_range: self.misleadingCmdsRange = args.misleading_commands_range else: self.misleadingCmdsRange = None if args.no_integer_mangling is not None: self.mangleIntegers = args.no_integer_mangling else: self.mangleIntegers = None if args.no_integer_expansion is not None: self.expandIntegers = args.no_integer_expansion else: self.expandIntegers = None if args.no_integer_base_randomization is not None: self.randomizeIntegerBases = args.no_integer_base_randomization else: self.randomizeIntegerBases = None if args.integer_expansion_depth: self.integerExpansionDepth = args.integer_expansion_depth else: self.integerExpansionDepth = None if args.no_terminator_randomization is not None: self.randomizeTerminators = args.no_terminator_randomization else: self.randomizeTerminators = None else: self.sizePref = 2 self.timePref = 2 self.binaryPref = None self.filePref = True self.writeDir = "/tmp/" self.full_ascii_strings = False self.debug = False self.clip = False self.userMutators = None self.enableMangling = None self.mangleBinaries = None self.binaryManglePercent = None self.randWhitespace = None self.randWhitespaceRange = None self.insertChars = None self.insertCharsRange = None self.misleadingCmds = None self.misleadingCmdsRange = None self.mangleIntegers = None self.expandIntegers = None self.randomizeIntegerBases = None self.integerExpansionDepth = None self.randomizeTerminators = None self.prevCmdOb = None self.mutatorList = [] self.randGen = RandomGen() if args and args.full_ascii_strings: self.randGen.setFullAsciiStrings()
[docs] def generatePayload(self): """ Generate the final payload. Obfuscates the original input by feeding it into Mutators a number of times as specified by the '--layers' option. :returns: a str containing the final obfuscated payload """ payload = self.originalCmd for i in range(self.layers): if self.userMutators: for userMutator in self.userMutators: userStub = None if userMutator.count("/") == 2: if userMutator[-1] == "/": userMutator = userMutator[:-1] else: userStub = userMutator.split("/")[2] userMutator = userMutator[:-int(len(userStub) + 1)] self.mutatorList.append(self.getMutator(userMutator, userStub, self.sizePref, self.timePref, self.binaryPref, self.filePref)) else: self.mutatorList.append(self.getMutator(sizePref=self.sizePref, timePref=self.timePref, binaryPref=self.binaryPref, filePref=self.filePref)) self.checkMutatorList() for mutator in self.mutatorList: mutator.writeDir = self.writeDir mutator.mangler._initialize(self.sizePref, self.enableMangling, self.mangleBinaries, self.binaryManglePercent, self.randWhitespace, self.randWhitespaceRange, self.insertChars, self.insertCharsRange, self.misleadingCmds, self.misleadingCmdsRange, self.mangleIntegers, self.expandIntegers, self.randomizeIntegerBases, self.integerExpansionDepth, self.randomizeTerminators, self.debug) payload = mutator.mutate(payload) mutator._obfuscatedCmd = payload self.randGen.forgetUniqueStrs() payload = self.evalWrap(payload, mutator) return payload
def checkMutatorList(self): reverseableMutator = "" nonReadableWarning = False for i, mutator in enumerate(self.mutatorList): if self.clip and ((mutator.unreadableOutput and not nonReadableWarning) or self.full_ascii_strings): printWarning("Output may consist of unreadable ASCII characters and probably won't execute from your clipboard correctly. Saving output with '-o' is recommended") nonReadableWarning = True if mutator.mutatorType == "command" and mutator.reversible: if reverseableMutator == mutator.longName: printWarning(f"{mutator.longName} used twice in a row, part of the output may be in the clear") reverseableMutator = "" else: reverseableMutator = mutator.longName else: reverseableMutator = "" def getMutator(self, userMutator=None, userStub=None, sizePref=None, timePref=None, binaryPref=None, filePref=None): selMutator = None if userMutator: mutatorType = userMutator.split("/")[0] if mutatorType == "command": selMutator = self.choosePrefMutator(self.cmdObfuscators, sizePref, timePref, binaryPref, filePref, self.prevCmdOb, userMutator, userStub) self.prevCmdOb = selMutator elif mutatorType == "string": selMutator = self.choosePrefMutator(self.strObfuscators, binaryPref=binaryPref, filePref=filePref, userMutator=userMutator) elif mutatorType == "token": selMutator = self.choosePrefMutator(self.tokObfuscators, binaryPref=binaryPref, filePref=filePref, userMutator=userMutator) elif mutatorType == "encode": selMutator = self.choosePrefMutator(self.encoders, binaryPref=binaryPref, filePref=filePref, userMutator=userMutator) elif mutatorType == "compress": selMutator = self.choosePrefMutator(self.compressors, binaryPref=binaryPref, filePref=filePref, userMutator=userMutator) else: printError(f"{mutatorType} isn't a valid mutator type") else: # TODO: handle case when no mutators of chosen type are compatible with user's preferences obChoice = self.randGen.randChoice(3) if obChoice == 0: selMutator = self.choosePrefMutator(self.cmdObfuscators, sizePref, timePref, binaryPref, filePref, self.prevCmdOb) self.prevCmdOb = selMutator elif obChoice == 1: selMutator = self.choosePrefMutator(self.strObfuscators, sizePref, timePref, binaryPref, filePref) else: selMutator = self.choosePrefMutator(self.tokObfuscators, sizePref, timePref) selMutator.sizePref = sizePref selMutator.timePref = timePref return selMutator # TODO: update docs
[docs] def genObfuscationLayer(self, payload, userMutator=None, userStub=None, sizePref=None, timePref=None, binaryPref=None, filePref=None, writeDir=None, enableMangling=None, mangleBinaries=None, binaryManglePercent=None, randWhitespace=None, randWhitespaceRange=None, insertChars=None, insertCharsRange=None, misleadingCmds=None, misleadingCmdsRange=None, mangleIntegers=None, expandIntegers=None, randomizeIntegerBases=None, integerExpansionDepth=None, randomizeTerminators=None, debug=None): """ Generate one layer of obfuscation. If called with the userMutator or userStub parameters, the Mutator and/or Stub specified by userMutator and/or userStub will be used to mutate the payload. If those parameters are not used, a Mutator and Stub (if appropriate) will be chosen automatically. .. note:: If not set, the sizePref, timePref, binaryPref, filePref, and writeDir parameters will be set to the coresponding attributes of the ObfuscationHandler object being called from. :param payload: input command(s) to obfuscate :type payload: str :param userMutator: the `longName` attribute of a :class:`bashfuscator.common.objects.Mutator` :type userMutator: lowercase str :param userStub: the `longName` attribute of a :class:`bashfuscator.common.objects.Stub` :type userStub: lowercase str :param sizePref: payload size user preference :type sizePref: int :param timePref: execution time user preference :type timePref: int :param binaryPref: list of binaries that the chosen Mutator should or should not use :type binaryPref: tuple containing a list of strs, and a bool :param filePref: file write user preference :type filePref: bool :returns: a str containing the 'payload' argument obfuscated by a single Mutator """ if sizePref is None: sizePref = self.sizePref if timePref is None: timePref = self.timePref if binaryPref is None: binaryPref = self.binaryPref if filePref is None: filePref = self.filePref if writeDir is None: writeDir = self.writeDir if enableMangling is None: enableMangling = self.enableMangling if mangleBinaries is None: mangleBinaries = self.mangleBinaries if binaryManglePercent is None: binaryManglePercent = self.binaryManglePercent if randWhitespace is None: randWhitespace = self.randWhitespace if randWhitespaceRange is None: randWhitespaceRange = self.randWhitespaceRange if insertChars is None: insertChars = self.insertChars if insertCharsRange is None: insertCharsRange = self.insertCharsRange if misleadingCmds is None: misleadingCmds = self.misleadingCmds if misleadingCmdsRange is None: misleadingCmdsRange = self.misleadingCmdsRange if mangleIntegers is None: mangleIntegers = self.mangleIntegers if expandIntegers is None: expandIntegers = self.expandIntegers if randomizeIntegerBases is None: randomizeIntegerBases = self.randomizeIntegerBases if integerExpansionDepth is None: integerExpansionDepth = self.integerExpansionDepth if randomizeTerminators is None: randomizeTerminators = self.randomizeTerminators if debug is None: debug = self.debug selMutator = self.getMutator(userMutator, userStub, sizePref, timePref, binaryPref, filePref) selMutator.writeDir = writeDir selMutator.mangler._initialize(sizePref, enableMangling, mangleBinaries, binaryManglePercent, randWhitespace, randWhitespaceRange, insertChars, insertCharsRange, misleadingCmds, misleadingCmdsRange, mangleIntegers, expandIntegers, randomizeIntegerBases, integerExpansionDepth, randomizeTerminators, debug) payload = selMutator.mutate(payload) selMutator._obfuscatedCmd = payload self.randGen.forgetUniqueStrs() payload = self.evalWrap(payload, selMutator) return payload
[docs] def evalWrap(self, payload, selMutator): """ Wrap the payload in an execution stub, to allow bash to execute the string produced by the payload. Will not wrap the payload if certain Mutators were used to generate the most recent layer of the payload. :param payload: input command(s) to wrap :type payload: str :param selMutator: Mutator used by :meth:`~ObfuscationHandler.genObfuscationLayer` to generate the most recent layer of obfuscation :type selMutator: :class:`bashfuscator.common.objects.Mutator` :returns: a str containing the wrapped payload, if appropriate """ if selMutator.evalWrap: evalMethodChoice = self.randGen.randChoice(3) if evalMethodChoice == 1: wrappedPayload = selMutator.mangler._mangleLine('* *:eval:^ ^"$(? ?DATA? ?)"* *', payload) else: shellChoice = self.randGen.randChoice(3) if shellChoice == 0: bashShell = ":bash:" elif shellChoice == 1: bashShell = "$BASH" else: bashShell = "${!#}" if evalMethodChoice == 2: wrappedPayload = selMutator.mangler._mangleLine(f'* *:printf:^ ^%s^ ^"$(? ?DATA? ?)"* *|* *{bashShell}* *', payload) else: wrappedPayload = selMutator.mangler._mangleLine(f'* *{bashShell}% %<<<^ ^"$(? ?DATA? ?)"* *', payload) # if the Mutator evals itself, wrap it in a subshell so it doesn't pollute the parent shell environment else: wrappedPayload = selMutator.mangler._mangleLine(f"? ?(? ?DATA? ?)", payload) return wrappedPayload
[docs] def choosePrefMutator(self, mutators, sizePref=None, timePref=None, binaryPref=None, filePref=None, prevCmdOb=None, userMutator=None, userStub=None): """ Chooses a Mutator from a list of mutators which is of the desired preferences, with a stub that uses desired binaries if appropriate. If called with the userMutator or userStub parameters, the Mutator and/or Stub specified by userMutator and/or userStub will be chosen. If those parameters are not used, a Mutator and Stub (if appropriate) will be chosen automatically based off of the values of the other parameters. :param mutators: list of Mutators to choose a Mutator from :param sizePref: payload size user preference :type sizePref: int :param timePref: execution time user preference :type timePref: int :param binaryPref: list of binaries that the chosen Mutator should or should not use :type binaryPref: tuple containing a list of strs, and a bool :param filePref: file write user preference :type filePref: bool :param prevCmdOb: the previous CommandObfuscator used. Should only be passed if a CommandObfuscator was used to generate the most recent obfuscation layer :type prevCmdOb: :class:`bashfuscator.lib.command_mutators.CommandObfuscator` :param userMutator: the specific Mutator the user chose to use :type userMutator: lowercase str :param userStub: the specific Stub the user chose to use :type userStub: lowercase str :returns: a :class:`bashfuscator.common.objects.Mutator` object """ selMutator = None if userMutator: if binaryPref: binList = binaryPref[0] includeBinary = binaryPref[1] for mutator in mutators: if mutator.longName == userMutator: if filePref is False and mutator.mutatorType != "command" and mutator.fileWrite != filePref: printWarning(f"'{userMutator}' mutator preforms file writes") elif binaryPref and mutator.mutatorType != "command": for binary in mutator.binariesUsed: if (binary in binList) != includeBinary: printWarning(f"'{userMutator}' mutator contains an unwanted binary") selMutator = mutator if selMutator.mutatorType == "command": selMutator.prefStubs = self.getPrefStubs(selMutator.stubs, sizePref, timePref, binaryPref, filePref) break if selMutator is None: printError(f"Selected mutator '{userMutator}' not found") else: prefMutators = self.getPrefMutators(mutators, sizePref, timePref, binaryPref, filePref, prevCmdOb) selMutator = self.randGen.randSelect(prefMutators) if selMutator is not None and selMutator.mutatorType == "command": selMutator.deobStub = self.choosePrefStub(selMutator.prefStubs, sizePref, timePref, binaryPref, filePref, userStub) if selMutator.deobStub: selMutator.deobStub.mangler = selMutator.mangler selMutator.deobStub.randGen = selMutator.mangler.randGen else: printError(f"All of '{selMutator.longName}'s Stubs do not fulfil your requirements") return selMutator
[docs] def getPrefMutators(self, mutators, sizePref, timePref, binaryPref=None, filePref=None, prevCmdOb=None): """ Get Mutators from a sequence which are suitable to use based off the user's preferences. :param seq: list of Mutators of Stubs :type seq: list :param sizePref: payload size user preference :type sizePref: int :param timePref: execution time user preference :type timePref: int :param binaryPref: list of binaries that the chosen Mutator should or should not use :type binaryPref: tuple containing a list of strs, and a bool :param filePref: file write user preference :type filePref: bool :param prevCmdOb: the previous CommandObfuscator used. Should only be passed if a CommandObfuscator was used to generate the most recent obfuscation layer :type prevCmdOb: :class:`bashfuscator.lib.command_mutators.CommandObfuscator` :returns: list of :class:`bashfuscator.common.objects.Mutator` objects, or None if there are no preferable Mutators in the 'mutators' argument """ goodMutators = self.getPrefItems(mutators, sizePref, timePref) if binaryPref: binList = binaryPref[0] includeBinary = binaryPref[1] prefMutators = [] for mutator in goodMutators: if mutator.mutatorType == "command": if prevCmdOb and prevCmdOb.reversible and prevCmdOb.name == mutator.name: continue prefStubs = self.getPrefStubs(mutator.stubs, sizePref, timePref, binaryPref, filePref) if prefStubs: mutator.prefStubs = prefStubs else: continue elif filePref is False and mutator.mutatorType != "command" and mutator.fileWrite != filePref: continue elif binaryPref: badBinary = False for binary in mutator.binariesUsed: # don't pick a mutator if it uses unwanted binaries, but allow mutators that aren't using # any binaries when the user is using the '--include-binaries' option if (binary in binList) != includeBinary: if includeBinary: if mutator.binariesUsed: badBinary = True break else: continue else: badBinary = True break if badBinary: continue prefMutators.append(mutator) return prefMutators
[docs] def choosePrefStub(self, stubs, sizePref, timePref, binaryPref, filePref, userStub=None): """ Choose a stub which is of the desired sizeRating, timeRating, and uses desired binaries. If the userStub parameter is passed, the specific stub defined by userStub is searched for and is checked to make sure it aligns with the users preferences for used binaries. :param stubs: list of Stubs to choose from :param sizePref: payload size user preference :type sizePref: int :param timePref: execution time user preference :type timePref: int :param binaryPref: list of binaries that the chosen Mutator should or should not use :type binaryPref: tuple containing a list of strs, and a bool :param userStub: the specific Stub the user chose to use :type userStub: lowercase str :returns: a :class:`bashfuscator.common.objects.Stub` object """ selStub = None if binaryPref is not None: binList = binaryPref[0] includeBinary = binaryPref[1] # attempt to find the specific stub the user wants if userStub is not None: for stub in stubs: if stub.longName == userStub: if binaryPref is not None: for binary in stub.binariesUsed: if (binary in binList) != includeBinary: printWarning(f"'{userStub}' stub contains an unwanted binary") if filePref is False and stub.fileWrite != filePref: printWarning(f"'{userStub}' stub preforms file writes") selStub = stub if selStub is None: printError(f"'{userStub}' stub not found") else: selStub = self.randGen.randSelect(stubs) return selStub
[docs] def getPrefStubs(self, stubs, sizePref, timePref, binaryPref, filePref): """ Get Stubs from a sequence which are suitable to use based off the user's preferences. :param seq: list of Mutators of Stubs :type seq: list :param sizePref: payload size user preference :type sizePref: int :param timePref: execution time user preference :type timePref: int :param binaryPref: list of binaries that the chosen Mutator should or should not use :type binaryPref: tuple containing a list of strs, and a bool :returns: list of :class:`bashfuscator.common.objects.Stub` objects, or None if there are no preferable Stubs in the 'stubs' argument """ prefStubs = self.getPrefItems(stubs, sizePref, timePref) if binaryPref is not None: binList = binaryPref[0] includeBinary = binaryPref[1] compatibleStubs = [] for stub in prefStubs: if filePref is False and stub.fileWrite != filePref: continue if binaryPref: badBinary = False for binary in stub.binariesUsed: # don't pick a stub if it uses unwanted binaries, but allow stubs that aren't using # any binaries when the user is using the '--include-binaries' option if (binary in binList) != includeBinary: if includeBinary: if stub.binariesUsed: badBinary = True break else: continue else: badBinary = True break if badBinary: continue compatibleStubs.append(stub) return compatibleStubs
[docs] def getPrefItems(self, seq, sizePref, timePref): """ Get Mutators or Stubs from a sequence which sizeRatings and timeRatings. :param seq: list of Mutators of Stubs :type seq: list :param sizePref: payload size user preference :type sizePref: int :param timePref: execution time user preference :type timePref: int :returns: a list of Mutators or Stubs """ minSize, maxSize = self.getPrefRange(sizePref) minTime, maxTime = self.getPrefRange(timePref) foundItem = False prefItems = [] while not foundItem: for item in seq: if minSize <= item.sizeRating <= maxSize: if timePref is None or (minTime <= item.timeRating <= maxTime): prefItems.append(item) foundItem = True if not foundItem: if not foundItem: if minSize > 1: minSize -= 1 elif maxSize < 5: maxSize += 1 if timePref is not None: if minTime > 1: minTime -= 1 elif maxTime < 5: maxTime += 1 return prefItems
[docs] def getPrefRange(self, pref): """ Get the minimum and maximum sizeRatings or timeRatings that should be used to select obfuscator and stubs :param pref: sizePref or timePref options :returns: tuple of minimum and maximum ratings """ if pref == 1: maxRating = 2 elif pref == 2: maxRating = 3 elif pref == 3: maxRating = 5 return (1, maxRating)