HackPy Part 3. – Wireshark

Reinventing the low-level network tools would never be complete without famous Wireshark and I’m going to fill this gap today. How to perform network analysis with scapy?

Before we begin, I’d like to remind you that you can sign up to the newsletter to get info about latest post and support me on Patreon.

The objective

Let’s say we want to be able to eavesdrop some network communication, maybe to a blog which is not secured by HTTPS. Moreover, we want to be able to perform some analysis of the packets on the fly, so actually using wireshark may not be the perfect choice. At the same time we don’t want to collect rubbish from other communication that we have, so the ability to filter out packets is a must have.

Sniffer

We’re going to need the sniff function from scapy’s library and two custom functions. The usage is very simple:

class Sniffer:
    # ...

def run(self):
    sniff(
        filter='tcp',
        lfilter=self.my_filter,
        iface=self.interface,
        prn=self.display_filtered_packet
    )

We’re telling it to apply tcp filter (more can be found in the docs), then also apply our locally defined my_filter method. The last step is to process packets using display_filtered_packet method. The filters argument AFAIK takes exactly the same syntax as the wireshark does and it allows for quite advanced filters.

The my_filter only checks if packet is sent from the desired host and port. It returns a boolean value so that indicating whether our code should pass or drop from further analysis.

def my_filter(self, packet: Packet) -> bool:
    return (
        packet.getlayer(IP).src == self.host and
        packet.getlayer(TCP).sport == 80
    )

Simple as it seems, this allows to do pattern matching inside packets. For example if I wanted to view only packets that contain information about redirects I could modify the above code and inspect payload with:

def my_filter(self, packet: Packet) -> bool:
    return (
        packet.getlayer(IP).src == self.host and
        packet.getlayer(TCP).sport == 80 and
        b'301' in bytes(packet.payload)
    )

Which would result in displaying:

$ sudo python3 sniffer.py 46.242.253.15 en0
b'E\x02\x01(\x9f\xc2@\x00:\x06\xb1\xfb.\xf2\xfd\x0f\xc0\xa8\x01f\x00P\xc3\x1f\xd0\xfc\x81\xefF\x05\x17\xe1\x80\x18\x00\xeb_\xb2\x00\x00\x01\x01\x08\n\xf4`\x1e\x861\xf8\t\xffHTTP/1.1 301 Moved Permanently\r\nDate: Tue, 17 Mar 2020 06:42:37 GMT\r\nContent-Type: text/html; charset=UTF-8\r\nTransfer-Encoding: chunked\r\nConnection: keep-alive\r\nServer: Apache\r\nX-Redirect-By: WordPress\r\nLocation: https://blacksheephacks.pl/\r\n\r\n'

Talking of which brings us to the last point.

Analyze network with bare eye

The above code was displayed with this simple function we passed as prn argument:

def display_filtered_packet(self, packet: Packet):
    return packet.payload

It is, however, barely readable. In order to improve it we can modify it the following way:

def display_filtered_packet(self, packet: Packet):
    return ''.join(
        chr(ss) 
        for ss in filter(
            lambda s: chr(s) in string.printable, bytes(packet.getlayer(TCP).payload)
        )
    )

And finally it’ll display:

$ sudo python3 sniffer.py 46.242.253.15 en0
HTTP/1.1 301 Moved Permanently
Date: Tue, 17 Mar 2020 07:03:08 GMT
Content-Type: text/html; charset=UTF-8
Transfer-Encoding: chunked
Connection: keep-alive
Server: Apache
X-Redirect-By: WordPress
Location: https://blacksheephacks.pl/

0

That’s it folks.

As always I encourage you to subscribe to the newsletter, support me on Patreon and make use of the following links:

Check out other posts: