In the good ol’ days to write a credential sniffer you had to spend alot of time learning C and libpcap this was extremely time consuming and no one really wants to go through that.

So today we have GoLang, the statically typed language from google, specifically the gopacket library from google.

Gopacket is a library that introduces packet filtering and capturing to go. It comes with C Bindings for libpcap, C Bindings for pfring, afpacket C Bindings and tcpassembly.

Before we begin

Before we go any further we need to install a couple of things, as well as golang (use your install method of choice) and libpcap (with development libs)

fedora/RHEL/CentOS etc… $ sudo dnf install libpcap libpcap-devel Ubuntu/Debian/Kali/Parrot $ sudo apt update && sudo apt install libpcap-dev

Now we have our deps we can begin

The anatomy of a packet

In this tutorial we are going to be targeting FTP Credentials sent in plain text over the network. We can have a look at our target traffic using tcpdump.

tcpdump prints out a description of packets into our command line. We can use BPF (Berkely Packet Filter) syntax to target TCP port 21. Once tcpdump is running you can connect to an FTP Server with your client of choice (I used the command line FTP client

$ sudo tcpdump tcp and port 21 dropped privs to tcpdump … 00:17:09.312853 IP .44860 > .ftp: Flags [P.], seq 1:28, ack 321, win 501, options [nop,nop,TS val 4275069681 ecr 3350561499], length 27: FTP: USER 00:17:20.938293 IP .44860 > .ftp: Flags [P.], seq 28:52, ack 374, win 501, options [nop,nop,TS val 4275081307 ecr 3350567300], length 24: FTP: PASS

Looking at the output from TCP Dump we can see a lot of information in the packet. What we are interested in really however is the Client (src), Server (dst) and the Payload (The USER and PASS sections highlighted).

To learn more about the output of Tcpdump check out this Masterclass.

Lets get coding

All the variables we are using will be hard coded for illustration purposes, If you wish you may parametrise the application.

Setting up

But lets begin by declaring the iface, buffer and filter variables.

iface is our interface, My FTP Server is running in docker so i’m sniffing traffic on my docker interface.

The buffer variable is our buffer size of 1600 bytes, This should be enough to capture a TCP Packet of every size

The filter is our BPF Filter, You can change this to match any filter you would use with TCPDump

1
2
3
4
5
  var (
    iface = “docker0”
    buffer = int32(1600)
    filter = “tcp and port 21  )

Now we will need to include some libs, Most are from the go standard library, However we do need google/gopacket and google/gopacket/pcap

1
2
3
4
5
6
7
8
    import (
      "bytes"
      "fmt"
      "log"

      "github.com/google/gopacket"
      "github.com/google/gopacket/pcap"
    )

Detecting our interface

Before we begin to capture traffic we need to detect if our interface is present on the system. To do this we can loop through our devices using pcap.FindAllDevs() to find a match for the name defined in the interface variable, So lets create a deviceExists bool function

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
    func deviceExists(name string) bool {
      devices, err := pcap.FindAllDevs()

      if err != nil {
        log.Panic(err)
      }

      for _, device := range devices {
        if device.Name == name {
         return true
        }
      }
      return false
    }

Now we can call this in an if statement as part of our main() function

1
2
3
4
5
    func main() {
      if !deviceExists(iface) {
        log.Fatal("Unable to open device ", iface)
      }
    }

If you have a non existent interface defined in the iface var when you run the code using go run you will get a Fatal error and the application will exit.

    $ go run main.go
    2020/11/30 20:05:04 Unable to open device docker1
    exit status 1

Capturing Packets

Now we know our interface exists we can proceed with capturing packets. To do this we need to open a live stream of packets using pcap. This is done using pcap.OpenLive we can add this to our main function after our device check.

1
2
3
4
5
6
    handler, err := pcap.OpenLive(iface, buffer, false, pcap.BlockForever)

    if err != nil {
      log.Fatal(err)
    }
    defer handler.Close()

The arguments used to open the interface are :

Now we can set our BPF Filter using the pcap.Handle.SetBPFFilter method

1
2
3
    if err := handler.SetBPFFilter(filter); err != nil {
      log.Fatal(err)
    }

Now we set our packet source using gopacket, This is how we parse our packets in a loop

1
2
3
4
    source := gopacket.NewPacketSource(handler, handler.LinkType())
    for packet := range source.Packets() {
      fmt.Println(packet)
    }

You can now run this code with sudo and attempt to authenticate to your target FTP server

    $ sudo go run main.go

    PACKET: 80 bytes, wire length 80 cap length 80 @ 2020-11-30 20:21:33.007534 +0000 GMT
    - Layer 1 (14 bytes) = Ethernet {Contents=[..14..] Payload=[..66..] SrcMAC=02:42:9a:92:0a:55 DstMAC=02:42:ac:11:00:02 EthernetType=IPv4 Length=0}
    - Layer 2 (20 bytes) = IPv4 {Contents=[..20..] Payload=[..46..] Version=4 IHL=5 TOS=16 Length=66 Id=51609 Flags=DF FragOffset=0 TTL=64 Protocol=TCP Checksum=6375 SrcIP=172.17.0.1 DstIP=172.17.0.2 Options=[] Padding=[]}
    - Layer 3 (32 bytes) = TCP {Contents=[..32..] Payload=[..14..] SrcPort=33748 DstPort=21(ftp) Seq=3778718902 Ack=277870828 DataOffset=8 FIN=false SYN=false RST=false PSH=true ACK=true URG=false ECE=false CWR=false NS=false Window=502 Checksum=22618 Urgent=0 Options=[TCPOption(NOP:), TCPOption(NOP:), TCPOption(Timestamps:2890546481/2237472386 0xac4a3d31855d1e82)] Padding=[]}
    - Layer 4 (14 bytes) = Payload 14 byte(s)

    PACKET: 66 bytes, wire length 66 cap length 66 @ 2020-11-30 20:21:33.007561 +0000 GMT

You may agree that this output is pretty much useless. So lets make it a little more useful.

Harvesting Credentials

From our TCP Dump example at the beginning we talked about how the USER and PASS payloads in each packet can be seen with TCP Dump. We can use our application here to make this easier. If we create a new harvestFTPCreds function.

First thing this function needs to do is confirm the Application Layer is available to the packet using Packet.ApplicationLayer(), Otherwise we will get a segfault at some point

1
2
3
4
5
6
    func harvestFTPCreds(packet gopacket.Packet) {
      app := packet.ApplicationLayer()
      if app != nil {
        // App is present
      }
    }

Now we can extract our Payload and Destination IP address

1
2
3
4
    if app != nil {
      payload := app.Payload()
      dst := packet.NetworkLayer().NetworkFlow().Dst()
    }

The final piece is to check if the payload contains our USER or PASS using bytes.Contains()

1
2
3
4
5
6
7
8
    if app != nil {
    ...
      if bytes.Contains(payload, []byte("USER")) {
        fmt.Print(dst, "  ->  ", string(payload))
      } else if bytes.Contains(payload, []byte("PASS")) {
        fmt.Print(dst, " -> ", string(payload))
      }
    }

Now our harvest function is complete we can go back to our main function and replace the fmt.Print with our new harvest function.

1
2
3
    for packet := range source.Packets() {
      harvestFTPCreds(packet)
    }

If we now run our code we should have nicely formatted USER/PASS combinations for our FTP server sniffed from the network interface.

1
2
3
    $ sudo go run main.go
    172.17.0.2  ->  USER ftpuser
    172.17.0.2 -> PASS ftppassword

Conclusion

I hope this has been informative and shows how easy it is to use golang as a simple and effective method of using pcap to capture, filter and sniff live network traffic.

I also hope this has informed a little bit about the importance of encrypting network traffic :)

Thanks for reading here is the complete source

https://gist.github.com/affix/51daf036faf68593fb6d87af9eba1f0f