├── .gitignore ├── LICENSE ├── README.md ├── requirements.txt ├── setup_supermem.sh └── winSuperMem.py /.gitignore: -------------------------------------------------------------------------------- 1 | # Byte-compiled / optimized / DLL files 2 | __pycache__/ 3 | *.py[cod] 4 | *$py.class 5 | 6 | # C extensions 7 | *.so 8 | 9 | # Distribution / packaging 10 | .Python 11 | build/ 12 | develop-eggs/ 13 | dist/ 14 | downloads/ 15 | eggs/ 16 | .eggs/ 17 | lib/ 18 | lib64/ 19 | parts/ 20 | sdist/ 21 | var/ 22 | wheels/ 23 | pip-wheel-metadata/ 24 | share/python-wheels/ 25 | *.egg-info/ 26 | .installed.cfg 27 | *.egg 28 | MANIFEST 29 | 30 | # PyInstaller 31 | # Usually these files are written by a python script from a template 32 | # before PyInstaller builds the exe, so as to inject date/other infos into it. 33 | *.manifest 34 | *.spec 35 | 36 | # Installer logs 37 | pip-log.txt 38 | pip-delete-this-directory.txt 39 | 40 | # Unit test / coverage reports 41 | htmlcov/ 42 | .tox/ 43 | .nox/ 44 | .coverage 45 | .coverage.* 46 | .cache 47 | nosetests.xml 48 | coverage.xml 49 | *.cover 50 | *.py,cover 51 | .hypothesis/ 52 | .pytest_cache/ 53 | 54 | # Translations 55 | *.mo 56 | *.pot 57 | 58 | # Django stuff: 59 | *.log 60 | local_settings.py 61 | db.sqlite3 62 | db.sqlite3-journal 63 | 64 | # Flask stuff: 65 | instance/ 66 | .webassets-cache 67 | 68 | # Scrapy stuff: 69 | .scrapy 70 | 71 | # Sphinx documentation 72 | docs/_build/ 73 | 74 | # PyBuilder 75 | target/ 76 | 77 | # Jupyter Notebook 78 | .ipynb_checkpoints 79 | 80 | # IPython 81 | profile_default/ 82 | ipython_config.py 83 | 84 | # pyenv 85 | .python-version 86 | 87 | # pipenv 88 | # According to pypa/pipenv#598, it is recommended to include Pipfile.lock in version control. 89 | # However, in case of collaboration, if having platform-specific dependencies or dependencies 90 | # having no cross-platform support, pipenv may install dependencies that don't work, or not 91 | # install all needed dependencies. 92 | #Pipfile.lock 93 | 94 | # PEP 582; used by e.g. github.com/David-OConnor/pyflow 95 | __pypackages__/ 96 | 97 | # Celery stuff 98 | celerybeat-schedule 99 | celerybeat.pid 100 | 101 | # SageMath parsed files 102 | *.sage.py 103 | 104 | # Environments 105 | .env 106 | .venv 107 | env/ 108 | venv/ 109 | ENV/ 110 | env.bak/ 111 | venv.bak/ 112 | 113 | # Spyder project settings 114 | .spyderproject 115 | .spyproject 116 | 117 | # Rope project settings 118 | .ropeproject 119 | 120 | # mkdocs documentation 121 | /site 122 | 123 | # mypy 124 | .mypy_cache/ 125 | .dmypy.json 126 | dmypy.json 127 | 128 | # Pyre type checker 129 | .pyre/ 130 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Copyright 2021 CrowdStrike, Inc. 2 | 3 | Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated 4 | documentation files (the "Software"), to deal in the Software without restriction, including without limitation 5 | the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and 6 | to permit persons to whom the Software is furnished to do so, subject to the following conditions: 7 | 8 | The above copyright notice and this permission notice shall be included in all copies or substantial portions of 9 | the Software. 10 | 11 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED 12 | TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL 13 | THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF 14 | CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER 15 | DEALINGS IN THE SOFTWARE. 16 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Overview 2 | 3 | A python script developed to process Windows memory images based on triage type. 4 | 5 | # Requirements 6 | - Python3 7 | - Bulk Extractor 8 | - Volatility2 with Community Plugins 9 | - Volatility3 10 | - Plaso 11 | - Yara 12 | 13 | # How to Use 14 | ## Quick Triage 15 | 16 | `python3 winSuperMem.py -f memdump.mem -o output/ -tt 1` 17 | 18 | ## Full Triage 19 | 20 | `python3 winSuperMem.py -f memdump.mem -o output/ -tt 2` 21 | 22 | ## Comprehensive Triage 23 | 24 | `python3 winSuperMem.py -f memdump.mem -o output/ -tt 3` 25 | 26 | # Installation 27 | 1. Install Python 3 28 | 2. Install Python 2 29 | 3. pip3 install -r requirements.txt 30 | 4. Install Volatility 3 Framework 31 | 5. Install Volatility 2 Framework 32 | 6. Download Volatility 2 Community Plugins 33 | 7. Install Bulk Extractor 34 | 8. Install Plaso 35 | 9. Install Yara 36 | 10. Install Strings 37 | 11. Install EVTxtract 38 | 39 | # How to Read the Output 40 | - Output directory structure of comprehensive triage: 41 | - BEoutputdir - Bulk Extractor output 42 | - DumpedDllsOutput - Dumped DLLs loaded into processes 43 | - DumpedFilesOutput - Dumped files in memory 44 | - DumpedModules - Dumped loaded drivers 45 | - DumpedProcessOutput - Dumped running processes 46 | - DumpedRegistry - Dumped loaded registry hives 47 | - EVTxtract - Extracted data with EVTxtract 48 | - IOCs.csv - Collected IPs identified in the output data set 49 | - Logging.log - Logging for the script 50 | - Plaso - Plaso master timeline 51 | - Strings - Unicode, Ascii, Big Endian strings output 52 | - Volatility2 - Volatility2 plugin output 53 | - Volatility3 - Volatility3 plugin output 54 | - Yara - Yara matches 55 | 56 | # Troubleshooting 57 | There are a number of known bugs, which are outlined in this section. 58 | - Dumping files may not work on Windows images below Windows8. The offset supplied by the volatility3 filescan plugin is sometimes physical and not virtual. There is not a descriptor specifying which is returned either. The current script is expecting virtual only. You can fix this by changing the dumpfiles function from `--virtaddr` to `--physaddr`. 59 | -------------------------------------------------------------------------------- /requirements.txt: -------------------------------------------------------------------------------- 1 | tqdm 2 | termcolor -------------------------------------------------------------------------------- /setup_supermem.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | # This is a helper script to assist with installation of the dependencies of CrowdStrike's SuperMem (https://github.com/CrowdStrike/SuperMem) 3 | # Written by J Marasinghe 4 | # Tested with Ubuntu 20.04.3 LTS 5 | 6 | # Copyright 2021 CrowdStrike, Inc. 7 | 8 | # Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated 9 | # documentation files (the "Software"), to deal in the Software without restriction, including without limitation 10 | # the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and 11 | # to permit persons to whom the Software is furnished to do so, subject to the following conditions: 12 | 13 | # The above copyright notice and this permission notice shall be included in all copies or substantial portions of 14 | # the Software. 15 | 16 | # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED 17 | # TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL 18 | # THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF 19 | # CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER 20 | # DEALINGS IN THE SOFTWARE. 21 | 22 | add-apt-repository ppa:gift/stable -y 23 | apt-get update 24 | apt-get install git python3 python2 python3-pip yara unzip zip plaso-tools -y 25 | 26 | #Setting up Volatility 2 27 | cd /opt/ 28 | wget http://downloads.volatilityfoundation.org/releases/2.6/volatility_2.6_lin64_standalone.zip 29 | unzip volatility_2.6_lin64_standalone.zip 30 | cd volatility_2.6_lin64_standalone 31 | mv volatility_2.6_lin64_standalone vol.py 32 | cp vol.py /usr/bin/ 33 | 34 | #Setting up Volatility 3 35 | cd /opt/ 36 | git clone --recursive https://github.com/volatilityfoundation/volatility3.git 37 | cd volatility3/ 38 | pip3 install -r requirements.txt 39 | python3 setup.py build 40 | python3 setup.py install 41 | mv vol.py vol3.py 42 | cp vol3.py /usr/bin/ 43 | 44 | 45 | #Download Volatility plugins 46 | cd /opt/ 47 | git clone --recursive https://github.com/volatilityfoundation/community.git 48 | 49 | #Installing evtxtract 50 | pip install evtxtract 51 | 52 | #Installing Bulk_extractor 53 | cd /opt/ 54 | git clone --recursive https://github.com/simsong/bulk_extractor.git 55 | echo -ne '\n' | bash bulk_extractor/etc/CONFIGURE_UBUNTU20LTS.bash 56 | cd bulk_extractor/ 57 | ./configure 58 | make 59 | make install 60 | 61 | #Downloading YARA rules 62 | cd /opt/ 63 | git clone --recursive https://github.com/Yara-Rules/rules.git 64 | 65 | #Setting up SuperMem 66 | git clone https://github.com/CrowdStrike/SuperMem.git 67 | cd SuperMem 68 | pip3 install -r requirements.txt 69 | -------------------------------------------------------------------------------- /winSuperMem.py: -------------------------------------------------------------------------------- 1 | ############################################################################### 2 | ## SuperMem for Windows Memory Analysis v1.0 3 | ## Written by James Lovato - CrowdStrike 4 | ## Copyright 2021 CrowdStrike, Inc. 5 | ############################################################################### 6 | 7 | import threading 8 | import queue 9 | import tqdm 10 | import os 11 | import re 12 | from argparse import ArgumentParser 13 | from argparse import RawTextHelpFormatter 14 | import subprocess 15 | import time 16 | import csv 17 | import logging 18 | import ipaddress 19 | import sys 20 | from termcolor import colored 21 | 22 | # Globals Likely Needing Updated 23 | THREADCOUNT = 12 24 | EVTXTRACTPATH = "/usr/local/bin/evtxtract" 25 | VOL3PATH = "/usr/bin/vol3" 26 | VOL2PATH = "/usr/bin/vol.py" 27 | VOL2EXTRAPLUGINS = "/usr/share/volatility/plugins/community/" 28 | BULKPATH = "/usr/bin/bulk_extractor" 29 | LOG2TIMELINEPATH = "/usr/bin/log2timeline.py" 30 | PSORTPATH = "/usr/bin/psort.py" 31 | YARAPATH = "/usr/bin/yara" 32 | STRINGSPATH = "/bin/strings" 33 | YARARULESFILE = "/path/to/yara/Yarafile.txt" 34 | 35 | # Globals for Output Files and Paths 36 | VOL3outputDir = "Volatility3" 37 | VOL2outputDir = "Volatility2" 38 | BEoutputDir = "BEoutputDir" 39 | STRINGSoutputDir = "Strings" 40 | DUMPFILESoutputDir = "DumpedFilesOutput" 41 | DUMPDLLSoutputDir = "DumpedDllsOutput" 42 | DUMPPROCESSESoutputDir = "DumpedProcessOutput" 43 | DUMPMODULESoutputDir = "DumpedModules" 44 | DUMPREGISTRYoutputDir = "DumpedRegistry" 45 | DUMPEVTXoutputDir = "Evtxtract" 46 | LOGGINGOUTPUT = "Logging.log" 47 | IOCOUTPUT = "IOCs.csv" 48 | PLASOOUTPUT = "Plaso" 49 | YARAoutputDir = "Yara" 50 | 51 | # Volatility3 Plugins for Quick Triage 52 | QUICKTRIAGEPLUGINS = [{"plugin": "windows.pstree.PsTree", "params": ""}, 53 | {"plugin": "windows.cmdline.CmdLine", "params": ""}, 54 | {"plugin": "windows.callbacks.Callbacks", "params": ""}, 55 | {"plugin": "windows.svcscan.SvcScan", "params": ""}, 56 | {"plugin": "windows.registry.userassist.UserAssist", "params": ""}, 57 | {"plugin": "windows.envars.Envars", "params": ""}, 58 | {"plugin": "windows.handles.Handles", "params": ""}, 59 | {"plugin": "windows.modules.Modules", "params": ""}, 60 | {"plugin": "windows.dlllist.DllList", "params": ""}, 61 | {"plugin": "windows.getsids.GetSIDs", "params": ""}, 62 | {"plugin": "windows.getservicesids.GetServiceSIDs", "params": ""}, 63 | {"plugin": "windows.malfind.Malfind", "params": ""}, 64 | {"plugin": "windows.pslist.PsList", "params": ""}, 65 | {"plugin": "windows.registry.hivelist.HiveList", "params": ""}, 66 | {"plugin": "windows.ssdt.SSDT", "params": ""}, 67 | {"plugin": "windows.registry.hivescan.HiveScan", "params": ""}] 68 | 69 | # Volatility3 Plugins for Full Triage 70 | FULLTRIAGEPLUGINS = [{"plugin": "windows.modscan.ModScan", "params": ""}, 71 | {"plugin": "windows.mutantscan.MutantScan", "params": ""}, 72 | {"plugin": "windows.psscan.PsScan", "params": ""}, 73 | {"plugin": "windows.driverscan.DriverScan", "params": ""}, 74 | {"plugin": "windows.symlinkscan.SymlinkScan", "params": ""}, 75 | {"plugin": "windows.driverirp.DriverIrp", "params": ""}, 76 | {"plugin": "windows.netscan.NetScan", "params": ""}, 77 | {"plugin": "windows.filescan.FileScan", "params": ""}, 78 | {"plugin": "windows.poolscanner.PoolScanner", "params": ""}] 79 | 80 | # Additional Volatility2 Plugins 81 | VOL2PLUGINS = [{"plugin": "amcache", "params": ""}, {"plugin": "getsids", "params": ""}, 82 | {"plugin": "clipboard", "params": ""}, 83 | {"plugin": "cmdscan", "params": ""}, {"plugin": "consoles", "params": ""}, 84 | {"plugin": "ldrmodules", "params": "--verbose"}, 85 | {"plugin": "mftparser", "params": "--output=body "}, {"plugin": "psxview", "params": "--apply-rules"}, 86 | {"plugin": "shellbags", "params": "--output=body"}, {"plugin": "shutdowntime", "params": ""}, 87 | {"plugin": "indx", "params": "--output=body"}, {"plugin": "logfile", "params": "--output=body"}, 88 | {"plugin": "prefetchparser", "params": "--full_paths"}, {"plugin": "schtasks", "params": ""}, 89 | {"plugin": "sessions", "params": ""}, {"plugin": "shimcachemem", "params": "--output=csv"}, 90 | {"plugin": "shimcache", "params": ""}, {"plugin": "sockets", "params": ""}, 91 | {"plugin": "sockscan", "params": ""}, {"plugin": "threads", "params": ""}, 92 | {"plugin": "usnjrnl", "params": "--output=body"}, {"plugin": "autoruns", "params": "-v"}, 93 | {"plugin": "connections", "params": ""}, {"plugin": "connscan", "params": ""}, 94 | {"plugin": "hollowfind", "params": ""}, {"plugin": "malthfind", "params": ""}, 95 | {"plugin": "timeliner", "params": "--output=body"}, {"plugin": "apihooks", "params": "--quick"}, {"plugin": "messagehooks", "params": ""}] 96 | 97 | 98 | # Logic for Printing to Console and Logging with Progress Bars 99 | def printLoggingLogic(message, pbar, typeOfLogging, color="green"): 100 | if typeOfLogging == "INFO": 101 | logging.info(message) 102 | if pbar: 103 | pbar.write(colored("INFO: " + message, color)) 104 | else: 105 | print(colored("INFO: " + message, color)) 106 | elif typeOfLogging == "ERROR": 107 | logging.error(message) 108 | if pbar: 109 | pbar.write(colored("ERROR: " + message, color)) 110 | else: 111 | print(colored("ERROR: " + message, color)) 112 | 113 | 114 | # Setup Muli-Threading and Progress Bar 115 | def threadPbar(input, description): 116 | # Define queue 117 | inputQueue = queue.Queue(maxsize=0) 118 | 119 | # Add items to queue 120 | for item in input: 121 | inputQueue.put(item) 122 | 123 | # Setup progress bar 124 | pbar = tqdm.tqdm(total=inputQueue.qsize(), desc=description, unit="Command") 125 | 126 | # Create threads for processing 127 | threads = [] 128 | for i in range(THREADCOUNT): 129 | t = threading.Thread(target=worker, args=(inputQueue, pbar)) 130 | threads.append(t) 131 | t.start() 132 | 133 | # Wait for threads to finish 134 | for thread in threads: 135 | thread.join() 136 | 137 | pbar.set_description(description + " Complete") 138 | pbar.close() 139 | 140 | 141 | # Thread Worker Function 142 | def worker(inputQueue, pbar): 143 | while not inputQueue.empty(): 144 | data = inputQueue.get() 145 | commandName = data['Name'] 146 | cmd = data['CMD'] 147 | printLoggingLogic("Started " + commandName, pbar, "INFO") 148 | startTime = time.time() 149 | runCMD(cmd, commandName) 150 | executionTime = (time.time() - startTime) 151 | pbar.update(1) 152 | printLoggingLogic("Finished " + commandName + " in " + str(int(executionTime)) + " seconds", pbar, "INFO") 153 | 154 | 155 | # Run Raw OS Commands 156 | def runCMD(cmd, commandName): 157 | try: 158 | logging.info(cmd) # Log Command Ran 159 | os.system(cmd) 160 | except Exception as e: 161 | logging.error("Command " + commandName + " caused the exception: " + str(e)) # Log Any Errors 162 | 163 | 164 | # Add Volatility Commands to Queue 165 | def volatility3Queue(chosenPlugins, memFullPath, outputDir): 166 | output = [] 167 | vol3outputDir = os.path.join(outputDir, VOL3outputDir) 168 | 169 | # Create Output Directory 170 | if not os.path.isdir(vol3outputDir): 171 | os.mkdir(vol3outputDir) 172 | 173 | # Used to Only Download the PDB Once 174 | printLoggingLogic("Setting up symbols for Volatility3 with windows.info.Info", False, "INFO") 175 | pluginName = "windows.info.Info" 176 | cmd = VOL3PATH + " -f " + '\"' + memFullPath + '\"' + " -r csv " + pluginName + " 2> " \ 177 | + os.path.join(vol3outputDir, pluginName + ".err") + " > " + os.path.join(vol3outputDir, 178 | pluginName + ".csv") 179 | runCMD(cmd, "Volatility3 plugin windows.info.Info") 180 | 181 | # Volatility3 Command Creation 182 | for p in chosenPlugins: 183 | pluginName = p['plugin'] 184 | params = p['params'] 185 | cmd = VOL3PATH + " -f " + '\"' + memFullPath + '\"' + " -r csv " + pluginName + " " + params + " 2> " \ 186 | + os.path.join(vol3outputDir, pluginName + ".err") + " > " + os.path.join(vol3outputDir, 187 | pluginName + ".csv") 188 | data = {'Name': "Volatility3 plugin " + pluginName, 'CMD': cmd} 189 | output.append(data) 190 | 191 | return output 192 | 193 | 194 | # Add Bulk Extractor Command to Queue 195 | def bulkExtractorQueue(outputDir, memFullPath): 196 | output = [] 197 | bulkOutput = os.path.join(outputDir, BEoutputDir) 198 | 199 | # Create Output Directory 200 | if not os.path.isdir(bulkOutput): 201 | os.mkdir(bulkOutput) 202 | 203 | # Run Bulk Extractor with Default Parameters 204 | cmd = BULKPATH + " -o " + bulkOutput + " \"" + memFullPath + "\" > /dev/null 2>&1" 205 | data = {'Name': 'Bulk Extractor', 'CMD': cmd} 206 | output.append(data) 207 | 208 | return output 209 | 210 | 211 | # Add EVTXtract Command to Queue 212 | def evtxtractQueue(outputDir, memFullPath): 213 | output = [] 214 | evtxOutput = os.path.join(outputDir, DUMPEVTXoutputDir) 215 | 216 | # Get File Name 217 | memFileName = memFullPath.split('/')[len(memFullPath.split('/')) - 1] 218 | 219 | # Create Output Directory 220 | if not os.path.isdir(evtxOutput): 221 | os.mkdir(evtxOutput) 222 | 223 | # EVTXtract Command Creation 224 | cmd = EVTXTRACTPATH + " \"" + memFullPath + "\" 2> " + os.path.join(evtxOutput, memFileName + ".err") \ 225 | + " > " + os.path.join(evtxOutput, memFileName + ".txt") 226 | data = {'Name': 'EVTXTRACT', 'CMD': cmd} 227 | output.append(data) 228 | 229 | return output 230 | 231 | 232 | # Add Strings Commands to Queue 233 | def stringsQueue(memFullPath, outputDir): 234 | output = [] 235 | stringsoutputDir = os.path.join(outputDir, STRINGSoutputDir) 236 | 237 | # Get File Name 238 | memFileName = memFullPath.split('/')[len(memFullPath.split('/')) - 1] 239 | 240 | # Create Output Directory 241 | if not os.path.isdir(stringsoutputDir): 242 | os.mkdir(stringsoutputDir) 243 | 244 | # Individual Strings Command Creation 245 | cmd = STRINGSPATH + " -td -el -a \"" + memFullPath + "\" > " + os.path.join(stringsoutputDir, memFileName + ".strings.unicode") 246 | data = {'Name': 'Strings unicode', 'CMD': cmd} 247 | output.append(data) 248 | cmd = STRINGSPATH + " -td -a \"" + memFullPath + "\" > " + os.path.join(stringsoutputDir, memFileName + ".strings.ascii") 249 | data = {'Name': 'Strings ascii', 'CMD': cmd} 250 | output.append(data) 251 | cmd = STRINGSPATH + " -td -eb -a \"" + memFullPath + "\" > " + os.path.join(stringsoutputDir, memFileName + ".strings.be") 252 | data = {'Name': 'Strings big endian', 'CMD': cmd} 253 | output.append(data) 254 | 255 | return output 256 | 257 | 258 | # Add Volatility2 Commands to Queue 259 | def volatility2Queue(chosenPlugins, memFullPath, outputDir, vol2Profile): 260 | output = [] 261 | vol2outputDir = os.path.join(outputDir, VOL2outputDir) 262 | 263 | # Create Output Directory 264 | if not os.path.isdir(vol2outputDir): 265 | os.mkdir(vol2outputDir) 266 | 267 | # Identify Profile, KDGB, and DTB Values for Volatility2 Processing 268 | try: 269 | profilesRex = "" 270 | cmdOutput = "" 271 | 272 | if not vol2Profile: 273 | printLoggingLogic("Locating profile, DTB, and KDGB for Volatility2", False, "INFO") 274 | cmdOutput = subprocess.run([VOL2PATH, '-f', memFullPath, 'imageinfo'], 275 | stdout=subprocess.PIPE, stderr=subprocess.DEVNULL).stdout.decode('utf-8') 276 | profilesRex = re.search('Suggested Profile\(s\) : (.+)', cmdOutput, re.IGNORECASE) 277 | else: 278 | printLoggingLogic("Locating kdbg and DTB for VOL2", False, "INFO") 279 | cmdOutput = subprocess.run([VOL2PATH, '-f', memFullPath, 'imageinfo', '--profile=' + vol2Profile], 280 | stdout=subprocess.PIPE, stderr=subprocess.DEVNULL).stdout.decode('utf-8') 281 | 282 | kdbgRex = re.search('KDBG : (.+?)L', cmdOutput, re.IGNORECASE) 283 | dtbRex = re.search('DTB : (.+?)L', cmdOutput, re.IGNORECASE) 284 | kdbg = "" 285 | dtb = "" 286 | profile = "" 287 | 288 | # Find Profile 289 | if not vol2Profile: 290 | if profilesRex: 291 | profile = profilesRex.group(1) 292 | if ',' in profile: 293 | profile = profile.split(',')[0] # Take the first profile if multiple are suggested 294 | else: 295 | printLoggingLogic("Cant find profile for Volatility2", False, "ERROR", "red") 296 | return [] 297 | else: 298 | profile = vol2Profile 299 | 300 | # Find KDGB 301 | if kdbgRex: 302 | kdbg = kdbgRex.group(1) 303 | else: 304 | printLoggingLogic("Cant find KDGB for Volatility2 caused by error in regex", False, "ERROR", "red") 305 | return [] 306 | 307 | # Find DTB 308 | if dtbRex: 309 | dtb = dtbRex.group(1) 310 | else: 311 | printLoggingLogic("Cant find DTB for Volatility2 caused by error in regex", False, "ERROR", "red") 312 | return [] 313 | 314 | # Write Output to File 315 | imageinfoFile = open(os.path.join(vol2outputDir, "imageinfo.txt"), 'w') 316 | imageinfoFile.write(cmdOutput) 317 | imageinfoFile.close() 318 | 319 | # Add Volatility2 Plugins to Queue 320 | for p in chosenPlugins: 321 | pluginName = p['plugin'] 322 | params = p['params'] 323 | cmd = VOL2PATH + " --plugins=" + VOL2EXTRAPLUGINS + " -f " + '\"' + memFullPath + '\"' + " --profile=" \ 324 | + profile + " --kdbg=" + kdbg + " --dtb=" + dtb + " " + pluginName + " " + params + " --output-file=" \ 325 | + os.path.join(vol2outputDir, pluginName + ".out") + " 2> " \ 326 | + os.path.join(vol2outputDir, pluginName + ".stderr") + " > " + os.path.join(vol2outputDir, 327 | pluginName + ".stdout") 328 | data = {'Name': "Volatility2 plugin " + pluginName, 'CMD': cmd} 329 | output.append(data) 330 | 331 | except Exception as e: 332 | printLoggingLogic(str(e), False, "ERROR", "red") 333 | 334 | return output 335 | 336 | 337 | # Dump Files Cached in Memory 338 | def dumpFilesQueue(outputDir, memFullPath, filetypes, filepaths): 339 | output = [] 340 | filescanOutput = os.path.join(outputDir, VOL3outputDir) 341 | filescanFullPath = os.path.join(filescanOutput, "windows.filescan.FileScan.csv") 342 | dumpFilesDir = os.path.join(outputDir, DUMPFILESoutputDir) 343 | 344 | # Create Output Directory 345 | if not os.path.isdir(dumpFilesDir): 346 | os.mkdir(dumpFilesDir) 347 | 348 | # Create Command to Dump Certain Files Cached in Memory 349 | try: 350 | if os.path.isfile(filescanFullPath): 351 | filescanobj = open(filescanFullPath, 'r') 352 | csvfilescanobj = csv.DictReader(filescanobj) 353 | 354 | # Loop Through Volatility3 File Scan Output 355 | for row in csvfilescanobj: 356 | exportfilename = row["Name"] 357 | if exportfilename: 358 | exportfilename = exportfilename.lower() 359 | if exportfilename.endswith(tuple(filetypes)) or ( 360 | "\\".join(exportfilename.split('\\')[:-1]) in filepaths) or len(filetypes) == 0: 361 | virtAddr = row["Offset"] 362 | cmd = VOL3PATH + " -f " + '\"' + memFullPath + '\"' + " -o " + dumpFilesDir + \ 363 | " windows.dumpfiles.DumpFiles --virtaddr " + virtAddr + " > /dev/null 2>&1" 364 | data = {'Name': "File Dumping for File " + exportfilename, 'CMD': cmd} 365 | output.append(data) 366 | else: 367 | printLoggingLogic("Cant Find File " + filescanFullPath, False, "ERROR", "red") 368 | except Exception as e: 369 | printLoggingLogic("Error in dumpFilesQueue", '', "ERROR", "red") 370 | 371 | return output 372 | 373 | 374 | # Dump Loaded DLLs 375 | def dumpDllsQueue(outputDir, memFullPath): 376 | output = [] 377 | dumpDllsOutput = os.path.join(outputDir, DUMPDLLSoutputDir) 378 | 379 | # Create Output Directory 380 | if not os.path.isdir(dumpDllsOutput): 381 | os.mkdir(dumpDllsOutput) 382 | 383 | # Create Command to Dump Loaded DLLs with Volatility3 384 | cmd = VOL3PATH + " -f " + '\"' + memFullPath + '\"' + " -o " + dumpDllsOutput + " windows.dlllist.DllList --dump > /dev/null 2>&1" 385 | data = {'Name': "Dumping DLLs", 'CMD': cmd} 386 | output.append(data) 387 | 388 | return output 389 | 390 | 391 | # Dump Processes 392 | def dumpProcessesQueue(outputDir, memFullPath): 393 | output = [] 394 | dumpProcessOutput = os.path.join(outputDir, DUMPPROCESSESoutputDir) 395 | 396 | # Create Output Directory 397 | if not os.path.isdir(dumpProcessOutput): 398 | os.mkdir(dumpProcessOutput) 399 | 400 | # Create Command to Dump Loaded Processes with Volatility3 401 | cmd = VOL3PATH + " -f " + '\"' + memFullPath + '\"' + " -o " + dumpProcessOutput + " windows.pslist.PsList --dump > /dev/null 2>&1" 402 | data = {'Name': "Dumping Processes", 'CMD': cmd} 403 | output.append(data) 404 | 405 | return output 406 | 407 | 408 | # Dump Modules 409 | def dumpModulesQueue(outputDir, memFullPath): 410 | output = [] 411 | dumpModulesOutput = os.path.join(outputDir, DUMPMODULESoutputDir) 412 | 413 | # Create Output Directory 414 | if not os.path.isdir(dumpModulesOutput): 415 | os.mkdir(dumpModulesOutput) 416 | 417 | # Create Command to Dump Loaded Modules with Volatility3 418 | cmd = VOL3PATH + " -f " + '\"' + memFullPath + '\"' + " -o " + dumpModulesOutput + " windows.modscan.ModScan --dump > /dev/null 2>&1" 419 | data = {'Name': "Dumping Modules", 'CMD': cmd} 420 | output.append(data) 421 | 422 | return output 423 | 424 | 425 | # Dump Registry 426 | def dumpRegistryQueue(outputDir, memFullPath): 427 | output = [] 428 | dumpRegistryOutput = os.path.join(outputDir, DUMPREGISTRYoutputDir) 429 | 430 | # Create Output Directory 431 | if not os.path.isdir(dumpRegistryOutput): 432 | os.mkdir(dumpRegistryOutput) 433 | 434 | # Create Command to Dump Loaded Registry Hives with Volatility3 435 | cmd = VOL3PATH + " -f " + '\"' + memFullPath + '\"' + " -o " + dumpRegistryOutput + " windows.registry.hivelist.HiveList --dump > /dev/null 2>&1" 436 | data = {'Name': "Dumping Registry", 'CMD': cmd} 437 | output.append(data) 438 | 439 | return output 440 | 441 | 442 | # Plaso Function 443 | def runPlaso(outputDir, memFullPath): 444 | printLoggingLogic("Running Plaso", False, "INFO") 445 | cleanUp = ["Worker_", "log2timeline-", "psort-"] 446 | plasoOutputPath = os.path.join(outputDir, PLASOOUTPUT) 447 | 448 | # Create Output Directory 449 | if not os.path.isdir(plasoOutputPath): 450 | os.mkdir(plasoOutputPath) 451 | 452 | # Get Output File Name/Paths 453 | memFileName = memFullPath.split('/')[len(memFullPath.split('/')) - 1] 454 | plasoOutputFullPath = os.path.join(plasoOutputPath, memFileName + ".plaso") 455 | tlnOutputFullPath = os.path.join(plasoOutputPath, memFileName + ".tln") 456 | 457 | # Run Log2Timeline 458 | cmd = LOG2TIMELINEPATH + " " + plasoOutputFullPath + " " + outputDir + " > /dev/null 2>&1" 459 | runCMD(cmd, "Log2Timeline") 460 | 461 | # Run Psort on the Output 462 | cmd = PSORTPATH + " -w " + tlnOutputFullPath + " " + plasoOutputFullPath + " > /dev/null 2>&1" 463 | runCMD(cmd, "PSORT") 464 | 465 | # Cleanup Left Over Plaso Files 466 | for file in os.listdir("."): 467 | if any(item in file for item in cleanUp): 468 | os.remove(file) 469 | 470 | 471 | # Get Network IOCs from Output 472 | def getNetIOCs(outputDir): 473 | netIOCData = [] 474 | vol3OutputPath = os.path.join(outputDir, VOL3outputDir) 475 | vol3NetScanPath = os.path.join(vol3OutputPath, "windows.netscan.NetScan.csv") 476 | 477 | try: 478 | # Volatility IP Extraction 479 | if os.path.isfile(vol3NetScanPath): 480 | vol3NetScanObj = csv.DictReader(open(vol3NetScanPath, 'r')) 481 | for row in vol3NetScanObj: 482 | foreignAddr = row['ForeignAddr'] 483 | if not foreignAddr == '*': 484 | if not ipaddress.ip_address(foreignAddr).is_private: 485 | data = {"Source": vol3NetScanPath, "Type": "IP Address", "Value": foreignAddr} 486 | netIOCData.append(data) 487 | except Exception as e: 488 | printLoggingLogic(str(e), False, "ERROR", "red") 489 | 490 | return netIOCData 491 | 492 | 493 | # Main Function for IOC Collection 494 | def getIOCs(outputDir): 495 | # Collect Network IOCs 496 | printLoggingLogic("Collecting Network IOCs", False, "INFO") 497 | iocs = getNetIOCs(outputDir) 498 | 499 | # Write Output to File 500 | if iocs: 501 | iocOutputObj = open(os.path.join(outputDir, IOCOUTPUT), 'w') 502 | csvWritter = csv.DictWriter(iocOutputObj, fieldnames=iocs[0].keys()) 503 | csvWritter.writeheader() 504 | for item in iocs: 505 | csvWritter.writerow(item) 506 | 507 | 508 | # Run Yara 509 | def runYara(outputDir): 510 | dumpModulesOutput = os.path.join(outputDir, DUMPMODULESoutputDir) 511 | dumpProcessOutput = os.path.join(outputDir, DUMPPROCESSESoutputDir) 512 | dumpDllsOutput = os.path.join(outputDir, DUMPDLLSoutputDir) 513 | yaraOutput = os.path.join(outputDir, YARAoutputDir) 514 | 515 | # Create Output Directory 516 | if not os.path.isdir(yaraOutput): 517 | os.mkdir(yaraOutput) 518 | 519 | # Run Yara Across the Dumped Files 520 | if os.path.isfile(YARARULESFILE): 521 | for directory in dumpModulesOutput, dumpProcessOutput, dumpDllsOutput: 522 | cmd = YARAPATH + " -g -m -s -e --threads=" + str(THREADCOUNT) + " " + YARARULESFILE + " -r " + directory + \ 523 | " 2> " + os.path.join(yaraOutput, "yara.stderr") + " > " + os.path.join(yaraOutput, "yara.stdout") 524 | 525 | printLoggingLogic("Running Yara Scan on " + directory, False, "INFO") 526 | runCMD(cmd, "Yara") 527 | else: 528 | printLoggingLogic("Cant Find File " + YARARULESFILE, False, "ERROR", 'red') 529 | 530 | 531 | # Processing Logic 532 | def processing(triageType, memFullPath, outputDir, vol2Profile): 533 | # Quick Triage Settings 534 | if triageType == 1: 535 | threadInput = volatility3Queue(QUICKTRIAGEPLUGINS, memFullPath, outputDir) 536 | threadInput += bulkExtractorQueue(outputDir, memFullPath) 537 | threadInput += stringsQueue(memFullPath, outputDir) 538 | threadPbar(threadInput, "Pre-Processing") 539 | 540 | # Full Triage Settings 541 | elif triageType == 2: 542 | threadInput = volatility3Queue((QUICKTRIAGEPLUGINS + FULLTRIAGEPLUGINS), memFullPath, outputDir) 543 | threadInput += bulkExtractorQueue(outputDir, memFullPath) 544 | threadInput += stringsQueue(memFullPath, outputDir) 545 | threadInput += volatility2Queue(VOL2PLUGINS, memFullPath, outputDir, vol2Profile) 546 | threadInput += evtxtractQueue(outputDir, memFullPath) 547 | threadInput += dumpRegistryQueue(outputDir, memFullPath) 548 | threadPbar(threadInput, "Pre-Processing") 549 | 550 | # Extract Certain Files/Directories from DumpFiles Output 551 | filetypes = ['.evtx', '.pf', '.lnk', '\\ntuser.dat', '\\usrclass.dat', '\\MPDetection-', '\\MPLog-', 552 | '\\WebCacheV01.dat', '.hve', '\\current.mdb', '$i30,', '$mft', '$logfile', '$j'] 553 | filepaths = ['\\recent\\automaticdestinations', '\\recent\\customdestinations', '\\windows\\system32\\config', 554 | "\\system32\\tasks"] 555 | threadInput = dumpFilesQueue(outputDir, memFullPath, filetypes, filepaths) 556 | threadPbar(threadInput, "Dumping Files") 557 | 558 | # Collect IOCs 559 | getIOCs(outputDir) 560 | 561 | # Run Plaso 562 | runPlaso(outputDir, memFullPath) 563 | 564 | # Comprehensive Triage Settings 565 | elif triageType == 3: 566 | threadInput = volatility3Queue((QUICKTRIAGEPLUGINS + FULLTRIAGEPLUGINS), memFullPath, outputDir) 567 | threadInput += bulkExtractorQueue(outputDir, memFullPath) 568 | threadInput += stringsQueue(memFullPath, outputDir) 569 | threadInput += volatility2Queue(VOL2PLUGINS, memFullPath, outputDir, vol2Profile) 570 | threadInput += evtxtractQueue(outputDir, memFullPath) 571 | threadInput += dumpRegistryQueue(outputDir, memFullPath) 572 | threadInput += dumpDllsQueue(outputDir, memFullPath) 573 | threadInput += dumpProcessesQueue(outputDir, memFullPath) 574 | threadInput += dumpModulesQueue(outputDir, memFullPath) 575 | threadPbar(threadInput, "Pre-Processing") 576 | 577 | # Extract Certain Files/Directories from DumpFiles Output 578 | filetypes = ['.evtx', '.pf', '.lnk', '\\ntuser.dat', '\\usrclass.dat', '\\MPDetection-', '\\MPLog-', 579 | '\\WebCacheV01.dat', '.hve', '\\current.mdb', '$i30,', '$mft', '$logfile', '$j'] 580 | filepaths = ['\\recent\\automaticdestinations', '\\recent\\customdestinations', '\\windows\\system32\\config', 581 | "\\system32\\tasks"] 582 | threadInput = dumpFilesQueue(outputDir, memFullPath, filetypes, filepaths) 583 | threadPbar(threadInput, "Dumping Files") 584 | 585 | # Collect IOCs 586 | getIOCs(outputDir) 587 | 588 | # Run Plaso 589 | runPlaso(outputDir, memFullPath) 590 | 591 | # Run Yara 592 | runYara(outputDir) 593 | 594 | 595 | # Main Function 596 | def main(): 597 | # Stats on Script Run Time 598 | startTime = time.time() 599 | 600 | # Define Argparser for Input 601 | triageOptions = {1: 'QuickTriage', 2: 'FullTriage', 3: 'ComprehensiveTriage'} 602 | parser = ArgumentParser(description='winSuperMem is a script to automate processing of a Windows memory images', formatter_class=RawTextHelpFormatter) 603 | parser.add_argument('-f', '--fullpath=', type=str, help='Full path to memory file', required=True, dest='FullPath') 604 | parser.add_argument('-o', '--output=', type=str, help='Full path to output directory', required=True, dest='Output') 605 | parser.add_argument('-p', '--profile=', type=str, help='Volatility2 profile', required=False, dest='Vol2Profile') 606 | parser.add_argument('-tt', '--triagetype=', help='Triage type: ' + str(triageOptions), type=int, 607 | required=True, dest='TriageType') 608 | args = parser.parse_args() 609 | 610 | # Grab Input Values 611 | memFullPath = os.path.abspath(args.FullPath) 612 | outputDir = os.path.abspath(args.Output) 613 | triageType = args.TriageType 614 | vol2Profile = args.Vol2Profile 615 | 616 | # Quit if an Invalid Triage Type was Supplied 617 | if triageType not in triageOptions.keys(): 618 | print(colored("Invalid Value for Triage Type"), 'red') 619 | quit() 620 | 621 | # Quit if You Can't Find the File 622 | if not os.path.isfile(memFullPath): 623 | print(colored("Can't find file: " + memFullPath), "red") 624 | quit() 625 | 626 | # Create Output Directory 627 | if not os.path.isdir(outputDir): 628 | os.mkdir(outputDir) 629 | 630 | # For Logging Output Messages 631 | logging.basicConfig(filename=os.path.join(outputDir, LOGGINGOUTPUT), 632 | format='%(asctime)s %(levelname)-8s %(message)s', level=logging.INFO, 633 | datefmt='%Y-%m-%dT%H:%M:%S') 634 | 635 | # Print Script Header 636 | printLoggingLogic("**************************", False, "INFO", "blue") 637 | printLoggingLogic("File Name: " + memFullPath, False, "INFO", "blue") 638 | printLoggingLogic("Output Directory: " + outputDir, False, "INFO", "blue") 639 | printLoggingLogic("Triage Type: " + triageOptions[triageType], False, "INFO", "blue") 640 | printLoggingLogic("Command: " + " ".join(sys.argv), False, "INFO", "blue") 641 | printLoggingLogic("**************************", False, "INFO", "blue") 642 | 643 | # Start Processing 644 | processing(triageType, memFullPath, outputDir, vol2Profile) 645 | executionTime = (time.time() - startTime) 646 | printLoggingLogic("Finished all processing in " + str(int(executionTime / 60)) + " minutes", False, "INFO", "blue") 647 | 648 | 649 | main() 650 | --------------------------------------------------------------------------------