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:
>>> from __future__ import absolute_import, division, print_function, unicode_literals
>>>
>>> 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:
>>> from __future__ import absolute_import, division, print_function, unicode_literals
>>>
>>> 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:
>>> from __future__ import absolute_import, division, print_function, unicode_literals
>>>
>>> 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:
>>> from __future__ import absolute_import, division, print_function, unicode_literals
>>>
>>> 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:
>>> from __future__ import absolute_import, division, print_function, unicode_literals
>>>
>>> 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.
>>> from __future__ import absolute_import, division, print_function, unicode_literals
>>>
>>> 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, b, count, seed=None)¶ A random integer fuzzing value generator.
- Parameters
a (int) – the lower limit for
random.randint()
.b (int) – the upper limit for
random.randint()
.count (int) – the number of values to be generated.
seed=None (int) – the seed for
random.seed()
. IfNone
is supplied no seed is set.
- Returns
- a callable that generates
count
random integer values between
a
andb
.
- a callable that generates
- Return type
callable
Random Float Values¶
Using oser.RandomFloatFuzzingValue()
you can create random float values.
>>> from __future__ import absolute_import, division, print_function, unicode_literals
>>>
>>> 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, count, seed=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()
. IfNone
is supplied no seed is set.
- Returns
- a callable that generates
count
random float values between
a
andb
.
- a callable that generates
- Return type
callable
Random Bits¶
Using oser.RandomBitsFuzzingValue()
you can create random bit values
based on a mersenne twister.
>>> from __future__ import absolute_import, division, print_function, unicode_literals
>>>
>>> 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, count, seed=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()
. IfNone
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.
>>> from __future__ import absolute_import, division, print_function, unicode_literals
>>>
>>> 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, count, chars=None, seed=None)¶ A random string fuzzing value generator. Random values are generated using
random.choice()
withchars
.- 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()
. IfNone
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.
>>> from __future__ import absolute_import, division, print_function, unicode_literals
>>>
>>> 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, count, choices=None, seed=None)¶ A random bytes fuzzing value generator. Random values are generated using
random.choice()
withchoices
.- 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()
. IfNone
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.
>>> from __future__ import absolute_import, division, print_function, unicode_literals
>>>
>>> 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.
>>> from __future__ import absolute_import, division, print_function, unicode_literals
>>>
>>> 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.
>>> from __future__ import absolute_import, division, print_function, unicode_literals
>>>
>>> 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()
).
>>> from __future__ import absolute_import, division, print_function, unicode_literals
>>>
>>> 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()
.
>>> from __future__ import absolute_import, division, print_function, unicode_literals
>>>
>>> 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.
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=False)¶ 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 (bool) – if set to
True
the generated fuzzing values are deep copies of the original. Creating these deep copies is slow. If set toFalse
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.
>>> from __future__ import absolute_import, division, print_function, unicode_literals
>>>
>>> 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¶
>>> from __future__ import absolute_import, division, print_function, unicode_literals
>>>
>>> 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
.
>>> from __future__ import absolute_import, division, print_function, unicode_literals
>>>
>>> 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.
>>> from __future__ import absolute_import, division, print_function, unicode_literals
>>>
>>> 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