A framework for analyzing/testing/fuzzing network applications.
Releases can be downloaded from: http://download.pureftpd.org/6jack/
6jack runs a command, intercepts calls to common network-related functions and passes them through a filter as MessagePack serialized objects.
App External process
--- ----------------
| (started on-demand)
V
+----------+
| .... |
+----------+
|
V +-----+
+----------+ pre-filter | |
| call() | ------------> | F |
+----------+ | i |
| l |
| t |
+----------+ post-filter | e |
| .... | <----------- | r |
+----------+ | |
| +-----+
V
6jack is especially suitable for:
-
Writing tests for clients and servers: Tests for networked applications are often limited to sending a bunch of requests and comparing the answers to the expected ones. However, in production, system calls can fail for a variety of reasons. Packets get lost or delayed. Weird queries are received because of bogus clients or attackers. Content can get altered by third parties. How do you test for that? For example, how often does your test suite actually include tests for cases like EINTR and ENOBUFS? 6jack makes it easy to simulate this kind of failure, without having to patch your existing software.
-
Debugging and reverse engineering protocols: tcpdump is a super powerful tool. However, it has been designed to log incoming and outgoing packets. 6jack can alter the data sent from and to system calls. It can help you understand what's going on over the wire by modifying stuff and observing the impact.
-
Sketching filtering proxies
-
Fuzzing
6jack works at application level. It's a simple library that gets preloaded before the actual application.
Pre-filters can inspect and alter the content prior to calling the actual function. Data, options and local/remote IP addresses and port can be easily changed by the filter. A pre-filter can also totally bypass the actual call, in order to simulate a call without actually hitting the network.
Post-filters can inspect and alter the content after the actual call.
In particular, post-filters can change return values and the errno
value in order to simulate failures.
6jack filter command [args]
A filter is a standalone application that can be written in any language currently supported by MessagePack: Ruby, C, C++, C#, Scala, Lua, PHP, Python, Java, D, Go, Node.JS, Perl, Haskell, Erlang, Javascript and OCaml.
A filter should read a stream of MessagePack objects from the
standard input (stdin
) and for every object, should push a
serialized reply to the standard output (stdout
).
Every diverted function sends exactly one PRE object and one POST object, and synchronously waits for a reply to each object.
This is a simple Ruby filter that forces everything written and read to
upper case, and logs every object to stderr
:
require "msgpack"
require "awesome_print"
pac = MessagePack::Unpacker.new
loop do
begin
data = STDIN.readpartial(65536)
rescue EOFError
break
end
pac.feed(data)
pac.each do |obj|
obj["data"].upcase! if obj["data"]
warn obj.awesome_inspect
STDOUT.write obj.to_msgpack
STDOUT.flush
end
end
Other examples are available in the example-filters directory.
Objects sent by all diverted functions include the following properties:
-
version
: Always1
for now. -
filter_type
: EitherPRE
orPOST
. -
pid
: The process ID. -
function
: The function name, for instancerecvfrom
. -
fd
: The file descriptor. The only pre-filter that doesn't includefd
is thesocket()
pre-filter. -
return_value
: The return value of the system call. -
errno
: The value oferrno
, only meaningful in POST-filters. -
local_host
: The IP address of the local side of the socket. IPv6 is fully supported. -
local_port
: The port of the local side of the socket. -
remote_host
: The IP address of the remote side of the socket. IPv6 is fully supported. -
remote_port
: The port of the remote side of the socket.
The reply from a filter should at least include:
version
: Should be identical to the previous value ofversion
:1
.
The reply from a filter can contain only this property. In this case, the filter will act as a pass-through: nothing will be altered, as if there was no filter at all.
Additional properties that can be added to replies for all functions (everything is optional):
-
return_value
: Change the return value. For example, even if a call tosocket()
returns a valid descriptor, a filter can override the return value and return -1 as if the call had failed. -
errno
: Override the value oferrno
. This way, you can test how your application recovers from different types of errors. -
force_close
: This value is a boolean. If TRUE, the descriptor is closed. -
bypass
: This value is a boolean. If TRUE, the actual system call is bypassed. It means that you can test how your application behaves without actually receiving or sending data from/to the network.
-
In:
data
: The data to be written, as a MessagePack raw object.
-
Out:
data
: Overrides the data that was supposed to be written. The new data can be of any size, and can even be larger than the initial data. The return value will be automatically adjusted.
-
In:
data
: The data that has been written, as a MessagePack raw object.
-
In:
-
domain
: One ofPF_LOCAL
,PF_UNIX
,PF_INET
,PF_ROUTE
,PF_KEY
,PF_INET6
,PF_SYSTEM
,PF_NDRV
,PF_NETLINK
andPF_FILE
. -
type
: One ofSOCK_STREAM
,SOCK_DGRAM
,SOCK_RAW
,SOCK_SEQPACKET
andSOCK_RDM
. -
protocol
: One ofIPPROTO_IP
,IPPROTO_ICMP
,IPPROTO_IGMP
,IPPROTO_IPV4
,IPPROTO_TCP
,IPPROTO_UDP
,IPPROTO_IPV6
,IPPROTO_ROUTING
,IPPROTO_FRAGMENT
,IPPROTO_GRE
,IPPROTO_ESP
,IPPROTO_AH
,IPPROTO_ICMPV6
,IPPROTO_NONE
,IPPROTO_DSTOPTS
,IPPROTO_IPCOMP
,IPPROTO_PIM
andIPPROTO_PGM
.
-
-
Out:
-
domain
: Override the domain. -
type
: Override the type. -
protocol
: Override the type.
-
Same as a PRE filter.
-
In:
-
flags
:sendto()
flags. -
data
: A raw MessagePack with the data to be send.
-
-
Out:
-
flags
: Alter the flags. -
data
: Alter the content to be send. The size can differ from the size of the initial data. Sending more data is allowed. -
remote_host
: Change the remote host by providing the IP address of the new host. IPv6 is fully supported. -
remote_port
: Change the port the data is sent to.
-
- In:
Same as the PRE filter.
-
In:
-
flags
:sendto()
flags. -
data
: A raw MessagePack with the data to be send. 6jack makes it appear as a single blob even though it might actually be fragmented in multiple vectors.
-
-
Out:
-
flags
: Alter the flags. -
data
: Alter the content to be send. The size can differ from the size of the initial data. Sending more data is allowed. -
remote_host
: Change the remote host by providing the IP address of the new host. IPv6 is fully supported. -
remote_port
: Change the port the data is sent to.
-
-
In:
-
data
: The data to be written, as a MessagePack raw object. -
flags
: Flags to send to the function.
-
-
Out:
-
data
: Overrides the data that was supposed to be written. The new data can be of any size, and can even be larger than the initial data. The return value will be automatically adjusted. -
flags
: Override the flags to be sent to the function.
-
-
In:
-
data
: The data that has been written, as a MessagePack raw object. -
flags
: Flags sent to the function.
-
-
In:
-
flags
: Flags to send to the function. -
nbyte
: The total size of the read buffer, even if it is split across multiple vectors.
-
-
Out:
flags
: Override flags.
-
In:
-
flags
: Flags used when calling the function. -
data
: Make as if this data had been read instead of the real data. The size shouldn't exceednbyte
. 6jack will automatically fragment it across multiple vectors if needed.
-
-
In:
-
flags
: Flags used when calling the function. -
nbyte
: The size of the receiving buffer.
-
-
Out:
-
flags
: Override the flags. -
nbyte
: Change the maximum size of the data to be read. It can only be lowered.
-
-
In:
-
flags
: Flags used when calling the function. -
data
: Data that has been read.
-
-
Out:
-
data
: Override the data. The size can differ from the one of the real data, but it shouldn't exceed the size of the supplied buffer (nbyte
). -
remote_host
: Change the remote host by providing the IP address of the new peer. IPv6 is fully supported. -
remote_port
: Change the port the data is sent from.
-
-
In:
-
flags
: Flags that will be used to call the function. -
nbyte
: The size of the read buffer.
-
-
Out:
-
flags
: Change the flags. -
nbyte
: Change the size of the read buffer. This value can only be lowered.
-
-
In:
-
flags
: Flags that have been used to call the function. -
data
: Data to be written.
-
-
Out:
data
: Override the read data. The size should be less than or equal to the size of the original buffer (nbyte
).
-
In:
nbyte
: The size of the read buffer.
-
Out:
nbyte
: Change the size of the read buffer. This value can only be lowered.
-
In:
data
: Data to be written.
-
Out:
data
: Override the read data. The size should be less than or equal to the size of the original buffer (nbyte
).
-
Out:
-
remote_host
: Change the remote host by providing the IP address of the host to connect to. IPv6 is fully supported. -
remote_port
: Change the port to connect to.
-
This function has no specific properties. However, local_host
,
local_port
, remote_host
and remote_port
are filled accordingly
in both types of filters.
-
Out:
-
local_host
: Override the IP address to bind. If the socket is large enough, it's possible to bind an IPv6 address even if the original address was an IPv4 address. In this case, don't forget to also override the call tosocket()
. -
local_port
: Change the port to bind.
-
When a SIXJACK_BYPASS
environment variable is defined, calls are not
diverted to the filter any more.
An application is free to set and unset SIXJACK_BYPASS
, in order to
explicitly disable 6jack in some sections.
$ 6jack /tmp/filter-passthrough.rb curl http://example.com
{
"version" => 1,
"filter_type" => "PRE",
"pid" => 2233,
"function" => "socket",
"fd" => -1,
"domain" => "PF_INET6",
"type" => "SOCK_DGRAM",
"protocol" => "IPPROTO_IP"
}
{
"version" => 1,
"filter_type" => "POST",
"pid" => 2233,
"function" => "socket",
"fd" => 7,
"return_code" => 7,
"errno" => 2,
"domain" => "PF_INET6",
"type" => "SOCK_DGRAM",
"protocol" => "IPPROTO_IP"
}
{
"version" => 1,
"filter_type" => "PRE",
"pid" => 2233,
"function" => "close",
"fd" => 7,
"local_host" => "::ffff:0.0.0.0",
"local_port" => 0
}
{
"version" => 1,
"filter_type" => "POST",
"pid" => 2233,
"function" => "close",
"fd" => 7,
"return_code" => 0,
"errno" => 2,
"local_host" => "::ffff:0.0.0.0",
"local_port" => 0
}
{
"version" => 1,
"filter_type" => "PRE",
"pid" => 2233,
"function" => "socket",
"fd" => -1,
"domain" => "PF_LOCAL",
"type" => "SOCK_STREAM",
"protocol" => "IPPROTO_IP"
}
{
"version" => 1,
"filter_type" => "POST",
"pid" => 2233,
"function" => "socket",
"fd" => 8,
"return_code" => 8,
"errno" => 2,
"domain" => "PF_LOCAL",
"type" => "SOCK_STREAM",
"protocol" => "IPPROTO_IP"
}
{
"version" => 1,
"filter_type" => "PRE",
"pid" => 2233,
"function" => "socket",
"fd" => -1,
"domain" => "PF_INET6",
"type" => "SOCK_STREAM",
"protocol" => "IPPROTO_TCP"
}
{
"version" => 1,
"filter_type" => "POST",
"pid" => 2233,
"function" => "socket",
"fd" => 7,
"return_code" => 7,
"errno" => 2,
"domain" => "PF_INET6",
"type" => "SOCK_STREAM",
"protocol" => "IPPROTO_TCP"
}
{
"version" => 1,
"filter_type" => "PRE",
"pid" => 2233,
"function" => "connect",
"fd" => 7,
"local_host" => "::",
"local_port" => 0,
"remote_host" => "2001:500:88:200::10",
"remote_port" => 80
}
{
"version" => 1,
"filter_type" => "POST",
"pid" => 2233,
"function" => "connect",
"fd" => 7,
"return_code" => -1,
"errno" => 65,
"local_host" => "::",
"local_port" => 50716,
"remote_host" => "2001:500:88:200::10",
"remote_port" => 80
}
{
"version" => 1,
"filter_type" => "PRE",
"pid" => 2233,
"function" => "close",
"fd" => 7,
"local_host" => "::",
"local_port" => 50716
}
{
"version" => 1,
"filter_type" => "POST",
"pid" => 2233,
"function" => "close",
"fd" => 7,
"return_code" => 0,
"errno" => 57,
"local_host" => "::",
"local_port" => 50716
}
{
"version" => 1,
"filter_type" => "PRE",
"pid" => 2233,
"function" => "socket",
"fd" => -1,
"domain" => "PF_INET",
"type" => "SOCK_STREAM",
"protocol" => "IPPROTO_TCP"
}
{
"version" => 1,
"filter_type" => "POST",
"pid" => 2233,
"function" => "socket",
"fd" => 7,
"return_code" => 7,
"errno" => 57,
"domain" => "PF_INET",
"type" => "SOCK_STREAM",
"protocol" => "IPPROTO_TCP"
}
{
"version" => 1,
"filter_type" => "PRE",
"pid" => 2233,
"function" => "connect",
"fd" => 7,
"local_host" => "0.0.0.0",
"local_port" => 0,
"remote_host" => "192.0.43.10",
"remote_port" => 80
}
{
"version" => 1,
"filter_type" => "POST",
"pid" => 2233,
"function" => "connect",
"fd" => 7,
"return_code" => -1,
"errno" => 36,
"local_host" => "192.168.100.7",
"local_port" => 50717,
"remote_host" => "192.0.43.10",
"remote_port" => 80
}
{
"version" => 1,
"filter_type" => "PRE",
"pid" => 2233,
"function" => "send",
"fd" => 7,
"local_host" => "192.168.100.7",
"local_port" => 50717,
"remote_host" => "192.0.43.10",
"remote_port" => 80,
"flags" => 0,
"data" => "GET / HTTP/1.1\r\nUser-Agent: curl/7.19.7 (universal-apple-darwin10.0) libcurl/7.19.7 OpenSSL/0.9.8l zlib/1.2.3\r\nHost: example.com\r\nAccept: */*\r\n\r\n"
}
{
"version" => 1,
"filter_type" => "POST",
"pid" => 2233,
"function" => "send",
"fd" => 7,
"return_code" => 145,
"errno" => 36,
"local_host" => "192.168.100.7",
"local_port" => 50717,
"remote_host" => "192.0.43.10",
"remote_port" => 80,
"flags" => 0,
"data" => "GET / HTTP/1.1\r\nUser-Agent: curl/7.19.7 (universal-apple-darwin10.0) libcurl/7.19.7 OpenSSL/0.9.8l zlib/1.2.3\r\nHost: example.com\r\nAccept: */*\r\n\r\n"
}
{
"version" => 1,
"filter_type" => "PRE",
"pid" => 2233,
"function" => "recv",
"fd" => 7,
"local_host" => "192.168.100.7",
"local_port" => 50717,
"remote_host" => "192.0.43.10",
"remote_port" => 80,
"flags" => 0,
"nbyte" => 16384
}
{
"version" => 1,
"filter_type" => "POST",
"pid" => 2233,
"function" => "recv",
"fd" => 7,
"return_code" => 128,
"errno" => 36,
"local_host" => "192.168.100.7",
"local_port" => 50717,
"remote_host" => "192.0.43.10",
"remote_port" => 80,
"flags" => 0,
"data" => "HTTP/1.0 302 Found\r\nLocation: http://www.iana.org/domains/example/\r\nServer: BigIP\r\nConnection: Keep-Alive\r\nContent-Length: 0\r\n\r\n"
}
{
"version" => 1,
"filter_type" => "PRE",
"pid" => 2233,
"function" => "close",
"fd" => 7,
"local_host" => "192.168.100.7",
"local_port" => 50717,
"remote_host" => "192.0.43.10",
"remote_port" => 80
}
{
"version" => 1,
"filter_type" => "POST",
"pid" => 2233,
"function" => "close",
"fd" => 7,
"return_code" => 0,
"errno" => 36,
"local_host" => "192.168.100.7",
"local_port" => 50717,
"remote_host" => "192.0.43.10",
"remote_port" => 80
}