mirror of
https://github.com/bettercap/bettercap
synced 2025-07-14 17:13:39 -07:00
new: graph module
This commit is contained in:
parent
6aa8f45d20
commit
db275429c2
7 changed files with 1078 additions and 0 deletions
35
modules/events_stream/events_view_graph.go
Normal file
35
modules/events_stream/events_view_graph.go
Normal file
|
@ -0,0 +1,35 @@
|
|||
package events_stream
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"io"
|
||||
|
||||
"github.com/bettercap/bettercap/session"
|
||||
"github.com/bettercap/bettercap/modules/graph"
|
||||
|
||||
"github.com/evilsocket/islazy/tui"
|
||||
)
|
||||
|
||||
func (mod *EventsStream) viewGraphEvent(output io.Writer, e session.Event) {
|
||||
if e.Tag == "graph.node.new" {
|
||||
node := e.Data.(*graph.Node)
|
||||
|
||||
fmt.Fprintf(output, "[%s] [%s] %s %s\n",
|
||||
e.Time.Format(mod.timeFormat),
|
||||
tui.Green(e.Tag),
|
||||
tui.Yellow(string(node.Type)),
|
||||
node.ID)
|
||||
} else if e.Tag == "graph.edge.new" {
|
||||
data := e.Data.(graph.EdgeEvent)
|
||||
fmt.Fprintf(output, "[%s] [%s] %s %s %s %s %s\n",
|
||||
e.Time.Format(mod.timeFormat),
|
||||
tui.Green(e.Tag),
|
||||
tui.Dim(string(data.Left.Type)),
|
||||
data.Left.ID,
|
||||
tui.Bold(string(data.Edge.Type)),
|
||||
tui.Dim(string(data.Right.Type)),
|
||||
data.Right.ID)
|
||||
}else {
|
||||
fmt.Fprintf(output, "[%s] [%s] %v\n", e.Time.Format(mod.timeFormat), tui.Green(e.Tag), e)
|
||||
}
|
||||
}
|
40
modules/graph/edge.go
Normal file
40
modules/graph/edge.go
Normal file
|
@ -0,0 +1,40 @@
|
|||
package graph
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"github.com/bettercap/bettercap/session"
|
||||
"time"
|
||||
)
|
||||
|
||||
type EdgeType string
|
||||
|
||||
const (
|
||||
Is EdgeType = "is"
|
||||
ProbesFor EdgeType = "probes_for"
|
||||
ConnectsTo EdgeType = "connects_to"
|
||||
Manages EdgeType = "manages"
|
||||
)
|
||||
|
||||
type EdgeEvent struct {
|
||||
Left *Node
|
||||
Edge *Edge
|
||||
Right *Node
|
||||
}
|
||||
|
||||
type Edge struct {
|
||||
Type EdgeType `json:"type"`
|
||||
CreatedAt time.Time `json:"created_at"`
|
||||
Position session.GPS `json:"position"`
|
||||
}
|
||||
|
||||
func (e Edge) Dot(left, right *Node) string {
|
||||
edgeLen := 2.0
|
||||
if e.Type == Is {
|
||||
edgeLen = 1.0
|
||||
}
|
||||
return fmt.Sprintf("\"%s\" -> \"%s\" [label=\"%s\", len=%.2f];",
|
||||
left.String(),
|
||||
right.String(),
|
||||
e.Type,
|
||||
edgeLen)
|
||||
}
|
399
modules/graph/graph.go
Normal file
399
modules/graph/graph.go
Normal file
|
@ -0,0 +1,399 @@
|
|||
package graph
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"strings"
|
||||
"github.com/bettercap/bettercap/session"
|
||||
"github.com/evilsocket/islazy/fs"
|
||||
"io/ioutil"
|
||||
"os"
|
||||
"path"
|
||||
"regexp"
|
||||
"sort"
|
||||
"sync"
|
||||
"time"
|
||||
)
|
||||
|
||||
var edgesParser = regexp.MustCompile(`^edges_(.+_[a-fA-F0-9:]{17})_(.+_.+)\.json$`)
|
||||
|
||||
type NodeCallback func(*Node)
|
||||
type EdgeCallback func(*Node, *Edge, *Node)
|
||||
|
||||
type Graph struct {
|
||||
sync.Mutex
|
||||
|
||||
path string
|
||||
}
|
||||
|
||||
func NewGraph(path string) (*Graph, error) {
|
||||
g := &Graph{
|
||||
path: path,
|
||||
}
|
||||
return g, nil
|
||||
}
|
||||
|
||||
func (g *Graph) EachNode(cb NodeCallback) error {
|
||||
g.Lock()
|
||||
defer g.Unlock()
|
||||
|
||||
for _, nodeType := range NodeTypes {
|
||||
err := fs.Glob(g.path, fmt.Sprintf("%s_*.json", nodeType), func(fileName string) error {
|
||||
var node Node
|
||||
if raw, err := ioutil.ReadFile(fileName); err != nil {
|
||||
return fmt.Errorf("error while reading %s: %v", fileName, err)
|
||||
} else if err = json.Unmarshal(raw, &node); err != nil {
|
||||
return fmt.Errorf("error while decoding %s: %v", fileName, err)
|
||||
} else {
|
||||
cb(&node)
|
||||
}
|
||||
return nil
|
||||
})
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (g *Graph) EachEdge(cb EdgeCallback) error {
|
||||
g.Lock()
|
||||
defer g.Unlock()
|
||||
|
||||
return fs.Glob(g.path, "edges_*.json", func(fileName string) error {
|
||||
matches := edgesParser.FindAllStringSubmatch(path.Base(fileName), -1)
|
||||
if len(matches) > 0 && len(matches[0]) == 3 {
|
||||
var left, right Node
|
||||
leftFileName := path.Join(g.path, matches[0][1]+".json")
|
||||
rightFileName := path.Join(g.path, matches[0][2]+".json")
|
||||
|
||||
if raw, err := ioutil.ReadFile(leftFileName); err != nil {
|
||||
return fmt.Errorf("error while reading %s: %v", leftFileName, err)
|
||||
} else if err = json.Unmarshal(raw, &left); err != nil {
|
||||
return fmt.Errorf("error while decoding %s: %v", leftFileName, err)
|
||||
} else if raw, err = ioutil.ReadFile(rightFileName); err != nil {
|
||||
return fmt.Errorf("error while reading %s: %v", rightFileName, err)
|
||||
} else if err = json.Unmarshal(raw, &right); err != nil {
|
||||
return fmt.Errorf("error while decoding %s: %v", rightFileName, err)
|
||||
}
|
||||
|
||||
var edges []*Edge
|
||||
if raw, err := ioutil.ReadFile(fileName); err != nil {
|
||||
return fmt.Errorf("error while reading %s: %v", fileName, err)
|
||||
} else if err = json.Unmarshal(raw, &edges); err != nil {
|
||||
return fmt.Errorf("error while decoding %s: %v", fileName, err)
|
||||
}
|
||||
|
||||
for _, edge := range edges {
|
||||
cb(&left, edge, &right)
|
||||
}
|
||||
} else {
|
||||
return fmt.Errorf("filename %s doesn't match edges parser", fileName)
|
||||
}
|
||||
return nil
|
||||
})
|
||||
}
|
||||
|
||||
func (g *Graph) Traverse(root string, onNode NodeCallback, onEdge EdgeCallback) error {
|
||||
if root == "" {
|
||||
// traverse the entire graph
|
||||
if err := g.EachNode(onNode); err != nil {
|
||||
return err
|
||||
} else if err = g.EachEdge(onEdge); err != nil {
|
||||
return err
|
||||
}
|
||||
} else {
|
||||
// start by a specific node
|
||||
roots, err := g.FindOtherTypes("", root)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
stack := NewStack()
|
||||
|
||||
for _, root := range roots {
|
||||
stack.Push(root)
|
||||
}
|
||||
|
||||
type edgeBucket struct {
|
||||
left *Node
|
||||
edge *Edge
|
||||
right *Node
|
||||
}
|
||||
|
||||
allEdges := make([]edgeBucket, 0)
|
||||
visited := make(map[string]bool)
|
||||
|
||||
for {
|
||||
if last := stack.Pop(); last == nil {
|
||||
break
|
||||
} else {
|
||||
node := last.(*Node)
|
||||
nodeID := node.String()
|
||||
if _, found := visited[nodeID]; found {
|
||||
continue
|
||||
} else {
|
||||
visited[nodeID] = true
|
||||
}
|
||||
|
||||
onNode(node)
|
||||
|
||||
// find all edges starting from this node
|
||||
edgesFilter := fmt.Sprintf("edges_%s_*.json", nodeID)
|
||||
err = fs.Glob(g.path, edgesFilter, func(edgeFileName string) error {
|
||||
right := new(Node)
|
||||
|
||||
base := path.Base(edgeFileName)
|
||||
base = strings.ReplaceAll(base, "edges_", "")
|
||||
base = strings.ReplaceAll(base, nodeID + "_", "")
|
||||
|
||||
// read right node
|
||||
rightFileName := path.Join(g.path, base)
|
||||
if raw, err := ioutil.ReadFile(rightFileName); err != nil {
|
||||
return fmt.Errorf("error while reading %s: %v", rightFileName, err)
|
||||
} else if err = json.Unmarshal(raw, right); err != nil {
|
||||
return fmt.Errorf("error while decoding %s: %v", rightFileName, err)
|
||||
}
|
||||
|
||||
stack.Push(right)
|
||||
|
||||
// read edges
|
||||
var edges []*Edge
|
||||
if raw, err := ioutil.ReadFile(edgeFileName); err != nil {
|
||||
return fmt.Errorf("error while reading %s: %v", edgeFileName, err)
|
||||
} else if err = json.Unmarshal(raw, &edges); err != nil {
|
||||
return fmt.Errorf("error while decoding %s: %v", edgeFileName, err)
|
||||
}
|
||||
|
||||
for _, edge := range edges {
|
||||
allEdges = append(allEdges, edgeBucket {
|
||||
left: node,
|
||||
edge: edge,
|
||||
right: right,
|
||||
})
|
||||
}
|
||||
|
||||
return nil
|
||||
})
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
for _, edge := range allEdges {
|
||||
onEdge(edge.left, edge.edge, edge.right)
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (g *Graph) Dot(filter, layout, name string) (string, error) {
|
||||
data := fmt.Sprintf("digraph %s {\n", name)
|
||||
data += fmt.Sprintf(" layout=%s\n", layout)
|
||||
|
||||
if err := g.Traverse(filter, func(node *Node) {
|
||||
data += fmt.Sprintf(" %s\n", node.Dot(filter == node.ID))
|
||||
}, func(left *Node, edge *Edge, right *Node) {
|
||||
data += fmt.Sprintf(" %s\n", edge.Dot(left, right))
|
||||
}); err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
data += "\n"
|
||||
data += " overlap=false\n"
|
||||
data += "}"
|
||||
|
||||
return data, nil
|
||||
}
|
||||
|
||||
func (g *Graph) FindNode(t NodeType, id string) (*Node, error) {
|
||||
g.Lock()
|
||||
defer g.Unlock()
|
||||
|
||||
nodeFileName := path.Join(g.path, fmt.Sprintf("%s_%s.json", t, id))
|
||||
if fs.Exists(nodeFileName) {
|
||||
var node Node
|
||||
if raw, err := ioutil.ReadFile(nodeFileName); err != nil {
|
||||
return nil, fmt.Errorf("error while reading %s: %v", nodeFileName, err)
|
||||
} else if err = json.Unmarshal(raw, &node); err != nil {
|
||||
return nil, fmt.Errorf("error while decoding %s: %v", nodeFileName, err)
|
||||
}
|
||||
return &node, nil
|
||||
}
|
||||
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
func (g *Graph) FindOtherTypes(t NodeType, id string) ([]*Node, error) {
|
||||
g.Lock()
|
||||
defer g.Unlock()
|
||||
|
||||
var otherNodes []*Node
|
||||
|
||||
for _, otherType := range NodeTypes {
|
||||
if otherType != t {
|
||||
if nodeFileName := path.Join(g.path, fmt.Sprintf("%s_%s.json", otherType, id)); fs.Exists(nodeFileName) {
|
||||
var node Node
|
||||
if raw, err := ioutil.ReadFile(nodeFileName); err != nil {
|
||||
return nil, fmt.Errorf("error while reading %s: %v", nodeFileName, err)
|
||||
} else if err = json.Unmarshal(raw, &node); err != nil {
|
||||
return nil, fmt.Errorf("error while decoding %s: %v", nodeFileName, err)
|
||||
} else {
|
||||
otherNodes = append(otherNodes, &node)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return otherNodes, nil
|
||||
}
|
||||
|
||||
func (g *Graph) CreateNode(t NodeType, id string, entity interface{}, annotations string) (*Node, error) {
|
||||
g.Lock()
|
||||
defer g.Unlock()
|
||||
|
||||
node := &Node{
|
||||
Type: t,
|
||||
ID: id,
|
||||
CreatedAt: time.Now(),
|
||||
Entity: entity,
|
||||
Annotations: annotations,
|
||||
}
|
||||
|
||||
nodeFileName := path.Join(g.path, fmt.Sprintf("%s.json", node.String()))
|
||||
if raw, err := json.Marshal(node); err != nil {
|
||||
return nil, fmt.Errorf("error creating data for %s: %v", nodeFileName, err)
|
||||
} else if err = ioutil.WriteFile(nodeFileName, raw, os.ModePerm); err != nil {
|
||||
return nil, fmt.Errorf("error creating %s: %v", nodeFileName, err)
|
||||
}
|
||||
|
||||
session.I.Events.Add("graph.node.new", node)
|
||||
|
||||
return node, nil
|
||||
}
|
||||
|
||||
func (g *Graph) UpdateNode(node *Node) error {
|
||||
g.Lock()
|
||||
defer g.Unlock()
|
||||
|
||||
node.UpdatedAt = time.Now()
|
||||
nodeFileName := path.Join(g.path, fmt.Sprintf("%s.json", node.String()))
|
||||
if raw, err := json.Marshal(node); err != nil {
|
||||
return fmt.Errorf("error creating new data for %s: %v", nodeFileName, err)
|
||||
} else if err = ioutil.WriteFile(nodeFileName, raw, os.ModePerm); err != nil {
|
||||
return fmt.Errorf("error updating %s: %v", nodeFileName, err)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (g *Graph) findEdgesUnlocked(from, to *Node) (string, []*Edge, error) {
|
||||
edgesFileName := path.Join(g.path, fmt.Sprintf("edges_%s_%s.json", from.String(), to.String()))
|
||||
if fs.Exists(edgesFileName) {
|
||||
var edges []*Edge
|
||||
if raw, err := ioutil.ReadFile(edgesFileName); err != nil {
|
||||
return edgesFileName, nil, fmt.Errorf("error while reading %s: %v", edgesFileName, err)
|
||||
} else if err = json.Unmarshal(raw, &edges); err != nil {
|
||||
return edgesFileName, nil, fmt.Errorf("error while decoding %s: %v", edgesFileName, err)
|
||||
}
|
||||
|
||||
// sort edges from oldest to newer
|
||||
sort.Slice(edges, func(i, j int) bool {
|
||||
return edges[i].CreatedAt.Before(edges[j].CreatedAt)
|
||||
})
|
||||
|
||||
return edgesFileName, edges, nil
|
||||
}
|
||||
|
||||
return edgesFileName, nil, nil
|
||||
}
|
||||
|
||||
func (g *Graph) FindEdges(from, to *Node) ([]*Edge, error) {
|
||||
g.Lock()
|
||||
defer g.Unlock()
|
||||
|
||||
_, edges, err := g.findEdgesUnlocked(from, to)
|
||||
return edges, err
|
||||
}
|
||||
|
||||
func (g *Graph) FindLastEdgeOfType(from, to *Node, edgeType EdgeType) (*Edge, error) {
|
||||
g.Lock()
|
||||
defer g.Unlock()
|
||||
|
||||
if _, edges, err := g.findEdgesUnlocked(from, to); err != nil {
|
||||
return nil, err
|
||||
} else {
|
||||
num := len(edges)
|
||||
for i := range edges {
|
||||
// loop backwards
|
||||
idx := num - 1 - i
|
||||
edge := edges[idx]
|
||||
if edge.Type == edgeType {
|
||||
return edge, nil
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
func (g *Graph) FindLastRecentEdgeOfType(from, to *Node, edgeType EdgeType, staleTime time.Duration) (*Edge, error) {
|
||||
g.Lock()
|
||||
defer g.Unlock()
|
||||
|
||||
if _, edges, err := g.findEdgesUnlocked(from, to); err != nil {
|
||||
return nil, err
|
||||
} else {
|
||||
num := len(edges)
|
||||
for i := range edges {
|
||||
// loop backwards
|
||||
idx := num - 1 - i
|
||||
edge := edges[idx]
|
||||
if edge.Type == edgeType {
|
||||
if time.Since(edge.CreatedAt) >= staleTime {
|
||||
return nil, nil
|
||||
}
|
||||
// edge is still fresh
|
||||
return edge, nil
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
func (g *Graph) CreateEdge(from, to *Node, edgeType EdgeType) (*Edge, error) {
|
||||
g.Lock()
|
||||
defer g.Unlock()
|
||||
|
||||
var edgesFileName string
|
||||
var edges []*Edge
|
||||
|
||||
edge := &Edge{
|
||||
Type: edgeType,
|
||||
CreatedAt: time.Now(),
|
||||
Position: session.I.GPS,
|
||||
}
|
||||
|
||||
if edgesFileName, edges, _ = g.findEdgesUnlocked(from, to); edges != nil {
|
||||
edges = append(edges, edge)
|
||||
} else {
|
||||
edges = []*Edge{edge}
|
||||
}
|
||||
|
||||
if raw, err := json.Marshal(edges); err != nil {
|
||||
return nil, fmt.Errorf("error creating data for %s: %v", edgesFileName, err)
|
||||
} else if err = ioutil.WriteFile(edgesFileName, raw, os.ModePerm); err != nil {
|
||||
return nil, fmt.Errorf("error writing %s: %v", edgesFileName, err)
|
||||
}
|
||||
|
||||
session.I.Events.Add("graph.edge.new", EdgeEvent{
|
||||
Left: from,
|
||||
Edge: edge,
|
||||
Right: to,
|
||||
})
|
||||
|
||||
return edge, nil
|
||||
}
|
466
modules/graph/module.go
Normal file
466
modules/graph/module.go
Normal file
|
@ -0,0 +1,466 @@
|
|||
package graph
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"github.com/bettercap/bettercap/caplets"
|
||||
"github.com/bettercap/bettercap/modules/wifi"
|
||||
"github.com/bettercap/bettercap/network"
|
||||
"github.com/bettercap/bettercap/session"
|
||||
"github.com/evilsocket/islazy/fs"
|
||||
"github.com/evilsocket/islazy/str"
|
||||
"io/ioutil"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"time"
|
||||
)
|
||||
|
||||
const (
|
||||
ifaceAnnotation = "<interface>"
|
||||
edgeStaleTime = time.Hour * 24
|
||||
)
|
||||
|
||||
type settings struct {
|
||||
path string
|
||||
layout string
|
||||
name string
|
||||
output string
|
||||
}
|
||||
|
||||
type Module struct {
|
||||
session.SessionModule
|
||||
|
||||
settings settings
|
||||
db *Graph
|
||||
gw *Node
|
||||
iface *Node
|
||||
eventBus session.EventBus
|
||||
}
|
||||
|
||||
func NewModule(s *session.Session) *Module {
|
||||
mod := &Module{
|
||||
SessionModule: session.NewSessionModule("graph", s),
|
||||
settings: settings{
|
||||
path: filepath.Join(caplets.InstallBase, "graph"),
|
||||
layout: "neato",
|
||||
name: "bettergraph",
|
||||
output: "bettergraph.dot",
|
||||
},
|
||||
}
|
||||
|
||||
mod.AddParam(session.NewStringParameter("graph.path",
|
||||
mod.settings.path,
|
||||
"",
|
||||
"Base path for the graph database."))
|
||||
|
||||
mod.AddParam(session.NewStringParameter("graph.dot.name",
|
||||
mod.settings.name,
|
||||
"",
|
||||
"Graph name in the dot output."))
|
||||
|
||||
mod.AddParam(session.NewStringParameter("graph.dot.layout",
|
||||
mod.settings.layout,
|
||||
"",
|
||||
"Layout for dot output."))
|
||||
|
||||
mod.AddParam(session.NewStringParameter("graph.dot.output",
|
||||
mod.settings.output,
|
||||
"",
|
||||
"File name for dot output."))
|
||||
|
||||
mod.AddHandler(session.NewModuleHandler("graph on", "",
|
||||
"Start the Module module.",
|
||||
func(args []string) error {
|
||||
return mod.Start()
|
||||
}))
|
||||
|
||||
mod.AddHandler(session.NewModuleHandler("graph off", "",
|
||||
"Stop the Module module.",
|
||||
func(args []string) error {
|
||||
return mod.Stop()
|
||||
}))
|
||||
|
||||
mod.AddHandler(session.NewModuleHandler("graph.to_dot MAC?",
|
||||
`graph\.to_dot\s*([^\s]*)`,
|
||||
"Generate a dot graph file from the current graph.",
|
||||
func(args []string) (err error) {
|
||||
bssid := ""
|
||||
if len(args) == 1 && args[0] != "" {
|
||||
bssid = network.NormalizeMac(str.Trim(args[0]))
|
||||
}
|
||||
return mod.generateDotGraph(bssid)
|
||||
}))
|
||||
|
||||
return mod
|
||||
}
|
||||
|
||||
func (mod *Module) Name() string {
|
||||
return "graph"
|
||||
}
|
||||
|
||||
func (mod *Module) Description() string {
|
||||
return "A module to build a graph of WiFi and LAN nodes."
|
||||
}
|
||||
|
||||
func (mod *Module) Author() string {
|
||||
return "Simone Margaritelli <evilsocket@gmail.com>"
|
||||
}
|
||||
|
||||
func (mod *Module) updateSettings() error {
|
||||
var err error
|
||||
|
||||
if err, mod.settings.name = mod.StringParam("graph.dot.name"); err != nil {
|
||||
return err
|
||||
} else if err, mod.settings.layout = mod.StringParam("graph.dot.layout"); err != nil {
|
||||
return err
|
||||
} else if err, mod.settings.output = mod.StringParam("graph.dot.output"); err != nil {
|
||||
return err
|
||||
} else if err, mod.settings.path = mod.StringParam("graph.path"); err != nil {
|
||||
return err
|
||||
} else if mod.settings.path, err = filepath.Abs(mod.settings.path); err != nil {
|
||||
return err
|
||||
} else if !fs.Exists(mod.settings.path) {
|
||||
if err = os.MkdirAll(mod.settings.path, os.ModePerm); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
if mod.db, err = NewGraph(mod.settings.path); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (mod *Module) Configure() (err error) {
|
||||
if mod.Running() {
|
||||
return session.ErrAlreadyStarted(mod.Name())
|
||||
} else if err = mod.updateSettings(); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// if have an IP
|
||||
if mod.Session.Gateway != nil && mod.Session.Interface != nil {
|
||||
// find or create gateway node
|
||||
gw := mod.Session.Gateway
|
||||
if mod.gw, err = mod.db.FindNode(Gateway, gw.HwAddress); err != nil {
|
||||
return err
|
||||
} else if mod.gw == nil {
|
||||
if mod.gw, err = mod.db.CreateNode(Gateway, gw.HwAddress, gw, ""); err != nil {
|
||||
return err
|
||||
}
|
||||
} else {
|
||||
if err = mod.db.UpdateNode(mod.gw); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
// find or create interface node
|
||||
iface := mod.Session.Interface
|
||||
if mod.iface, err = mod.db.FindNode(Endpoint, iface.HwAddress); err != nil {
|
||||
return err
|
||||
} else if mod.iface == nil {
|
||||
// create the interface node
|
||||
if mod.iface, err = mod.db.CreateNode(Endpoint, iface.HwAddress, iface, ifaceAnnotation); err != nil {
|
||||
return err
|
||||
}
|
||||
} else if err = mod.db.UpdateNode(mod.iface); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// create relations if needed
|
||||
if manages, err := mod.db.FindLastRecentEdgeOfType(mod.gw, mod.iface, Manages, edgeStaleTime); err != nil {
|
||||
return err
|
||||
} else if manages == nil {
|
||||
if manages, err = mod.db.CreateEdge(mod.gw, mod.iface, Manages); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
if connects_to, err := mod.db.FindLastEdgeOfType(mod.iface, mod.gw, ConnectsTo); err != nil {
|
||||
return err
|
||||
} else if connects_to == nil {
|
||||
if connects_to, err = mod.db.CreateEdge(mod.iface, mod.gw, ConnectsTo); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
mod.eventBus = mod.Session.Events.Listen()
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (mod *Module) generateDotGraph(bssid string) error {
|
||||
start := time.Now()
|
||||
|
||||
if err := mod.updateSettings(); err != nil {
|
||||
return err
|
||||
} else if data, err := mod.db.Dot(bssid, mod.settings.layout, mod.settings.name); err != nil {
|
||||
return err
|
||||
} else if err := ioutil.WriteFile(mod.settings.output, []byte(data), os.ModePerm); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
mod.Info("graph saved to %s in %v", mod.settings.output, time.Since(start))
|
||||
return nil
|
||||
}
|
||||
|
||||
func (mod *Module) createIPGraph(endpoint *network.Endpoint) (*Node, bool, error) {
|
||||
node, err := mod.db.FindNode(Endpoint, endpoint.HwAddress)
|
||||
isNew := node == nil
|
||||
if err != nil {
|
||||
return nil, false, err
|
||||
} else if isNew {
|
||||
if node, err = mod.db.CreateNode(Endpoint, endpoint.HwAddress, endpoint, ""); err != nil {
|
||||
return nil, false, err
|
||||
}
|
||||
} else {
|
||||
if err = mod.db.UpdateNode(node); err != nil {
|
||||
return nil, false, err
|
||||
}
|
||||
}
|
||||
|
||||
// create relations if needed
|
||||
if manages, err := mod.db.FindLastRecentEdgeOfType(mod.gw, node, Manages, edgeStaleTime); err != nil {
|
||||
return nil, false, err
|
||||
} else if manages == nil {
|
||||
if manages, err = mod.db.CreateEdge(mod.gw, node, Manages); err != nil {
|
||||
return nil, false, err
|
||||
}
|
||||
}
|
||||
|
||||
if connects_to, err := mod.db.FindLastRecentEdgeOfType(node, mod.gw, ConnectsTo, edgeStaleTime); err != nil {
|
||||
return nil, false, err
|
||||
} else if connects_to == nil {
|
||||
if connects_to, err = mod.db.CreateEdge(node, mod.gw, ConnectsTo); err != nil {
|
||||
return nil, false, err
|
||||
}
|
||||
}
|
||||
|
||||
return node, isNew, nil
|
||||
}
|
||||
|
||||
func (mod *Module) createDot11ApGraph(ap *network.AccessPoint) (*Node, bool, error) {
|
||||
node, err := mod.db.FindNode(AccessPoint, ap.HwAddress)
|
||||
isNew := node == nil
|
||||
if err != nil {
|
||||
return nil, false, err
|
||||
} else if isNew {
|
||||
if node, err = mod.db.CreateNode(AccessPoint, ap.HwAddress, ap, ""); err != nil {
|
||||
return nil, false, err
|
||||
}
|
||||
} else if err = mod.db.UpdateNode(node); err != nil {
|
||||
return nil, false, err
|
||||
}
|
||||
return node, isNew, nil
|
||||
}
|
||||
|
||||
func (mod *Module) createDot11SSIDGraph(hw string, ssid string) (*Node, bool, error) {
|
||||
node, err := mod.db.FindNode(SSID, hw)
|
||||
isNew := node == nil
|
||||
if err != nil {
|
||||
return nil, false, err
|
||||
} else if isNew {
|
||||
if node, err = mod.db.CreateNode(SSID, hw, ssid, ""); err != nil {
|
||||
return nil, false, err
|
||||
}
|
||||
} else if err = mod.db.UpdateNode(node); err != nil {
|
||||
return nil, false, err
|
||||
}
|
||||
return node, isNew, nil
|
||||
}
|
||||
|
||||
func (mod *Module) createDot11StaGraph(station *network.Station) (*Node, bool, error) {
|
||||
node, err := mod.db.FindNode(Station, station.HwAddress)
|
||||
isNew := node == nil
|
||||
if err != nil {
|
||||
return nil, false, err
|
||||
} else if isNew {
|
||||
if node, err = mod.db.CreateNode(Station, station.HwAddress, station, ""); err != nil {
|
||||
return nil, false, err
|
||||
}
|
||||
} else if err = mod.db.UpdateNode(node); err != nil {
|
||||
return nil, false, err
|
||||
}
|
||||
return node, isNew, nil
|
||||
}
|
||||
|
||||
func (mod *Module) createDot11Graph(ap *network.AccessPoint, station *network.Station) (*Node, bool, *Node, bool, error) {
|
||||
apNode, apIsNew, err := mod.createDot11ApGraph(ap)
|
||||
if err != nil {
|
||||
return nil, false, nil, false, err
|
||||
}
|
||||
|
||||
staNode, staIsNew, err := mod.createDot11StaGraph(station)
|
||||
if err != nil {
|
||||
return nil, false, nil, false, err
|
||||
}
|
||||
|
||||
// create relations if needed
|
||||
if manages, err := mod.db.FindLastRecentEdgeOfType(apNode, staNode, Manages, edgeStaleTime); err != nil {
|
||||
return nil, false, nil, false, err
|
||||
} else if manages == nil {
|
||||
if manages, err = mod.db.CreateEdge(apNode, staNode, Manages); err != nil {
|
||||
return nil, false, nil, false, err
|
||||
}
|
||||
}
|
||||
|
||||
if connects_to, err := mod.db.FindLastRecentEdgeOfType(staNode, apNode, ConnectsTo, edgeStaleTime); err != nil {
|
||||
return nil, false, nil, false, err
|
||||
} else if connects_to == nil {
|
||||
if connects_to, err = mod.db.CreateEdge(staNode, apNode, ConnectsTo); err != nil {
|
||||
return nil, false, nil, false, err
|
||||
}
|
||||
}
|
||||
|
||||
return apNode, apIsNew, staNode, staIsNew, nil
|
||||
}
|
||||
|
||||
func (mod *Module) createDot11ProbeGraph(ssid string, station *network.Station) (*Node, bool, *Node, bool, error) {
|
||||
apNode, apIsNew, err := mod.createDot11SSIDGraph(station.HwAddress + fmt.Sprintf(":PROBE:%x", ssid), ssid)
|
||||
if err != nil {
|
||||
return nil, false, nil, false, err
|
||||
}
|
||||
|
||||
staNode, staIsNew, err := mod.createDot11StaGraph(station)
|
||||
if err != nil {
|
||||
return nil, false, nil, false, err
|
||||
}
|
||||
|
||||
// create relations if needed
|
||||
if probes_for, err := mod.db.FindLastRecentEdgeOfType(staNode, apNode, ProbesFor, edgeStaleTime); err != nil {
|
||||
return nil, false, nil, false, err
|
||||
} else if probes_for == nil {
|
||||
if probes_for, err = mod.db.CreateEdge(staNode, apNode, ProbesFor); err != nil {
|
||||
return nil, false, nil, false, err
|
||||
}
|
||||
}
|
||||
|
||||
return apNode, apIsNew, staNode, staIsNew, nil
|
||||
}
|
||||
|
||||
func (mod *Module) createBLEServerGraph(dev *network.BLEDevice) (*Node, bool, error) {
|
||||
mac := network.NormalizeMac(dev.Device.ID())
|
||||
node, err := mod.db.FindNode(BLEServer, mac)
|
||||
isNew := node == nil
|
||||
if err != nil {
|
||||
return nil, false, err
|
||||
} else if isNew {
|
||||
if node, err = mod.db.CreateNode(BLEServer, mac, dev, ""); err != nil {
|
||||
return nil, false, err
|
||||
}
|
||||
} else if err = mod.db.UpdateNode(node); err != nil {
|
||||
return nil, false, err
|
||||
}
|
||||
return node, isNew, nil
|
||||
}
|
||||
|
||||
func (mod *Module) connectAsSame(a, b *Node) error {
|
||||
if aIsB, err := mod.db.FindLastEdgeOfType(a, b, Is); err != nil {
|
||||
return err
|
||||
} else if aIsB == nil {
|
||||
if aIsB, err = mod.db.CreateEdge(a, b, Is); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
if bIsA, err := mod.db.FindLastEdgeOfType(b, a, Is); err != nil {
|
||||
return err
|
||||
} else if bIsA == nil {
|
||||
if bIsA, err = mod.db.CreateEdge(b, a, Is); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (mod *Module) onEvent(e session.Event) {
|
||||
var entities []*Node
|
||||
|
||||
if e.Tag == "endpoint.new" {
|
||||
endpoint := e.Data.(*network.Endpoint)
|
||||
if entity, _, err := mod.createIPGraph(endpoint); err != nil {
|
||||
mod.Error("%s", err)
|
||||
} else {
|
||||
entities = append(entities, entity)
|
||||
}
|
||||
} else if e.Tag == "wifi.ap.new" {
|
||||
ap := e.Data.(*network.AccessPoint)
|
||||
if entity, _, err := mod.createDot11ApGraph(ap); err != nil {
|
||||
mod.Error("%s", err)
|
||||
} else {
|
||||
entities = append(entities, entity)
|
||||
}
|
||||
} else if e.Tag == "wifi.client.new" {
|
||||
ce := e.Data.(wifi.ClientEvent)
|
||||
if apEntity, _, staEntity, _, err := mod.createDot11Graph(ce.AP, ce.Client); err != nil {
|
||||
mod.Error("%s", err)
|
||||
} else {
|
||||
entities = append(entities, apEntity)
|
||||
entities = append(entities, staEntity)
|
||||
}
|
||||
} else if e.Tag == "wifi.client.probe" {
|
||||
probe := e.Data.(wifi.ProbeEvent)
|
||||
station := network.Station{
|
||||
RSSI: probe.RSSI,
|
||||
Endpoint: &network.Endpoint{
|
||||
HwAddress: probe.FromAddr,
|
||||
Vendor: probe.FromVendor,
|
||||
Alias: probe.FromAlias,
|
||||
},
|
||||
}
|
||||
|
||||
if _, _, staEntity, _, err := mod.createDot11ProbeGraph(probe.SSID, &station); err != nil {
|
||||
mod.Error("%s", err)
|
||||
} else {
|
||||
// don't add fake ap to entities, no need to correlate
|
||||
entities = append(entities, staEntity)
|
||||
}
|
||||
} else if e.Tag == "ble.device.new" {
|
||||
// surprisingly some devices, like DLink IPCams have BLE, Dot11 and LAN hardware address in common
|
||||
dev := e.Data.(*network.BLEDevice)
|
||||
if entity, _, err := mod.createBLEServerGraph(dev); err != nil {
|
||||
mod.Error("%s", err)
|
||||
} else {
|
||||
entities = append(entities, entity)
|
||||
}
|
||||
}
|
||||
|
||||
// if there's at least an entity node, search for other nodes with the
|
||||
// same mac address but different type and connect them as needed
|
||||
for _, entity := range entities {
|
||||
if others, err := mod.db.FindOtherTypes(entity.Type, entity.ID); err != nil {
|
||||
mod.Error("%s", err)
|
||||
} else if len(others) > 0 {
|
||||
for _, other := range others {
|
||||
if err = mod.connectAsSame(entity, other); err != nil {
|
||||
mod.Error("%s", err)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (mod *Module) Start() error {
|
||||
if err := mod.Configure(); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return mod.SetRunning(true, func() {
|
||||
mod.Info("started with database @ %s", mod.settings.path)
|
||||
|
||||
for mod.Running() {
|
||||
select {
|
||||
case e := <-mod.eventBus:
|
||||
mod.onEvent(e)
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
func (mod *Module) Stop() error {
|
||||
return mod.SetRunning(false, func() {
|
||||
mod.Session.Events.Unlisten(mod.eventBus)
|
||||
})
|
||||
}
|
89
modules/graph/node.go
Normal file
89
modules/graph/node.go
Normal file
|
@ -0,0 +1,89 @@
|
|||
package graph
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"time"
|
||||
)
|
||||
|
||||
type NodeType string
|
||||
|
||||
const (
|
||||
SSID NodeType = "ssid"
|
||||
BLEServer NodeType = "ble_server"
|
||||
Station NodeType = "station"
|
||||
AccessPoint NodeType = "access_point"
|
||||
Endpoint NodeType = "endpoint"
|
||||
Gateway NodeType = "gateway"
|
||||
)
|
||||
|
||||
var NodeTypes = []NodeType{
|
||||
SSID,
|
||||
Station,
|
||||
AccessPoint,
|
||||
Endpoint,
|
||||
Gateway,
|
||||
BLEServer,
|
||||
}
|
||||
|
||||
var nodeDotStyles = map[NodeType]string{
|
||||
SSID: "shape=diamond",
|
||||
BLEServer: "shape=box, style=filled, color=dodgerblue3",
|
||||
Endpoint: "shape=box, style=filled, color=azure2",
|
||||
Gateway: "shape=diamond, style=filled, color=azure4",
|
||||
Station: "shape=box, style=filled, color=gold",
|
||||
AccessPoint: "shape=diamond, style=filled, color=goldenrod3",
|
||||
}
|
||||
|
||||
type Node struct {
|
||||
Type NodeType `json:"type"`
|
||||
CreatedAt time.Time `json:"created_at"`
|
||||
UpdatedAt time.Time `json:"updated_at"`
|
||||
ID string `json:"id"`
|
||||
Annotations string `json:"annotations"`
|
||||
Entity interface{} `json:"entity"`
|
||||
}
|
||||
|
||||
func (n Node) String() string {
|
||||
return fmt.Sprintf("%s_%s", n.Type, n.ID)
|
||||
}
|
||||
|
||||
func (n Node) Label() string {
|
||||
switch n.Type {
|
||||
case SSID:
|
||||
return n.Entity.(string)
|
||||
case BLEServer:
|
||||
return fmt.Sprintf("%s\\n(%s)",
|
||||
n.Entity.(map[string]interface{})["mac"].(string),
|
||||
n.Entity.(map[string]interface{})["vendor"].(string))
|
||||
case Station:
|
||||
return fmt.Sprintf("%s\\n(%s)",
|
||||
n.Entity.(map[string]interface{})["mac"].(string),
|
||||
n.Entity.(map[string]interface{})["vendor"].(string))
|
||||
case AccessPoint:
|
||||
return fmt.Sprintf("%s\\n(%s)",
|
||||
n.Entity.(map[string]interface{})["hostname"].(string),
|
||||
n.Entity.(map[string]interface{})["mac"].(string))
|
||||
case Endpoint:
|
||||
return fmt.Sprintf("%s\\n(%s %s)",
|
||||
n.Entity.(map[string]interface{})["ipv4"].(string),
|
||||
n.Entity.(map[string]interface{})["mac"].(string),
|
||||
n.Entity.(map[string]interface{})["vendor"].(string))
|
||||
case Gateway:
|
||||
return fmt.Sprintf("%s\\n(%s %s)",
|
||||
n.Entity.(map[string]interface{})["ipv4"].(string),
|
||||
n.Entity.(map[string]interface{})["mac"].(string),
|
||||
n.Entity.(map[string]interface{})["vendor"].(string))
|
||||
}
|
||||
return "?"
|
||||
}
|
||||
|
||||
func (n Node) Dot(isTarget bool) string {
|
||||
style := nodeDotStyles[n.Type]
|
||||
if isTarget {
|
||||
style += ", color=red"
|
||||
}
|
||||
return fmt.Sprintf("node [%s]; {node [label=\"%s\"] \"%s\";};",
|
||||
style,
|
||||
n.Label(),
|
||||
n.String())
|
||||
}
|
47
modules/graph/stack.go
Normal file
47
modules/graph/stack.go
Normal file
|
@ -0,0 +1,47 @@
|
|||
package graph
|
||||
|
||||
import "sync"
|
||||
|
||||
type element struct {
|
||||
data interface{}
|
||||
next *element
|
||||
}
|
||||
|
||||
type stack struct {
|
||||
lock *sync.Mutex
|
||||
head *element
|
||||
Size int
|
||||
}
|
||||
|
||||
func (stk *stack) Push(data interface{}) {
|
||||
stk.lock.Lock()
|
||||
|
||||
element := new(element)
|
||||
element.data = data
|
||||
temp := stk.head
|
||||
element.next = temp
|
||||
stk.head = element
|
||||
stk.Size++
|
||||
|
||||
stk.lock.Unlock()
|
||||
}
|
||||
|
||||
func (stk *stack) Pop() interface{} {
|
||||
if stk.head == nil {
|
||||
return nil
|
||||
}
|
||||
stk.lock.Lock()
|
||||
r := stk.head.data
|
||||
stk.head = stk.head.next
|
||||
stk.Size--
|
||||
|
||||
stk.lock.Unlock()
|
||||
|
||||
return r
|
||||
}
|
||||
|
||||
func NewStack() *stack {
|
||||
stk := new(stack)
|
||||
stk.lock = &sync.Mutex{}
|
||||
return stk
|
||||
}
|
|
@ -11,6 +11,7 @@ import (
|
|||
"github.com/bettercap/bettercap/modules/dns_spoof"
|
||||
"github.com/bettercap/bettercap/modules/events_stream"
|
||||
"github.com/bettercap/bettercap/modules/gps"
|
||||
"github.com/bettercap/bettercap/modules/graph"
|
||||
"github.com/bettercap/bettercap/modules/hid"
|
||||
"github.com/bettercap/bettercap/modules/http_proxy"
|
||||
"github.com/bettercap/bettercap/modules/http_server"
|
||||
|
@ -63,6 +64,7 @@ func LoadModules(sess *session.Session) {
|
|||
sess.Register(hid.NewHIDRecon(sess))
|
||||
sess.Register(c2.NewC2(sess))
|
||||
sess.Register(ndp_spoof.NewNDPSpoofer(sess))
|
||||
sess.Register(graph.NewModule(sess))
|
||||
|
||||
sess.Register(caplets.NewCapletsModule(sess))
|
||||
sess.Register(update.NewUpdateModule(sess))
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue