From 469a05aa61959c11b2fd3fc16aa139816ed8cfb3 Mon Sep 17 00:00:00 2001 From: maride Date: Wed, 27 Nov 2019 20:24:12 +0100 Subject: [PATCH] Add support for ARP packets --- src/ethernet/arp/arp.go | 126 ++++++++++++++++++++++++++++++++++ src/ethernet/arp/arpDevice.go | 6 ++ src/ethernet/arp/arpStats.go | 9 +++ src/ethernet/arp/common.go | 30 ++++++++ src/ethernet/ethernet.go | 18 ++--- 5 files changed, 181 insertions(+), 8 deletions(-) create mode 100644 src/ethernet/arp/arp.go create mode 100644 src/ethernet/arp/arpDevice.go create mode 100644 src/ethernet/arp/arpStats.go create mode 100644 src/ethernet/arp/common.go diff --git a/src/ethernet/arp/arp.go b/src/ethernet/arp/arp.go new file mode 100644 index 0000000..c088084 --- /dev/null +++ b/src/ethernet/arp/arp.go @@ -0,0 +1,126 @@ +package arp + +import ( + "fmt" + "github.com/fatih/color" + "github.com/google/gopacket" + "github.com/google/gopacket/layers" + "log" + "net" +) + +var ( + arpStatsList []arpStats + devices []arpDevice +) + +// Called on every ARP packet +func ProcessARPPacket(packet gopacket.Packet) error { + var arppacket layers.ARP + + // Decode raw packet into ARP + decodeErr := arppacket.DecodeFromBytes(packet.Layer(layers.LayerTypeARP).LayerContents(), gopacket.NilDecodeFeedback) + if decodeErr != nil { + // Encountered an error during decoding, most likely a broken packet + return decodeErr + } + + // Convert MAC address byte array to string + sourceAddr := net.HardwareAddr(arppacket.SourceHwAddress).String() + participant := getStatOrCreate(sourceAddr) + + // Raise stats + if arppacket.Operation == layers.ARPRequest { + // Request packet + participant.asked++ + appendIfUnique(net.IP(arppacket.DstProtAddress).String(), participant.askedList) + } else { + // Response packet + participant.answered++ + appendIfUnique(net.IP(arppacket.SourceProtAddress).String(), participant.answeredList) + + // Add device entry + addDeviceEntry(sourceAddr, net.IP(arppacket.SourceProtAddress).String()) + } + + return nil +} + +// Print a summary after all packets are processed +func PrintARPSummary() { + headline := color.New(color.FgRed, color.Bold) + headline.Println("ARP traffic summary") + printTrafficStats() + headline.Println("ARP LAN overview") + printLANOverview() +} + +// Constructs an answer regarding the ARP traffic +func printTrafficStats() { + var tmparr []string + + // Iterate over all participants + for _, p := range arpStatsList { + tmparr = append(tmparr, fmt.Sprintf("%s asked for %d addresses and answered %d requests", p.macaddr, p.asked, p.answered)) + } + + // And print it as a tree + printTree(tmparr) +} + +// Prints an overview over all connected devices in the LAN +func printLANOverview() { + var tmparr []string + + // iterate over all devices + for _, d := range devices { + tmparr = append(tmparr, fmt.Sprintf("%s got address %s", d.macaddr, d.ipaddr)) + } + + // And print it as a tree + printTree(tmparr) +} + +// Returns the arpStats object for the given MAC address, or creates a new one +func getStatOrCreate(macaddr string) *arpStats { + // Try to find the given macaddr + for i := 0; i < len(arpStatsList); i++ { + if arpStatsList[i].macaddr == macaddr { + // Found, return it + return &arpStatsList[i] + } + } + + // None found yet, we need to create a new one + arpStatsList = append(arpStatsList, arpStats{ + macaddr: macaddr, + }) + + // And return it + return &arpStatsList[len(arpStatsList)-1] +} + +// Adds a new entry to the devices array, checking if there may be a collision (=ARP Spoofing) +func addDeviceEntry(macaddr string, ipaddr string) { + for i := 0; i < len(devices); i++ { + // check if we found a collision (possible ARP spoofing) + if (devices[i].macaddr == macaddr) != (devices[i].ipaddr == ipaddr) { + // this operation is practically XOR (which golang doesn't provide e.g. with ^) + log.Printf("Found possible ARP spoofing! Old: (MAC=%s, IP=%s), New: (MAC=%s, IP=%s). Overriding...", devices[i].macaddr, devices[i].ipaddr, macaddr, ipaddr) + devices[i].macaddr = macaddr + devices[i].ipaddr = ipaddr + return + } + + if devices[i].macaddr == macaddr && devices[i].ipaddr == ipaddr { + // Found collision, but no ARP spoofing (both values are identical) + return + } + } + + // No device found, add a new entry + devices = append(devices, arpDevice{ + macaddr: macaddr, + ipaddr: ipaddr, + }) +} diff --git a/src/ethernet/arp/arpDevice.go b/src/ethernet/arp/arpDevice.go new file mode 100644 index 0000000..dd6b89f --- /dev/null +++ b/src/ethernet/arp/arpDevice.go @@ -0,0 +1,6 @@ +package arp + +type arpDevice struct { + macaddr string + ipaddr string +} diff --git a/src/ethernet/arp/arpStats.go b/src/ethernet/arp/arpStats.go new file mode 100644 index 0000000..d10666d --- /dev/null +++ b/src/ethernet/arp/arpStats.go @@ -0,0 +1,9 @@ +package arp + +type arpStats struct { + macaddr string + asked int + answered int + askedList []string + answeredList []string +} diff --git a/src/ethernet/arp/common.go b/src/ethernet/arp/common.go new file mode 100644 index 0000000..12962d6 --- /dev/null +++ b/src/ethernet/arp/common.go @@ -0,0 +1,30 @@ +package arp + +import "fmt" + +// Appends the appendee to the array if it does not contain appendee yet +func appendIfUnique(appendee string, array []string) []string { + // Iterate over all elements and check values + for _, elem := range array { + if elem == appendee { + // ... found. Stop here + return array + } + } + + // None found, append + return append(array, appendee) +} + +// Prints each element, along with a small ASCII tree +func printTree(strarr []string) { + // iterate over each element + for iter, elem := range strarr { + // check if we got the last element + if iter < len(strarr) - 1 { + fmt.Printf("|- %s\n", elem) + } else { + fmt.Printf("'- %s\n\n", elem) + } + } +} diff --git a/src/ethernet/ethernet.go b/src/ethernet/ethernet.go index 8bd32d6..b7a1d42 100644 --- a/src/ethernet/ethernet.go +++ b/src/ethernet/ethernet.go @@ -4,6 +4,7 @@ import ( "github.com/google/gopacket" "github.com/google/gopacket/layers" "log" + "./arp" "./dns" ) @@ -20,14 +21,14 @@ func Analyze(source *gopacket.PacketSource) error { continue } - // Check if we can do some Application Layer statistics with this packet - if packet.ApplicationLayer() != nil { - // We can, switch over the type - switch packet.ApplicationLayer().LayerType() { - case layers.LayerTypeDNS: - // Handle DNS packet - dns.ProcessDNSPacket(packet) - } + if packet.Layer(layers.LayerTypeDNS) != nil { + // Handle DNS packet + dns.ProcessDNSPacket(packet) + } + + if packet.Layer(layers.LayerTypeARP) != nil { + // Handle ARP packet + arp.ProcessARPPacket(packet) } } @@ -39,5 +40,6 @@ func Analyze(source *gopacket.PacketSource) error { // Prints all the summaries. func printSummary() { + arp.PrintARPSummary() dns.PrintDNSSummary() }