Marionette is a programmable client-server proxy that enables the user to control network traffic features with a lightweight domain-specific language. The marionette system is described in [2] and builds on ideas from other papers, such as Format-Transforming Encryption [1].
- Protocol Misidentification Made Easy with Format-Transforming Encryption url: https://kpdyer.com/publications/ccs2013-fte.pdf
- Marionette: A Programmable Network Traffic Obfuscation System url: https://kpdyer.com/publications/usenix2015-marionette.pdf
$ sudo apt-get update && sudo apt-get upgrade
$ sudo apt-get install git libgmp-dev python-pip python-dev curl
$ git clone https://github.com/kpdyer/marionette.git
$ cd marionette
$ sudo pip install -r requirements.txt
$ python setup.py build
$ sudo python setup.py install
$ sudo yum update
$ yum install epel-release # EPEL may be required for some distros
$ sudo yum groupinstall "Development Tools"
$ sudo yum install git gmp-devel python-pip python-devel curl
$ git clone https://github.com/kpdyer/marionette.git
$ cd marionette
$ sudo pip install -r requirements.txt
$ python setup.py build
$ sudo python setup.py install
Requires homebrew.
$ brew install python gmp curl
$ git clone https://github.com/kpdyer/marionette.git
$ cd marionette
$ python setup.py install
$ python setup.py test
...
----------------------------------------------------------------------
Ran 13 tests in Xs
OK
And then testing with the servers...
$ ./bin/socksserver --local_port 8081 &
$ ./bin/marionette_server --server_ip 127.0.0.1 --proxy_ip 127.0.0.1 --proxy_port 8081 --format dummy&
$ ./bin/marionette_client --server_ip 127.0.0.1 --client_ip 127.0.0.1 --client_port 8079 --format dummy&
$ curl --socks4a 127.0.0.1:8079 example.com
A complete list of options is available with the --help
parameter.
general.debug
- [boolean] print useful debug information to the consolegeneral.autoupdate
- [boolean] enable automatic checks for new marionette formatsgeneral.update_server
- [string] the remote address of the server we should use for marionette updatesclient.client_ip
- [string] the iface we should listen on if it isn't specified on the CLIclient.client_port
- [int] the port we should listen on if it isn't specified on the CLIserver.server_ip
- [string] the iface we should listen on if it isn't specified on the CLIserver.proxy_ip
- [string] the iface we should forward connects to if it isn't specified on the CLIserver.proxy_port
- [int] the port we should forward connects to if it isn't specified on the CLI
Marionette's DSL is
connection([connection_type], [port]):
start [dst] [block_name] [prob | error]
[src] [dst] [block_name] [prob | error]
...
[src] end [block_name] [prob | error]
action [block_name]:
[client | server] [module].[func](arg1, arg2, ...)
[client | server] [module].[func](arg1, arg2, ...) [if regex_match_incoming(regex)]
...
The only connection_type
currently supported is tcp. The port specifies the port that the server listens on and client connects to. The block_name
specifies the named action that should be exected when transitioning from src to dst. A single error transition can be specified for each src and will be executed if all other potential transitions from src are impossible.
Action blocks specify actions by either a client or server. For brevity we allow specification of an action, such as fte.send
fte.send(regex, msg_len)
- sends a string on the channel that's encrypted with fte underregex
.fte.send_async(regex, msg_len)
- sends a string on the channel that's encrypted with fte underregex
, does not block receiver-side when waiting for the incoming message.tg.send(grammar_name)
- send a message using template grammargrammar_name
io.puts(str)
- send stringstr
on the channel.model.sleep(n)
- sleep forn
seconds.model.spawn(format_name, n)
- spawnn
instances of modelformat_name
, blocks until completion.
note: by specifying a send or a puts, that implicitly invokes a recv or a gets on the receiver side.
The following format generates a TCP connection sends one upstream GET and is followed by a downstream OK.
connection(tcp, 80):
start client NULL 1.0
client server http_get 1.0
server end http_ok 1.0
action http_get:
client fte.send("^GET\ \/([a-zA-Z0-9\.\/]*) HTTP/1\.1\r\n\r\n$", 128)
action http_ok:
server fte.send("^HTTP/1\.1\ 200 OK\r\nContent-Type:\ ([a-zA-Z0-9]+)\r\n\r\n\C*$", 128)
We use error transitions in the following format to deal with incoming connections that aren't from a marionette client. The conditionals are used to match a regex aginst the incoming request.
connection(tcp, 8080):
start upstream NULL 1.0
upstream downstream http_get 1.0
upstream downstream_err NULL error
downstream_err end http_ok_err 1.0
downstream end http_ok 1.0
action http_get:
client fte.send("^GET\ \/([a-zA-Z0-9\.\/]*) HTTP/1\.1\r\n\r\n$", 128)
action http_ok:
server fte.send("^HTTP/1\.1\ 200 OK\r\nContent-Type:\ ([a-zA-Z0-9]+)\r\n\r\n\C*$", 128)
action http_ok_err:
server io.puts("HTTP/1.1 200 OK\r\n\r\nHello, World!") if regex_match_incoming("^GET /(index\.html)? HTTP/1\.(0|1).*")
server io.puts("HTTP/1.1 404 File Not Found\r\n\r\nFile not found!") if regex_match_incoming("^GET /.* HTTP/1\.(0|1).*")
server io.puts("HTTP/1.1 400 Bad Request\r\n\r\nBad request!") if regex_match_incoming("^.+")