Fuzzing Automata#

Mutation of a protocol state machine is provided by the mutate() method of the Automata class.

Automata.mutate(strategy=None, target=None, generator=None, seed=None)[source]#

This is the mutation method of the automaton. This method returns a new automaton that may be used for fuzzing purpose.

The mutate method expects some parameters:

Parameters
  • strategy (AutomataMutatorStrategy, optional) –

    The strategy used to build the new automaton.

    The following strategies are available:

    • AutomataMutatorStrategy.RANDOM: Randomly insert and remove transitions between states of the original automaton,

    • AutomataMutatorStrategy.FULL: At each state of the automaton, it is possible to reach any states,

    • AutomataMutatorStrategy.ONESTATE: Build an automaton with one main state that accepts every symbols.

    • AutomataMutatorStrategy.TARGETED: Build an automaton similar to the original one, where a targeted state, given in parameters, will accept every symbols.

    If set to None, the default strategy is AutomataMutatorStrategy.RANDOM.

  • target (str, optional) – The name of the state considered for targeted fuzzing (should be used with AutomataMutatorStrategy.TARGETED).

  • generator (iter, optional) –

    The underlying generator used to produce pseudo-random or deterministic values.

    Default generator is 'xorshift', which is efficient to produce unique pseudo-random numbers.

  • seed (int, optional) –

    An integer used to initialize the underlying generator.

    If None, the default value will be set to Mutator.SEED_DEFAULT. The Mutator.SEED_DEFAULT constant is initialized from the configuration variable Conf.seed from the Netzob API Conf class.

Returns

The mutated automata.

Return type

Automata

Catching abnormal responses from the remote target

By default, the state machine is configured so that the reception of abnormal messages from the remote peer will terminate the visit loop of the automaton from the current actor. When applying fuzzing, this behavior could be annoying as it will quickly stop the fuzzing session as soon as a non-legitimate response is received. In order to catch this kind of responses and adapt the current actor behavior, it is recommended to set the following callbacks on the automaton:

The following example shows how to specify a global behavior, on all states and transitions, in order to catch reception of unexpected symbols (i.e. symbols that are known but not expected at this state/transition) and unknown messages (i.e. messages that cannot be abstracted to a symbol).

>>> from netzob.all import *
>>> import time
>>>
>>> # First we create the symbols
>>> symbol1 = Symbol(name="Hello1", fields=[Field("hello1")])
>>> symbol2 = Symbol(name="Hello2", fields=[Field("hello2")])
>>> symbolList = [symbol1, symbol2]
>>>
>>> # Create Bob's automaton
>>> bob_s0 = State(name="S0")
>>> bob_s1 = State(name="S1")
>>> bob_s2 = State(name="S2")
>>> bob_s3 = State(name="S3")
>>> bob_error_state = State(name="Error state")
>>> bob_openTransition = OpenChannelTransition(startState=bob_s0, endState=bob_s1, name="Open")
>>> bob_mainTransition = Transition(startState=bob_s1, endState=bob_s2,
...                                 inputSymbol=symbol1, outputSymbols=[symbol2],
...                                 name="T1")
>>> bob_closeTransition1 = CloseChannelTransition(startState=bob_error_state, endState=bob_s3, name="Close")
>>> bob_closeTransition2 = CloseChannelTransition(startState=bob_s2, endState=bob_s3, name="Close")
>>> bob_automata = Automata(bob_s0, symbolList)
>>>
>>> def cbk_method(current_state, current_transition, received_symbol, received_message, received_structure):
...     return bob_error_state
>>> bob_automata.set_cbk_read_unexpected_symbol(cbk_method)
>>> bob_automata.set_cbk_read_unknown_symbol(cbk_method)
>>>
>>> automata_ascii = bob_automata.generateASCII()
>>> print(automata_ascii)
#=========================#
H           S0            H
#=========================#
  |
  | OpenChannelTransition
  v
+-------------------------+
|           S1            |
+-------------------------+
  |
  | T1 (Hello1;{Hello2})
  v
+-------------------------+
|           S2            |
+-------------------------+
  |
  | CloseChannelTransition
  v
+-------------------------+
|           S3            |
+-------------------------+

>>>
>>> # Create Alice's automaton
>>> alice_s0 = State(name="S0")
>>> alice_s1 = State(name="S1")
>>> alice_s2 = State(name="S2")
>>> alice_openTransition = OpenChannelTransition(startState=alice_s0, endState=alice_s1, name="Open")
>>> alice_mainTransition = Transition(startState=alice_s1, endState=alice_s1,
...                                   inputSymbol=symbol1, outputSymbols=[symbol1],
...                                   name="T1")
>>> alice_closeTransition = CloseChannelTransition(startState=alice_s1, endState=alice_s2, name="Close")
>>> alice_automata = Automata(alice_s0, symbolList)
>>>
>>> automata_ascii = alice_automata.generateASCII()
>>> print(automata_ascii)
#=========================#
H           S0            H
#=========================#
  |
  | OpenChannelTransition
  v
+-------------------------+   T1 (Hello1;{Hello1})
|                         | -----------------------+
|           S1            |                        |
|                         | <----------------------+
+-------------------------+
  |
  | CloseChannelTransition
  v
+-------------------------+
|           S2            |
+-------------------------+

>>>
>>> # Create Bob actor (a client)
>>> channel = UDPClient(remoteIP="127.0.0.1", remotePort=8887, timeout=1.)
>>> bob = Actor(automata=bob_automata, channel=channel, name="Bob")
>>> bob.nbMaxTransitions = 10
>>>
>>> # Create Alice actor (a server)
>>> channel = UDPServer(localIP="127.0.0.1", localPort=8887, timeout=1.)
>>> alice = Actor(automata=alice_automata, channel=channel, initiator=False, name="Alice")
>>>
>>> alice.start()
>>> time.sleep(0.5)
>>> bob.start()
>>>
>>> time.sleep(1)
>>>
>>> bob.stop()
>>> alice.stop()
>>>
>>> print(bob.generateLog())
Activity log for actor 'Bob' (initiator):
  [+] At state 'S0'
  [+]   Randomly choosing a transition to execute or to wait for an input symbol
  [+]   Picking transition 'Open' (open channel)
  [+]   Transition 'Open' lead to state 'S1'
  [+] At state 'S1'
  [+]   Randomly choosing a transition to execute or to wait for an input symbol
  [+]   Picking transition 'T1' (initiator)
  [+]   During transition 'T1', sending input symbol ('Hello1')
  [+]   During transition 'T1', receiving unexpected symbol triggered a callback that lead to state 'Error state'
  [+] At state 'Error state'
  [+]   Randomly choosing a transition to execute or to wait for an input symbol
  [+]   Picking transition 'Close' (close channel)
  [+]   Transition 'Close' lead to state 'S3'
  [+] At state 'S3'
  [+]   Randomly choosing a transition to execute or to wait for an input symbol
>>> print(alice.generateLog())
Activity log for actor 'Alice' (not initiator):
  [+] At state 'S0'
  [+]   Randomly choosing a transition to execute or to wait for an input symbol
  [+]   Picking transition 'Open' (open channel)
  [+]   Going to execute transition 'Open'
  [+]   Transition 'Open' lead to state 'S1'
  [+] At state 'S1'
  [+]   Randomly choosing a transition to execute or to wait for an input symbol
  [+]   Waiting for an input symbol to decide the transition (not initiator)
  [+]   Input symbol 'Hello1' corresponds to transition 'T1'
  [+]   During transition 'T1', choosing an output symbol ('Hello1')
  [+]   Transition 'T1' lead to state 'S1'
  [+] At state 'S1'
  [+]   Randomly choosing a transition to execute or to wait for an input symbol
  [+]   Waiting for an input symbol to decide the transition (not initiator)

Basic example of automata fuzzing

Mutators may be used in order to create fuzzed/mutated automaton.

The following code shows the creation of the new automaton with random transitions between the existing states:

