mirror of
https://github.com/bettercap/bettercap
synced 2025-07-16 10:03:39 -07:00
new: graph.to_json
This commit is contained in:
parent
e465f9b145
commit
1be487843b
7 changed files with 525 additions and 235 deletions
137
graphpage.html
Normal file
137
graphpage.html
Normal file
|
@ -0,0 +1,137 @@
|
||||||
|
<head>
|
||||||
|
<style> body {
|
||||||
|
margin: 0;
|
||||||
|
} </style>
|
||||||
|
<script src="//unpkg.com/3d-force-graph"></script>
|
||||||
|
<script src="//unpkg.com/three"></script>
|
||||||
|
<script src="//unpkg.com/three/examples/js/renderers/CSS2DRenderer.js"></script>
|
||||||
|
<script src="//unpkg.com/three-spritetext"></script>
|
||||||
|
|
||||||
|
<style>
|
||||||
|
.node-label {
|
||||||
|
font-size: 1rem;
|
||||||
|
padding: 1px 4px;
|
||||||
|
border-radius: 4px;
|
||||||
|
background-color: rgba(0, 0, 0, 0.5);
|
||||||
|
user-select: none;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
</head>
|
||||||
|
|
||||||
|
<body>
|
||||||
|
<div id="3d-graph"></div>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
|
||||||
|
const typeColors = {
|
||||||
|
'ble_server': '#0066ff',
|
||||||
|
|
||||||
|
'ssid': '#ffff99',
|
||||||
|
'station': '#ffcc33',
|
||||||
|
'access_point': '#ff9900',
|
||||||
|
|
||||||
|
'endpoint': '#33cc33',
|
||||||
|
'gateway': '#006600'
|
||||||
|
};
|
||||||
|
|
||||||
|
const Graph = ForceGraph3D({
|
||||||
|
extraRenderers: [new THREE.CSS2DRenderer()]
|
||||||
|
})
|
||||||
|
(document.getElementById('3d-graph'))
|
||||||
|
.jsonUrl('bettergraph.json')
|
||||||
|
.nodeLabel('id')
|
||||||
|
.nodeColor(node => typeColors[node.type])
|
||||||
|
.linkDirectionalArrowLength(3.5)
|
||||||
|
.linkDirectionalArrowRelPos(1)
|
||||||
|
/*
|
||||||
|
.linkThreeObjectExtend(true)
|
||||||
|
.linkThreeObject(link => {
|
||||||
|
const sprite = new SpriteText(link.edge.type);
|
||||||
|
sprite.color = 'lightgrey';
|
||||||
|
sprite.textHeight = 1.5;
|
||||||
|
return sprite;
|
||||||
|
})
|
||||||
|
.linkPositionUpdate((sprite, {start, end}) => {
|
||||||
|
const middlePos = Object.assign(...['x', 'y', 'z'].map(c => ({
|
||||||
|
[c]: start[c] + (end[c] - start[c]) / 2 // calc middle point
|
||||||
|
})));
|
||||||
|
Object.assign(sprite.position, middlePos);
|
||||||
|
})
|
||||||
|
*/
|
||||||
|
.nodeThreeObject(node => {
|
||||||
|
const nodeEl = document.createElement('div');
|
||||||
|
|
||||||
|
switch (node.type) {
|
||||||
|
case 'ssid':
|
||||||
|
nodeEl.innerHTML = `<small>${node.entity}</small>`
|
||||||
|
break;
|
||||||
|
|
||||||
|
case 'access_point':
|
||||||
|
var ap = node.entity;
|
||||||
|
nodeEl.innerHTML = `
|
||||||
|
<center>
|
||||||
|
<b>${ap.hostname}</b> (${ap.encryption})
|
||||||
|
<br/>
|
||||||
|
${ap.mac}
|
||||||
|
${ap.vendor? '<br/>(' + ap.vendor + ')' : ''}
|
||||||
|
${ap.wps.length? '<br/>' + JSON.stringify(ap.wps) : ''}
|
||||||
|
</center>`;
|
||||||
|
break;
|
||||||
|
|
||||||
|
case 'station':
|
||||||
|
var sta = node.entity;
|
||||||
|
nodeEl.innerHTML = `
|
||||||
|
<center>
|
||||||
|
${sta.mac}
|
||||||
|
${sta.vendor? '<br/>(' + sta.vendor + ')' : ''}
|
||||||
|
</center>`;
|
||||||
|
break;
|
||||||
|
|
||||||
|
case 'ble_server':
|
||||||
|
var dev = node.entity;
|
||||||
|
nodeEl.innerHTML = `
|
||||||
|
<center>
|
||||||
|
${dev.mac}
|
||||||
|
${dev.vendor? '<br/>(' + dev.vendor + ')' : ''}
|
||||||
|
</center>`;
|
||||||
|
break;
|
||||||
|
|
||||||
|
case 'endpoint':
|
||||||
|
var ip = node.entity;
|
||||||
|
nodeEl.innerHTML = `
|
||||||
|
<center>
|
||||||
|
${ip.hostname? '<b>' + ip.hostname + '</b><br/>' : ''}
|
||||||
|
${ip.ipv4? ip.ipv4 + '<br/>' : ''}
|
||||||
|
${ip.ipv6? ip.ipv6 + '<br/>' : ''}
|
||||||
|
<br/>
|
||||||
|
${ip.mac}
|
||||||
|
${ip.vendor? '<br/>(' + ip.vendor + ')' : ''}
|
||||||
|
${ip.meta.values.length? '<br/>' + JSON.stringify(ip.meta.values) : ''}
|
||||||
|
</center>`;
|
||||||
|
break;
|
||||||
|
|
||||||
|
case 'gateway':
|
||||||
|
var ip = node.entity;
|
||||||
|
nodeEl.innerHTML = `
|
||||||
|
<center>
|
||||||
|
${ip.hostname? '<b>' + ip.hostname + '</b><br/>' : ''}
|
||||||
|
${ip.ipv4? ip.ipv4 + '<br/>' : ''}
|
||||||
|
${ip.ipv6? ip.ipv6 + '<br/>' : ''}
|
||||||
|
<br/>
|
||||||
|
${ip.mac}
|
||||||
|
${ip.vendor? '<br/>(' + ip.vendor + ')' : ''}
|
||||||
|
${ip.meta.values.length? '<br/>' + JSON.stringify(ip.meta.values) : ''}
|
||||||
|
</center>`;
|
||||||
|
break;
|
||||||
|
|
||||||
|
default:
|
||||||
|
nodeEl.innerHTML = `<b>${node.id}</b>`
|
||||||
|
}
|
||||||
|
|
||||||
|
nodeEl.style.color = typeColors[node.type];
|
||||||
|
nodeEl.className = 'node-label';
|
||||||
|
return new THREE.CSS2DObject(nodeEl);
|
||||||
|
})
|
||||||
|
.nodeThreeObjectExtend(true);
|
||||||
|
</script>
|
||||||
|
</body>
|
176
modules/graph/create.go
Normal file
176
modules/graph/create.go
Normal file
|
@ -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
|
||||||
|
}
|
37
modules/graph/dot.go
Normal file
37
modules/graph/dot.go
Normal file
|
@ -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
|
||||||
|
}
|
|
@ -1,6 +1,7 @@
|
||||||
package graph
|
package graph
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"encoding/json"
|
||||||
"fmt"
|
"fmt"
|
||||||
"github.com/bettercap/bettercap/session"
|
"github.com/bettercap/bettercap/session"
|
||||||
"github.com/evilsocket/islazy/fs"
|
"github.com/evilsocket/islazy/fs"
|
||||||
|
@ -204,28 +205,28 @@ func (g *Graph) Dot(filter, layout, name string, disconnected bool) (string, int
|
||||||
}
|
}
|
||||||
|
|
||||||
/*
|
/*
|
||||||
data += "\n"
|
data += "\n"
|
||||||
data += "node [style=filled height=0.55 fontname=\"Verdana\" fontsize=10];\n"
|
data += "node [style=filled height=0.55 fontname=\"Verdana\" fontsize=10];\n"
|
||||||
data += "subgraph legend {\n" +
|
data += "subgraph legend {\n" +
|
||||||
"graph[style=dotted];\n" +
|
"graph[style=dotted];\n" +
|
||||||
"label = \"Legend\";\n"
|
"label = \"Legend\";\n"
|
||||||
|
|
||||||
var types []NodeType
|
var types []NodeType
|
||||||
for nodeType, _ := range typeMap {
|
for nodeType, _ := range typeMap {
|
||||||
types = append(types, nodeType)
|
types = append(types, nodeType)
|
||||||
node := Node{
|
node := Node{
|
||||||
Type: nodeType,
|
Type: nodeType,
|
||||||
Annotations: nodeTypeDescs[nodeType],
|
Annotations: nodeTypeDescs[nodeType],
|
||||||
Dummy: true,
|
Dummy: true,
|
||||||
|
}
|
||||||
|
data += fmt.Sprintf(" %s\n", node.Dot(false))
|
||||||
}
|
}
|
||||||
data += fmt.Sprintf(" %s\n", node.Dot(false))
|
|
||||||
}
|
|
||||||
|
|
||||||
ntypes := len(types)
|
ntypes := len(types)
|
||||||
for i := 0; i < ntypes - 1; i++ {
|
for i := 0; i < ntypes - 1; i++ {
|
||||||
data += fmt.Sprintf(" \"%s\" -> \"%s\" [style=invis];\n", types[i], types[i + 1])
|
data += fmt.Sprintf(" \"%s\" -> \"%s\" [style=invis];\n", types[i], types[i + 1])
|
||||||
}
|
}
|
||||||
data += "}\n"
|
data += "}\n"
|
||||||
*/
|
*/
|
||||||
|
|
||||||
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
|
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) {
|
func (g *Graph) FindNode(t NodeType, id string) (*Node, error) {
|
||||||
g.Lock()
|
g.Lock()
|
||||||
defer g.Unlock()
|
defer g.Unlock()
|
||||||
|
|
34
modules/graph/json.go
Normal file
34
modules/graph/json.go
Normal file
|
@ -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
|
||||||
|
}
|
|
@ -1,14 +1,12 @@
|
||||||
package graph
|
package graph
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"fmt"
|
|
||||||
"github.com/bettercap/bettercap/caplets"
|
"github.com/bettercap/bettercap/caplets"
|
||||||
"github.com/bettercap/bettercap/modules/wifi"
|
"github.com/bettercap/bettercap/modules/wifi"
|
||||||
"github.com/bettercap/bettercap/network"
|
"github.com/bettercap/bettercap/network"
|
||||||
"github.com/bettercap/bettercap/session"
|
"github.com/bettercap/bettercap/session"
|
||||||
"github.com/evilsocket/islazy/fs"
|
"github.com/evilsocket/islazy/fs"
|
||||||
"github.com/evilsocket/islazy/str"
|
"github.com/evilsocket/islazy/str"
|
||||||
"io/ioutil"
|
|
||||||
"os"
|
"os"
|
||||||
"path/filepath"
|
"path/filepath"
|
||||||
"regexp"
|
"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})")
|
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 {
|
type settings struct {
|
||||||
path string
|
path string
|
||||||
layout string
|
dot dotSettings
|
||||||
name string
|
json jsonSettings
|
||||||
output string
|
|
||||||
disconnected bool
|
disconnected bool
|
||||||
privacy bool
|
privacy bool
|
||||||
}
|
}
|
||||||
|
@ -45,10 +52,15 @@ func NewModule(s *session.Session) *Module {
|
||||||
mod := &Module{
|
mod := &Module{
|
||||||
SessionModule: session.NewSessionModule("graph", s),
|
SessionModule: session.NewSessionModule("graph", s),
|
||||||
settings: settings{
|
settings: settings{
|
||||||
path: filepath.Join(caplets.InstallBase, "graph"),
|
path: filepath.Join(caplets.InstallBase, "graph"),
|
||||||
layout: "neato",
|
dot: dotSettings{
|
||||||
name: "bettergraph",
|
layout: "neato",
|
||||||
output: "bettergraph.dot",
|
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."))
|
"Base path for the graph database."))
|
||||||
|
|
||||||
mod.AddParam(session.NewStringParameter("graph.dot.name",
|
mod.AddParam(session.NewStringParameter("graph.dot.name",
|
||||||
mod.settings.name,
|
mod.settings.dot.name,
|
||||||
"",
|
"",
|
||||||
"Graph name in the dot output."))
|
"Graph name in the dot output."))
|
||||||
|
|
||||||
mod.AddParam(session.NewStringParameter("graph.dot.layout",
|
mod.AddParam(session.NewStringParameter("graph.dot.layout",
|
||||||
mod.settings.layout,
|
mod.settings.dot.layout,
|
||||||
"",
|
"",
|
||||||
"Layout for dot output."))
|
"Layout for dot output."))
|
||||||
|
|
||||||
mod.AddParam(session.NewStringParameter("graph.dot.output",
|
mod.AddParam(session.NewStringParameter("graph.dot.output",
|
||||||
mod.settings.output,
|
mod.settings.dot.output,
|
||||||
"",
|
"",
|
||||||
"File name for 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",
|
"false",
|
||||||
"Include disconnected edges in then output graph."))
|
"Include disconnected edges in then output graph."))
|
||||||
|
|
||||||
mod.AddParam(session.NewBoolParameter("graph.dot.privacy",
|
mod.AddParam(session.NewBoolParameter("graph.privacy",
|
||||||
"false",
|
"false",
|
||||||
"Obfuscate mac addresses."))
|
"Obfuscate mac addresses."))
|
||||||
|
|
||||||
|
@ -103,6 +120,17 @@ func NewModule(s *session.Session) *Module {
|
||||||
return mod.generateDotGraph(bssid)
|
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
|
return mod
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -121,15 +149,17 @@ func (mod *Module) Author() string {
|
||||||
func (mod *Module) updateSettings() error {
|
func (mod *Module) updateSettings() error {
|
||||||
var err 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
|
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
|
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
|
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
|
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
|
return err
|
||||||
} else if err, mod.settings.path = mod.StringParam("graph.path"); err != nil {
|
} else if err, mod.settings.path = mod.StringParam("graph.path"); err != nil {
|
||||||
return err
|
return err
|
||||||
|
@ -213,203 +243,6 @@ func (mod *Module) Configure() (err error) {
|
||||||
return nil
|
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) {
|
func (mod *Module) onEvent(e session.Event) {
|
||||||
var entities []*Node
|
var entities []*Node
|
||||||
|
|
||||||
|
|
|
@ -155,3 +155,15 @@ func (n Node) Dot(isTarget bool) string {
|
||||||
style,
|
style,
|
||||||
n.Label())
|
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
|
||||||
|
}
|
Loading…
Add table
Add a link
Reference in a new issue