├── README.md
└── trust_explorer.py
/README.md:
--------------------------------------------------------------------------------
1 | DomainTrustExplorer
2 | ====================
3 |
4 | Python script for analyis of the "Trust.csv" file generated by Veil PowerView. Provides graph based analysis and output. The graph output will represent access direction (opposite of trust direction)
5 |
6 | Tested on Mac OSX with yEd for visualization.
7 | Parsing colors and labels in yEd (GraphML):
8 | -Edit->Properties Mapper
9 | -Add a configuration for node
10 | -Add a mapping for label/Label Text
11 | -Add a configuration for edge
12 | -Add a mapping for color/Line Color
13 |
14 | Setup & Requirements
15 | ====================
16 |
17 | pip install networkx
18 | trust_explorer.py (csvfile)
19 |
20 |
--------------------------------------------------------------------------------
/trust_explorer.py:
--------------------------------------------------------------------------------
1 | #!/usr/bin/python3
2 |
3 | ######
4 | # Author: @sixdub
5 | # Description: Shell based wrapper for networkx library that
6 | # provides functionality to analyze domain trust relationships
7 | # generated by powerview
8 | #
9 | ######
10 |
11 | import networkx as nx
12 | import matplotlib.pyplot as plt
13 | from cmd import Cmd
14 | import sys, os, argparse
15 |
16 | #Define my graph and the filename read in
17 | G = nx.DiGraph()
18 | filename=""
19 |
20 | #Instance of cmd.Cmd object which gives us the shell
21 | class GraphShell(Cmd):
22 | global G
23 | global filename
24 | last_output=''
25 |
26 | #Set our custom prompt
27 | def __init__(self):
28 | Cmd.__init__(self)
29 | self.prompt = "TrustExplore> "
30 |
31 | #Allow user to run local shell commands and print output
32 | def do_shell(self, line):
33 | "Run a shell command"
34 | if line:
35 | print("[*] Running shell command:", line)
36 | output = os.popen(line).read()
37 | print(output)
38 | self.last_output = output
39 | else:
40 | print("[-] Error: Cmd Needed")
41 |
42 | #Handle our commands for exit, Ctrl-D and EOF
43 | def do_exit(self,line):
44 | "Exit the application. Can also use Ctrl-D"
45 | return True
46 |
47 | def do_EOF(self, line):
48 | "Exit the application with Crtl-D"
49 | return True
50 |
51 | #Dump the graph in GML format
52 | def do_gml_dump(self, line):
53 | "Creates output as GML of access graph"
54 | ofile = filename+".gml"
55 | nx.write_gml(G, ofile)
56 | print("[*] %s written!"%ofile)
57 |
58 | #Dump the graph in GraphML format
59 | def do_graphml_dump(self, line):
60 | "Creates output as GraphML of access graph"
61 | ofile = filename+".graphml"
62 | nx.write_graphml(G, ofile)
63 | print("\n[*] %s written!"%ofile)
64 | print ("\n[*] Red = ParentChild, Green = External, Blue = CrossLink\n")
65 |
66 | def do_list_nodes(self,line):
67 | "List all nodes for the graph"
68 | print("[*] All nodes: ")
69 | for n in nx.nodes_iter(G):
70 | print(n)
71 |
72 | #Command go show all shortest paths
73 | def do_path(self, args):
74 | "Display the shortest path between two nodes"
75 |
76 | arglist = args.split(" ")
77 |
78 | if arglist[0] and arglist[1]:
79 | #Grab the args
80 | node1=arglist[0].upper()
81 | node2=arglist[1].upper()
82 | else:
83 | print("[-] Error: Args Needed")
84 |
85 | #ensure they exist
86 | if G.has_node(node1) and G.has_node(node2):
87 | if (nx.has_path(G,node1,node2)):
88 | print("[*] Shortest Paths from %s to %s" %(node1,node2))
89 | #Get the shortest paths
90 | paths = nx.all_shortest_paths(G, node1, node2)
91 |
92 | #Print all paths in pretty format
93 | for p in paths:
94 | outputpath = "[*] "
95 | for n in p:
96 | outputpath+=n+" -> "
97 | print(outputpath[:-4])
98 | else:
99 | print("[-] No path exist :(")
100 | else:
101 | print("[-] Node %s or %s does not exist :(" % (node1, node2))
102 |
103 | #Show all paths
104 | def do_all_paths(self,args):
105 | "Display all paths between two nodes"
106 |
107 | arglist = args.split(" ")
108 |
109 | if arglist[0] and arglist[1]:
110 | #Grab the args
111 | node1=arglist[0].upper()
112 | node2=arglist[1].upper()
113 | else:
114 | print("[-] Error: Args Needed")
115 |
116 | #ensure they exist
117 | if G.has_node(node1) and G.has_node(node2):
118 | if (nx.has_path(G,node1,node2)):
119 | print("[*] All Paths from %s to %s" %(node1,node2))
120 | #Get the shortest paths
121 | paths = nx.all_simple_paths(G, node1, node2)
122 |
123 | #Print all paths in pretty format
124 | for p in paths:
125 | outputpath = "[*] "
126 | for n in p:
127 | outputpath+=n+" -> "
128 | print(outputpath[:-4])
129 | else:
130 | print("[-] No path exist :(")
131 | else:
132 | print("[-] Node %s or %s does not exist :(" % (node1, node2))
133 |
134 | #Show all domains that can be reached from a source domain
135 | def do_connected(self, args):
136 | "Show all nodes able to be reached from a source node"
137 | if args:
138 | node = args.upper()
139 | if G.has_node(node):
140 | conn_count = 0
141 | print("[*] Domains reachable from \"%s\""%(node))
142 | for dest in G.nodes():
143 | if nx.has_path(G, node, dest):
144 | print(dest)
145 | conn_count+=1
146 | print("[*] %d domains reachable from source"%conn_count)
147 | else:
148 | print("[-] Error: No node in the graph")
149 | else:
150 | print("[-] Error: Args Needed")
151 |
152 | #Print all neighbors of a certain node
153 | def do_neighbors(self,args):
154 | "Show all the neighbors for a certain node"
155 |
156 | if args:
157 | node = args.upper()
158 | if G.has_node(node):
159 | l = G.neighbors(node)
160 |
161 | print("[*] Neighbors:")
162 | for n in l:
163 | print(n)
164 | else:
165 | print("[-] Error: No node in the graph")
166 | else:
167 | print("[-] Error: Args Needed")
168 |
169 | #print all isolated nodes
170 | def do_isolated(self, args):
171 | "Show all nodes that are isolated"
172 | print("[*] Isolated Nodes:")
173 | for n in G.nodes():
174 | if len(G.neighbors(n)) ==1:
175 | print(n)
176 |
177 | #calculate degree centrality and print top 5
178 | def do_center(self,args):
179 | "Show the top 5 most central nodes"
180 | d = nx.out_degree_centrality(G)
181 | cent_items=[(b,a) for (a,b) in d.iteritems()]
182 | cent_items.sort()
183 | cent_items.reverse()
184 | print("[*] Most Central Nodes")
185 | for i in range(0,5):
186 | if cent_items[i]:
187 | print(cent_items[i])
188 |
189 | #print some statistics
190 | def do_summary(self, args):
191 | "Show statistics on my trust map"
192 | ncount = len(G)
193 | ecount = G.number_of_edges()
194 | print("[*] Summary:")
195 | print("Filename: %s"%filename)
196 | print("Node Count: %d"%ncount)
197 | print("Edge Count: %d"%ecount)
198 |
199 | #notify the user if a node exist
200 | def do_is_node(self, args):
201 | "Tell the user if the node is in the graph"
202 | if args:
203 | print("[*] "+args.upper()+": "+G.has_node(args.upper()))
204 |
205 |
206 | if __name__ == '__main__':
207 |
208 | parser = argparse.ArgumentParser()
209 |
210 | parser.add_argument("-f", "--inputfile", help='Input .csv file to process.', required=True)
211 | parser.add_argument("-r", "--relationship", help='PARENTCHILD, EXTERNAL, CROSSLINK')
212 | parser.add_argument("-g", "--graphml", action='store_true', help='Dump graphml instead of proceeding to a shell.')
213 |
214 | args = parser.parse_args()
215 |
216 | # open our file
217 | with open(args.inputfile, "r") as f:
218 | data = f.readlines()
219 |
220 | # save the name for later
221 | filename = args.inputfile
222 | c = 0
223 |
224 | # print "\n[*] Red = ParentChild, Green = External, Blue = CrossLink\n"
225 |
226 | # for every line in our CSV
227 | for line in data[1:]:
228 | # strip off quotes and newline characters
229 | stripdata = line.replace('"',"").replace('\n','')
230 |
231 | # split the CSV and store normalized values
232 | values = stripdata.split(',')
233 | node1 = values[0].upper()
234 | node2 = values[1].upper()
235 | relationship = values[2].upper()
236 | edgetype=values[4].upper()
237 |
238 | if( (args.relationship and (args.relationship.upper() in relationship)) or (not args.relationship)):
239 |
240 | # assign edge colors based on the kind of relationship
241 | ecolor ='#000000'
242 |
243 | # blue
244 | if "CROSSLINK" in relationship:
245 | ecolor='#0000CC'
246 | # red
247 | elif "PARENTCHILD" in relationship:
248 | ecolor='#FF0000'
249 | # green
250 | elif "EXTERNAL" in relationship:
251 | ecolor='#009900'
252 |
253 | # Add the nodes with labels
254 | G.add_node(node1, label=node1)
255 | G.add_node(node2, label=node2)
256 |
257 | # add the edges to the graph
258 | if "BIDIRECTIONAL" in edgetype:
259 | G.add_edge(node1, node2, color=ecolor)
260 | G.add_edge(node2, node1, color=ecolor)
261 | elif "OUTBOUND" in edgetype:
262 | G.add_edge(node2, node1, color=ecolor)
263 | elif "INBOUND" in edgetype:
264 | G.add_edge(node1, node2, color=ecolor)
265 | else:
266 | print("[-] UNRECOGNIZED RELATIONSHIP DIRECTION")
267 | exit()
268 |
269 | c+=1
270 |
271 | if args.graphml:
272 | GraphShell().do_graphml_dump(args)
273 |
274 | else:
275 | print("[*] %d relationships read in... starting shell" % c)
276 | GraphShell().cmdloop()
277 |
--------------------------------------------------------------------------------