Fuzzing

Fuzzing or fuzz testing is a software testing technique that provides random, invalid or unexpected data to the inputs of a system. It is often automated or semi-automated. While providing data the system is checked to still work normally. Fuzzing proves the robustness of a system if it does not fail. If a test fails it may uncover bugs.

If a system has communication interfaces fuzzing can be used to test these interfaces and the software behind it.

Using OSER fuzzing testing is easy. The user only has to set fuzzing data to some fields of a struct that will be used and iterate over all combinations.

For each combination the data can be sent to the target and after that the target has to be checked to still work normally.

That’s all.

Concepts

Fuzzing value combinations are generated by building the cartesian product of all fuzzing values.

For example you may have a and b. The fuzzing values for a are [1, 2] and the fuzzing values for b are [3, 4].

The combinations are [1, 3], [1, 4], [2, 3] and [2, 4]. The number of combinations is the product of all value’s lengths which is 4 in the example.

Values can be also random. Let the fuzzing values for b be [random(), random()] the cartesian product combinations are [1, random()], [1, random()], [2, random()] and [2, random()].

In the example above always 4 combinations are used to be iterated for a fuzzing test.

With OSER it is possible to set fuzzing values using oser.ByteType.set_fuzzing_values() respectively oser.BitType.set_fuzzing_values() (which is inherited to all other types).

The fuzzing iterator is created by calling oser.ByteStruct.fuzzing_iterator() respectively oser.BitStruct.fuzzing_iterator().

The first example from above looks like this:

>>> import oser

>>> class Struct(oser.ByteStruct):
...     def __init__(self):
...         super(Struct, self).__init__()
...         self.a = oser.UBInt8()
...         self.b = oser.UBInt8()
...
>>> struct = Struct()
>>> struct.a.set_fuzzing_values([1, 2])
>>> struct.b.set_fuzzing_values([3, 4])

>>> for product in struct.fuzzing_iterator():
...     print(product)
...
Struct():
    a: 1 (UBInt8)
    b: 3 (UBInt8)

Struct():
    a: 1 (UBInt8)
    b: 4 (UBInt8)

Struct():
    a: 2 (UBInt8)
    b: 3 (UBInt8)

Struct():
    a: 2 (UBInt8)
    b: 4 (UBInt8)

You can see that all cartesian product’s combinations are created.

The second example with random values for b looks like:

>>> import oser

>>> class Struct(oser.ByteStruct):
...     def __init__(self):
...         super(Struct, self).__init__()
...         self.a = oser.UBInt8()
...         self.b = oser.UBInt8()
...
>>> struct = Struct()
>>> struct.a.set_fuzzing_values([1, 2])
>>> struct.b.set_fuzzing_values(
...     oser.RandomIntegerFuzzingValue(a=0, b=10, count=2))

>>> for product in struct.fuzzing_iterator():
...     print(product)
...
Struct():
    a: 1 (UBInt8)
    b: 5 (UBInt8)

Struct():
    a: 1 (UBInt8)
    b: 8 (UBInt8)

Struct():
    a: 2 (UBInt8)
    b: 4 (UBInt8)

Struct():
    a: 2 (UBInt8)
    b: 0 (UBInt8)

You can see that the value of struct.b is always random and that 4 combinations are generated, too.

The fuzzing values can also be a generator:

>>> import oser

>>> class Struct(oser.ByteStruct):
...     def __init__(self):
...         super(Struct, self).__init__()
...         self.a = oser.UBInt8()
...         self.b = oser.UBInt8()
...
>>> struct = Struct()
>>> struct.a.set_fuzzing_values([1, 2])


>>> def b_fuzzing_values():
...     yield 11
...     yield 12
...
>>> struct.b.set_fuzzing_values(b_fuzzing_values)

>>> for product in struct.fuzzing_iterator():
...     print(product)
...
Struct():
    a: 1 (UBInt8)
    b: 11 (UBInt8)

Struct():
    a: 1 (UBInt8)
    b: 12 (UBInt8)

Struct():
    a: 2 (UBInt8)
    b: 11 (UBInt8)

Struct():
    a: 2 (UBInt8)
    b: 12 (UBInt8)

Again 4 combinations are generated using the generated values of b_fuzzing_values() each twice.

