Processing Tshark Streams With PowerShell

Wireshark is a packet capture and analysis tool, however, not as well known is the command line version that is bundled into the install - tshark. One huge advantage of tshark is its ability to write packet data directly to disk, which avoids a common issue with leaving Wireshark running for a long time, ever increasing memory usage. But that’s not what this post is about.

While tshark can write packets directly to a file, it can also stream them to standard output. We can take this stream and hand it off to PowerShell for processing. I’ll split this into two sections, the tshark commands and then the associated PowerShell code.

Tshark Stream Output

If we run tshark.exe without any options well get output resembling the following:

PS C:\> & 'C:\Program Files\Wireshark\tshark.exe'
Capturing on 'Ethernet'
    1   0.000000 10.250.1.100 → 10.250.10.20 TCP 139 44711 → 443 [PSH, ACK] Seq=1 Ack=1 Win=1022 Len=85
    2   0.000031 10.250.10.20 → 10.250.1.100 TCP 54 443 → 44711 [ACK] Seq=1 Ack=86 Win=63814 Len=0
    3   0.019745 10.250.1.100 → 10.250.10.20 TCP 155 44711 → 443 [PSH, ACK] Seq=86 Ack=1 Win=1022 Len=101
    4   0.044931 10.250.1.100 → 10.250.10.20 TCP 155 44711 → 443 [PSH, ACK] Seq=187 Ack=1 Win=1022 Len=101
    5   0.044963 10.250.10.20 → 10.250.1.100 TCP 54 443 → 44711 [ACK] Seq=1 Ack=288 Win=63612 Len=0
    6   0.086335 62.134.171.29 → 10.250.10.20 UDP 60 57708 → 61054 Len=16
    7   0.093126 10.110.10.59 → 10.250.10.20 TCP 105 443 → 51155 [PSH, ACK] Seq=1 Ack=1 Win=63416 Len=51
    8   0.103271 10.250.10.20 → 10.110.10.59 TCP 54 51155 → 443 [ACK] Seq=1 Ack=52 Win=4124 Len=0
    9   0.141331 10.250.10.20 → 62.134.171.29 UDP 66 61054 → 57708 Len=24

While we could probably write a parser for this in PowerShell, it’s not the best option. Let’s have a look at some command line options. The full list of options can be found here.

The first few we’re going to be interested in are as follows

  • -n: Disable network object name resolution (such as hostname, TCP and UDP port names)
  • -l: Flush the standard output after the information for each packet is printed
  • -T: Set the format of the output when viewing decoded packet data.

The -T option needs to be followed by an output format. All formats are documented in the linked docs, but we’re going to be using ek which is for newline delimited JSON. Let’s see what that looks like:

