mirror of
https://github.com/bettercap/bettercap.git
synced 2024-11-08 06:30:13 -08:00
340 lines
7.8 KiB
Go
340 lines
7.8 KiB
Go
package net_recon
|
|
|
|
import (
|
|
"fmt"
|
|
"sort"
|
|
"strings"
|
|
"time"
|
|
|
|
"github.com/bettercap/bettercap/v2/modules/syn_scan"
|
|
|
|
"github.com/bettercap/bettercap/v2/network"
|
|
"github.com/bettercap/bettercap/v2/packets"
|
|
|
|
"github.com/dustin/go-humanize"
|
|
|
|
"github.com/evilsocket/islazy/str"
|
|
"github.com/evilsocket/islazy/tui"
|
|
)
|
|
|
|
const (
|
|
AliveTimeInterval = time.Duration(10) * time.Second
|
|
PresentTimeInterval = time.Duration(1) * time.Minute
|
|
JustJoinedTimeInterval = time.Duration(10) * time.Second
|
|
)
|
|
|
|
type ProtoPair struct {
|
|
Protocol string
|
|
Hits uint64
|
|
}
|
|
|
|
type ProtoPairList []ProtoPair
|
|
|
|
func (p ProtoPairList) Len() int { return len(p) }
|
|
func (p ProtoPairList) Less(i, j int) bool { return p[i].Hits < p[j].Hits }
|
|
func (p ProtoPairList) Swap(i, j int) { p[i], p[j] = p[j], p[i] }
|
|
|
|
func (mod *Discovery) getRow(e *network.Endpoint, withMeta bool) [][]string {
|
|
sinceStarted := time.Since(mod.Session.StartedAt)
|
|
sinceFirstSeen := time.Since(e.FirstSeen)
|
|
|
|
addr := e.IpAddress
|
|
mac := e.HwAddress
|
|
if mod.Session.Lan.WasMissed(e.HwAddress) {
|
|
// if endpoint was not found in ARP at least once
|
|
addr = tui.Dim(addr)
|
|
mac = tui.Dim(mac)
|
|
} else if sinceStarted > (JustJoinedTimeInterval*2) && sinceFirstSeen <= JustJoinedTimeInterval {
|
|
// if endpoint was first seen in the last 10 seconds
|
|
addr = tui.Bold(addr)
|
|
mac = tui.Bold(mac)
|
|
}
|
|
|
|
name := ""
|
|
if e == mod.Session.Interface {
|
|
name = e.Name()
|
|
} else if e == mod.Session.Gateway {
|
|
name = "gateway"
|
|
} else if e.Alias != "" {
|
|
name = tui.Green(e.Alias)
|
|
} else if e.Hostname != "" {
|
|
name = tui.Yellow(e.Hostname)
|
|
}
|
|
|
|
var traffic *packets.Traffic
|
|
var found bool
|
|
var v interface{}
|
|
if v, found = mod.Session.Queue.Traffic.Load(e.IpAddress); !found {
|
|
traffic = &packets.Traffic{}
|
|
} else {
|
|
traffic = v.(*packets.Traffic)
|
|
}
|
|
|
|
seen := e.LastSeen.Format("15:04:05")
|
|
sinceLastSeen := time.Since(e.LastSeen)
|
|
if sinceStarted > AliveTimeInterval && sinceLastSeen <= AliveTimeInterval {
|
|
// if endpoint seen in the last 10 seconds
|
|
seen = tui.Bold(seen)
|
|
} else if sinceLastSeen <= PresentTimeInterval {
|
|
// if endpoint seen in the last 60 seconds
|
|
} else {
|
|
// not seen in a while
|
|
seen = tui.Dim(seen)
|
|
}
|
|
|
|
row := []string{
|
|
addr,
|
|
mac,
|
|
name,
|
|
tui.Dim(e.Vendor),
|
|
humanize.Bytes(traffic.Sent),
|
|
humanize.Bytes(traffic.Received),
|
|
seen,
|
|
}
|
|
|
|
if !withMeta {
|
|
return [][]string{row}
|
|
} else if e.Meta.Empty() {
|
|
return [][]string{append(row, tui.Dim("-"))}
|
|
}
|
|
|
|
metas := []string{}
|
|
e.Meta.Each(func(name string, value interface{}) {
|
|
s := ""
|
|
if sv, ok := value.(string); ok {
|
|
s = sv
|
|
} else {
|
|
s = fmt.Sprintf("%+v", value)
|
|
}
|
|
|
|
metas = append(metas, fmt.Sprintf("%s:%s", tui.Green(name), tui.Yellow(s)))
|
|
})
|
|
sort.Strings(metas)
|
|
|
|
rows := make([][]string, 0, len(metas))
|
|
for i, m := range metas {
|
|
if i == 0 {
|
|
rows = append(rows, append(row, m))
|
|
} else {
|
|
rows = append(rows, []string{"", "", "", "", "", "", "", m})
|
|
}
|
|
}
|
|
|
|
return rows
|
|
}
|
|
|
|
func (mod *Discovery) doFilter(target *network.Endpoint) bool {
|
|
if mod.selector.Expression == nil {
|
|
return true
|
|
}
|
|
return mod.selector.Expression.MatchString(target.IpAddress) ||
|
|
mod.selector.Expression.MatchString(target.Ip6Address) ||
|
|
mod.selector.Expression.MatchString(target.HwAddress) ||
|
|
mod.selector.Expression.MatchString(target.Hostname) ||
|
|
mod.selector.Expression.MatchString(target.Alias) ||
|
|
mod.selector.Expression.MatchString(target.Vendor)
|
|
}
|
|
|
|
func (mod *Discovery) doSelection(arg string) (err error, targets []*network.Endpoint) {
|
|
if err = mod.selector.Update(); err != nil {
|
|
return
|
|
}
|
|
|
|
if arg != "" {
|
|
if targets, err = network.ParseEndpoints(arg, mod.Session.Lan); err != nil {
|
|
return
|
|
}
|
|
} else {
|
|
targets = mod.Session.Lan.List()
|
|
}
|
|
|
|
filtered := []*network.Endpoint{}
|
|
for _, target := range targets {
|
|
if mod.doFilter(target) {
|
|
filtered = append(filtered, target)
|
|
}
|
|
}
|
|
targets = filtered
|
|
|
|
switch mod.selector.SortField {
|
|
case "ip":
|
|
sort.Sort(ByIpSorter(targets))
|
|
case "mac":
|
|
sort.Sort(ByMacSorter(targets))
|
|
case "seen":
|
|
sort.Sort(BySeenSorter(targets))
|
|
case "sent":
|
|
sort.Sort(BySentSorter(targets))
|
|
case "rcvd":
|
|
sort.Sort(ByRcvdSorter(targets))
|
|
default:
|
|
sort.Sort(ByAddressSorter(targets))
|
|
}
|
|
|
|
// default is asc
|
|
if mod.selector.Sort == "desc" {
|
|
// from https://github.com/golang/go/wiki/SliceTricks
|
|
for i := len(targets)/2 - 1; i >= 0; i-- {
|
|
opp := len(targets) - 1 - i
|
|
targets[i], targets[opp] = targets[opp], targets[i]
|
|
}
|
|
}
|
|
|
|
if mod.selector.Limit > 0 {
|
|
limit := mod.selector.Limit
|
|
max := len(targets)
|
|
if limit > max {
|
|
limit = max
|
|
}
|
|
targets = targets[0:limit]
|
|
}
|
|
|
|
return
|
|
}
|
|
|
|
func (mod *Discovery) colNames(hasMeta bool) []string {
|
|
colNames := []string{"IP", "MAC", "Name", "Vendor", "Sent", "Recvd", "Seen"}
|
|
if hasMeta {
|
|
colNames = append(colNames, "Meta")
|
|
}
|
|
|
|
switch mod.selector.SortField {
|
|
case "mac":
|
|
colNames[1] += " " + mod.selector.SortSymbol
|
|
case "sent":
|
|
colNames[4] += " " + mod.selector.SortSymbol
|
|
case "rcvd":
|
|
colNames[5] += " " + mod.selector.SortSymbol
|
|
case "seen":
|
|
colNames[6] += " " + mod.selector.SortSymbol
|
|
case "ip":
|
|
colNames[0] += " " + mod.selector.SortSymbol
|
|
}
|
|
|
|
return colNames
|
|
}
|
|
|
|
func (mod *Discovery) showStatusBar() {
|
|
parts := []string{
|
|
fmt.Sprintf("%s %s", tui.Red("↑"), humanize.Bytes(mod.Session.Queue.Stats.Sent)),
|
|
fmt.Sprintf("%s %s", tui.Green("↓"), humanize.Bytes(mod.Session.Queue.Stats.Received)),
|
|
fmt.Sprintf("%d pkts", mod.Session.Queue.Stats.PktReceived),
|
|
}
|
|
|
|
if nErrors := mod.Session.Queue.Stats.Errors; nErrors > 0 {
|
|
parts = append(parts, fmt.Sprintf("%d errs", nErrors))
|
|
}
|
|
|
|
mod.Printf("\n%s\n\n", strings.Join(parts, " / "))
|
|
}
|
|
|
|
func (mod *Discovery) Show(arg string) (err error) {
|
|
var targets []*network.Endpoint
|
|
if err, targets = mod.doSelection(arg); err != nil {
|
|
return
|
|
}
|
|
|
|
pad := 1
|
|
if mod.Session.Interface.HwAddress == mod.Session.Gateway.HwAddress {
|
|
pad = 0
|
|
targets = append([]*network.Endpoint{mod.Session.Interface}, targets...)
|
|
} else {
|
|
targets = append([]*network.Endpoint{mod.Session.Interface, mod.Session.Gateway}, targets...)
|
|
}
|
|
|
|
hasMeta := false
|
|
if err, showMeta := mod.BoolParam("net.show.meta"); err != nil {
|
|
return err
|
|
} else if showMeta {
|
|
for _, t := range targets {
|
|
if !t.Meta.Empty() {
|
|
hasMeta = true
|
|
break
|
|
}
|
|
}
|
|
}
|
|
|
|
colNames := mod.colNames(hasMeta)
|
|
padCols := make([]string, len(colNames))
|
|
|
|
rows := make([][]string, 0)
|
|
for i, t := range targets {
|
|
rows = append(rows, mod.getRow(t, hasMeta)...)
|
|
if i == pad {
|
|
rows = append(rows, padCols)
|
|
}
|
|
}
|
|
|
|
tui.Table(mod.Session.Events.Stdout, colNames, rows)
|
|
|
|
mod.showStatusBar()
|
|
|
|
mod.Session.Refresh()
|
|
|
|
return nil
|
|
}
|
|
|
|
func (mod *Discovery) showMeta(arg string) (err error) {
|
|
var targets []*network.Endpoint
|
|
if err, targets = mod.doSelection(arg); err != nil {
|
|
return
|
|
}
|
|
|
|
colNames := []string{"Name", "Value"}
|
|
any := false
|
|
|
|
for _, t := range targets {
|
|
keys := []string{}
|
|
|
|
t.Meta.Each(func(name string, value interface{}) {
|
|
keys = append(keys, name)
|
|
})
|
|
|
|
if len(keys) > 0 {
|
|
sort.Strings(keys)
|
|
rows := [][]string{
|
|
{
|
|
tui.Green("address"),
|
|
t.IP.String(),
|
|
},
|
|
}
|
|
|
|
for _, k := range keys {
|
|
meta := t.Meta.Get(k)
|
|
val := ""
|
|
if s, ok := meta.(string); ok {
|
|
val = s
|
|
} else if ports, ok := meta.(map[int]*syn_scan.OpenPort); ok {
|
|
val = "ports: "
|
|
for _, info := range ports {
|
|
val += fmt.Sprintf("%s:%d", info.Proto, info.Port)
|
|
if info.Service != "" {
|
|
val += fmt.Sprintf("(%s)", info.Service)
|
|
}
|
|
if info.Banner != "" {
|
|
val += fmt.Sprintf(" [%s]", info.Banner)
|
|
}
|
|
val += " "
|
|
}
|
|
val = str.Trim(val)
|
|
} else {
|
|
val = fmt.Sprintf("%#v", meta)
|
|
}
|
|
rows = append(rows, []string{
|
|
tui.Green(k),
|
|
tui.Yellow(val),
|
|
})
|
|
}
|
|
|
|
any = true
|
|
tui.Table(mod.Session.Events.Stdout, colNames, rows)
|
|
}
|
|
}
|
|
|
|
if any {
|
|
mod.Session.Refresh()
|
|
}
|
|
|
|
return nil
|
|
}
|