Fuzzing also works on hierarchies:

>>> import oser

>>> class Struct(oser.ByteStruct):
...     def __init__(self):
...         super(Struct, self).__init__()
...         self.a = oser.UBInt8()
...         self.struct = oser.ByteStruct()
...         self.struct.a = oser.UBInt8()
...         self.struct.struct = oser.ByteStruct()
...         self.struct.struct.a = oser.UBInt8()
...
>>> struct = Struct()
>>> struct.a.set_fuzzing_values([1, 2])
>>> struct.struct.a.set_fuzzing_values([3, 4])
>>> struct.struct.struct.a.set_fuzzing_values([5, 6])

>>> for product in struct.fuzzing_iterator():
...     print(product)
...
Struct():
    a: 1 (UBInt8)
    struct: ByteStruct():
        a: 3 (UBInt8)
        struct: ByteStruct():
            a: 5 (UBInt8)

Struct():
    a: 1 (UBInt8)
    struct: ByteStruct():
        a: 3 (UBInt8)
        struct: ByteStruct():
            a: 6 (UBInt8)

Struct():
    a: 1 (UBInt8)
    struct: ByteStruct():
        a: 4 (UBInt8)
        struct: ByteStruct():
            a: 5 (UBInt8)

Struct():
    a: 1 (UBInt8)
    struct: ByteStruct():
        a: 4 (UBInt8)
        struct: ByteStruct():
            a: 6 (UBInt8)

Struct():
    a: 2 (UBInt8)
    struct: ByteStruct():
        a: 3 (UBInt8)
        struct: ByteStruct():
            a: 5 (UBInt8)

Struct():
    a: 2 (UBInt8)
    struct: ByteStruct():
        a: 3 (UBInt8)
        struct: ByteStruct():
            a: 6 (UBInt8)

Struct():
    a: 2 (UBInt8)
    struct: ByteStruct():
        a: 4 (UBInt8)
        struct: ByteStruct():
            a: 5 (UBInt8)

Struct():
    a: 2 (UBInt8)
    struct: ByteStruct():
        a: 4 (UBInt8)
        struct: ByteStruct():
            a: 6 (UBInt8)

Deep Copies

The fuzzing iterator supports deep copies of the iterated elements so that these can be stored for later use. If elements are not copied do not store them because every other iteration will overwrite the values.

Example:

>>> import oser

>>> class Struct(oser.ByteStruct):
...     def __init__(self):
...         super(Struct, self).__init__()
...         self.a = oser.UBInt8()
...
>>> struct = Struct()
>>> struct.a.set_fuzzing_values([1, 2])
>>> print(id(struct))
139796212522008
>>> for product in struct.fuzzing_iterator(copy=False):
...     print(id(product), product)  # the id is always the same
...
139796212522008 Struct():
    a: 1 (UBInt8)

139796212522008 Struct():
    a: 2 (UBInt8)

>>> for product in struct.fuzzing_iterator(copy=True):
...     print(id(product), product)  # the id is unique
...
139796212556800 Struct():
    a: 1 (UBInt8)

139796212568528 Struct():
    a: 2 (UBInt8)

If you set copy=True in oser.ByteStruct.fuzzing_iterator() the ids of product is not the id of struct (no aliases are created).

Warning

copying is slow since the element is encoded and decoded in background.

Fuzzing Random Data Adapters

Fuzzing Random Data Adapters help to build random fuzzing easily.

Random Integer Values

Using oser.RandomIntegerFuzzingValue() you can create random integer numbers.

>>> import oser

>>> class Struct(oser.ByteStruct):
...     def __init__(self):
...         super(Struct, self).__init__()
...         self.a = oser.UBInt8()
...         self.b = oser.UBInt8()
...
>>> struct = Struct()
>>> struct.a.set_fuzzing_values([1, 2])
>>> struct.b.set_fuzzing_values(
...     oser.RandomIntegerFuzzingValue(a=0, b=10, count=2))

>>> for product in struct.fuzzing_iterator():
...     print(product)
...
Struct():
    a: 1 (UBInt8)
    b: 5 (UBInt8)

Struct():
    a: 1 (UBInt8)
    b: 8 (UBInt8)

