├── 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 | --------------------------------------------------------------------------------