>>> from netzob.all import *
>>> import time
>>> sym1 = Symbol([Field(String(nbChars=3))], name='Sym1')
>>> sym2 = Symbol([Field(String(nbChars=5))], name='Sym2')
>>> symbols = [sym1, sym2]
>>> s0 = State(name="s0")
>>> s1 = State(name="s1")
>>> s2 = State(name="s2")
>>> s3 = State(name="s3")
>>> s4 = State(name="s4")
>>> t0 = OpenChannelTransition(startState=s0, endState=s1,
...                            name="t0")
>>> t1 = Transition(startState=s1, endState=s1,
...                 inputSymbol=sym1, outputSymbols=[sym1],
...                 name="t1")
>>> t2 = Transition(startState=s1, endState=s2,
...                 inputSymbol=sym2, outputSymbols=[sym2],
...                 name="t2")
>>> t3 = Transition(startState=s2, endState=s3,
...                 inputSymbol=sym1, outputSymbols=[sym1],
...                 name="t3")
>>> t4 = CloseChannelTransition(startState=s2, endState=s4,
...                             name="t4")
>>>
>>> automata = Automata(s0, symbols=symbols)
>>> automata_ascii = automata.generateASCII()
>>> print(automata_ascii)
                                 #========================#
                                 H           s0           H
                                 #========================#
                                   |
                                   | OpenChannelTransition
                                   v
                                 +------------------------+   t1 (Sym1;{Sym1})
                                 |                        | -------------------+
                                 |           s1           |                    |
                                 |                        | <------------------+
                                 +------------------------+
                                   |
                                   | t2 (Sym2;{Sym2})
                                   v
+----+  CloseChannelTransition   +------------------------+
| s4 | <------------------------ |           s2           |
+----+                           +------------------------+
                                   |
                                   | t3 (Sym1;{Sym1})
                                   v
                                 +------------------------+
                                 |           s3           |
                                 +------------------------+

>>>
>>> # Generate a random automaton
>>>
>>> mutatedAutomata = automata.mutate()
>>> automata_ascii_2 = mutatedAutomata.generateASCII()
>>> print(automata_ascii_2)
#========================#
H           s0           H
#========================#
  |
  | OpenChannelTransition
  v
+----------------------------------------------------------------------------------------+   t1 (Sym1;{Sym1})
|                                                                                        | -------------------+
|                                           s1                                           |                    |
|                                                                                        | <------------------+
+----------------------------------------------------------------------------------------+
  |                         ^                               ^
  | t2 (Sym2;{Sym2})        | t_random (Sym1;{Sym1,Sym2})   | t_random (Sym2;{Sym1,Sym2})
  v                         |                               |
+----------------------------------------------------------------------------------------+
|                                           s2                                           |
+----------------------------------------------------------------------------------------+
  |                                                         |
  | t3 (Sym1;{Sym1})                                        | CloseChannelTransition
  v                                                         v
+------------------------+                                +------------------------------+
|           s3           |                                |              s4              |
+------------------------+                                +------------------------------+

>>>
>>> # Generate a full automaton
>>>
>>> mutatedAutomata = automata.mutate(strategy=AutomataMutatorStrategy.FULL)
>>>
>>> # The ASCII representation is not displayed as it is too big
>>>
>>> # Generate an automaton with one main state
>>>
>>> mutatedAutomata = automata.mutate(strategy=AutomataMutatorStrategy.ONESTATE)
>>> automata_ascii_2 = mutatedAutomata.generateASCII()
>>> print(automata_ascii_2)
                                  #========================#
                                  H     Initial state      H
                                  #========================#
                                    |
                                    | OpenChannelTransition
                                    v
    t_random (Sym2;{Sym1,Sym2})   +------------------------+   t_random (Sym1;{Sym1,Sym2})
  +------------------------------ |                        | ------------------------------+
  |                               |       Main state       |                               |
  +-----------------------------> |                        | <-----------------------------+
                                  +------------------------+

>>>
>>> # Generate an automaton with targeted fuzzing on one specific state
>>>
>>> mutatedAutomata = automata.mutate(strategy=AutomataMutatorStrategy.TARGETED, target=s2.name)
>>> automata_ascii_2 = mutatedAutomata.generateASCII()
>>> print(automata_ascii_2)
                                  #========================#
                                  H           s0           H
                                  #========================#
                                    |
                                    | OpenChannelTransition
                                    v
                                  +------------------------+
                                  |           s1           |
                                  +------------------------+
                                    |
                                    | t2 (Sym2;{Sym2})
                                    v
    t_random (Sym2;{Sym1,Sym2})   +------------------------+   t_random (Sym1;{Sym1,Sym2})
  +------------------------------ |                        | ------------------------------+
  |                               |           s2           |                               |
  +-----------------------------> |                        | <-----------------------------+
                                  +------------------------+

Combining message formats and automata fuzzing

By combining message formats and automata fuzzing, it is possible to fuzz specific message formats at specific states in the automaton.