Struct():
    a: 2 (UBInt8)
    b: 4 (UBInt8)

Struct():
    a: 2 (UBInt8)
    b: 0 (UBInt8)
oser.RandomIntegerFuzzingValue(a: int, b: int, count: int, seed: int | None = None) Callable[[], Generator[int, None, None]]

A random integer fuzzing value generator.

Parameters:
Returns:

a callable that generates count random integer

values between a and b.

Return type:

callable

Random Float Values

Using oser.RandomFloatFuzzingValue() you can create random float values.

>>> import oser

>>> class Struct(oser.ByteStruct):
...     def __init__(self):
...         super(Struct, self).__init__()
...         self.a = oser.BFloat()
...
>>> struct = Struct()
>>> struct.a.set_fuzzing_values(
...     oser.RandomFloatFuzzingValue(factor=23.0, count=4)
... )

>>> for product in struct.fuzzing_iterator():
...     print(product)
...
Struct():
    a: 13.792093371685636 (BFloat)

Struct():
    a: 5.5145125854582 (BFloat)

Struct():
    a: 14.942608671817528 (BFloat)

Struct():
    a: 8.38521903857164 (BFloat)
oser.RandomFloatFuzzingValue(factor: float, count: int, seed: int | float | None = None) Callable[[], Generator[float, None, None]]

A random float fuzzing value generator. Random values are generated using random.random().

Parameters:
  • factor (float) – the factor the random value is multiplied by.

  • count (int) – the number of values to be generated.

  • seed=None (int) – the seed for random.seed(). If None is supplied no seed is set.

Returns:

a callable that generates count random float values

between a and b.

Return type:

callable

Random Bits

Using oser.RandomBitsFuzzingValue() you can create random bit values based on a mersenne twister.

>>> import oser

>>> class Struct(oser.BitStruct):
...     def __init__(self):
...         super(Struct, self).__init__()
...         self.data = oser.BitField(length=32)
...
>>> struct = Struct()
>>> struct.data.set_fuzzing_values(
...     oser.RandomBitsFuzzingValue(length=32, count=4)
... )

>>> for product in struct.fuzzing_iterator():
...     print(product)
...     encoded = product.encode()
...     print(oser.to_hex(encoded))
...
Struct():
    data: 3281267388 (BitField(32))

   0|  1|  2|  3
\xc3\x94\x2a\xbc
Struct():
    data: 2188825415 (BitField(32))

   0|  1|  2|  3
\x82\x76\xd3\x47
Struct():
    data: 2834423999 (BitField(32))

   0|  1|  2|  3
\xa8\xf1\xe0\xbf
Struct():
    data: 4167436224 (BitField(32))

   0|  1|  2|  3
\xf8\x66\x07\xc0
oser.RandomBitsFuzzingValue(length: int, count: int, seed: int | None = None) Callable[[], Generator[int, None, None]]

A random bits fuzzing value generator. Random values are generated using random.getrandbits().

Parameters:
  • length (int) – the number of bits to be generated.

  • count (int) – the number of values to be generated.

  • seed=None (int) – the seed for random.seed(). If None is supplied no seed is set.

Returns:

a callable that generates count random bits.

Return type:

callable

Random Strings

Using oser.RandomStringFuzzingValue() you can create random strings with printable letters.

>>> import oser
>>> import string


>>> class Struct(oser.ByteStruct):
...     def __init__(self):
...         super(Struct, self).__init__()
...         self.length = oser.UBInt8()
...         self.data = oser.String(length=lambda self: self.length.get())
...
>>> struct = Struct()
>>> struct.length.set_fuzzing_values(
...     oser.RandomIntegerFuzzingValue(a=0, b=10, count=2))
>>> struct.data.set_fuzzing_values(
...     oser.RandomStringFuzzingValue(length=10, count=2, chars=string.ascii_letters)
... )

>>> for product in struct.fuzzing_iterator():
...     print(product)
...
Struct():
    length: 3 (UBInt8)
    data: b'bZS'

Struct():
    length: 3 (UBInt8)
    data: b'KKg'

Struct():
    length: 8 (UBInt8)
    data: b'iUFEtnDF'

Struct():
    length: 8 (UBInt8)
    data: b'ksuQCkmg'
