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:
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.
>>> 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()
. 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.
>>> 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()
. 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.
>>> 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()
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.
>>> 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()
withchoices
.- Parameters:
- 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.
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 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.
>>> 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