The following code shows the creation of a mutated automaton with targeted automaton mutations at state ‘s6’, and with a precision concerning the state at which fuzzing of message formats will be performed. Here, the message format fuzzing only applies at state ‘s6’. An actor is also created to simulate a target.

>>> from netzob.all import *
>>> import time
>>> sym1 = Symbol([Field(uint16())], name='Sym1')
>>> symbols = [sym1]
>>> s0 = State(name="s0")
>>> s1 = State(name="s1")
>>> s2 = State(name="s2")
>>> s3 = State(name="s3")
>>> s4 = State(name="s4")
>>> s5 = State(name="s5")
>>> s6 = State(name="s6")
>>> s7 = State(name="s7")
>>> t0 = OpenChannelTransition(startState=s0, endState=s1,
...                            name="t0")
>>> t1 = Transition(startState=s1, endState=s1,
...                 inputSymbol=sym1, outputSymbols=[sym1],
...                 name="t1")
>>> t2 = Transition(startState=s1, endState=s2,
...                 inputSymbol=sym1, outputSymbols=[sym1],
...                 name="t2")
>>> t3 = Transition(startState=s2, endState=s3,
...                 inputSymbol=sym1, outputSymbols=[sym1],
...                 name="t3")
>>> t4 = Transition(startState=s2, endState=s4,
...                 inputSymbol=sym1, outputSymbols=[sym1],
...                 name="t4")
>>> t5 = Transition(startState=s4, endState=s6,
...                 inputSymbol=sym1, outputSymbols=[sym1],
...                 name="t5")
>>> t6 = Transition(startState=s3, endState=s5,
...                 inputSymbol=sym1, outputSymbols=[sym1],
...                 name="t6")
>>> t7 = Transition(startState=s5, endState=s6,
...                 inputSymbol=sym1, outputSymbols=[sym1],
...                 name="t7")
>>> t8 = Transition(startState=s6, endState=s6,
...                 inputSymbol=sym1, outputSymbols=[sym1],
...                 name="t8")
>>> t9 = CloseChannelTransition(startState=s6, endState=s7,
...                             name="t9")
>>>
>>> automata = Automata(s0, symbols=symbols)
>>> automata_ascii = automata.generateASCII()
>>> print(automata_ascii)
                           #=========================#
                           H           s0            H
                           #=========================#
                             |
                             | OpenChannelTransition
                             v
                           +-------------------------+   t1 (Sym1;{Sym1})
                           |                         | -------------------+
                           |           s1            |                    |
                           |                         | <------------------+
                           +-------------------------+
                             |
                             | t2 (Sym1;{Sym1})
                             v
+----+  t4 (Sym1;{Sym1})   +-------------------------+
| s4 | <------------------ |           s2            |
+----+                     +-------------------------+
  |                          |
  |                          | t3 (Sym1;{Sym1})
  |                          v
  |                        +-------------------------+
  |                        |           s3            |
  |                        +-------------------------+
  |                          |
  |                          | t6 (Sym1;{Sym1})
  |                          v
  |                        +-------------------------+
  |                        |           s5            |
  |                        +-------------------------+
  |                          |
  |                          | t7 (Sym1;{Sym1})
  |                          v
  |                        +-------------------------+   t8 (Sym1;{Sym1})
  |                        |                         | -------------------+
  |    t5 (Sym1;{Sym1})    |           s6            |                    |
  +----------------------> |                         | <------------------+
                           +-------------------------+
                             |
                             | CloseChannelTransition
                             v
                           +-------------------------+
                           |           s7            |
                           +-------------------------+

>>> # Creation of a mutated automaton
>>> mutatedAutomata = automata.mutate(strategy=AutomataMutatorStrategy.TARGETED, target=s6.name, seed=42)
>>> automata_ascii = mutatedAutomata.generateASCII()
>>> print(automata_ascii)
#========================#
H           s0           H
#========================#
  |
  | OpenChannelTransition
  v
+------------------------+
|           s1           |
+------------------------+
  |
  | t2 (Sym1;{Sym1})
  v
+------------------------+
|           s2           |
+------------------------+
  |
  | t4 (Sym1;{Sym1})
  v
+------------------------+
|           s4           |
+------------------------+
  |
  | t5 (Sym1;{Sym1})
  v
+------------------------+   t_random (Sym1;{Sym1})
|                        | -------------------------+
|           s6           |                          |
|                        | <------------------------+
+------------------------+