oser.RandomStringFuzzingValue(length: int, count: int, chars: str | None = None, seed: int | None = None) Callable[[], Generator[bytes, None, None]]

A random string fuzzing value generator. Random values are generated using random.choice() with chars.

Parameters:
  • length (int) – the number of characters to be generated..

  • count (int) – the number of values to be generated.

  • chars=None (string) – if set to None string.printable is used (digits, letters, punctuation and whispaces).

  • seed=None (int) – the seed for random.seed(). If None is supplied no seed is set.

Returns:

a callable that generates count random string values.

Return type:

callable

Random Bytes

Using oser.RandomStringFuzzingValue() you can create random bytes.

>>> import oser

>>> class Struct(oser.ByteStruct):
...     def __init__(self):
...         super(Struct, self).__init__()
...         self.length = oser.UBInt8()
...         self.data = oser.String(length=lambda self: self.length.get())
...
>>> struct = Struct()
>>> struct.length.set_fuzzing_values(
...     oser.RandomIntegerFuzzingValue(a=0, b=10, count=2))
>>> struct.data.set_fuzzing_values(
...     oser.RandomBytesFuzzingValue(length=10, count=2)
... )

>>> for product in struct.fuzzing_iterator():
...     print(product)
...
Struct():
    length: 10 (UBInt8)
    data: b'\x15\xb0=\x14X\xa2\xf2}-\x02'

Struct():
    length: 10 (UBInt8)
    data: b'\x93)"\xd3\x13sM\xa6P\xa4'

Struct():
    length: 4 (UBInt8)
    data: b'ch0\xf5'

Struct():
    length: 4 (UBInt8)
    data: b'\xe2]\x88%'
oser.RandomBytesFuzzingValue(length: int, count: int, choices: List[int] | None = None, seed: int | None = None) Callable[[], Generator[bytes, None, None]]

A random bytes fuzzing value generator. Random values are generated using random.choice() with choices.

Parameters:
  • length (int) – the number of bytes to be generated..

  • count (int) – the number of values to be generated.

  • choices=None (list of int) – if set to None list(range(256)) is used.

  • seed=None (int) – the seed for random.seed(). If None is supplied no seed is set.

Returns:

a callable that generates count random bytes values.

Return type:

callable

Special Elements

Special elements that check values while decoding, have sideeffects while encoding or are context-based elements are mentioned below.

The elements with sideeffects and checks are Checksum Types, oser.Padding and oser.PaddingFlag.

The special context-based elements are oser.IfElse, oser.If and oser.Switch.

CRC

CRCs can be used to add a checksum after fuzzed values or can be used with fuzzing values set, as well.

If no fuzzing values are set all CRCs work normally.

If fuzzing values are set the strict and the automatic_calculation features are temporarily disabled while the fuzzing iterator is alive.

>>> import oser

>>> class Struct(oser.ByteStruct):
...     def __init__(self):
...         super(Struct, self).__init__()
...         self.a = oser.UBInt16(23)
...         self.crc = oser.CRCB32(strict=True, automatic_calculation=True)
...
>>> struct = Struct()
>>> struct.a.set_fuzzing_values(
...     oser.RandomIntegerFuzzingValue(a=0, b=10, count=2)
... )

>>> # the crc works normally
... for product in struct.fuzzing_iterator():
...     encoded = product.encode()  # build crc
...     print(product)
...
Struct():
    a: 5 (UBInt16)
    crc: 1705890373 (CRCB32)

Struct():
    a: 8 (UBInt16)
    crc: 4142103048 (CRCB32)

>>> # the crc used as a fuzzing value
... struct.a.set_fuzzing_values(None)  # disable a fuzzing

>>> struct.crc.set_fuzzing_values(
...     oser.RandomIntegerFuzzingValue(a=0, b=1024, count=2)
... )

>>> for product in struct.fuzzing_iterator():
...     encoded = product.encode()  # build crc
...     print(product)
...
Struct():
    a: 8 (UBInt16)
    crc: 98 (CRCB32)

Struct():
    a: 8 (UBInt16)
    crc: 985 (CRCB32)

>>> # at the end the crc still works normally if no fuzzing values are set
... struct.crc.set_fuzzing_values(None)