PS C:\> & 'C:\Program Files\Wireshark\tshark.exe' -n -l -T ek
Capturing on 'Ethernet'
{"index" : {"_index": "packets-2020-02-14", "_type": "pcap_file", "_score": null}}
{"timestamp" : "1581679764286", "layers" : {"frame": {"frame_frame_interface_id": "0","frame_interface_id_frame_interface_name": "\\Device\\NPF_{6BB6F2BF-FA98-4F1B-8071-A433570450F3}","frame_frame_encap_type": "1","frame_frame_time": "Feb 14, 2020 21:59:24.286039000 Cen. Australia Daylight Time","frame_frame_offset_shift": "0.000000000","frame_frame_time_epoch": "1581679764.286039000","frame_frame_time_delta": "0.000000000","frame_frame_time_delta_displayed": "0.000000000","frame_frame_time_relative": "0.000000000","frame_frame_number": "1","frame_frame_len": "74","frame_frame_cap_len": "74","frame_frame_marked": "0","frame_frame_ignored": "0","frame_frame_protocols": "eth:ethertype:ip:icmp:data"},"eth": {"eth_eth_dst": "00:00:0c:9f:f0:00","eth_dst_eth_dst_resolved": "00:00:0c:9f:f0:00","eth_dst_eth_addr": "00:00:0c:9f:f0:00","eth_dst_eth_addr_resolved": "00:00:0c:9f:f0:00","eth_dst_eth_lg": "0","eth_dst_eth_ig": "0","eth_eth_src": "00:50:56:be:26:45","eth_src_eth_src_resolved": "00:50:56:be:26:45","eth_src_eth_addr": "00:50:56:be:26:45","eth_src_eth_addr_resolved": "00:50:56:be:26:45","eth_src_eth_lg": "0","eth_src_eth_ig": "0","eth_eth_type": "0x00000800"},"ip": {"ip_ip_version": "4","ip_ip_hdr_len": "20","ip_ip_dsfield": "0x00000000","ip_dsfield_ip_dsfield_dscp": "0","ip_dsfield_ip_dsfield_ecn": "0","ip_ip_len": "60","ip_ip_id": "0x00002e7f","ip_ip_flags": "0x00000000","ip_flags_ip_flags_rb": "0","ip_flags_ip_flags_df": "0","ip_flags_ip_flags_mf": "0","ip_ip_frag_offset": "0","ip_ip_ttl": "128","ip_ip_proto": "1","ip_ip_checksum": "0x00009a6a","ip_ip_checksum_status": "2","ip_ip_src": "10.250.10.20","ip_ip_addr": "10.250.10.20","ip_ip_src_host": "10.250.10.20","ip_ip_host": "10.250.10.20","ip_ip_dst": "10.250.10.25","ip_ip_addr": "10.250.10.25","ip_ip_dst_host": "10.250.10.25","ip_ip_host": "10.250.10.25","ip_text": "Source GeoIP: Unknown","ip_text": "Destination GeoIP: Unknown"},"icmp": {"icmp_icmp_type": "8","icmp_icmp_code": "0","icmp_icmp_checksum": "0x00006c53","icmp_icmp_checksum_status": "1","icmp_icmp_ident": "13","icmp_icmp_ident": "3328","icmp_icmp_seq": "57595","icmp_icmp_seq_le": "64480","icmp_data": {"data_data_data": "61:62:63:64:65:66:67:68:69:6a:6b:6c:6d:6e:6f:70:71:72:73:74:75:76:77:61:62:63:64:65:66:67:68:69","data_data_len": "32"}}}}

Unlike the previous output, which showed several packets, the output above is for a single packet. That’s a lot of a data, and we definitely don’t want to throw all of that at PowerShell because it won’t keep up, and the CPU cost will be too high. Let’s see if we can limit the output to to only show the properties we need. In this example we’ll limit it to the protocol, source and destination IPs, and the respective ports.

We can achieve this with the -e option which allows us to specify fields we want. We need to have an -e option for every field we want to display. Our tshark command will now become:

& 'C:\Program Files\Wireshark\tshark.exe' -n -l -T ek -e _ws.col.Protocol -e ip.proto -e ip.src -e ip.dst -e tcp.srcport -e tcp.dstport -e udp.srcport -e udp.dstport

Let’s see what output this gives us.

PS>& 'C:\Program Files\Wireshark\tshark.exe' -n -l -T ek -e _ws.col.Protocol -e ip.proto -e ip.src -e ip.dst -e tcp.srcport -e tcp.dstport -e udp.srcport -e udp.dstport
Capturing on 'Ethernet'
{"index" : {"_index": "packets-2020-02-14", "_type": "pcap_file", "_score": null}}
{"timestamp" : "1581680589477", "layers" : {"ip_proto": ["1"],"ip_src": ["10.250.10.20"],"ip_dst": ["10.250.10.25"]}}

That is a lot cleaner!

We’re also going to want to apply a filter to capture only the traffic we’re interested in. Again, we don’t want to overload PowerShell with every packet as we’re most likely looking for something specific. If however, you do want every packet, you’ll be better off saving the output to a PCAP file and processing that after the capture is saved.

We define capture filters with the -f option, these are the same filters you may already be familiar with in Wireshark. Capture filter docs with examples can be found here.

In this example let’s filter out packets sourced from local (RFC1918) addresses (meaning we’re only looking at conversations between us and Internet hosts), and let’s limit TCP traffic to SYN packets. We’ll also accept UDP, but ignore other protocols.