>>>
>>> # Define fuzzing configuration
>>> preset_symbol1 = Preset(sym1)
>>> preset_symbol1.fuzz(sym1)
>>>
>>> # Creation of an automaton visitor/actor and a channel on which to emit the fuzzed symbol
>>> bob_channel = UDPClient(remoteIP="127.0.0.1", remotePort=8887, timeout=1.)
>>> bob_actor = Actor(automata=mutatedAutomata, channel=bob_channel, name='Fuzzer')
>>> bob_actor.fuzzing_presets = [preset_symbol1]
>>> bob_actor.fuzzing_states = [s6.name]
>>> bob_actor.nbMaxTransitions = 7
>>>
>>> # Create Alice's automaton
>>> alice_s0 = State(name="s0")
>>> alice_s1 = State(name="s1")
>>> alice_openTransition = OpenChannelTransition(startState=alice_s0, endState=alice_s1, name="Open")
>>> alice_transition1 = Transition(startState=alice_s1, endState=alice_s1,
...                                inputSymbol=sym1, outputSymbols=[sym1],
...                                name="T1")
>>> alice_transition2 = Transition(startState=alice_s1, endState=alice_s1,
...                                inputSymbol=sym2, outputSymbols=[sym2],
...                                name="T2")
>>> alice_automata = Automata(alice_s0, symbols)
>>> automata_ascii = alice_automata.generateASCII()
>>> print(automata_ascii)
                       #========================#
                       H           s0           H
                       #========================#
                         |
                         | OpenChannelTransition
                         v
    T2 (Sym2;{Sym2})   +------------------------+   T1 (Sym1;{Sym1})
  +------------------- |                        | -------------------+
  |                    |           s1           |                    |
  +------------------> |                        | <------------------+
                       +------------------------+

>>>
>>> # Creation of an automaton visitor/actor and a channel on which to receive the fuzzing traffic
>>> alice_channel = UDPServer(localIP="127.0.0.1", localPort=8887, timeout=1.)
>>>
>>> # Creation of a callback function that returns a new transition
>>> def cbk_modifyTransition(availableTransitions, nextTransition, current_state,
...                          last_sent_symbol, last_sent_message, last_sent_structure,
...                          last_received_symbol, last_received_message, last_received_structure, memory):
...     if nextTransition is None:
...         return alice_transition2
...     else:
...         return nextTransition
>>>
>>> alice_automata.getState('s1').add_cbk_modify_transition(cbk_modifyTransition)
>>>
>>> alice_actor = Actor(automata=alice_automata, channel=alice_channel, initiator=False, name='Target')
>>>
>>> # We start the targeted actor
>>> alice_actor.start()
>>> time.sleep(0.5)
>>>
>>> # We start the visitor, thus the fuzzing of message formats will be applied when specific states are reached
>>> bob_actor.start()
>>> time.sleep(1)
>>>
>>> bob_actor.stop()
>>> alice_actor.stop()
>>>
>>> print(bob_actor.generateLog())
Activity log for actor 'Fuzzer' (initiator):
  [+] At state 's0'
  [+]   Randomly choosing a transition to execute or to wait for an input symbol
  [+]   Picking transition 't0' (open channel)
  [+]   Transition 't0' lead to state 's1'
  [+] At state 's1'
  [+]   Randomly choosing a transition to execute or to wait for an input symbol
  [+]   Picking transition 't2' (initiator)
  [+]   During transition 't2', sending input symbol ('Sym1')
  [+]   During transition 't2', receiving expected output symbol ('Sym1')
  [+]   Transition 't2' lead to state 's2'
  [+] At state 's2'
  [+]   Randomly choosing a transition to execute or to wait for an input symbol
  [+]   Picking transition 't4' (initiator)
  [+]   During transition 't4', sending input symbol ('Sym1')
  [+]   During transition 't4', receiving expected output symbol ('Sym1')
  [+]   Transition 't4' lead to state 's4'
  [+] At state 's4'
  [+]   Randomly choosing a transition to execute or to wait for an input symbol
  [+]   Picking transition 't5' (initiator)
  [+]   During transition 't5', sending input symbol ('Sym1')
  [+]   During transition 't5', receiving expected output symbol ('Sym1')
  [+]   Transition 't5' lead to state 's6'
  [+] At state 's6'
  [+]   Randomly choosing a transition to execute or to wait for an input symbol
  [+]   Picking transition 't_random' (initiator)
  [+]   During transition 't_random', sending input symbol ('Sym1')
  [+]   During transition 't_random', fuzzing activated
  [+]   During transition 't_random', receiving expected output symbol ('Sym1')
  [+]   Transition 't_random' lead to state 's6'
  [+] At state 's6'
  [+]   Randomly choosing a transition to execute or to wait for an input symbol
  [+]   Picking transition 't_random' (initiator)
  [+]   During transition 't_random', sending input symbol ('Sym1')
  [+]   During transition 't_random', fuzzing activated
  [+]   During transition 't_random', receiving expected output symbol ('Sym1')
  [+]   Transition 't_random' lead to state 's6'
  [+] At state 's6'
  [+]   Randomly choosing a transition to execute or to wait for an input symbol
  [+]   Picking transition 't_random' (initiator)
  [+]   During transition 't_random', sending input symbol ('Sym1')
  [+]   During transition 't_random', fuzzing activated
  [+]   During transition 't_random', receiving expected output symbol ('Sym1')
  [+]   Transition 't_random' lead to state 's6'
  [+] At state 's6', we reached the max number of transitions (7), so we stop