>>> for product in struct.fuzzing_iterator():
...     encoded = product.encode()  # build crc
...     print(product)
...
Struct():
    a: 8 (UBInt16)
    crc: 4142103048 (CRCB32)

Padding

The strict feature of oser.Padding works normally while no fuzzing values are set and is temporarily disabled if fuzzing values are set.

>>> import oser

>>> class Struct(oser.ByteStruct):
...     def __init__(self):
...         super(Struct, self).__init__()
...         self.padding = oser.Padding(value=b"\x00", strict=True)
...
>>> struct = Struct()

>>> struct.padding.set_fuzzing_values(
...     oser.RandomBytesFuzzingValue(length=1, count=2)
... )

>>> for product in struct.fuzzing_iterator():
...     print(product)
...
Struct():
    padding: 206 (Padding)

Struct():
    padding: 90 (Padding)

PaddingFlag

The strict feature of oser.PaddingFlag works normally while no fuzzing values are set and is temporarily disabled if fuzzing values are set.

>>> import oser

>>> class Struct(oser.BitStruct):
...     def __init__(self):
...         super(Struct, self).__init__()
...         self.padding_flag = oser.PaddingFlag(value=False, strict=True)
...
>>> struct = Struct()

>>> struct.padding_flag.set_fuzzing_values(
...     oser.RandomBitsFuzzingValue(length=1, count=4)
... )

>>> for product in struct.fuzzing_iterator():
...     print(product)
...
Struct():
    padding_flag: 0 (PaddingFlag)

Struct():
    padding_flag: 1 (PaddingFlag)

Struct():
    padding_flag: 0 (PaddingFlag)

Struct():
    padding_flag: 1 (PaddingFlag)

IfElse and If

oser.IfElse and oser.If hide the values that depend on a condition.

To set fuzzing values these values can be accessed using oser.IfElse.get_true_value() (or oser.If.get_true_value()) respectively oser.IfElse.get_false_value() (or oser.If.get_false_value()).

>>> import oser

>>> class Struct(oser.ByteStruct):
...     def __init__(self):
...         super(Struct, self).__init__()
...         self.condition = oser.UBInt8(23)
...         self.data = oser.IfElse(
...             condition=lambda self: self.condition < 128,
...             if_true=oser.UBInt8(0),
...             if_false=oser.UBInt32(0xffffffff)
...         )
...
>>> struct = Struct()
>>> struct.condition.set_fuzzing_values(
...     oser.RandomIntegerFuzzingValue(a=0, b=255, count=4)
... )

>>> struct.data.get_true_value().set_fuzzing_values(
...     oser.RandomIntegerFuzzingValue(a=0, b=255, count=2)
... )

>>> struct.data.get_false_value().set_fuzzing_values(
...     oser.RandomIntegerFuzzingValue(a=0xfffffff0, b=0xffffffff, count=2)
... )

>>> for product in struct.fuzzing_iterator():
...     print(product)
...
Struct():
    condition: 124 (UBInt8)
    data: 42 (UBInt8)

Struct():
    condition: 124 (UBInt8)
    data: 208 (UBInt8)

Struct():
    condition: 63 (UBInt8)
    data: 34 (UBInt8)

Struct():
    condition: 63 (UBInt8)
    data: 223 (UBInt8)

Struct():
    condition: 5 (UBInt8)
    data: 84 (UBInt8)

Struct():
    condition: 5 (UBInt8)
    data: 251 (UBInt8)

Struct():
    condition: 192 (UBInt8)
    data: 4294967291 (UBInt32)

Struct():
    condition: 192 (UBInt8)
    data: 4294967295 (UBInt32)

Switch

oser.Switch hide the values that depend on a condition.

To set fuzzing values these values can be accessed using oser.Switch.get_value().

>>> import oser

>>> class Message(oser.ByteStruct):
...     def __init__(self):
...         super(Message, self).__init__()
...         self.command = oser.UBInt8()
...         self.payload = oser.Switch(
...             condition=lambda self: self.command.get(),
...             values={
...                 0: oser.UBInt8(0),
...                 1: oser.UBInt16(1),
...                 2: oser.UBInt32(2),
...             },
...             default=oser.Nothing()
...             )
...

>>> message = Message()

