diff --git a/graphpage.html b/graphpage.html new file mode 100644 index 00000000..25147b44 --- /dev/null +++ b/graphpage.html @@ -0,0 +1,137 @@ + + + + + + + + + + + +
+ + + \ No newline at end of file diff --git a/modules/graph/create.go b/modules/graph/create.go new file mode 100644 index 00000000..a1dfceb5 --- /dev/null +++ b/modules/graph/create.go @@ -0,0 +1,176 @@ +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(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 +} diff --git a/modules/graph/dot.go b/modules/graph/dot.go new file mode 100644 index 00000000..e80c8ddb --- /dev/null +++ b/modules/graph/dot.go @@ -0,0 +1,37 @@ +package graph + +import ( + "io/ioutil" + "os" + "time" +) + +func (mod *Module) generateDotGraph(bssid string) error { + 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 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) + } + return nil +} \ No newline at end of file diff --git a/modules/graph/graph.go b/modules/graph/graph.go index d300d0a9..eecd96d9 100644 --- a/modules/graph/graph.go +++ b/modules/graph/graph.go @@ -1,6 +1,7 @@ package graph import ( + "encoding/json" "fmt" "github.com/bettercap/bettercap/session" "github.com/evilsocket/islazy/fs" @@ -204,28 +205,28 @@ func (g *Graph) Dot(filter, layout, name string, disconnected bool) (string, int } /* - 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" + 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, + 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)) } - 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" + 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" @@ -235,6 +236,66 @@ func (g *Graph) Dot(filter, layout, name string, disconnected bool) (string, int 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 || node.Type == SSID { // we don't create backwards edges for SSID + 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() diff --git a/modules/graph/json.go b/modules/graph/json.go new file mode 100644 index 00000000..f9562941 --- /dev/null +++ b/modules/graph/json.go @@ -0,0 +1,34 @@ +package graph + +import ( + "io/ioutil" + "os" + "time" +) + +func (mod *Module) generateJSONGraph(bssid string) error { + 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 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) + } + return nil +} \ No newline at end of file diff --git a/modules/graph/module.go b/modules/graph/module.go index c316625d..117e9dd0 100644 --- a/modules/graph/module.go +++ b/modules/graph/module.go @@ -1,14 +1,12 @@ 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" "regexp" @@ -22,11 +20,20 @@ const ( 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 - layout string - name string - output string + dot dotSettings + json jsonSettings disconnected bool privacy bool } @@ -45,10 +52,15 @@ 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", + path: filepath.Join(caplets.InstallBase, "graph"), + dot: dotSettings{ + layout: "neato", + name: "bettergraph", + output: "bettergraph.dot", + }, + json: jsonSettings{ + output: "bettergraph.json", + }, }, } @@ -58,25 +70,30 @@ func NewModule(s *session.Session) *Module { "Base path for the graph database.")) mod.AddParam(session.NewStringParameter("graph.dot.name", - mod.settings.name, + mod.settings.dot.name, "", "Graph name in the dot output.")) mod.AddParam(session.NewStringParameter("graph.dot.layout", - mod.settings.layout, + mod.settings.dot.layout, "", "Layout for dot output.")) mod.AddParam(session.NewStringParameter("graph.dot.output", - mod.settings.output, + mod.settings.dot.output, "", "File name for dot output.")) - mod.AddParam(session.NewBoolParameter("graph.dot.disconnected", + 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.dot.privacy", + mod.AddParam(session.NewBoolParameter("graph.privacy", "false", "Obfuscate mac addresses.")) @@ -103,6 +120,17 @@ func NewModule(s *session.Session) *Module { 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 } @@ -121,15 +149,17 @@ func (mod *Module) Author() string { func (mod *Module) updateSettings() error { var err error - if err, mod.settings.name = mod.StringParam("graph.dot.name"); err != nil { + if err, mod.settings.dot.name = mod.StringParam("graph.dot.name"); err != nil { return err - } else if err, mod.settings.layout = mod.StringParam("graph.dot.layout"); err != nil { + } else if err, mod.settings.dot.layout = mod.StringParam("graph.dot.layout"); err != nil { return err - } else if err, mod.settings.output = mod.StringParam("graph.dot.output"); err != nil { + } else if err, mod.settings.dot.output = mod.StringParam("graph.dot.output"); err != nil { return err - } else if err, mod.settings.disconnected = mod.BoolParam("graph.dot.disconnected"); err != nil { + } else if err, mod.settings.json.output = mod.StringParam("graph.json.output"); err != nil { return err - } else if err, mod.settings.privacy = mod.BoolParam("graph.dot.privacy"); err != nil { + } 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 @@ -213,203 +243,6 @@ func (mod *Module) Configure() (err error) { return nil } -func (mod *Module) generateDotGraph(bssid string) error { - start := time.Now() - if err := mod.updateSettings(); err != nil { - return err - } - - data, size, discarded, err := mod.db.Dot(bssid, mod.settings.layout, mod.settings.name, mod.settings.disconnected) - if err != nil { - return err - } - - if mod.settings.privacy { - data = privacyFilter.ReplaceAllString(data, "$1:$2:xx:xx:xx:xx") - } - - if err := ioutil.WriteFile(mod.settings.output, []byte(data), os.ModePerm); err != nil { - return err - } else { - mod.Info("graph saved to %s in %v (%d edges, %d discarded)", - mod.settings.output, - time.Since(start), - size, - discarded) - } - 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 diff --git a/modules/graph/node.go b/modules/graph/node.go index ed648426..314e0fd4 100644 --- a/modules/graph/node.go +++ b/modules/graph/node.go @@ -155,3 +155,15 @@ func (n Node) Dot(isTarget bool) string { style, 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 +} \ No newline at end of file