├── README.md ├── ezVolGraph.sh ├── old ├── ezvol-plaintext.sh └── volCombine-v1.3.4.py ├── sampledata ├── readme.md ├── volGraph-1551571181-dot.png └── volGraph-1551571385-dot.png └── volGraph.py /README.md: -------------------------------------------------------------------------------- 1 | # volatilityGrapher 2 | Force-directed graph generator for Volatility visualizations 3 | - Requires Python 3, GraphViz, and Volatility 4 | - v1.5.4 (01 Mar 2019) 5 | - added apihooks, connscan support (for pre-Vista network connections) 6 | - fixed an issue where red malfind nodes could be converted back to orange 7 | - re-enabled showing the first few malfind bytes 8 | - v1.5.3 (15 Oct 2018) 9 | - added cmdline support, fixed this description 10 | - v1.5.2 (09 Oct 2018) 11 | - totally re-written for Python 3 12 | - absolutely requires JSON input from Volatility 13 | - supports Volatility's netscan module 14 | - no longer requires pslist; everything will be blue if you don't include it though 15 | - ezVolGraph.sh is a quick-and-dirty way to automate the JSON and graph-making process (see below under Usage) 16 | - fixed a bug where PIDs wouldn't display properly on nodes 17 | 18 | ## Supports visualizing JSON output from the following Volatility modules (note - this is NOT a Volatility plugin!) 19 | pslist, psscan, envars, malfind, netscan (Vista+), connscan (XP/2k3), cmdline, apihooks 20 | - the module name MUST be somewhere in the input filename 21 | 22 | ## Workflow Concept 23 | - collect memory --> run Volatility modules specifying JSON output --> send module output through volGraph.py 24 | 25 | ## volGraph.py Overview 26 | - blue lines and cyan nodes mean the relationship was found in psscan, but not pslist (future: psxview usage) 27 | - yellow nodes mean that process was found in apihooks as having one module that hooked another 28 | - orange nodes mean the process was in malfind, without MZ 29 | - red nodes mean the process was in malfind, with MZ (4d5a) 30 | - Colorization is purely based on what's found in psscan, apihooks, and malfind outputs 31 | 32 | ## To get JSON output from Volatility: 33 | Add these switches: ```--output=json [module] --output-file=[module]-[youroutputname].json``` 34 | 35 | ## Usage 36 | ### The module name for each JSON file MUST be somewhere in the filename! 37 | - Basic with only pslist ```volGraph.py pslist.json``` 38 | - With supported inputs: ```volGraph.py pslist.json envars.json psscan.json ... ``` 39 | - Glob input: ```volGraph.py *.json``` 40 | - Easy mode, use the provided Bash script with a memory capture and a Volatility profile to generate all of the necessary files: ```ezVolGraph.sh somefile.dmp profile``` 41 | ### Note that cmdline might make the nodes very large (they will be linewrapped... eventually) 42 | 43 | 44 | ## TODO: 45 | - dedup code, better classes, subgrouping, modules 46 | - add psxview support 47 | 48 | ## Example output: 49 | ### Powershell Empire on Win7: 50 | ![volGraph.py](https://github.com/bonifield/volatilityGrapher/blob/master/sampledata/volGraph-1551571181-dot.png) 51 | ### Meterpreter on WinXP: 52 | ![volGraph.py](https://github.com/bonifield/volatilityGrapher/blob/master/sampledata/volGraph-1551571385-dot.png) 53 | 54 | -------------------------------------------------------------------------------- /ezVolGraph.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | # last updated 01 Mar 2019, use with 1.5.x of volGraph.py 3 | 4 | echo 5 | echo "Usage: ezvol.sh somefile.dmp profile" 6 | echo "Running. This may take a minute..." 7 | vol.py -f $1 --profile=$2 --output=json pslist --output-file=$1-$2-pslist.json 2> /dev/null 8 | vol.py -f $1 --profile=$2 --output=json psscan --output-file=$1-$2-psscan.json 2> /dev/null 9 | vol.py -f $1 --profile=$2 --output=json envars --output-file=$1-$2-envars.json 2> /dev/null 10 | vol.py -f $1 --profile=$2 --output=json malfind --output-file=$1-$2-malfind.json 2> /dev/null 11 | vol.py -f $1 --profile=$2 --output=json netscan --output-file=$1-$2-netscan.json 2> /dev/null 12 | vol.py -f $1 --profile=$2 --output=json cmdline --output-file=$1-$2-cmdline.json 2> /dev/null 13 | vol.py -f $1 --profile=$2 --output=json connscan --output-file=$1-$2-connscan.json 2> /dev/null 14 | read -e -p "Run apihooks? It may take quite a while. (y/n) " yesno 15 | if [[ $yesno =~ [Yy] ]]; then 16 | vol.py -f $1 --profile=$2 --output=json apihooks --output-file=$1-$2-apihooks.json 2> /dev/null 17 | else 18 | echo "skipping apihooks" 19 | fi 20 | volGraph.py $1-$2-*.json 21 | echo "...done." 22 | echo 23 | -------------------------------------------------------------------------------- /old/ezvol-plaintext.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | # v1.0 - to assist with volCombine.py v1.3.4 3 | # note this script uses "volatility" vs "vol.py" 4 | 5 | if [ $# -eq 0 ]; then 6 | echo "Usage: ezvol.sh your-memory-dump.dmp" 7 | exit 1 8 | else 9 | if [ $(which volatility | wc -l) -gt 0 ]; then 10 | fileName=$1 11 | else 12 | echo "volatility not present or not in your system path (do you have vol.py instead of volatility?)" 13 | exit 2 14 | fi 15 | 16 | echo -e "\n---==== ezvol ====---" 17 | 18 | echo -ne "running imageinfo\r" 19 | 20 | ii="$fileName-imageinfo.txt" 21 | volatility -f $fileName imageinfo > $ii 2> /dev/null 22 | prof=`grep 'Profile' $ii | sed -e 's/ \+//g' | cut -d ':' -f2 | cut -d ',' -f1` 23 | 24 | pl="$fileName-$prof-pslist.txt" 25 | ps="$fileName-$prof-psscan.txt" 26 | mf="$fileName-$prof-malfind.txt" 27 | en="$fileName-$prof-envars.txt" 28 | cn="$fileName-$prof-connections.txt" 29 | cs="$fileName-$prof-connscan.txt" 30 | ss="$fileName-$prof-sockets.txt" 31 | so="$fileName-$prof-sockscan.txt" 32 | ns="$fileName-$prof-netscan.txt" 33 | 34 | echo -ne "found best profile: $prof\r\n" 35 | echo -ne "running pslist \r" 36 | volatility -f $fileName --profile=$prof pslist > $pl 2> /dev/null 37 | echo -ne "running psscan \r" 38 | volatility -f $fileName --profile=$prof psscan > $ps 2> /dev/null 39 | echo -ne "running malfind \r" 40 | volatility -f $fileName --profile=$prof malfind > $mf 2> /dev/null 41 | echo -ne "running envars \r" 42 | volatility -f $fileName --profile=$prof envars > $en 2> /dev/null 43 | 44 | if [[ $prof =~ "WinXP" || $prof =~ "Win2003" ]]; then 45 | echo -ne "running connections \r" 46 | volatility -f $fileName --profile=$prof connections > $cn 2> /dev/null 47 | echo -ne "running connscan \r" 48 | volatility -f $fileName --profile=$prof connscan > $cs 2> /dev/null 49 | echo -ne "running sockets \r" 50 | volatility -f $fileName --profile=$prof sockets > $ss 2> /dev/null 51 | echo -ne "running sockscan \r" 52 | volatility -f $fileName --profile=$prof sockscan > $so 2> /dev/null 53 | elif [[ $prof =~ "Win7" || $prof =~ "Win2008" || $prof =~ "Win2012" ]] 54 | echo -ne "running netscan \r" 55 | volatility -f $fileName --profile=$prof netscan > $ns 2> /dev/null 56 | fi 57 | 58 | echo -ne "done \r\n" 59 | ls $fileName*.txt 60 | echo 61 | 62 | if [ $(which dot | wc -l) -gt 0 ]; then 63 | if [ $(which volCombine.py | wc -l) -gt 0 ]; then 64 | python volCombine.py $pl $ps $mf $en 65 | else 66 | echo "did not find volCombine.py in the system path... may need to type: export PATH=\$PATH:/your/volCombine/folder/" 67 | echo 68 | exit 3 69 | fi 70 | else 71 | echo "did not find dot (part of graphviz)" 72 | exit 4 73 | fi 74 | 75 | exit 0 76 | -------------------------------------------------------------------------------- /old/volCombine-v1.3.4.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/python 2 | 3 | #============== 4 | # volCombine.py v1.3.4 5 | # last update: 17 Aug 2018 6 | # Feed the script plaintext output from pslist, envars, psscan, and malfind (Volatility modules) 7 | # Requires pslist.txt at a minimum 8 | # Using both pslist and psscan helps to QUICKLY identify deltas between the two files (unlinked EPROCESS trees are not in pslist) 9 | # --Blue lines mean a new link was found in psscan that wasn't in pslist 10 | # --Cyan nodes mean that process was found in psscan that wasn't in pslist (malfind colors will override this, link line remains blue) 11 | # ----Orange nodes mean the process was found in malfind without MZ (4d5a) 12 | # ----Red nodes mean the process was found in malfind with MZ 13 | # Processing order (not impacted by the order these files are presented to the script): pslist --> psscan --> envars --> malfind 14 | # Usage: volCombine.py pslist.txt 15 | # Usage: volCombine.py pslist.txt envars.txt psscan.txt malfind.txt 16 | # TODO: dedup code usage, add classes 17 | # - v1.3.3 (16 Aug 2017) - added funky() to remove some reptitive lines 18 | # - v1.3.4 (17 Aug 2018, yes 2018) - added psscan_fixer() for Volatility profile Win10x64_17134 19 | #============== 20 | 21 | import os,re,sys,time 22 | from itertools import islice 23 | 24 | syslist = sys.argv 25 | psli = '' 26 | enva = '' 27 | pssc = '' 28 | malf = '' 29 | epoch = int(time.time()) 30 | 31 | #============== 32 | 33 | if len(syslist) > 1: 34 | for i in syslist: 35 | if re.search('pslist', i): 36 | psli = i 37 | if re.search('envar', i): 38 | enva = i 39 | if re.search('psscan', i): 40 | pssc = i 41 | if re.search('malf', i): 42 | malf = i 43 | 44 | else: 45 | print 46 | print 'You need at least pslist.txt and one or more of the following: envars.txt, psscan.txt, malfind.txt' 47 | print 'Usage examples:' 48 | print 'volCombine.py pslist.txt' 49 | print 'volCombine.py pslist.txt envars.txt' 50 | print 'volCombine.py pslist.txt envars.txt psscan.txt' 51 | print 'volCombine.py pslist.txt envars.txt psscan.txt malfind.txt' 52 | print 53 | sys.exit(1) 54 | 55 | #============== 56 | 57 | dotFile = str('combine-'+str(epoch)+'.dot') 58 | dotOutputFile = str(dotFile.replace('.dot','.png')) 59 | #circoOutputFile = str(psli.replace('.txt','-')+enva.replace('.txt','-')+'circo.png') 60 | #neatoOutputFile = str(psli.replace('.txt','-')+enva.replace('.txt','-')+'neato.png') 61 | 62 | #============== 63 | 64 | listy = [] 65 | dicty = {} 66 | d = {} 67 | 68 | #============== 69 | 70 | def funky(d,k,v): 71 | if k not in d.keys(): 72 | d.setdefault(k,[]) 73 | d[k].append(v) 74 | elif k in d.keys(): 75 | d[k].append(v) 76 | 77 | def psscan_fixer(p): 78 | h = [p.index(i) for i in p if re.findall('0x[A-Za-z0-9]{16}', i)] 79 | return [p[0], ' '.join(p[h[1]+1:]), p[h[1]-2], p[h[1]-1], p[h[1]]] 80 | 81 | def makeGraph(): 82 | print 'Making combined graph. This may take a second...' 83 | 84 | if psli: 85 | with open(psli,'r') as f: 86 | for i,line in enumerate(f): 87 | k = '' 88 | name = '' 89 | if i >= 2: 90 | l = line.split() 91 | name = l[1] 92 | pid = l[2] 93 | ppid = l[3] 94 | listy.append('"%s" -> "%s";\n' % (ppid, pid)) 95 | k = pid 96 | funky(dicty,k,name) 97 | f.close() 98 | else: 99 | print 100 | print 'You need pslist.txt at a minumum to run this script!' 101 | print 102 | sys.exit(1) 103 | 104 | if pssc: 105 | with open(pssc, 'r') as f: 106 | for i, line in enumerate(f): 107 | pid = '' 108 | st = '' 109 | et = '' 110 | if i >= 2: 111 | # for use with Volatility profile Win10x64_17134 112 | l = psscan_fixer(line.split()) 113 | #l = line.split() 114 | name = l[1] 115 | pid = l[2] 116 | ppid = l[3] 117 | if len(l) > 5: # if there is a start time 118 | st = str('Start: '+l[5]+' '+l[6]+' '+l[7][:3]) 119 | if len(l) > 8: # if there is an exit time 120 | et = str('Exit: '+l[8]+' '+l[9]+' '+l[10][:3]) 121 | slist = str('"%s" -> "%s";\n' % (ppid, pid)) 122 | try: 123 | if slist not in listy: 124 | listy.append('"%s" -> "%s" [color="blue", penwidth=3];\n' % (ppid, pid)) # append a new link if found in psscan but not in pslist 125 | except: 126 | pass 127 | if pid not in dicty.keys(): 128 | dicty.setdefault(pid,[]) 129 | if not et: 130 | dicty[pid].extend((name, st, 'b')) # add name and orange flag if the pid is only found in psscan 131 | else: 132 | dicty[pid].extend((name, st, et, 'b')) # double parentheses because extend takes one argument 133 | elif pid in dicty.keys(): 134 | if not et: 135 | dicty[pid].append(st) 136 | else: 137 | dicty[pid].extend((st, et)) 138 | f.close() 139 | 140 | if enva: 141 | with open(enva, 'r') as f: 142 | u = re.compile('USERNAME') 143 | for i,line in enumerate(f): 144 | pid = '' 145 | username = '' 146 | if i >= 2: 147 | if u.findall(line): 148 | l = line.split() 149 | pid = l[0] 150 | username = ' '.join(l[4:]) 151 | funky(dicty,pid,username) 152 | f.close() 153 | 154 | if malf: 155 | with open(malf, 'r') as f: 156 | p = re.compile('Process') 157 | mz = re.compile('4d5a', re.I) 158 | for line in f: 159 | if p.match(line): 160 | try: 161 | l = line.split() 162 | pid = l[3] 163 | v = '' 164 | a = str(''.join(list(islice(f, 4)))).replace('\r\n','').replace(' ','') # islice returns line + 4 as an object, show that object as a list, wrangle it into form 165 | if mz.findall(a): 166 | v = str('r') 167 | else: 168 | v = str('o') 169 | funky(d,pid,v) 170 | except: 171 | pass 172 | f.close() 173 | 174 | for k in d.keys(): 175 | if len(k) == 0: # delete blank keys 176 | del d[k] 177 | val = list(set(d[k])) # make a uniq value list 178 | if len(val) >= 2: # if 'o' and 'r' are both in the value list 179 | if 'o' in val: 180 | val.remove('o') # delete 'o', meaning the node will be prioritized red 181 | d[k]=val 182 | 183 | for k,v in dicty.items(): 184 | if len(k) == 0: # delete blank keys that make empty nodes on the graph 185 | del dicty[k] 186 | if k in d.keys(): # check node for malfind colorization 187 | dicty[k].append(d[k][0]) # add the malfind node color to the pid (key) values in the main dictionary 188 | for val in v: 189 | if len(val) == 0: # remove blank values 190 | v.remove(val) 191 | if 'b' in v and 'o' in v: # prioritize orange over blue (psscan-identified link lines will still be blue though) 192 | v.remove('b') 193 | if 'b' in v and 'r' in v: # prioritize red over blue 194 | v.remove('b') 195 | 196 | with open(dotFile,'w') as o: 197 | o.write('digraph output {\nnode[shape = Mrecord];\nfontsize=16;\nnodesep=1.5;\nranksep=1;\nrankdir=LR;\n') 198 | for i in list(set(listy)): 199 | o.write(i) 200 | if dicty: 201 | for k in dicty.keys(): 202 | stringy = '' 203 | blue = str('style="filled", fillcolor="cyan"') # process found in psscan but not pslist 204 | orange = str('style="filled", fillcolor="orange"') # process in malfind without MZ 205 | red = str('style="filled", fillcolor="red"') # process in malfind with MZ 206 | if 'b' in dicty[k] or 'o' in dicty[k] or 'r' in dicty[k]: 207 | col = '' 208 | v = '' 209 | if 'b' in dicty[k]: 210 | dicty[k].remove('b') 211 | col = blue 212 | v = str('|'.join(dicty[k])) 213 | elif 'o' in dicty[k]: 214 | dicty[k].remove('o') 215 | col = orange 216 | v = str('|'.join(dicty[k])) 217 | elif 'r' in dicty[k]: 218 | dicty[k].remove('r') 219 | col = red 220 | v = str('|'.join(dicty[k])) 221 | if len(dicty[k]) == 0: 222 | stringy = '"%s" [label="%s", %s]' % (k, k, col) 223 | elif len(dicty[k]) >= 1: 224 | stringy = '"%s" [label="%s|%s", %s]' % (k, k, v, col) 225 | else: 226 | v = str('|'.join(dicty[k])) 227 | if len(dicty[k]) == 0: 228 | stringy = '"%s" [label="%s"]' % (k, k) 229 | elif len(dicty[k]) >= 1: 230 | stringy = '"%s" [label="%s|%s"]' % (k, k, v) 231 | o.write(stringy+';\n') 232 | o.write('\n}') 233 | o.close() 234 | 235 | os.popen('dot -Tpng %s -o %s' % (dotFile, dotOutputFile)) 236 | # os.popen('circo -Tpng %s -o %s' % (dotFile, circoOutputFile)) 237 | # os.popen('neato -Goverlap=scale -Tpng %s -o %s' % (dotFile, neatoOutputFile)) 238 | os.remove(dotFile) 239 | print 'Made %s' % (dotOutputFile) 240 | 241 | #============== 242 | 243 | if __name__ == '__main__': 244 | makeGraph() 245 | -------------------------------------------------------------------------------- /sampledata/readme.md: -------------------------------------------------------------------------------- 1 | Sample outputs from volGraph.py 2 | -------------------------------------------------------------------------------- /sampledata/volGraph-1551571181-dot.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bonifield/volatilityGrapher/b30bc1ab89b36b137bfd5609db6b4edbcd8c20d7/sampledata/volGraph-1551571181-dot.png -------------------------------------------------------------------------------- /sampledata/volGraph-1551571385-dot.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bonifield/volatilityGrapher/b30bc1ab89b36b137bfd5609db6b4edbcd8c20d7/sampledata/volGraph-1551571385-dot.png -------------------------------------------------------------------------------- /volGraph.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/python3 2 | 3 | #============== 4 | # volGraph.py v1.5.4 5 | # Last Update: 01 Mar 2019 6 | # Feed this script JSON output from the following Volatility modules: pslist, psscan, malfind, envars, cmdline, netscan, connscan, apihooks 7 | # Previously known as volCombine.py (last plain output-processing version: v1.3.4) 8 | # Using both pslist and psscan helps to QUICKLY identify deltas between the two files 9 | # --Blue lines mean a new link was found in psscan that wasn't in pslist 10 | # --Cyan nodes mean that process was found in psscan that wasn't in pslist (malfind colors will override this, connection line remains blue) 11 | # ----Yellow nodes mean that process was found in apihooks as having one module that hooked another 12 | # ----Orange nodes mean the process was found via malfind without MZ (4d5a) 13 | # ----Red nodes mean the process was found via malfind with MZ 14 | # Processing order (not impacted by the order these files are presented to the script): pslist --> psscan --> malfind --> envars --> netscan --> cmdline 15 | # PSLIST NOT REQUIRED BUT DEFINITELY RECOMMENDED TO BASELINE THE COLORS 16 | # Usage: volGraph.py pslist.json 17 | # Usage: volGraph.py pslist.json envars.json psscan.json malfind.json netscan.json cmdline.json connscan.json apihooks.json 18 | # Usage: volGraph.py *pslist*json *envars*json *psscan*json *malfind*json *netscan*json *cmdline*json *connscan*json *apih*json 19 | # Usage: volGraph.py *json 20 | # TODO: dedup code, psxview ingest, additional module support (apihooks, etc), proper Python modules 21 | # - v1.3.3 (16 Aug 2017) - added funky() to remove some reptitive lines 22 | # - v1.3.4 (17 Aug 2018, yes 2018) - added psscan_fixer() for Win10x Volatility profiles (note - now deprecated) 23 | # - v1.5.1 (08 Sep 2018) - re-wrote in Python 3, with a hard requirement for JSON input (Volatilty switches: --output=json [module] --output-file=[module]-[youroutputname].json) 24 | # - added netscan support 25 | # - added a main class to handle the heavy lifting, make future object calls easier 26 | # - cleaner ingest, LOTS of code deduplication (more to come) 27 | # - NO LONGER REQUIRES PSLIST; omitting pslist will make all of the lines and nodes blue though :) 28 | # - v1.5.2 (09 Oct 2018) - fixed a major bug where PIDs would not display on the nodes... now they do! 29 | # - v1.5.3 (15 Oct 2018) - added cmdline support, fixed this description 30 | # - v1.5.4 (01 Mar 2019) - added apihooks, connscan support (for pre-Vista network connections), added the below text about adding new module support 31 | # - fixed an issue where red malfind nodes could be converted back to orange 32 | # - re-enabled showing the first few malfind bytes 33 | # 34 | # ADDING NEW VOLATILITY MODULE SUPPORT (TODO - proper Python modules): 35 | # - add a new variable for the module name (currently using four-letter names) 36 | # - add an if-statement check that calls opener() (right below opener) 37 | # - add the module name/variable to dicty 38 | # - add a new if-statement in Combiner's __init__() 39 | # - add a statement in Combiner's dworker (optional - add color here) 40 | # - add a statement in Combiner's __repr__ 41 | # 42 | #============== 43 | 44 | import json,os,re,sys,time 45 | 46 | #============== 47 | 48 | syslist = sys.argv 49 | psli = None 50 | pssc = None 51 | malf = None 52 | enva = None 53 | nets = None 54 | cmdl = None 55 | cnsc = None 56 | apih = None 57 | epoch = str(int(time.time())) 58 | 59 | dotFile = str('volGraph-{}.dot'.format(epoch)) 60 | dotImage = str('volGraph-{}-dot.png'.format(epoch)) 61 | circoImage = str('volGraph-{}-circo.png'.format(epoch)) 62 | neatoImage = str('volGraph-{}-neato.png'.format(epoch)) 63 | 64 | #============== 65 | 66 | def opener(o): 67 | try: 68 | with open(o, 'r') as f: 69 | data = json.load(f) 70 | f.close() 71 | return data 72 | except Exception as e: 73 | print(str(e)) 74 | print('ERROR: All inputs must be JSON.') 75 | print('USAGE: volGraph.py some-module-output.json some-other-module.json yet-another-module.json etc...') 76 | print('Supports the following Volatility module output in JSON format: pslist, psscan, malfind, envars, netscan, cmdline, connscan, apihooks') 77 | print('Use the following switches with Volatility: "--output=json [module] --output-file=[module]-[youroutputname].json"') 78 | sys.exit(1) 79 | 80 | if len(syslist) > 1: 81 | for i in syslist[1:]: 82 | if re.search('pslist', i): 83 | psli = opener(i) 84 | if re.search('psscan', i): 85 | pssc = opener(i) 86 | if re.search('malf', i): 87 | malf = opener(i) 88 | if re.search('envar', i): 89 | enva = opener(i) 90 | if re.search('netsc', i): 91 | nets = opener(i) 92 | if re.search('cmdline', i): 93 | cmdl = opener(i) 94 | if re.search('connscan', i): 95 | cnsc = opener(i) 96 | if re.search('apiho', i): 97 | apih = opener(i) 98 | 99 | dicty = {"pslist":psli,"psscan":pssc,"envars":enva,"netscan":nets,"cmdline":cmdl,"connscan":cnsc,"apihooks":apih,"malfind":malf} 100 | 101 | #============== 102 | 103 | class Combiner(object): 104 | def __init__(self, idict): 105 | """ initialize all values and individual dictionaries which will be combined into one cohesive data structure """ 106 | self.idict = idict 107 | self.d = {} 108 | self.l = [] 109 | if idict["pslist"]: 110 | self.pslist = idict["pslist"]["rows"] 111 | else: 112 | self.pslist = None 113 | if idict["psscan"]: 114 | self.psscan = idict["psscan"]["rows"] 115 | else: 116 | self.psscan = None 117 | if idict["envars"]: 118 | self.envars = idict["envars"]["rows"] 119 | else: 120 | self.envars = None 121 | if idict["netscan"]: 122 | self.netscan = idict["netscan"]["rows"] 123 | else: 124 | self.netscan = None 125 | if idict["cmdline"]: 126 | self.cmdline = idict["cmdline"]["rows"] 127 | else: 128 | self.cmdline = None 129 | if idict["connscan"]: 130 | self.connscan = idict["connscan"]["rows"] 131 | else: 132 | self.connscan = None 133 | if idict["apihooks"]: 134 | self.apihooks = idict["apihooks"]["rows"] 135 | else: 136 | self.apihooks = None 137 | if idict["malfind"]: 138 | self.malfind = idict["malfind"]["rows"] 139 | else: 140 | self.malfind = None 141 | 142 | def dgen(self, d,k,v): 143 | """ worker function to generate the main combined dictionary as directed by dworker """ 144 | if k not in d.keys(): 145 | d.setdefault(k,{}) 146 | d[k].update(v) 147 | elif k in d.keys(): 148 | # remove psscan colors if pslist already found something 149 | if "color" in d[k].keys(): 150 | if d[k]["color"] == "black": 151 | if "color" in v.keys(): 152 | if v["color"] == "blue": 153 | del v["color"] 154 | if "fillcolor" in v.keys(): 155 | if v["fillcolor"] == "cyan": 156 | del v["fillcolor"] 157 | d[k].update(v) 158 | 159 | def dworker(self): 160 | """ generates the main combined dictionary as directed by data """ 161 | """ {pid:{subkey:subvalue}} """ 162 | """ line colors in use: black, blue, red""" 163 | """ fill colors in use: cyan, yellow, orange, red""" 164 | if self.pslist: 165 | for i in self.pslist: 166 | self.dgen(self.d, i[2], {"name":i[1], "ppid":i[3], "created":i[8], "color":"black"}) 167 | if self.psscan: 168 | for i in self.psscan: 169 | self.dgen(self.d, i[2], {"name":i[1], "ppid":i[3], "created":i[5], "color":"blue", "fillcolor":"cyan"}) 170 | if len(i[6]) > 0: 171 | self.d[i[2]]["exited"]=i[6] 172 | if self.envars: 173 | for i in self.envars: 174 | if i[3] == "USERNAME": 175 | self.dgen(self.d, i[0], {"username":i[4]}) 176 | if self.netscan: 177 | for i in self.netscan: 178 | self.dgen(self.d, i[5], {"proto":i[1], "localaddr":i[2], "foreignaddr":i[3], "state":i[4], "conn-owner":i[6], "conn-created":i[7]}) 179 | if self.cmdline: 180 | for i in self.cmdline: 181 | if len(i[2]) > 0: 182 | cfix = i[2].replace("\"", "").replace("\\", "\\\\").replace("{", "\{").replace("}", "\}") 183 | self.dgen(self.d, i[1], {"cmdline":cfix}) 184 | if self.connscan: 185 | for i in self.connscan: 186 | self.dgen(self.d, i[3], {"loc_addr":i[1], "rem_addr":i[2]}) 187 | if self.apihooks: 188 | for i in self.apihooks: 189 | self.dgen(self.d, i[3], {"apihooks": "{} {} hooked {} via {}".format(i[0], i[9], i[4], i[1]).replace("<","").replace(">",""), "fillcolor":"yellow"}) 190 | if self.malfind: 191 | for i in self.malfind: 192 | if re.search('4d5a', i[6]): 193 | self.dgen(self.d, i[1], {"malf_protection":i[4], "malf_bytes":i[6][:10], "fillcolor":"red"}) 194 | else: 195 | if "malf_bytes" not in self.d[i[1]].keys(): 196 | self.dgen(self.d, i[1], {"malf_protection":i[4], "malf_bytes":i[6][:10], "fillcolor":"orange"}) 197 | 198 | def data(self): 199 | """ creates and returns the dictionary """ 200 | self.dworker() 201 | return self.d 202 | 203 | def lworker(self): 204 | """ generates the list of connections as directed by listy """ 205 | for k,v in self.data().items(): 206 | try: 207 | if v['color'] == 'blue': 208 | self.l.append('"{}" -> "{}" [color="{}", penwidth=3]'.format(str(v.get("ppid")), str(k), str(v.get("color")))) 209 | else: 210 | self.l.append('"{}" -> "{}" [color="{}"]'.format(str(v.get("ppid")), str(k), str(v.get("color")))) 211 | except: 212 | self.l.append('"{}" -> "{}"'.format(str(v.get("ppid")), str(k))) 213 | 214 | def listy(self): 215 | """ creates and returns the list of connections based on the combined dictionary """ 216 | self.lworker() 217 | return self.l 218 | 219 | def label(self): 220 | for k,v in self.data().items(): 221 | # remove keys not to be displayed on individual nodes 222 | try: 223 | del v['color'] 224 | except: 225 | pass 226 | try: 227 | del v['ppid'] 228 | except: 229 | pass 230 | # try: 231 | # del v['malf_bytes'] 232 | # except: 233 | # pass 234 | x = None 235 | if 'fillcolor' in v.keys(): 236 | x = '"{}" [style="filled", fillcolor="{}", label="{}"]'.format(str(k), str(v.pop('fillcolor')), str(str(k)+'|'+'|'.join([': '.join(t) for t in [(str(key),str(val)) for key,val in v.items()]]))) 237 | else: 238 | try: 239 | x = '"{}" [label="{}"]'.format(str(k), str(str(k)+'|'+'|'.join([': '.join(t) for t in [(str(key),str(val)) for key,val in v.items()]]))) 240 | except Exception as e: 241 | # x = str(e) 242 | x = '"{}" [label="{}"]'.format(str(k), str(e)) 243 | 244 | if x is not None: 245 | yield(x) 246 | 247 | def __repr__(self): 248 | return str('pslist: {}\tpsscan: {}\tmalfind:{}\tenvars: {}\tnetscan: {}\tcmdline: {}\tconnscan: {}\tapihooks: {}'.format(type(self.pslist).__name__, type(self.psscan).__name__, type(self.malfind).__name__, type(self.envars).__name__, type(self.netscan).__name__, type(self.cmdline).__name__, type(self.connscan).__name__, type(self.apihooks).__name__)) 249 | 250 | #============== 251 | 252 | # instantiate the class 253 | c = Combiner(dicty) 254 | 255 | def makeGraph(): 256 | print('Making {}'.format(dotFile)) 257 | with open(dotFile,'w') as o: 258 | o.write('digraph output {\nnode[shape = Mrecord];\nfontsize=16;\nnodesep=1.5;\nranksep=1;\nrankdir=LR;\n') 259 | for i in c.listy(): 260 | o.write(i+';\n') 261 | for i in c.label(): 262 | o.write(i+';\n') 263 | o.write('\n}') 264 | o.close() 265 | print('Making output images... these may take a minute to render.') 266 | try: 267 | # UNCOMMENT THE FORMATS YOU WANT TO SEE OUTPUT 268 | # CLASSIC DOT IMAGE OUTPUT 269 | os.popen('dot -Tpng {} -o {}'.format(dotFile, dotImage)) 270 | # CIRCO IMAGE OUTPUT 271 | #os.popen('circo -Tpng {} -o {}'.format(dotFile, circoImage)) 272 | #print('Made {}'.format(circoImage)) 273 | # NEATO IMAGE OUTPUT 274 | #os.popen('neato -Goverlap=scale -Tpng {} -o {}'.format(dotFile, neatoImage)) 275 | #print('Made {}'.format(neatoImage)) 276 | except: 277 | print('ERROR: "dot", "circo", and/or "neato" not found via path variable - is GraphViz installed on this system?') 278 | sys.exit(5) 279 | # comment this line to preserve the dot-formatted text file used to generate the images via GraphViz 280 | # os.remove(dotFile) 281 | print('Made {}'.format(dotImage)) 282 | 283 | #============== 284 | 285 | if __name__ == '__main__': 286 | makeGraph() 287 | --------------------------------------------------------------------------------