Source code for boofuzz.primitives.bytes

import functools
import operator
from collections.abc import ByteString

from funcy import compose

from ..fuzzable import Fuzzable


[docs] class Bytes(Fuzzable): """Primitive that fuzzes a binary byte string with arbitrary length. :type name: str, optional :param name: Name, for referencing later. Names should always be provided, but if not, a default name will be given, defaults to None :type default_value: bytes, optional :param default_value: Value used when the element is not being fuzzed - should typically represent a valid value, defaults to b"" :type size: int, optional :param size: Static size of this field, leave None for dynamic, defaults to None :type padding: chr, optional :param padding: Value to use as padding to fill static field size, defaults to b"\\x00" :type max_len: int, optional :param max_len: Maximum string length, defaults to None :type fuzzable: bool, optional :param fuzzable: Enable/disable fuzzing of this primitive, defaults to true """ # These binary strings are always included as testcases. _fuzz_library = [ b"", b"\x00", b"\xFF", b"A" * 10, b"A" * 100, b"A" * 1000, b"A" * 5000, b"A" * 10000, b"A" * 100000, ] # from https://en.wikipedia.org/wiki/Magic_number_(programming)#Magic_debug_values _magic_debug_values = [ b"\x00\x00\x81#", b"\x00\xfa\xca\xde", b"\x1b\xad\xb0\x02", b"\x8b\xad\xf0\r", b"\xa5\xa5\xa5\xa5", b"\xa5", b"\xab\xab\xab\xab", b"\xab\xad\xba\xbe", b"\xab\xba\xba\xbe", b"\xab\xad\xca\xfe", b"\xb1k\x00\xb5", b"\xba\xad\xf0\r", b"\xba\xaa\xaa\xad", b'\xba\xd2""', b"\xba\xdb\xad\xba\xdb\xad", b"\xba\xdc\x0f\xfe\xe0\xdd\xf0\r", b"\xba\xdd\xca\xfe", b"\xbb\xad\xbe\xef", b"\xbe\xef\xca\xce", b"\xc0\x00\x10\xff", b"\xca\xfe\xba\xbe", b"\xca\xfe\xd0\r", b"\xca\xfe\xfe\xed", b"\xcc\xcc\xcc\xcc", b"\xcd\xcd\xcd\xcd", b"\r\x15\xea^", b"\xdd\xdd\xdd\xdd", b"\xde\xad\x10\xcc", b"\xde\xad\xba\xbe", b"\xde\xad\xbe\xef", b"\xde\xad\xca\xfe", b"\xde\xad\xc0\xde", b"\xde\xad\xfa\x11", b"\xde\xad\xf0\r", b"\xde\xfe\xc8\xed", b"\xde\xad\xde\xad", b"\xeb\xeb\xeb\xeb", b"\xfa\xde\xde\xad", b"\xfd\xfd\xfd\xfd", b"\xfe\xe1\xde\xad", b"\xfe\xed\xfa\xce", b"\xfe\xee\xfe\xee", ] # This is a list of "interesting" 1,2 and 4 byte binary strings. # The lists are used to replace each block of 1, 2 or 4 byte in the original # value with each of those "interesting" values. _fuzz_strings_1byte = [b"\x00", b"\x01", b"\x7F", b"\x80", b"\xFF"] + [ i for i in _magic_debug_values if len(i) == 1 ] _fuzz_strings_2byte = [ b"\x00\x00", b"\x01\x00", b"\x00\x01", b"\x7F\xFF", b"\xFF\x7F", b"\xFE\xFF", b"\xFF\xFE", b"\xFF\xFF", ] + [i for i in _magic_debug_values if len(i) == 2] _fuzz_strings_4byte = [ b"\x00\x00\x00\x00", b"\x00\x00\x00\x01", b"\x01\x00\x00\x00", b"\x7F\xFF\xFF\xFF", b"\xFF\xFF\xFF\x7F", b"\xFE\xFF\xFF\xFF", b"\xFF\xFF\xFF\xFE", b"\xFF\xFF\xFF\xFF", ] + [i for i in _magic_debug_values if len(i) == 4] _mutators_of_default_value = [ functools.partial(operator.mul, 2), functools.partial(operator.mul, 10), functools.partial(operator.mul, 100), ] def __init__( self, name: str = None, default_value: bytes = b"", size: int = None, padding: bytes = b"\x00", max_len: int = None, *args, **kwargs ): if not isinstance(default_value, ByteString): raise TypeError("default_value of Bytes must be of ByteString type") if not isinstance(padding, ByteString): raise TypeError("padding of Bytes must be of ByteString type") super(Bytes, self).__init__(name=name, default_value=default_value, *args, **kwargs) self.size = size self.max_len = max_len if self.size is not None: self.max_len = self.size self.padding = padding def mutations(self, default_value): for fuzz_value in self._iterate_fuzz_cases(default_value): if callable(fuzz_value): yield compose(self._adjust_mutation_for_size, fuzz_value) else: yield self._adjust_mutation_for_size(fuzz_value=fuzz_value) def _adjust_mutation_for_size(self, fuzz_value): if self.size is not None: if len(fuzz_value) > self.size: return fuzz_value[: self.max_len] else: return fuzz_value + self.padding * (self.size - len(fuzz_value)) elif self.max_len is not None and len(fuzz_value) > self.max_len: return fuzz_value[: self.max_len] else: return fuzz_value def _iterate_fuzz_cases(self, default_value): for fuzz_value in self._fuzz_library: yield fuzz_value for fuzz_value in self._mutators_of_default_value: yield fuzz_value for fuzz_value in self._magic_debug_values: yield fuzz_value for i in range(0, len(default_value)): for fuzz_bytes in self._fuzz_strings_1byte: def f(value): if i < len(value): return value[:i] + fuzz_bytes + value[i + 1 :] else: return value yield f for i in range(0, len(default_value) - 1): for fuzz_bytes in self._fuzz_strings_2byte: def f(value): if i < len(value) - 1: return value[:i] + fuzz_bytes + value[i + 2 :] else: return value yield f for i in range(0, len(default_value) - 3): for fuzz_bytes in self._fuzz_strings_4byte: def f(value): if i < len(value) - 3: return value[:i] + fuzz_bytes + value[i + 4 :] else: return value yield f def num_mutations(self, default_value): """ Calculate and return the total number of mutations for this individual primitive. @rtype: int @return: Number of mutated forms this primitive can take :param default_value: """ return sum( ( len(self._fuzz_library), len(self._mutators_of_default_value), len(self._magic_debug_values), len(self._fuzz_strings_1byte) * max(0, len(default_value) - 0), len(self._fuzz_strings_2byte) * max(0, len(default_value) - 1), len(self._fuzz_strings_4byte) * max(0, len(default_value) - 3), ) ) def encode(self, value, mutation_context): if value is None: value = b"" return value