2

TL;DR: How to pipe properly over UART the output of a remote tcpdump to a local wireshark ?

I try to capture packets that flow through an embedded device to which I don't have the ability to install anything. Fortunately, there is a getty opened on the serial interface, and tcpdump installed. Sadly, no SSH, no dumpcap, no tshark.

Direct pipe

I first tried to configure the tty and pass the data to wireshark through pipes.

stty -F /dev/ttyUSB0 raw
stty -F /dev/ttyUSB0 -echo -echoe -echok
cat /dev/ttyUSB0 | wireshark -k -i -
# On another terminal:
echo "tcpdump -U -s0 -i eth0 -w - 2>/dev/null" > /dev/ttyUSB0

Wireshark complains that the input is not valid libpcap format, certainly because the command gets echoed back and I didn't manage to get rid of that.

Using raw PySerial

So I decided to create a python script to control how the piping would work:

import serial
import sys
import subprocess
import fcntl

def main(args):
    with serial.Serial('/dev/ttyUSB0', 115200, timeout=0) as ser:
        length = ser.write(b"tcpdump -U -s0 -i eth0 -w - 2> /dev/null\n") + 1
        # Discard the echoed command line
        while length > 0:
            discard = ser.read(length)
            length -= len(discard)
        # Spawn wireshark
        wireshark = subprocess.Popen(
            ["wireshark", "-k", "-i", "-"], stdin=subprocess.PIPE
        )
        # Pipe data from serial to wireshark's input
        while True:
            data = ser.read(256)
            wireshark.stdin.write(data)
            try:
                wireshark.stdin.flush()
            except BrokenPipeError as e:
                break
            if len(data) > 0: print(data)
        # Send "Ctrl+C" to tcpdump
        ser.write(b"\x03")
        wireshark.wait()
    return 0

if __name__ == '__main__':
    import sys
    sys.exit(main(sys.argv))

Leaving aside some problems with how the script should end properly, this didn't work as well as I imagined. Wireshark is happy for some time, but quite soon the input gets corrupt and the recording stops. I think this is because the tty on the host still converts some special characters, probably the line feed or carriage return.

Getting stupid: hexdump over PySerial

So I know this is lame, but as I didn't have other ideas, this is what I came up with:

import serial
import sys
import subprocess
import binascii

def main(args):
    with serial.Serial('/dev/ttyUSB0', 115200, timeout=5) as ser:
        # Spawn tcpdump on the host and convert the raw output to stupid hex format
        # We need hexdump -C because that's the only format that doesn't mess up with the endianess
        length = ser.write(b"tcpdump -U -s256 -i eth0 -w - 2> /dev/null | hexdump -C\n")
        # Discard command line that is echoed
        discard = ser.readline()
        # Spawn wireshark
        wireshark = subprocess.Popen(
            ["wireshark", "-k", "-i", "-"], stdin=subprocess.PIPE
        )
        while True:
            # Process each line separately
            data = ser.readline().decode('ascii')
            elements = data.split()
            # Remove the address and ascii convertion of hexdump and spaces
            hexa = "".join(elements[1:17])
            # Convert back hex to binary
            real_data = binascii.unhexlify(hexa)
            # Feed to the shark
            wireshark.stdin.write(real_data)
            try:
                wireshark.stdin.flush()
            except BrokenPipeError as e:
                break
        # Stop tcpdump
        ser.write(b"\x03")
        wireshark.wait()
    return 0

if __name__ == '__main__':
    import sys
    sys.exit(main(sys.argv))

Alas, though it works for a bit longer than the previous version, when the frames are a bit too big, wireshark pops a problem saying that the frame is too big, with a length that is indeed ridiculous (like -1562980309832), and again the recording stops.

Please, help! :)

You can note that I tried to play with the -s option of tcpdump, but it didn't work, even with low amounts.

I also tried piping from picocom, to no avail.

So, if you have any idea, any UART tunnelling software that would work, any remark on my (incompetent) use of stty, or any improvement to my python scripts, I would be very happy !

Wireshark is 2.2.5, tcpdump is 4.5.0 with libpcap 1.5.0.

  • 1
    Is there ppp software on the remote device, so you could setup a network connection and then transfer over that? – thrig Apr 17 '17 at 15:50
  • Do you have netcat or nc on the embedded device? busybox usually provides nc. – dirkt Apr 17 '17 at 16:50
  • @thrig, thanks for your suggestion. It was a good idea, as the device is supposed to be able to connect to the 3G/4G network too, but unfortunately, ppp is not available either. – Cilyan Apr 18 '17 at 9:27
  • @dirkt, thanks for the idea. I had checked netcat, but not nc, unfortunately that either is not available... I guess they have stripped down busybox as much as possible...? – Cilyan Apr 18 '17 at 9:28
2

Finally, I got it really working. This is not the perfect setup, but at least it works so maybe it can help someone in the future.

I used a Python script on top of PySerial to start tcpdump over the UART and use hexdump so that the binary data can traverse the link without being modified by the tty transcription rules. Then the Python script converts back the data and pipes it to wireshark. The below script is the result, compared to the one from the question, I added the -v option to hexdump so that it doesn't try to compress lines that are the same.

import serial
import sys
import subprocess
import binascii

def main(args):
    with serial.Serial('/dev/ttyUSB0', 115200, timeout=5) as ser:
        # Spawn tcpdump on the host and convert the raw output to stupid hex format
        # We need hexdump -C because that's the only format that doesn't mess up with the endianess
        length = ser.write(b"tcpdump -U -s256 -i eth0 -w - 2> /dev/null | hexdump -Cv\n")
        # Discard command line that is echoed
        discard = ser.readline()
        # Spawn wireshark
        wireshark = subprocess.Popen(
            ["wireshark", "-k", "-i", "-"], stdin=subprocess.PIPE
        )
        while True:
            # Process each line separately
            data = ser.readline().decode('ascii')
            elements = data.split()
            # Remove the address and ascii convertion of hexdump and spaces
            hexa = "".join(elements[1:17])
            # Convert back hex to binary
            real_data = binascii.unhexlify(hexa)
            # Feed to the shark
            wireshark.stdin.write(real_data)
            try:
                wireshark.stdin.flush()
            except BrokenPipeError as e:
                break
        # Stop tcpdump
        ser.write(b"\x03")
        wireshark.wait()
    return 0

if __name__ == '__main__':
    import sys
    sys.exit(main(sys.argv))
  • Note that I'm still interested in your suggestions, especially if they are less convoluted! – Cilyan Apr 18 '17 at 18:47
  • Can you explain whats going on with tcpdump being directed to /dev/null and piped to hexdump? I think you're using tcpdump to "packetize" the mstp data coming from your port but I'm not sure how. – Michael Aug 3 '17 at 19:36
  • No sure I understand your question, but what's piped to /dev/null is the stderr onto which debug information is sent that would not be understood by wireshark if found in the middle of the data stream. Hexdump is used to encode the binary output (stdout) of tcpdump in order to work around the escaping of special characters done by the UART. – Cilyan Aug 3 '17 at 20:28
  • So you're capturing from /dev/ttyUSB0, sending to TCPDump (discarding the stderr) and piping that output to HexDump to convert to hex essentially? I think i'm tripped up on why you're sending the input from your USB device to tcpdump – Michael Aug 23 '17 at 21:02
  • @Michael Err, no. I am logged to a "remote" device over serial tty. I use tcpdump on the "remote" device to capture from eth0 and send the result of the capture live on the serial link. On the other side of the serial link (that is, my PC), I use wireshark to grab that data and display it. Over the serial link (e.g. between tcpdump output and wireshark input) I have to convert to ascii because pure binary data gets altered by the way the tty is configured (e.g carriage returns, nulls... and I can't change that). – Cilyan Aug 25 '17 at 9:31

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.