This Time Self-Hosted
dark mode light mode Search

USB capturing in 2020

The vast majority of the glucometer devices I reverse the protocol of use USB to connect to a computer. You could say that all of those that I successfully reversed up to now are USB based. Over the years, the way I capture USB packets to figure out a protocol changed significantly, starting from proprietary Windows-based sniffers, and more recently involving my own opensource trace tools. The process evolution was not always intentional — in the case of USBlyzer, it was pretty much dead a few years after I started using it, plus the author refused to document the file format, and by then even my sacrificial laptop was not powerful enough to keep running all the tools I needed.

I feel I’m close to another step on the evolution of my process, and once again it’s not because of me looking to improve the process as much as is the process not working on modern tools. Let me start by explaining what the situation is, because there are two nearly separate issues at play here.

The first issue is that either OpenSuse or the kernel changed the way the debugfs is handled. For those who have not looked at this before, debugfs is what lives in /sys/kernel/debug, and provides the more modern interface for usbmon access; the old method via /dev/usbmonX is deprecated, and Wireshark will not even show up the ability to capture USB packets without debugfs. Previously, I was able to manually change the ownership of the usbmon debugfs paths to my user, and started Wireshark as user to do the capturing, but as of January 2020, it does not seem to be possible to do that anymore: the debugfs mount is only accessible to root.

Using Wireshark as root is generally considered a really bad idea, because it has a huge attack surface, in particular when doing network captures, where the input would literally be to the discretion of external actors. It’s a tinsy bit safer when capturing USB because even when the device is fairly unknown, the traffic is not as controllable, so I would have flinched, but not terribly, to use Wireshark as root — except that I can’t sudo wireshark and have it paint on X. So the remaining alternative is to use tshark, which is a terminal utility that implements the same basics as Wireshark.

Unfortunately here’s the second problem: the last time I ran a lot of captures was when I was working on the Beurer glucometer (which I still haven’t gotten back to, because Linux 5.5 is still unreleased at the time of writing, and that’s the first version that’s not going to go into a reset loop with the device), and I was doing that work from my laptop, and that’s relevant. While the laptop’s keyboard and touchpad are USB, the ports are connected to a different bus internally. Since usbmon interfaces are set by bus, that made it very handy: I only needed to capture on the “ports” bus, and no matter how much and what I typed, it wouldn’t interfere in my captures at all.

You can probably see where this is going: I’m now using a NUC on my desk, with an external keyboard and the Elecom trackball (because I did manage to hurt my wrist while working on the laptop, but that’s a story for another post). And now all the USB 2.0 ports are connected to the same bus. Capturing the bus means getting all the events for keypresses, mouse movements, and so on.

If you have some experience with tcpdump or tshark, you’d think that this is an easy problem to solve: it’s not uncommon having to capture network packets from an SSH connection, which you want to exclude from the capture itself. And the solution for that is to apply a capture filter, such as port not 22.

Unfortunately, it looks like libpcap (which means Wireshark and tshark) does not support capture filters on usbmon. The reasoning provided is that since the capture filters for network are implemented in BPF, there’s no fallback for usbmon that does not have any BPF capabilities in the kernel. I’m not sure about the decision, but there you go. You could also argue that adding BPF to usbmon would be interesting to avoid copying too much data from the kernel, but that’s not something I have particular interest in exploring right now.

So how do you handle this? The suggested option is to capture everything, then use Wireshark to select a subset of packets and save the capture again. This should allow you to have a limited capture that you can share without risking having shared a keylogger off your system. But it also made me think a bit more.

The pcapng format, which Wireshark stores usbmon captures in, is a fairly complicated one, because it can include a lot of different protocol information, and it has multiple typed blocks to store things like hardware interface descriptions. But for USB captures, there’s not much use in the format: not only the Linux and Windows captures (the latter via usbpcap) are different formats altogether, but also the whole interface definition is, as far as I can tell, completely ignored. Instead, if you need a device descriptor, you need to scan the capture for a corresponding request (which usbmon-tools now does.)

I’m now considering just providing a simpler format to store captured data with usbmon-tools, either a simple 1:1 conversion from pcapng, with each packet just size-prefixed, and a tool to filter down the capture on the command line (because honestly, having to load Wireshark to cut down a capture is a pain), or a more complicated format that can store the descriptors separately, and maybe bundle/unbundle them across captures so that you can combine multiple fragments later. If I was in my bubble, I would be using protocol buffers, but that’s not particularly friendly to integrate in a Python module, as far as I can tell. Particularly if you want to be able to use the tools straight out of the git clone.

I guess that since I’m already using construct, I could instead design my own simplistic format. Or maybe I could just bite the bullet, use base64-encoded bytearrays, and write the whole capture session out in JSON.

As I said above, pcapng supports Windows and Linux captures differently: on Linux, the capture format is effectively the wire format of usbmon, while on Linux, it’s the format used by usbpcap. While I have not (yet, at the time of writing) added support to usbmon-tools to load the usbpcap captures, I don’t see why it shouldn’t work out that way. If I do manage to load usbpcap files, though, I would need a custom format to copy these to.

If anyone has a suggestion I’m open to them. One thing that I may try is to use Protocol Buffers but submit the generated source files to parse and serialize the object.

Comments 4
  1. I’ve had success in the past with “[ssh …] sudo tcpdump [-Z …] -U -w – | wireshark -k -i -” pipelines. Perhaps that’s something that you could use here to avoid sudo’ing wireshark or falling back to tshark.

    USB/IP and plugging the device in question into e.g. a Raspberry Pi could also be helpful for your other use case there. You could still run the proprietary software on a Windows machine, and yet capture the traffic (USB or USB/IP) on your Pi that will have no other USB devices on it. Never tried any of that though, sorry 🙂

  2. Hey! Great work. Thank you for your effort! I find the situation with missing linux/usb/mon.h headers unresolved yet – from kernel point of view. However, I found your latest implementation of cython-linux-usbmon and it worked! …and I’m surprised it worked, because earlier I got struggled by passing /sys/kernel/debug/usb/usbmon/ file paths, which are not seekable and you know… Finaly, using /dev/usbmon worked.
    I think you could include that information in the readme (any kind of usage example). I’m one of the guys that don’t want to use wireshark for capturing pcaps. I need live USB sniffing in binary form in my PyQt application. Across all the sollutions I found – only yours gives me what I want.
    Once again – thank you!

    1. I’m glad it helped! And yeah I should add more documentation of how to use it, as I have left it mostly hanging there for quite a while now.

      The headers problem ended up going down a yak shave and I haven’t had the energy and time to dedicate to go back to it 🙁

  3. “as of January 2020, it does not seem to be possible to do that anymore: the debugfs mount is only accessible to root.”

    Easily fixed. Create the file “/etc/udev/rules.d/90-usbmon.rules” and add the following on one line:
    KERNEL==”usbmon[0-8]*”, SUBSYSTEM==”usbmon”, GROUP=”plugdev”, MODE=”0660″, ACTION==”add”, PROGRAM=”/bin/sh -c ‘chown -R :plugdev /sys/devices/virtual/usbmon’”, PROGRAM=”/bin/sh -c ‘chmod -R g+w /sys/devices/virtual/usbmon’”

    Save, run: “udevadm control –reload”, then “modprobe usbmon”. Both the legacy devices in /dev/ and /sys/devices/virtual/usbmon/* will be owned by the group plugdev. Make sure your user is in that group and you should be good.

    Now if I could only find something like usbmon for firewire….

Leave a Reply to FlameeyesCancel reply

This site uses Akismet to reduce spam. Learn how your comment data is processed.