Closed tintinweb closed 8 years ago
+2 ;). Looks good.
More general question, is there a way to "break out" of the automata state at some point? The idea would be to use the automata to reach a known good state (say TLSClientKeyExchange) and then start fuzzing the state machine by sending out of state packets. I think that would be efficient in fuzzing the state machine from a given state. Any thoughts?
The first commit was my initial try with the TLSSocket (some months ago) and the second (54a22f9) switches to the new TLSSocket.accept
interface doing the conversion early (just took this from your rsa_server example :)).
I like your fuzzing approach which is better than what I had in mind :) I am currently thinking about adding an interface to the automata that allows an implementation to override any state/transition. That way we'd have a protocol conform SM if you do not override anything and you can still selectively change actions/states/conditions from a custom implementation.
here's my first try of a generic interface to override SM states/actions/conditions but it is still a bit sub-optimal because
sock.recvall
gets the data) to the original implementation. To overcome this we could a) move away from the deco approach and define explicit hook points (begin of function, after recv, before send) or b) pass the received packet down the function chain where a) keeps the code readable and b) requires extra code.At this point I am a bit in favor of 2.a. What do you think?
Here's an example of the current approach that skips the server certificates message.
create the SM
auto_srv = TLSServerAutomata(debug=9,
bind=target,
pemcert=pemcert,
cipher_suite=TLSCipherSuite.RSA_WITH_AES_128_CBC_SHA,
response="HTTP/1.1 200 OK\r\n\r\n")
register any number of callbacks for state/condition/action function names:
def jump_to_server_hello_done(*args, **kwargs):
raw_input(" **** -------------> override state, directly jump to SERVER_CERTIFICATES_SENT aka. SERVER_HELLO_DONE")
raise auto_srv.SERVER_CERTIFICATES_SENT()
auto_srv.register_callback('send_server_hello', jump_to_server_hello_done)
run the SM
print auto_srv.run()
client:
>>> TLS 1.0 Handshake [length 00c6], ClientHello
<<< TLS 1.0 Handshake [length 003e], ServerHello
<<< TLS 1.0 Handshake [length 0004], ServerHelloDone
>>> TLS 1.0 Alert [length 0002], fatal unexpected_message
This is nice!
I haven't had a look at the details yet, but I don't think we need to bother with callback chaining. What use case did you have in mind where you'd need to chain callbacks?
I think your current approach is really good, we probably just need to add a static array of states where one can do something like this:
# STATES = [SERVER_HELLO_DONE, ...]
def jump_to_server_hello_done(*args, **kwargs):
raw_input(" **** -------------> override state, directly jump to SERVER_CERTIFICATES_SENT aka. SERVER_HELLO_DONE")
raise auto_srv.STATES[random.randint(0, len(STATES) - 1)]()
auto_srv.register_callback('send_server_hello', jump_to_server_hello_done)
One thing which could be nice would be to have a mapping between states and scapy pkts, say {TLSServerHello: "send_server_hello"}
, It could make the callback setting more readable. E.g:
auto_srv.register_callback(TLSServerHello, jump_to_server_hello_done)
and the corresponding raise auto_srv.STATES[TLSServerHelloDone]()
Otherwise, this is going to make state fuzzing completely approachable! This is super neat.
sry, been busy. will have the PR updated with your suggestions by the end of the week, probably earlier.
I've removed the chaining (did not make sense to have) and added a mapping from scapy tls-layer
to state
and actions
. Besides that the automata
baseclass already tracks a list of all available states or actions (and conditions) in automata.states
resp. automata.actions
that would provide an interface to random transitions like raise getattr(auto_srv,random.choice(auto_srv.states.keys()))()
. This allows easy callback registrations like:
auto_srv.register_callback(auto_srv.ACTIONS[TLSServerHello], jump_to_server_hello_done)
I've added a line to examples.tls_server_automata
to prints states/actions and the mapping on execution so that we do not have to look this up.
The bigger problem is that the @hookable
decorator in combination with @ATMT.condition
breaks scapy.automata.graph()
(the resulting graph is missing states or edges). This is unfortunate as decorating the states and transitions is pretty comfortable and enhances code readability in contrast to having some inspect.stack
magic and explicit calls to run potential callbacks in every hookable function :/ The decorator breaks graph()
as we're actually wrapping the original function which causes graph()
s voodo magic to fail. I do not see a nice workaround for this.
I've also added the server_key_exchange
states just to complete the SM. They're not doing anything atm. Here's the full graph:
I currently see two options:
graph()
by using @hookable
decorator => readable codegraph()
issue by removing the decorator, adding a two-liner to every hookable function instead that checks if a callback is available and executes it if so => makes code less readable (it isn't easy to follow anyway) pretty unhappy with both options.
Hi Tin,
Didn't have time to check this out in detail yet, but from you're description, it's looking good. I'm hoping to have a more detailed look tomorrow. Sorry about the delay!
Just had a quick look at this. This might be stupid, but would this not work if you switched the decorator order? You'd just be re-wrapping the stuff scapy does. I looked internally, and it seems to add some attributes to the decorated functions. But that mixed with graph() being a meta-class function will make things a bit crazy to tshoot... It might also being functools.wrap() interfering with scapy internals, since it redefines a bunch of __ variables.
this caused me a lot of headache.
root cause
scapy relies on reflection (investigating <caller_f>.func_code
) to plot ATMT.Conditions
with graph()
; see: https://github.com/secdev/scapy/blob/40045ea3e97e78be085a315520b3a86bbb3fe0ac/scapy/automaton.py#L304. therefore wrapping an ATMT.Condition
kind of function is going to break plotting for conditions as scapy simply cannot find any known state names in the caller func_code. Changing the decorator order won't help either.
solution
hookable
being the outer deco. (thats more natural and this way we get access to the state decorated f
)@hookable
works fine for ATMT.action
, ATMT.state
I've added special treatment for ATMT.Condition
to not wrap the function but store the wrapper_f as an attribute to the wrapped function returning the original f
. This way, graph()
will still work fine as it sees the original function and reflection will be able to find state vars within the caller code. Everyone's happy ;). On run()
we're going to fix functions in self.conditions
(populated by the state decorators) to use the wrapper instead of the unwrapped function.this way, graph()
works fine as long as it is being called before run()
. I guess this is the typical use-case. If it is not, we could as well fix it after run()
.
//had to rebase off master.
its a pretty old PR therefore I'd vote to merge (if there are no blockers) and refine it with specific use-cases in mind. :)
Agreed. It doesn't touch any core code anyway. I'll try and give this a test run next week. Got some use cases for it.
Nice stuff ;) Alex
:+1:
since @alexmgr provided the missing ingredient for server mode tlssockets here's a basic server automata. Atm. it is polling the client in a 5s interval for data, will have to change that in future versions.
start the server:
automata debug log with state transitions:
client output