>>> print(alice_actor.generateLog())
Activity log for actor 'Target' (not initiator):
  [+] At state 's0'
  [+]   Randomly choosing a transition to execute or to wait for an input symbol
  [+]   Picking transition 'Open' (open channel)
  [+]   Going to execute transition 'Open'
  [+]   Transition 'Open' lead to state 's1'
  [+] At state 's1'
  [+]   Randomly choosing a transition to execute or to wait for an input symbol
  [+]   Waiting for an input symbol to decide the transition (not initiator)
  [+]   Input symbol 'Sym1' corresponds to transition 'T1'
  [+]   Changing transition to 'T1' (not initiator), through callback
  [+]   During transition 'T1', choosing an output symbol ('Sym1')
  [+]   Transition 'T1' lead to state 's1'
  [+] At state 's1'
  [+]   Randomly choosing a transition to execute or to wait for an input symbol
  [+]   Waiting for an input symbol to decide the transition (not initiator)
  [+]   Input symbol 'Sym1' corresponds to transition 'T1'
  [+]   Changing transition to 'T1' (not initiator), through callback
  [+]   During transition 'T1', choosing an output symbol ('Sym1')
  [+]   Transition 'T1' lead to state 's1'
  [+] At state 's1'
  [+]   Randomly choosing a transition to execute or to wait for an input symbol
  [+]   Waiting for an input symbol to decide the transition (not initiator)
  [+]   Input symbol 'Sym1' corresponds to transition 'T1'
  [+]   Changing transition to 'T1' (not initiator), through callback
  [+]   During transition 'T1', choosing an output symbol ('Sym1')
  [+]   Transition 'T1' lead to state 's1'
  [+] At state 's1'
  [+]   Randomly choosing a transition to execute or to wait for an input symbol
  [+]   Waiting for an input symbol to decide the transition (not initiator)
  [+]   Input symbol 'Sym1' corresponds to transition 'T1'
  [+]   Changing transition to 'T1' (not initiator), through callback
  [+]   During transition 'T1', choosing an output symbol ('Sym1')
  [+]   Transition 'T1' lead to state 's1'
  [+] At state 's1'
  [+]   Randomly choosing a transition to execute or to wait for an input symbol
  [+]   Waiting for an input symbol to decide the transition (not initiator)
  [+]   Input symbol 'Sym1' corresponds to transition 'T1'
  [+]   Changing transition to 'T1' (not initiator), through callback
  [+]   During transition 'T1', choosing an output symbol ('Sym1')
  [+]   Transition 'T1' lead to state 's1'
  [+] At state 's1'
  [+]   Randomly choosing a transition to execute or to wait for an input symbol
  [+]   Waiting for an input symbol to decide the transition (not initiator)
  [+]   Input symbol 'Sym1' corresponds to transition 'T1'
  [+]   Changing transition to 'T1' (not initiator), through callback
  [+]   During transition 'T1', choosing an output symbol ('Sym1')
  [+]   Transition 'T1' lead to state 's1'
  [+] At state 's1'
  [+]   Randomly choosing a transition to execute or to wait for an input symbol
  [+]   Waiting for an input symbol to decide the transition (not initiator)