6jack ===== A framework for analyzing/testing/fuzzing network applications. ## DESCRIPTION **6jack** runs a command, intercepts calls to common network-related functions and pass 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 ofte 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 "quota exceeded" or "no space left on device"? 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__ * __Fuzzying__ **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](https://github.com/jedisct1/6jack/tree/master/example-filters). ## 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 }