Source code for bashfuscator.core.engine.random

"""
Defines RandomGen, a wrapper around all common functions relying on
randomness.
"""
import string
import random
import re


[docs]class RandomGen(object): """ Wrapper around :py:class:`random.SystemRandom`. Provided for ease of use and to avoid having to initialize a SystemRandom object every time something random is desired. .. note:: The default character set when generating random variable names or strings is the alphanumeric charset, or the (almost) full ASCII charset if :meth:`~RandomGen.setFullAsciiStrings` is called. """ randGen = random.SystemRandom() _generatedVars = set() _uniqueRandStrs = set() _randStrCharList = [c for c in string.ascii_letters + string.digits + string.punctuation] _randStrCharList.remove("'") _randStrCharList.remove("/") _reservedVars = {"auto_resume", "BASH", "BASH_ENV", "BASH_VERSINFO", "BASH_VERSION", "CDPATH", "COLUMNS", "COMP_CWORD", "COMP_LINE", "COMP_POINT", "COMPREPLY", "COMP_WORDS", "DIRSTACK", "EUID", "FCEDIT", "FIGNORE", "FUNCNAME", "GLOBIGNORE", "GROUPS", "histchars", "HISTCMD", "HISTCONTROL", "HISTFILE", "HISTFILESIZE", "HISTIGNORE", "HISTSIZE", "HOME", "HOSTFILE", "HOSTNAME", "HOSTTYPE", "IFS", "IGNOREEOF", "INPUTRC", "LANG", "LC_ALL", "LC_COLLATE", "LC_CTYPE", "LC_MESSAGES", "LC_NUMERIC", "LINENO", "LINES", "MACHTYPE", "MAIL", "MAILCHECK", "MAILPATH", "OLDPWD", "OPTARG", "OPTERR", "OPTIND", "OSTYPE", "PATH", "PIPESTATUS", "POSIXLY_CORRECT", "PPID", "PROMPT_COMMAND", "PS1", "PS2", "PS3", "PS4", "PWD", "RANDOM", "REPLY", "SECONDS", "SHELLOPTS", "SHLVL", "TIMEFORMAT", "TMOUT", "UID"} _boblReservedStrsRegex = re.compile("DATA|END") _boblSyntaxRegex = re.compile(r":\w+:|\^ \^|\? \?|% %|\* \*|#\d+#|&\d+&|DATA|END") def __init__(self): self.sizePref = None
[docs] def setFullAsciiStrings(self): """ Set the default charset used when generating random variables and strings to the (almost) full ASCII charset. Only "'" and "/" are not used. """ RandomGen._randStrCharList = [chr(i) for i in range(1, 128)] RandomGen._randStrCharList.remove("'") RandomGen._randStrCharList.remove("/")
# TODO: make this functionality local to each RandomGen instance
[docs] def forgetUniqueStrs(self): """ Clear the sets of previously generated variable names and strings. Should be called when random variable names/strings are needed but can have the same name as previously generated variable names/strings without causing conflicts. """ RandomGen._generatedVars.clear() RandomGen._uniqueRandStrs.clear()
[docs] def randGenNum(self, min, max): """ Randomly generate an integer inclusively. :param min: minimum integer that can be returned :type min: int :param max: maximum integer that can be returned :type max: int """ return RandomGen.randGen.randint(min, max)
[docs] def randChoice(self, max): """ Generate a random choice. Useful when you need to choose between a set number of choices randomly. :param max: maximum integer that can be returned :returns: integer from 0 to max-1 inclusively """ return self.randGenNum(0, max - 1)
[docs] def probibility(self, prob): """ Return True a certain percentage of the time. :param prob: probability of returning True :type prob: int :returns: True prob percent of the time, False otherwise """ randNum = self.randGenNum(0, 100) return randNum <= prob
[docs] def randSelect(self, seq): """ Randomly select an element from a sequence. If the argument 'seq' is a dict, a randomly selected key will be returned. :param seq: sequence to randomly select from :type seq: list :returns: element from seq if seq is a list, a key if seq is a dict, or None if seq is empty """ if isinstance(seq, dict): selection = RandomGen.randGen.choice(list(seq.keys())) elif seq: selection = RandomGen.randGen.choice(seq) else: selection = None return selection
[docs] def randShuffle(self, seq): """ Randomly shuffle a sequence in-place. :param seq: sequence to shuffle randomly :type seq: list """ RandomGen.randGen.shuffle(seq)
[docs] def randGenVar(self, minVarLen=None, maxVarLen=None): """ Generate a unique randomly named variable. Variable names can consist of uppercase and lowercase letters, digits, and underscores, but will always start with a letter or underscore. :param sizePref: sizePref user option. Controls the minimum and maximum length of generated variable names :type sizePref: int :returns: unique random variable name .. note:: :meth:`~RandomGen.randUniqueStr` is called under the hood, therefore the same performance concerns apply. """ minVarLen, maxVarLen = self._getSizes(minVarLen, maxVarLen) randVarCharList = string.ascii_letters + string.digits + "_" while True: randomVar = self.randSelect(string.ascii_letters + "_") randomVar += self.randGenStr(minVarLen, maxVarLen - 1, randVarCharList) if len(randomVar) == 1 and randomVar.isdigit(): continue if RandomGen._boblReservedStrsRegex.search(randomVar): continue if randomVar not in RandomGen._generatedVars and randomVar not in RandomGen._reservedVars: break RandomGen._generatedVars.add(randomVar) return randomVar
[docs] def randUniqueStr(self, minStrLen=None, maxStrLen=None, charList=None, escapeChars="", noBOBL=True): """ Generate a random string that is guaranteed to be unique. :param minStrLen: minimum length of generated string :type minStrLen: int :param maxStrLen: maximum length of generated string :type maxStrLen: int :param charList: list of characters that will be used when generating the random string. If it is not specified, the default character set will be used :type charList: str or list of chrs :returns: unique random string .. note:: Runtime will increase incrementally as more and more unique strings are generated, unless :meth:`~RandomGen.forgetUniqueStrs` is called. """ minStrLen, maxStrLen = self._getSizes(minStrLen, maxStrLen) if charList is None: charList = RandomGen._randStrCharList commonStrNum = 0 while True: randStr = self.randGenStr(minStrLen, maxStrLen, charList, escapeChars, noBOBL) if randStr not in RandomGen._uniqueRandStrs: break else: commonStrNum += 1 # if 5 collisions are generated in a row, chances are that we are reaching the upper bound # of our keyspace, so make the keyspace bigger so we can keep generating unique strings if commonStrNum == 5: minStrLen = maxStrLen maxStrLen += 1 commonStrNum = 0 RandomGen._uniqueRandStrs.add(randStr) return randStr
[docs] def randGenStr(self, minStrLen=None, maxStrLen=None, charList=None, escapeChars="", noBOBL=True): """ Generate a random string. Functions the same as :meth:`~RandomGen.randUniqueStr`, the only difference being that the generated string is NOT guaranteed to be unique. """ minStrLen, maxStrLen = self._getSizes(minStrLen, maxStrLen) if charList is None: charList = RandomGen._randStrCharList randStrLen = RandomGen.randGen.randint(minStrLen, maxStrLen) randStr = "".join(self.randSelect(charList) for x in range(randStrLen)) if noBOBL: while RandomGen._boblSyntaxRegex.search(randStr): randStr = "".join(self.randSelect(charList) for x in range(randStrLen)) # escape 'escapeChars', making sure that an already escaped char isn't # accidentally un-escaped by adding an extra '\' for char in escapeChars: randStr = re.sub(r"(?<!\\)(\\{2})*(?!\\)" + re.escape(char), "\g<1>\\" + char, randStr) return randStr
def _getSizes(self, minLen, maxLen): if minLen is None or maxLen is None: if self.sizePref == 1: defaultMinLen = 1 elif self.sizePref == 2: defaultMinLen = 4 else: defaultMinLen = 8 defaultMaxLen = defaultMinLen * 2 if minLen is None: minLen = defaultMinLen if maxLen is None: maxLen = defaultMaxLen return (minLen, maxLen)