/6Jack

A framework for analyzing/testing/fuzzing network applications.

Primary LanguageCOtherNOASSERTION

6jack

A framework for analyzing/testing/fuzzing network applications.

Releases can be downloaded from: http://download.pureftpd.org/6jack/

DESCRIPTION

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.

SYNOPSIS

6jack filter command [args]

FILTERS

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.

SAMPLE FILTER

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.

COMMON PROPERTIES

Objects sent by all diverted functions include the following properties:

  • version: Always 1 for now.

  • filter_type: Either PRE or POST.

  • pid: The process ID.

  • function: The function name, for instance recvfrom.

  • fd: The file descriptor. The only pre-filter that doesn't include fd is the socket() pre-filter.

  • return_value: The return value of the system call.

  • errno: The value of errno, 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 of version: 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 to socket() returns a valid descriptor, a filter can override the return value and return -1 as if the call had failed.

  • errno: Override the value of errno. 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.

FUNCTION-SPECIFIC PROPERTIES

write()

PRE filter

  • 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.

POST filter

  • In:

    • data: The data that has been written, as a MessagePack raw object.

socket()

PRE filter

  • In:

    • domain: One of PF_LOCAL, PF_UNIX, PF_INET, PF_ROUTE, PF_KEY, PF_INET6, PF_SYSTEM, PF_NDRV, PF_NETLINK and PF_FILE.

    • type: One of SOCK_STREAM, SOCK_DGRAM, SOCK_RAW, SOCK_SEQPACKET and SOCK_RDM.

    • protocol: One of IPPROTO_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 and IPPROTO_PGM.

  • Out:

    • domain: Override the domain.

    • type: Override the type.

    • protocol: Override the type.

POST filter

Same as a PRE filter.

sendto()

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.

POST filter

  • In:

Same as the PRE filter.

sendmsg()

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.

send()

PRE filter

  • 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.

POST filter

  • In:

    • data: The data that has been written, as a MessagePack raw object.

    • flags: Flags sent to the function.

recvmsg()

PRE filter

  • 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.

POST filter

  • 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 exceed nbyte. 6jack will automatically fragment it across multiple vectors if needed.

recvfrom()

PRE filter

  • 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.

POST filter

  • 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.

recv()

PRE filter

  • 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.

POST filter

  • 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).

read()

PRE filter

  • In:

    • nbyte: The size of the read buffer.
  • Out:

    • nbyte: Change the size of the read buffer. This value can only be lowered.

POST filter

  • 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).

connect()

PRE filter

  • 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.

close()

This function has no specific properties. However, local_host, local_port, remote_host and remote_port are filled accordingly in both types of filters.

bind()

PRE filter

  • 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 to socket().

    • local_port: Change the port to bind.

ENVIRONMENT

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.

SAMPLE OUTPUT

$ 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
}