diff --git a/_example/example.js b/_example/example.js index 70d73bdc..cd9e9ebf 100644 --- a/_example/example.js +++ b/_example/example.js @@ -36,4 +36,7 @@ onEvent('wifi.client.handshake', onHandshake); onEvent('wifi.ap.new', onNewAP); // register for new nodes in the graph -onEvent('graph.node.new', onNewNode); \ No newline at end of file +onEvent('graph.node.new', onNewNode); + +// register for gateway changes +onEvent('gateway.change', onGatewayChange) \ No newline at end of file diff --git a/_example/functions.js b/_example/functions.js index 99a9a7e1..e48ed21f 100644 --- a/_example/functions.js +++ b/_example/functions.js @@ -90,6 +90,17 @@ function onNewNode(event) { } } +function onGatewayChange(event) { + var change = event.data; + + var message = '🚨 Detected ' + change.type + ' gateway change, possible MITM attack:\n\n' + + 'Prev: ' + change.prev.ip + ' (' + change.prev.mac + ")\n" + + 'New: ' + change.new.ip + ' (' + change.new.mac + ")"; + + // send to telegram bot + sendMessage(message); +} + function onTick(event) { run('wifi.probe ' + fakeBSSID + ' ' + fakeESSID); } \ No newline at end of file diff --git a/modules/events_stream/events_view.go b/modules/events_stream/events_view.go index 4cc14160..ed0a5ec1 100644 --- a/modules/events_stream/events_view.go +++ b/modules/events_stream/events_view.go @@ -129,6 +129,8 @@ func (mod *EventsStream) Render(output io.Writer, e session.Event) { mod.viewUpdateEvent(output, e) } else if strings.HasPrefix(e.Tag, "graph.") { mod.viewGraphEvent(output, e) + } else if e.Tag == "gateway.change" { + mod.viewGatewayEvent(output, e) } else if e.Tag != "tick" { fmt.Fprintf(output, "[%s] [%s] %v\n", e.Time.Format(mod.timeFormat), tui.Green(e.Tag), e) } diff --git a/modules/events_stream/events_view_gateway.go b/modules/events_stream/events_view_gateway.go new file mode 100644 index 00000000..88066ef6 --- /dev/null +++ b/modules/events_stream/events_view_gateway.go @@ -0,0 +1,23 @@ +package events_stream + +import ( + "fmt" + "io" + + "github.com/bettercap/bettercap/session" + + "github.com/evilsocket/islazy/tui" +) + +func (mod *EventsStream) viewGatewayEvent(output io.Writer, e session.Event) { + change := e.Data.(session.GatewayChange) + + fmt.Fprintf(output, "[%s] [%s] %s gateway changed: '%s' (%s) -> '%s' (%s)\n", + e.Time.Format(mod.timeFormat), + tui.Red(e.Tag), + string(change.Type), + change.Prev.IP, + change.Prev.MAC, + change.New.IP, + change.New.MAC) +} \ No newline at end of file diff --git a/network/arp_parser_linux.go b/network/arp_parser_linux.go index 7d4d0b23..90a38232 100644 --- a/network/arp_parser_linux.go +++ b/network/arp_parser_linux.go @@ -2,7 +2,7 @@ package network import "regexp" -var ArpTableParser = regexp.MustCompile(`^([a-f\d\.:]+)\s+dev\s+(\w+)\s+\w+\s+([a-f0-9:]{17})\s+\w+$`) +var ArpTableParser = regexp.MustCompile(`^([a-f\d\.:]+)\s+dev\s+(\w+)\s+\w+\s+([a-f0-9:]{17})\s+.+$`) var ArpTableTokens = 4 var ArpTableTokenIndex = []int{1, 3, 2} var ArpCmd = "ip" diff --git a/routing/route.go b/routing/route.go index e0f6bd39..59871742 100644 --- a/routing/route.go +++ b/routing/route.go @@ -1,10 +1,10 @@ package routing -type RouteType int +type RouteType string const ( - IPv4 RouteType = 0 - IPv6 RouteType = 1 + IPv4 RouteType = "IPv4" + IPv6 RouteType = "IPv6" ) type Route struct { diff --git a/session/session.go b/session/session.go index 39e3cef1..fc921d1b 100644 --- a/session/session.go +++ b/session/session.go @@ -255,8 +255,12 @@ func (s *Session) Start() error { s.Events.Log(level, "%s", err.Error()) } + // we are the gateway if s.Gateway == nil || s.Gateway.IpAddress == s.Interface.IpAddress { s.Gateway = s.Interface + } else { + // start monitoring for gateway changes + go s.routeMon() } s.Firewall = firewall.Make(s.Interface) diff --git a/session/session_routing.go b/session/session_routing.go new file mode 100644 index 00000000..46fb6682 --- /dev/null +++ b/session/session_routing.go @@ -0,0 +1,96 @@ +package session + +import ( + "github.com/bettercap/bettercap/network" + "github.com/bettercap/bettercap/routing" + "github.com/evilsocket/islazy/log" + "time" +) + +type gateway struct { + IP string `json:"ip"` + MAC string `json:"mac"` +} + +type GatewayChange struct { + Type string `json:"type"` + Prev gateway `json:"prev"` + New gateway `json:"new"` +} + +func (s *Session) routeMon() { + var err error + var gw4 *network.Endpoint + var gwIP6, gwMAC6 string + + s.Events.Log(log.INFO, "gateway monitor started ...") + + gw4 = s.Gateway + + gwIP6, err = routing.Gateway(routing.IPv6, s.Interface.Name()) + if err != nil { + s.Events.Log(log.ERROR, "error getting ipv6 gateway: %v", err) + } else if gwIP6 != "" { + gwMAC6, err = network.ArpLookup(s.Interface.Name(), gwIP6, true) + if err != nil { + s.Events.Log(log.DEBUG, "error getting %s ipv6 gateway mac: %v", gwIP6, err) + } + } + + for { + s.Events.Log(log.DEBUG, "[gw] ipv4=%s(%s) ipv6=%s(%s)", gw4.IP, gw4.HwAddress, gwIP6, gwMAC6) + + time.Sleep(5 * time.Second) + + gw4now, err := network.FindGateway(s.Interface) + if err != nil { + s.Events.Log(log.ERROR, "error getting ipv4 gateway: %v", err) + } else if gw4now == nil { + s.Events.Log(log.ERROR, "null ipv4 gateway") + } else { + if gw4now.IpAddress != gw4.IpAddress || gw4now.HwAddress != gw4.HwAddress { + s.Events.Add("gateway.change", GatewayChange{ + Type: string(routing.IPv4), + Prev: gateway{ + IP: gw4.IpAddress, + MAC: gw4.HwAddress, + }, + New: gateway{ + IP: gw4now.IpAddress, + MAC: gw4now.HwAddress, + }, + }) + } + } + + gw4 = gw4now + + gwMAC6now := "" + gwIP6now, err := routing.Gateway(routing.IPv6, s.Interface.Name()) + if err != nil { + s.Events.Log(log.ERROR, "error getting ipv6 gateway: %v", err) + } else if gwIP6now != "" { + gwMAC6now, err = network.ArpLookup(s.Interface.Name(), gwIP6now, true) + if err != nil { + s.Events.Log(log.DEBUG, "error getting %s ipv6 gateway mac: %v", gwIP6now, err) + } + } + + if gwIP6now != gwIP6 || gwMAC6now != gwMAC6 { + s.Events.Add("gateway.change", GatewayChange{ + Type: string(routing.IPv6), + Prev: gateway{ + IP: gwIP6, + MAC: gwMAC6, + }, + New: gateway{ + IP: gwIP6now, + MAC: gwMAC6now, + }, + }) + } + + gwIP6 = gwIP6now + gwMAC6 = gwMAC6now + } +}