A little quirk I found when tshark output is piped to something else it writes the packet number to standard error, so we’ll need to append 2>$null to our command to prevent this otherwise we end up with numbers printed to our console. This also gets rid of the Capturing on 'Ethernet' message present in the previous output examples.

Our command now looks like this:

& 'C:\Program Files\Wireshark\tshark.exe' -n -l -T ek -e _ws.col.Protocol -e ip.proto -e ip.src -e ip.dst -e tcp.srcport -e tcp.dstport -e udp.srcport -e udp.dstport -f "(src net not (10.0.0.0/8 or 172.16.0.0/12 or 192.168.0.0/16)) and (tcp[0xd]&18=2 or udp)" 2>$null

That’s all we need in the way of tshark options. Obviously you’ll need to modify the various filters to suit your own needs. Finally, we can wrap this in a small function.

function RunTshark() {
    & 'C:\Program Files\Wireshark\tshark.exe' `
    -n `
    -l `
    -T ek `
    -e _ws.col.Protocol `
    -e ip.proto `
    -e ip.src `
    -e ip.dst `
    -e tcp.srcport `
    -e tcp.dstport `
    -e udp.srcport `
    -e udp.dstport `
    -f "(src net not (10.0.0.0/8 or 172.16.0.0/12 or 192.168.0.0/16)) and (tcp[0xd]&18=2 or udp)" `
    2>$null
}

This can be written better to parameterize all the options, but let’s keep things on point and simple for the example.

PowerShell Tshark Stream Processing Function

Looking at the JSON output produced by tshark we can build a function to convert packets into objects.

function ProcessPacket($InPacketJson) {

    $InPacket = (ConvertFrom-Json $InPacketJson).layers

    if ($InPacket.ip_src) {
        if ($InPacket.tcp_srcport) {$SrcPort = $InPacket.tcp_srcport[0]} elseif ($InPacket.udp_srcport) {$SrcPort = $InPacket.udp_srcport[0]} else {$SrcPort = $null}
        if ($InPacket.tcp_dstport) {$DstPort = $InPacket.tcp_dstport[0]} elseif ($InPacket.udp_dstport) {$DstPort = $InPacket.udp_dstport[0]} else {$DstPort = $null}

        Switch ($InPacket.ip_proto) {
            1 { $Protocol = "ICMP"; break }
            6 { $Protocol = "TCP"; break }
            17 { $Protocol = "UDP"; break }
            default { $Protocol = $InPacket.ip_proto; break }
        }

        $Packet = New-Object -TypeName PSObject -Property @{
            SrcIP   = $InPacket.ip_src[0]
            DstIP   = $InPacket.ip_dst[0]
            Protocol = $Protocol
            SrcPort = $SrcPort
            DstPort = $DstPort
        }

        Write-Output $Packet | Select Protocol, SrcIP, SrcPort, DstIP, DstPort
    }
}

I won’t break down the function too much as it’s mostly self explanatory, but there are a few things worth noting. We first convert the layers property to a PowerShell object. We can see from the tshark json output this is where all relevant data is. If this object is empty we know we’re not processing packet data and the function essentially ends there as the if statement that contains the rest of our logic evaluates the condition to false.

Tshark puts TCP and UDP port numbers in different fields prefixed by the protocol (tcp_ and udp_). I wanted to combine these 4 properties into 2 protocol independent source and destination port properties. This is accomplished by the two long if/elseif/else lines. Some traffic such as ICMP won’t have port numbers, so we set those ports to $null.

Putting it together

All we need to do now is pipe tshark into our function.

PS C:\> RunTshark | % {ProcessPacket $_}

Protocol : TCP
SrcIP    : 200.68.125.177
SrcPort  : 63099
DstIP    : 10.250.10.20
DstPort  : 443

Protocol : UDP
SrcIP    : 62.134.171.29
SrcPort  : 58911
DstIP    : 10.250.10.20
DstPort  : 63951

Protocol : UDP
SrcIP    : 62.134.171.29
SrcPort  : 58911
DstIP    : 10.250.10.20
DstPort  : 63951

There is a lot more that can be done with this, a good first step would be adding some options for what interface tshark listens on, but I’ll leave it here for now.


Share this post on , , , or