From f2b6d9b708301f67ed0bffc1407c843a8960abd9 Mon Sep 17 00:00:00 2001 From: Simone Margaritelli Date: Mon, 12 Apr 2021 14:09:03 +0200 Subject: [PATCH] misc: moved graph module to experimental branch for now --- _example/example.js | 7 - _example/functions.js | 33 -- graphpage.html | 195 ---------- modules/events_stream/events_view.go | 2 - modules/events_stream/events_view_graph.go | 35 -- modules/graph/create.go | 184 --------- modules/graph/edge.go | 42 -- modules/graph/edges.go | 158 -------- modules/graph/graph.go | 425 --------------------- modules/graph/js_builtin.go | 15 - modules/graph/module.go | 348 ----------------- modules/graph/node.go | 170 --------- modules/graph/stack.go | 47 --- modules/graph/to_dot.go | 45 --- modules/graph/to_json.go | 43 --- modules/modules.go | 2 - 16 files changed, 1751 deletions(-) delete mode 100644 graphpage.html delete mode 100644 modules/events_stream/events_view_graph.go delete mode 100644 modules/graph/create.go delete mode 100644 modules/graph/edge.go delete mode 100644 modules/graph/edges.go delete mode 100644 modules/graph/graph.go delete mode 100644 modules/graph/js_builtin.go delete mode 100644 modules/graph/module.go delete mode 100644 modules/graph/node.go delete mode 100644 modules/graph/stack.go delete mode 100644 modules/graph/to_dot.go delete mode 100644 modules/graph/to_json.go diff --git a/_example/example.js b/_example/example.js index cd9e9ebf..2ca86409 100644 --- a/_example/example.js +++ b/_example/example.js @@ -4,10 +4,6 @@ require("functions") log("session script loaded, fake AP is " + fakeESSID); -// enable the graph module so we can extract more historical info -// for each device we see -run('graph on') - // create an empty ticker so we can run commands every few seconds // this will inject decoy wifi client probes used to detect KARMA // attacks and in general rogue access points @@ -35,8 +31,5 @@ onEvent('wifi.client.handshake', onHandshake); // register for wifi.ap.new events (used to detect rogue APs) onEvent('wifi.ap.new', onNewAP); -// register for new nodes in the graph -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 e48ed21f..63e2474f 100644 --- a/_example/functions.js +++ b/_example/functions.js @@ -1,19 +1,9 @@ var fakeESSID = random.String(16, 'abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ'); var fakeBSSID = random.Mac() -// uses graph.to_dot and graphviz to generate a png graph -function createGraph(who, where) { - // generates a .dot file with the graph for this mac - run('graph.to_dot ' + who); - // uses graphviz to make a png of it - run('!dot -Tpng bettergraph.dot > ' + where); -} - function onDeauthentication(event) { var data = event.data; - createGraph(data.address1, '/tmp/graph_deauth.png'); - var message = '🚨 Detected deauthentication frame:\n\n' + // 'Time: ' + event.time + "\n" + // 'GPS: lat=' + session.GPS.Latitude + " lon=" + session.GPS.Longitude + " updated_at=" + @@ -28,14 +18,11 @@ function onDeauthentication(event) { // send to telegram bot sendMessage(message); - sendPhoto("/tmp/graph_deauth.png"); } function onNewAP(event){ var ap = event.data; if(ap.hostname == fakeESSID) { - createGraph(ap.mac, '/tmp/graph_ap.png'); - var message = '🦠 Detected rogue AP:\n\n' + // 'Time: ' + event.time + "\n" + // 'GPS: lat=' + session.GPS.Latitude + " lon=" + session.GPS.Longitude + " updated_at=" + @@ -44,7 +31,6 @@ function onNewAP(event){ // send to telegram bot sendMessage(message); - sendPhoto("/tmp/graph_ap.png"); } } @@ -52,8 +38,6 @@ function onHandshake(event){ var data = event.data; var what = 'handshake'; - createGraph(data.station, '/tmp/graph_handshake.png'); - if(data.pmkid != null) { what = "RSN PMKID"; } else if(data.full) { @@ -71,23 +55,6 @@ function onHandshake(event){ // send to telegram bot sendMessage(message); - sendPhoto("/tmp/graph_handshake.png"); -} - -function onNewNode(event) { - var node = event.data; - - if(node.type != 'ssid' && node.type != 'ble_server' && graph.IsConnected(node.type, node.id)) { - createGraph(node.id, '/tmp/graph_node.png'); - - var message = '🖥️ Detected previously unknown ' + node.type + ':\n\n' + - 'Type: ' + node.type + "\n" + - 'MAC: ' + node.id; - - // send to telegram bot - sendMessage(message); - sendPhoto("/tmp/graph_node.png"); - } } function onGatewayChange(event) { diff --git a/graphpage.html b/graphpage.html deleted file mode 100644 index 8fe6210a..00000000 --- a/graphpage.html +++ /dev/null @@ -1,195 +0,0 @@ - - - - - - - - - - - - -
- - - - - - - \ No newline at end of file diff --git a/modules/events_stream/events_view.go b/modules/events_stream/events_view.go index ed0a5ec1..ce9479f2 100644 --- a/modules/events_stream/events_view.go +++ b/modules/events_stream/events_view.go @@ -127,8 +127,6 @@ func (mod *EventsStream) Render(output io.Writer, e session.Event) { mod.viewSynScanEvent(output, e) } else if e.Tag == "update.available" { 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" { diff --git a/modules/events_stream/events_view_graph.go b/modules/events_stream/events_view_graph.go deleted file mode 100644 index 6fc2cc7c..00000000 --- a/modules/events_stream/events_view_graph.go +++ /dev/null @@ -1,35 +0,0 @@ -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) - } -} diff --git a/modules/graph/create.go b/modules/graph/create.go deleted file mode 100644 index f4779df8..00000000 --- a/modules/graph/create.go +++ /dev/null @@ -1,184 +0,0 @@ -package graph - -import ( - "fmt" - "github.com/bettercap/bettercap/network" -) - -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(hex string, ssid string) (*Node, bool, error) { - node, err := mod.db.FindNode(SSID, hex) - isNew := node == nil - if err != nil { - return nil, false, err - } else if isNew { - if node, err = mod.db.CreateNode(SSID, hex, 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) { - ssidNode, ssidIsNew, err := mod.createDot11SSIDGraph(fmt.Sprintf("%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, ssidNode, ProbesFor, edgeStaleTime); err != nil { - return nil, false, nil, false, err - } else if probes_for == nil { - if probes_for, err = mod.db.CreateEdge(staNode, ssidNode, ProbesFor); err != nil { - return nil, false, nil, false, err - } - } - - if probed_by, err := mod.db.FindLastRecentEdgeOfType(ssidNode, staNode, ProbedBy, edgeStaleTime); err != nil { - return nil, false, nil, false, err - } else if probed_by == nil { - if probed_by, err = mod.db.CreateEdge(ssidNode, staNode, ProbedBy); err != nil { - return nil, false, nil, false, err - } - } - - return ssidNode, ssidIsNew, 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 -} diff --git a/modules/graph/edge.go b/modules/graph/edge.go deleted file mode 100644 index 4c6eff93..00000000 --- a/modules/graph/edge.go +++ /dev/null @@ -1,42 +0,0 @@ -package graph - -import ( - "fmt" - "github.com/bettercap/bettercap/session" - "time" -) - -type EdgeType string - -const ( - Is EdgeType = "is" - ProbesFor EdgeType = "probes_for" - ProbedBy EdgeType = "probed_by" - 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,omitempty"` -} - -func (e Edge) Dot(left, right *Node, width float64) string { - edgeLen := 1.0 - if e.Type == Is { - edgeLen = 0.3 - } - return fmt.Sprintf("\"%s\" -> \"%s\" [label=\"%s\", len=%.2f, penwidth=%.2f];", - left.String(), - right.String(), - e.Type, - edgeLen, - width) -} diff --git a/modules/graph/edges.go b/modules/graph/edges.go deleted file mode 100644 index eae8ac02..00000000 --- a/modules/graph/edges.go +++ /dev/null @@ -1,158 +0,0 @@ -package graph - -import ( - "encoding/json" - "github.com/evilsocket/islazy/fs" - "io/ioutil" - "os" - "path" - "sort" - "sync" - "time" -) - -const edgesIndexName = "edges.json" - -type EdgesTo map[string][]Edge - -type EdgesCallback func(string, []Edge, string) error - -type Edges struct { - sync.RWMutex - timestamp time.Time - fileName string - size int - from map[string]EdgesTo -} - -type edgesJSON struct { - Timestamp time.Time `json:"timestamp"` - Size int `json:"size"` - Edges map[string]EdgesTo `json:"edges"` -} - -func LoadEdges(basePath string) (*Edges, error) { - edges := Edges{ - fileName: path.Join(basePath, edgesIndexName), - from: make(map[string]EdgesTo), - } - - if fs.Exists(edges.fileName) { - var js edgesJSON - - if raw, err := ioutil.ReadFile(edges.fileName); err != nil { - return nil, err - } else if err = json.Unmarshal(raw, &js); err != nil { - return nil, err - } - - edges.timestamp = js.Timestamp - edges.from = js.Edges - edges.size = js.Size - } - - return &edges, nil -} - -func (e *Edges) flush() error { - e.timestamp = time.Now() - js := edgesJSON{ - Timestamp: e.timestamp, - Size: e.size, - Edges: e.from, - } - - if raw, err := json.Marshal(js); err != nil { - return err - } else if err = ioutil.WriteFile(e.fileName, raw, os.ModePerm); err != nil { - return err - } - - return nil -} - -func (e *Edges) Flush() error { - e.RLock() - defer e.RUnlock() - return e.flush() -} - -func (e *Edges) ForEachEdge(cb EdgesCallback) error { - e.RLock() - defer e.RUnlock() - - for from, edgesTo := range e.from { - for to, edges := range edgesTo { - if err := cb(from, edges, to); err != nil { - return err - } - } - } - - return nil -} - -func (e *Edges) ForEachEdgeFrom(nodeID string, cb EdgesCallback) error { - e.RLock() - defer e.RUnlock() - - if edgesTo, found := e.from[nodeID]; found { - for to, edges := range edgesTo { - if err := cb(nodeID, edges, to); err != nil { - return err - } - } - } - - return nil -} - -func (e *Edges) IsConnected(nodeID string) bool { - e.RLock() - defer e.RUnlock() - - if edgesTo, found := e.from[nodeID]; found { - return len(edgesTo) > 0 - } - - return false -} - -func (e *Edges) FindEdges(fromID, toID string, doSort bool) []Edge { - e.RLock() - defer e.RUnlock() - - if edgesTo, foundFrom := e.from[fromID]; foundFrom { - if edges, foundTo := edgesTo[toID]; foundTo { - if doSort { - // sort edges from oldest to newer - sort.Slice(edges, func(i, j int) bool { - return edges[i].CreatedAt.Before(edges[j].CreatedAt) - }) - } - return edges - } - } - - return nil -} - -func (e *Edges) Connect(fromID, toID string, edge Edge) error { - e.Lock() - defer e.Unlock() - - if edgesTo, foundFrom := e.from[fromID]; foundFrom { - edges := edgesTo[toID] - edges = append(edges, edge) - e.from[fromID][toID] = edges - } else { - // create the entire path - e.from[fromID] = EdgesTo{ - toID: {edge}, - } - } - - e.size++ - - return e.flush() -} diff --git a/modules/graph/graph.go b/modules/graph/graph.go deleted file mode 100644 index 1a32e017..00000000 --- a/modules/graph/graph.go +++ /dev/null @@ -1,425 +0,0 @@ -package graph - -import ( - "encoding/json" - "fmt" - "github.com/bettercap/bettercap/session" - "github.com/evilsocket/islazy/fs" - "path" - "sync" - "time" -) - -var Loaded = (* Graph)(nil) - -type NodeCallback func(*Node) -type EdgeCallback func(*Node, []Edge, *Node) - -type Graph struct { - sync.Mutex - - path string - edges *Edges -} - -func NewGraph(path string) (*Graph, error) { - if edges, err := LoadEdges(path); err != nil { - return nil, err - } else { - Loaded = &Graph{ - path: path, - edges: edges, - } - return Loaded, 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 { - if node, err := ReadNode(fileName); err != nil { - return 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 g.edges.ForEachEdge(func(fromID string, edges []Edge, toID string) error { - var left, right *Node - var err error - - leftFileName := path.Join(g.path, fromID+".json") - rightFileName := path.Join(g.path, toID+".json") - - if left, err = ReadNode(leftFileName); err != nil { - return err - } else if right, err = ReadNode(rightFileName); err != nil { - return err - } - - cb(left, edges, right) - - 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 - edges []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) - - // collect all edges starting from this node - err = g.edges.ForEachEdgeFrom(nodeID, func(_ string, edges []Edge, toID string) error { - rightFileName := path.Join(g.path, toID+".json") - if right, err := ReadNode(rightFileName); err != nil { - return err - } else { - // collect new node - if _, found := visited[toID]; !found { - stack.Push(right) - } - // collect all edges, we'll emit this later - allEdges = append(allEdges, edgeBucket{ - left: node, - edges: edges, - right: right, - }) - } - return nil - }) - } - } - - for _, edge := range allEdges { - onEdge(edge.left, edge.edges, edge.right) - } - } - - return nil -} - -func (g *Graph) IsConnected(nodeType string, nodeID string) bool { - return g.edges.IsConnected(fmt.Sprintf("%s_%s", nodeType, nodeID)) -} - -func (g *Graph) Dot(filter, layout, name string, disconnected bool) (string, int, int, error) { - size := 0 - discarded := 0 - - data := fmt.Sprintf("digraph %s {\n", name) - data += fmt.Sprintf(" layout=%s\n", layout) - - typeMap := make(map[NodeType]bool) - - type typeCount struct { - edge Edge - count int - } - - if err := g.Traverse(filter, func(node *Node) { - include := false - if disconnected { - include = true - } else { - include = g.edges.IsConnected(node.String()) - } - - if include { - size++ - typeMap[node.Type] = true - data += fmt.Sprintf(" %s\n", node.Dot(filter == node.ID)) - } else { - discarded++ - } - }, func(left *Node, edges []Edge, right *Node) { - // collect counters by edge type in order to calculate proportional widths - byType := make(map[string]typeCount) - tot := len(edges) - - for _, edge := range edges { - if c, found := byType[string(edge.Type)]; found { - c.count++ - } else { - byType[string(edge.Type)] = typeCount{ - edge: edge, - count: 1, - } - } - } - - max := 2.0 - for _, c := range byType { - w := max * float64(c.count/tot) - if w < 0.5 { - w = 0.5 - } - data += fmt.Sprintf(" %s\n", c.edge.Dot(left, right, w)) - } - }); err != nil { - return "", 0, 0, err - } - - /* - data += "\n" - data += "node [style=filled height=0.55 fontname=\"Verdana\" fontsize=10];\n" - data += "subgraph legend {\n" + - "graph[style=dotted];\n" + - "label = \"Legend\";\n" - - var types []NodeType - for nodeType, _ := range typeMap { - types = append(types, nodeType) - node := Node{ - Type: nodeType, - Annotations: nodeTypeDescs[nodeType], - Dummy: true, - } - data += fmt.Sprintf(" %s\n", node.Dot(false)) - } - - ntypes := len(types) - for i := 0; i < ntypes - 1; i++ { - data += fmt.Sprintf(" \"%s\" -> \"%s\" [style=invis];\n", types[i], types[i + 1]) - } - data += "}\n" - */ - - data += "\n" - data += " overlap=false\n" - data += "}" - - return data, size, discarded, nil -} - -func (g *Graph) JSON(filter string, disconnected bool) (string, int, int, error) { - size := 0 - discarded := 0 - - type link struct { - Source string `json:"source"` - Target string `json:"target"` - Edge interface{} `json:"edge"` - } - - type data struct { - Nodes []map[string]interface{} `json:"nodes"` - Links []link `json:"links"` - } - - jsData := data{ - Nodes: make([]map[string]interface{}, 0), - Links: make([]link, 0), - } - - if err := g.Traverse(filter, func(node *Node) { - include := false - if disconnected { - include = true - } else { - include = g.edges.IsConnected(node.String()) - } - - if include { - size++ - - if nm, err := node.ToMap(); err != nil { - panic(err) - } else { - // patch id - nm["id"] = node.String() - jsData.Nodes = append(jsData.Nodes, nm) - } - } else { - discarded++ - } - }, func(left *Node, edges []Edge, right *Node) { - for _, edge := range edges { - jsData.Links = append(jsData.Links, link{ - Source: left.String(), - Target: right.String(), - Edge: edge, - }) - } - }); err != nil { - return "", 0, 0, err - } - - if raw, err := json.Marshal(jsData); err != nil { - return "", 0, 0, err - } else { - return string(raw), size, discarded, 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) { - return ReadNode(nodeFileName) - } - - 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) { - if node, err := ReadNode(nodeFileName); err != nil { - return nil, 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, - Entity: entity, - Annotations: annotations, - } - - nodeFileName := path.Join(g.path, fmt.Sprintf("%s.json", node.String())) - if err := CreateNode(nodeFileName, node); err != nil { - return nil, err - } - - session.I.Events.Add("graph.node.new", node) - - return node, nil -} - -func (g *Graph) UpdateNode(node *Node) error { - g.Lock() - defer g.Unlock() - - nodeFileName := path.Join(g.path, fmt.Sprintf("%s.json", node.String())) - if err := UpdateNode(nodeFileName, node); err != nil { - return err - } - - return nil -} - -func (g *Graph) FindLastEdgeOfType(from, to *Node, edgeType EdgeType) (*Edge, error) { - edges := g.edges.FindEdges(from.String(), to.String(), true) - 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) { - edges := g.edges.FindEdges(from.String(), to.String(), true) - 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 - } - return &edge, nil - } - } - - return nil, nil -} - -func (g *Graph) CreateEdge(from, to *Node, edgeType EdgeType) (*Edge, error) { - edge := Edge{ - Type: edgeType, - CreatedAt: time.Now(), - } - - if session.I.GPS.Updated.IsZero() == false { - edge.Position = &session.I.GPS - } - - if err := g.edges.Connect(from.String(), to.String(), edge); err != nil { - return nil, err - } - - session.I.Events.Add("graph.edge.new", EdgeEvent{ - Left: from, - Edge: &edge, - Right: to, - }) - - return &edge, nil -} diff --git a/modules/graph/js_builtin.go b/modules/graph/js_builtin.go deleted file mode 100644 index 49252d47..00000000 --- a/modules/graph/js_builtin.go +++ /dev/null @@ -1,15 +0,0 @@ -package graph - -import ( - "github.com/bettercap/bettercap/log" -) - -type graphPackage struct{} - -func (g graphPackage) IsConnected(nodeType, nodeID string) bool { - if Loaded == nil { - log.Error("graph.IsConnected: graph not loaded") - return false - } - return Loaded.IsConnected(nodeType, nodeID) -} diff --git a/modules/graph/module.go b/modules/graph/module.go deleted file mode 100644 index 211d24ca..00000000 --- a/modules/graph/module.go +++ /dev/null @@ -1,348 +0,0 @@ -package graph - -import ( - "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/plugin" - "github.com/evilsocket/islazy/str" - "os" - "path/filepath" - "regexp" - "sync" - "time" -) - -const ( - ifaceAnnotation = "" - edgeStaleTime = time.Hour * 24 -) - -var privacyFilter = regexp.MustCompile("(?i)([a-f0-9]{2}):([a-f0-9]{2}):([a-f0-9]{2}):([a-f0-9]{2}):([a-f0-9]{2}):([a-f0-9]{2})") - -type dotSettings struct { - layout string - name string - output string -} - -type jsonSettings struct { - output string -} - -type settings struct { - path string - dot dotSettings - json jsonSettings - disconnected bool - privacy bool -} - -type Module struct { - session.SessionModule - - settings settings - db *Graph - gw *Node - iface *Node - eventBus session.EventBus - wLock sync.Mutex -} - -func init() { - plugin.Defines["graph"] = graphPackage{} -} - -func NewModule(s *session.Session) *Module { - mod := &Module{ - SessionModule: session.NewSessionModule("graph", s), - settings: settings{ - path: filepath.Join(caplets.InstallBase, "graph"), - dot: dotSettings{ - layout: "neato", - name: "bettergraph", - output: "bettergraph.dot", - }, - json: jsonSettings{ - output: "bettergraph.json", - }, - }, - } - - mod.AddParam(session.NewStringParameter("graph.path", - mod.settings.path, - "", - "Base path for the graph database.")) - - mod.AddParam(session.NewStringParameter("graph.dot.name", - mod.settings.dot.name, - "", - "Graph name in the dot output.")) - - mod.AddParam(session.NewStringParameter("graph.dot.layout", - mod.settings.dot.layout, - "", - "Layout for dot output.")) - - mod.AddParam(session.NewStringParameter("graph.dot.output", - mod.settings.dot.output, - "", - "File name for dot output.")) - - mod.AddParam(session.NewStringParameter("graph.json.output", - mod.settings.json.output, - "", - "File name for JSON output.")) - - mod.AddParam(session.NewBoolParameter("graph.disconnected", - "false", - "Include disconnected edges in then output graph.")) - - mod.AddParam(session.NewBoolParameter("graph.privacy", - "false", - "Obfuscate mac addresses.")) - - 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) - })) - - mod.AddHandler(session.NewModuleHandler("graph.to_json MAC?", - `graph\.to_json\s*([^\s]*)`, - "Generate a JSON 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.generateJSONGraph(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 " -} - -func (mod *Module) updateSettings() error { - var err error - - if err, mod.settings.dot.name = mod.StringParam("graph.dot.name"); err != nil { - return err - } else if err, mod.settings.dot.layout = mod.StringParam("graph.dot.layout"); err != nil { - return err - } else if err, mod.settings.dot.output = mod.StringParam("graph.dot.output"); err != nil { - return err - } else if err, mod.settings.json.output = mod.StringParam("graph.json.output"); err != nil { - return err - } else if err, mod.settings.disconnected = mod.BoolParam("graph.disconnected"); err != nil { - return err - } else if err, mod.settings.privacy = mod.BoolParam("graph.privacy"); 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 - } - } - - // only reload if needed - if mod.db != nil && mod.db.path != mod.settings.path { - mod.db = nil - } - - if mod.db == nil { - 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 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 - } - - // 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 - } - } - - // create relations if needed - if iface.HwAddress == gw.HwAddress { - if err = mod.connectAsSame(mod.gw, mod.iface); err != nil { - return err - } - } else { - 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) 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) - }) -} diff --git a/modules/graph/node.go b/modules/graph/node.go deleted file mode 100644 index 9f60b1cd..00000000 --- a/modules/graph/node.go +++ /dev/null @@ -1,170 +0,0 @@ -package graph - -import ( - "encoding/json" - "fmt" - "io/ioutil" - "os" - "strings" - "time" - "unicode" -) - -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 nodeTypeDescs = map[NodeType]string{ - SSID: "WiFI SSID probe", - BLEServer: "BLE Device", - Station: "WiFi Client", - AccessPoint: "WiFi AP", - Endpoint: "IP Client", - Gateway: "IP Gateway", -} - -var nodeDotStyles = map[NodeType]string{ - SSID: "shape=circle style=filled color=lightgray fillcolor=lightgray fixedsize=true penwidth=0.5", - 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"` - Dummy bool `json:"-"` -} - -func ReadNode(fileName string) (*Node, error) { - var node Node - if raw, err := ioutil.ReadFile(fileName); err != nil { - return nil, fmt.Errorf("error while reading %s: %v", fileName, err) - } else if err = json.Unmarshal(raw, &node); err != nil { - return nil, fmt.Errorf("error while decoding %s: %v", fileName, err) - } - return &node, nil -} - -func WriteNode(fileName string, node *Node, update bool) error { - if update { - node.UpdatedAt = time.Now() - } else { - node.CreatedAt = time.Now() - } - - if raw, err := json.Marshal(node); err != nil { - return fmt.Errorf("error creating data for %s: %v", fileName, err) - } else if err = ioutil.WriteFile(fileName, raw, os.ModePerm); err != nil { - return fmt.Errorf("error creating %s: %v", fileName, err) - } - return nil -} - -func CreateNode(fileName string, node *Node) error { - return WriteNode(fileName, node, false) -} - -func UpdateNode(fileName string, node *Node) error { - return WriteNode(fileName, node, true) -} - -func (n Node) String() string { - if n.Dummy == false { - return fmt.Sprintf("%s_%s", n.Type, n.ID) - } - return string(n.Type) -} - -func (n Node) Label() string { - if n.Dummy { - return n.Annotations - } - - switch n.Type { - case SSID: - s := n.Entity.(string) - allPrint := true - - for _, rn := range s { - if !unicode.IsPrint(rune(rn)) { - allPrint = false - break - } - } - - if !allPrint { - s = fmt.Sprintf("0x%x", s) - } - return s - 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(%s)", - n.Entity.(map[string]interface{})["hostname"].(string), - n.Entity.(map[string]interface{})["mac"].(string), - n.Entity.(map[string]interface{})["vendor"].(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("\"%s\" [%s, label=\"%s\"];", - n.String(), - style, - strings.ReplaceAll(n.Label(), "\"", "\\\"")) -} - -func (n Node) ToMap() (map[string]interface{}, error) { - var m map[string]interface{} - - if raw, err := json.Marshal(n); err != nil { - return nil, err - } else if err = json.Unmarshal(raw, &m); err != nil { - return nil, err - } - - return m, nil -} diff --git a/modules/graph/stack.go b/modules/graph/stack.go deleted file mode 100644 index cf22db2f..00000000 --- a/modules/graph/stack.go +++ /dev/null @@ -1,47 +0,0 @@ -package graph - -import "sync" - -type entry struct { - data interface{} - next *entry -} - -type Stack struct { - lock *sync.Mutex - head *entry - Size int -} - -func (stk *Stack) Push(data interface{}) { - stk.lock.Lock() - - element := new(entry) - 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 -} diff --git a/modules/graph/to_dot.go b/modules/graph/to_dot.go deleted file mode 100644 index e6f2b491..00000000 --- a/modules/graph/to_dot.go +++ /dev/null @@ -1,45 +0,0 @@ -package graph - -import ( - "io/ioutil" - "os" - "time" -) - -func (mod *Module) generateDotGraph(bssid string) error { - mod.wLock.Lock() - defer mod.wLock.Unlock() - - start := time.Now() - if err := mod.updateSettings(); err != nil { - return err - } - - data, size, discarded, err := mod.db.Dot(bssid, - mod.settings.dot.layout, - mod.settings.dot.name, - mod.settings.disconnected) - if err != nil { - return err - } - - if size > 0 { - if mod.settings.privacy { - data = privacyFilter.ReplaceAllString(data, "$1:$2:xx:xx:xx:xx") - } - - if err := ioutil.WriteFile(mod.settings.dot.output, []byte(data), os.ModePerm); err != nil { - return err - } else { - mod.Info("graph saved to %s in %v (%d edges, %d discarded)", - mod.settings.dot.output, - time.Since(start), - size, - discarded) - } - } else { - mod.Info("graph is empty") - } - - return nil -} \ No newline at end of file diff --git a/modules/graph/to_json.go b/modules/graph/to_json.go deleted file mode 100644 index dccd725a..00000000 --- a/modules/graph/to_json.go +++ /dev/null @@ -1,43 +0,0 @@ -package graph - -import ( - "io/ioutil" - "os" - "time" -) - -func (mod *Module) generateJSONGraph(bssid string) error { - mod.wLock.Lock() - defer mod.wLock.Unlock() - - start := time.Now() - if err := mod.updateSettings(); err != nil { - return err - } - - data, size, discarded, err := mod.db.JSON(bssid, mod.settings.disconnected) - if err != nil { - return err - } - - if size > 0 { - - if mod.settings.privacy { - data = privacyFilter.ReplaceAllString(data, "$1:$2:xx:xx:xx:xx") - } - - if err := ioutil.WriteFile(mod.settings.json.output, []byte(data), os.ModePerm); err != nil { - return err - } else { - mod.Info("graph saved to %s in %v (%d edges, %d discarded)", - mod.settings.json.output, - time.Since(start), - size, - discarded) - } - } else { - mod.Info("graph is empty") - } - - return nil -} \ No newline at end of file diff --git a/modules/modules.go b/modules/modules.go index 5bd481df..78d62e92 100644 --- a/modules/modules.go +++ b/modules/modules.go @@ -11,7 +11,6 @@ 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" @@ -64,7 +63,6 @@ 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))