>>> message.command.set_fuzzing_values(
...     oser.RandomIntegerFuzzingValue(a=0, b=2, count=5))

>>> # set fuzzing values for payload string
... message.payload.get_value(key=0).set_fuzzing_values(
...     oser.RandomIntegerFuzzingValue(a=0, b=10, count=2)
... )

>>> message.payload.get_value(key=1).set_fuzzing_values(
...     oser.RandomIntegerFuzzingValue(a=11, b=20, count=2)
... )

>>> message.payload.get_value(key=2).set_fuzzing_values(
...     oser.RandomIntegerFuzzingValue(a=21, b=30, count=2)
... )

>>> for product in message.fuzzing_iterator():
...     print(product)
...
Message():
    command: 0 (UBInt8)
    payload: 6 (UBInt8)

Message():
    command: 0 (UBInt8)
    payload: 4 (UBInt8)

Message():
    command: 2 (UBInt8)
    payload: 27 (UBInt32)

Message():
    command: 2 (UBInt8)
    payload: 24 (UBInt32)

Message():
    command: 2 (UBInt8)
    payload: 22 (UBInt32)

Message():
    command: 2 (UBInt8)
    payload: 30 (UBInt32)

Message():
    command: 1 (UBInt8)
    payload: 11 (UBInt16)

Message():
    command: 1 (UBInt8)
    payload: 20 (UBInt16)

Message():
    command: 2 (UBInt8)
    payload: 24 (UBInt32)

Message():
    command: 2 (UBInt8)
    payload: 27 (UBInt32)

Mixins

Fuzzing Type Mixin

The oser.core.FuzzingTypeMixin is used by every type in OSER. It lets you set the fuzzing values and offers fuzzing values iteration and setting the next fuzzing value.

class oser.core.FuzzingTypeMixin

A type mixin for fuzzing tests.

set_fuzzing_values(values: Generator[Any, None, None] | List[Any] | None) None

Set fuzzing values.

Parameters:

values – the values used for fuzzing.

Fuzzing Struct Mixin

The oser.core.FuzzingStructMixin is used by oser.ByteStruct and oser.BitStruct. It offers a fuzzing iterator that is used to iterate all fuzzing combinations.

class oser.core.FuzzingStructMixin

A struct mixin for fuzzing tests.

fuzzing_iterator(copy: bool = False) Generator[Any, None, None]

The fuzzing iterator iterates over all combinations of set fuzzing values (oser.ByteType.set_fuzzing_values()). If no fuzzing values are set the current struct is yielded.

Parameters:

copy=False – if set to True the generated fuzzing values are deep copies of the original. Creating these deep copies is slow. If set to False the original struct is retruned and the generated value must be used immediately since the next generated overwrites the values on the same value.

Yields:

the fuzzing combinations.

Examples

Random Integer Length for a String

oser.RandomIntegerFuzzingValue() can also be used in a lambda-expression for a oser.String length.

>>> import oser
>>> import string


>>> _random_length_iterator = oser.RandomIntegerFuzzingValue(a=0, b=10, count=1)


>>> class Struct(oser.ByteStruct):
...     def __init__(self):
...         super(Struct, self).__init__()
...         self.data = oser.String(length=lambda self: next(_random_length_iterator()))
...
>>> struct = Struct()
>>> struct.data.set_fuzzing_values(
...     oser.RandomStringFuzzingValue(length=10, count=4, chars=string.ascii_letters)
... )

>>> for product in struct.fuzzing_iterator():
...     print(product)
...
Struct():
    data: b's'

Struct():
    data: b''

Struct():
    data: b'CDJEsB'

Struct():
    data: b'WqOOOFldm'

Random Payload With a Valid CRC

>>> import oser
>>> import string


>>> class Struct(oser.ByteStruct):
...     def __init__(self):
...         super(Struct, self).__init__()
...         self.length = oser.UBInt8()
...         self.data = oser.String(length=lambda self: self.length.get())
...         self.crc = oser.CRCB32(strict=True, automatic_calculation=True)
...
>>> struct = Struct()
>>> struct.length.set_fuzzing_values(
...     oser.RandomIntegerFuzzingValue(a=0, b=10, count=2))
>>> struct.data.set_fuzzing_values(
...     oser.RandomStringFuzzingValue(length=10, count=2, chars=string.ascii_letters)
... )

>>> for product in struct.fuzzing_iterator():
...     encoded = product.encode()
...     print(product)
...     print(oser.to_hex(encoded))
...
Struct():
    length: 8 (UBInt8)
    data: b'SSosfNbZ'
    crc: 381554597 (CRCB32)

   0|  1|  2|  3|  4|  5|  6|  7|  8|  9| 10| 11| 12
\x08\x53\x53\x6f\x73\x66\x4e\x62\x5a\x16\xbe\x0f\xa5
Struct():
    length: 8 (UBInt8)
    data: b'aKDKYIza'
    crc: 2493435128 (CRCB32)

   0|  1|  2|  3|  4|  5|  6|  7|  8|  9| 10| 11| 12
\x08\x61\x4b\x44\x4b\x59\x49\x7a\x61\x94\x9e\xcc\xf8
Struct():
    length: 3 (UBInt8)
    data: b'xyj'
    crc: 2464266468 (CRCB32)

   0|  1|  2|  3|  4|  5|  6|  7
\x03\x78\x79\x6a\x92\xe1\xb8\xe4
Struct():
    length: 3 (UBInt8)
    data: b'oyH'
    crc: 2247939372 (CRCB32)

   0|  1|  2|  3|  4|  5|  6|  7
\x03\x6f\x79\x48\x85\xfc\xd5\x2c

Inconsistent Messages

Lets assume you have a protocol to command a device. It consists of a field for a command, a complex command structure and a crc32 at the end. Every command needs a different payload length that is not encoded in the message.

The fuzzing test shall test valid and invalid command numbers with a random payload with an unexpected length. The crc at the end shall be valid.

To accomplish the complex payload is replaced by a simple oser.String with a random length (in each product). For the random values a oser.RandomStringFuzzingValue() is applied on the new payload.

>>> import oser
>>> import string


>>> def Payload():
...     payload = oser.Switch(
...         condition=lambda self: self.command.get(),
...         values={
...             0: oser.Nothing(),
...             1: oser.UBInt8(0),
...             2: oser.UBInt16(0),
...             3: oser.UBInt32(0),
...         },
...         default=oser.Nothing()
...         )
...     return payload
...

>>> class Message(oser.ByteStruct):
...     def __init__(self):
...         super(Message, self).__init__()
...         self.command = oser.UBInt8()
...         self.payload = Payload()
...         self.crc = oser.CRCB32(strict=True, automatic_calculation=True)
...
>>> message = Message()
>>> message.command.set_fuzzing_values(
...     oser.RandomIntegerFuzzingValue(a=0, b=10, count=4))
>>> # replace payload with a random length string
... _random_length_iterator = oser.RandomIntegerFuzzingValue(a=0, b=10, count=1)
>>> message.payload = oser.String(length=lambda self: next(_random_length_iterator()))

>>> # set fuzzing values for payload string
... message.payload.set_fuzzing_values(
...     oser.RandomStringFuzzingValue(length=10, count=2, chars=string.ascii_letters)
... )

>>> for product in message.fuzzing_iterator():
...     encoded = product.encode()  # to update the crc
...     print(product)
...
Message():
    command: 0 (UBInt8)
    payload: b'zmH'
    crc: 0 (CRCB32)

Message():
    command: 0 (UBInt8)
    payload: b'HGbgNbsSjC'
    crc: 1130147872 (CRCB32)

Message():
    command: 7 (UBInt8)
    payload: b'AKJBtT'
    crc: 237586712 (CRCB32)

Message():
    command: 7 (UBInt8)
    payload: b'x'
    crc: 2717259878 (CRCB32)

Message():
    command: 9 (UBInt8)
    payload: b'mJg'
    crc: 2778241945 (CRCB32)

Message():
    command: 9 (UBInt8)
    payload: b'RRjrE'
    crc: 1580841628 (CRCB32)

Message():
    command: 7 (UBInt8)
    payload: b'HEQExDgrmC'
    crc: 1477774535 (CRCB32)

Message():
    command: 7 (UBInt8)
    payload: b'XnLI'
    crc: 2554068935 (CRCB32)

