项目作者: sevagh

项目描述 :
write wireshark dissectors in Rust via C FFI
高级语言: Rust
项目地址: git://github.com/sevagh/wireshark-dissector-rs.git
创建时间: 2019-11-30T05:21:06Z
项目社区:https://github.com/sevagh/wireshark-dissector-rs

开源协议:MIT License

下载


Writing a Wireshark dissector in Rust

What this repo contains

  • Basic TCP server written in Rust, listening on port 8888 for a made-up binary protocol called dummy
  • Basic TCP client which sends dummy protocol messages to the server on port 8888
  • Wireshark dissector which, when compiled, allows Wireshark to understand the dummy protocol

The dummy protocol

16-byte payload - first 8 bytes is an 8-char string representing the “version”, the second 8 bytes is an 8-char string representing the “body”.

The catch is that these are encoded (with transform). The encode/decode is just add/subtract 1 from the chars. Yep, not safe, overflow, underflow - it’s just for demonstration.

We “encode” (i.e. increment each char) the data before sending it on the wire in client/src/main.rs:

  1. let version = encode(&CString::new("testver3").unwrap().into_bytes());
  2. let body = encode(&CString::new("hifriend").unwrap().into_bytes());

I use CStrings here to use into_bytes to drop the implicit NUL termination of Rust.

  1. let mut payload = [&version[..], &body[..]].concat();
  2. let nsent = unsafe { send(fd, payload.as_mut_ptr() as *mut c_void, MSGLEN, 0) };
  3. if nsent < 0 {
  4. bail!("recv failed: {}", Errno::last());
  5. } else {
  6. println!("Sent: {} bytes...\n", nsent);
  7. }

These are decoded server-side:

  1. let vers = decode(&payload[..8]);
  2. let body = decode(&payload[8..16]);
  3. println!(
  4. "\tversion: {:?}\n\tbody: {:?}",
  5. String::from_utf8_lossy(&vers),
  6. String::from_utf8_lossy(&body)
  7. );

tcpdump - why can’t I understand it?

From the client we saw that we’re sending a Dummy packet with:

  1. version: "testver3"
  2. body: "hifriend'

However, since it’s encoded in this protocol, you can’t understand the plain-text from tcpdump.

Client:

  1. sevagh:wireshark-dissector-rs $ ./target/debug/client
  2. Sent: 16 bytes...
  3. sevagh:wireshark-dissector-rs $

Server:

  1. sevagh:wireshark-dissector-rs $ ./target/debug/server
  2. Read 16 bytes...
  3. version: "testver3"
  4. body: "hifriend"

Sometimes in prod we want to check the packets with tcpdump - debugging in case the server isn’t receiving, or any other reason:

  1. sevagh:wireshark-dissector-rs $ sudo tcpdump -i any dst port 8888 -A
  2. tcpdump: verbose output suppressed, use -v or -vv for full protocol decode
  3. listening on any, link-type LINUX_SLL (Linux cooked), capture size 262144 bytes
  4. 11:04:38.803566 IP localhost.41104 > localhost.ddi-tcp-1: Flags [P.], seq 0:16, ack 1, win 342, options [nop,nop,TS val 2431784080 ecr 2431784080], length 16
  5. E..D..@.@............."..Y.........V.8.....
  6. ........uftuwfs4ijgsjfoe

Here we see the body: uftuwfs4 ijgsjfoe. This is testver3 hifriend with all the chars incremented, but pretend that this is a more complicated protocol with more fields and more rigorous encoding.

Rust FFI in the Wireshark dissector

I’ll skip the repetition and give a link-dump of some posts I perused to understand how to write a Wireshark dissector in C:

All of this code can be found in ./dissector/plugins.

Importantly, these are the additions I made for Rust FFI inside the dissector.

In dummy/Makefile.rust, the rule to create libdummy_dissector.a:

  1. libdummy_dissector:
  2. @cargo build
  3. @cp ../../../target/debug/libdummy_dissector.a ./

This places the compiled Rust/FFI .a file into the dummy plugin directory.

dummy/Cargo.toml:

  1. [lib]
  2. name = "dummy_dissector"
  3. crate-type = ["staticlib"]
  4. [dependencies]
  5. transform = { path = "../../../transform" }
  6. common = { path = "../../../common" }
  7. libc = "0.2.29"

The function signature inside dummy/src/lib.rs:

  1. #![crate_type = "staticlib"]
  2. #[no_mangle]
  3. pub extern "C" fn dissect_dummy_rs(data: &mut [u8]) -> i32 {

In dummy/Makefile.am:

  1. dummy_la_LDFLAGS = $(PLUGIN_LDFLAGS) libdummy_dissector.a

In dummy/packet-dummy.c:

  1. extern int32_t
  2. dissect_dummy_rs(const void *data);

After this, copying the dummy plugin folder to your Wireshark sources (more here) should allow you to compile Wireshark with the FFI. There’s a Make rule:

  1. wireshark_plugin:
  2. @if test -z "$$WIRESHARK_SRC_DIR"; then echo "Please define WIRESHARK_SRC_DIR" && exit -1; fi;
  3. @cd $(WIRESHARK_PLUGIN_DIR)/dummy && make -f ./Makefile.rust
  4. @cp -r $(WIRESHARK_PLUGIN_DIR) $$WIRESHARK_SRC_DIR
  5. @cd $$WIRESHARK_SRC_DIR && ./autogen.sh && make -C plugins && make && sudo make install && cd -

The result

When you launch Wireshark with dummy.pcap file, you’re greeted with a parsed protocol:

screenie

We taught Wireshark how to understand dummy!

How?

In the body of the dissector method in packet-dummy.c, we delegate decoding the packet to Rust:

  1. static int
  2. dissect_dummy(tvbuff_t *tvb, packet_info *pinfo, proto_tree *tree, void *data)
  3. {
  4. // this is our rust function!
  5. // it modifies the packet in place to decode it
  6. dissect_dummy_rs(tvb->real_data);
  7. gint offset = 0;
  8. // here we print the decoded data (once rust is done decoding it)
  9. if (tree) {
  10. proto_item *ti;
  11. ti = proto_tree_add_item(tree, proto_dummy, tvb, 0, -1, FALSE);
  12. tree = proto_item_add_subtree(ti, ett_dummy);
  13. proto_tree_add_item(tree, hf_dummy_version, tvb, offset, 8, FT_STRING);
  14. offset += 8;
  15. proto_tree_add_item(tree, hf_dummy_body, tvb, offset, 8, FT_STRING);
  16. }

The Rust code looks like this:

  1. use transform::decode;
  2. pub extern "C" fn dissect_dummy_rs(data: &mut [u8]) -> i32 {
  3. if data.len() > 2*MSGLEN {
  4. return -1;
  5. }
  6. let mut decoded_version = decode(&data[..8]);
  7. let mut decoded_body = decode(&data[8..]);
  8. // some unsafe memcpies
  9. }

The beauty of this is that we used the same transform crate that the client and server use.

What does this imply? You can re-use protocol decoding logic from your Rust application code inside the Wireshark dissector instead of needing to rewrite it in C or Lua.