Full Fuzzing Example

In this example a send and a check method is added to demonstrate how a real fuzzing test can look like.

The same protocol already mentioned is reused here.

>>> import oser
>>> import string


>>> def Payload():
...     payload = oser.Switch(
...         condition=lambda self: self.command.get(),
...         values={
...             0: oser.Nothing(),
...             1: oser.UBInt8(0),
...             2: oser.UBInt16(0),
...             3: oser.UBInt32(0),
...         },
...         default=oser.Nothing()
...         )
...     return payload
...

>>> class Message(oser.ByteStruct):
...     def __init__(self):
...         super(Message, self).__init__()
...         self.command = oser.UBInt8()
...         self.payload = Payload()
...         self.crc = oser.CRCB32(strict=True, automatic_calculation=True)
...
>>> message = Message()
>>> message.command.set_fuzzing_values(
...     oser.RandomIntegerFuzzingValue(a=0, b=10, count=2000))
>>> # replace payload with a random length string
... _random_length_iterator = oser.RandomIntegerFuzzingValue(a=0, b=10, count=1)
>>> message.payload = oser.String(length=lambda self: next(_random_length_iterator()))

>>> # set fuzzing values for payload string
... message.payload.set_fuzzing_values(
...     oser.RandomStringFuzzingValue(length=10, count=2000, chars=string.ascii_letters)
... )


>>> def send(binary_data):
...     """
...     This method send the ``binary_data`` to
...     the target.
...     """
...     # ...
...

>>> def check():
...     """
...     This method checks if the target still works.
...
...     Raises:
...         Exception: if the target does not work properly anymore
...     """
...
>>> for product in message.fuzzing_iterator():
...     encoded = product.encode()  # to update the crc
...     print(product)
...     print(oser.to_hex(encoded))
...     send(encoded)
...     check()
...
Message():
    command: 9 (UBInt8)
    payload: b'tsLpqQCiWR'
    crc: 3896448329 (CRCB32)

   0|  1|  2|  3|  4
\x09\xe8\x3f\x15\x49
Message():
    command: 9 (UBInt8)
    payload: b'zvVSZoCpSC'
    crc: 488979088 (CRCB32)

   0|  1|  2|  3|  4|  5|  6|  7|  8|  9| 10| 11
\x09\x7a\x76\x56\x53\x5a\x6f\x43\x1d\x25\x3a\x90
Message():
    command: 1 (UBInt8)
    payload: b'gAzRLVBRka'
    crc: 4032940286 (CRCB32)

   0|  1|  2|  3|  4|  5|  6|  7|  8|  9| 10| 11
\x01\x67\x41\x7a\x52\x4c\x56\x42\xf0\x61\xc8\xfe
Message():
    command: 1 (UBInt8)
    payload: b'vYNceTLpIY'
    crc: 795191758 (CRCB32)

   0|  1|  2|  3|  4|  5|  6|  7
\x01\x76\x59\x4e\x2f\x65\xa9\xce
Message():
    command: 0 (UBInt8)
    payload: b'LWHwQuNRtl'
    crc: 3587833413 (CRCB32)

   0|  1|  2|  3|  4|  5|  6|  7|  8|  9| 10| 11
\x00\x4c\x57\x48\x77\x51\x75\x4e\xd5\xd9\xfe\x45
Message():
    command: 0 (UBInt8)
    payload: b'DvzLhAaxbz'
    crc: 2470879835 (CRCB32)

   0|  1|  2|  3|  4|  5|  6|  7|  8|  9| 10| 11
\x00\x44\x76\x7a\x4c\x68\x41\x61\x93\x46\xa2\x5b
Message():
    command: 1 (UBInt8)
    payload: b'AQFkyHKKwp'
    crc: 680941664 (CRCB32)

   0|  1|  2|  3|  4|  5|  6|  7|  8|  9| 10| 11
\x01\x41\x51\x46\x6b\x79\x48\x4b\x28\x96\x58\x60
Message():
    command: 1 (UBInt8)
    payload: b'uUhByovAZf'
    crc: 165847963 (CRCB32)

   0|  1|  2|  3|  4|  5|  6|  7
\x01\x75\x55\x68\x09\xe2\xa3\x9b

# and so on