├── vol2 ├── lastpass │ ├── __init__.py │ ├── README.md │ └── lastpass.py └── usbstor │ ├── __init__.py │ ├── README.md │ ├── README.md~ │ └── usbstor.py ├── LICENSE ├── .gitignore ├── vol3 ├── zone-identifier │ └── zoneid3.py ├── richheader │ └── richheader.py ├── passwordmanagers │ └── passwordmanagers.py └── cobaltstrike │ └── cobaltstrike.py └── README.md /vol2/lastpass/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /vol2/usbstor/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2016 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /.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 | env/ 12 | build/ 13 | develop-eggs/ 14 | dist/ 15 | downloads/ 16 | eggs/ 17 | .eggs/ 18 | lib/ 19 | lib64/ 20 | parts/ 21 | sdist/ 22 | var/ 23 | *.egg-info/ 24 | .installed.cfg 25 | *.egg 26 | 27 | # PyInstaller 28 | # Usually these files are written by a python script from a template 29 | # before PyInstaller builds the exe, so as to inject date/other infos into it. 30 | *.manifest 31 | *.spec 32 | 33 | # Installer logs 34 | pip-log.txt 35 | pip-delete-this-directory.txt 36 | 37 | # Unit test / coverage reports 38 | htmlcov/ 39 | .tox/ 40 | .coverage 41 | .coverage.* 42 | .cache 43 | nosetests.xml 44 | coverage.xml 45 | *,cover 46 | .hypothesis/ 47 | 48 | # Translations 49 | *.mo 50 | *.pot 51 | 52 | # Django stuff: 53 | *.log 54 | local_settings.py 55 | 56 | # Flask stuff: 57 | instance/ 58 | .webassets-cache 59 | 60 | # Scrapy stuff: 61 | .scrapy 62 | 63 | # Sphinx documentation 64 | docs/_build/ 65 | 66 | # PyBuilder 67 | target/ 68 | 69 | # IPython Notebook 70 | .ipynb_checkpoints 71 | 72 | # pyenv 73 | .python-version 74 | 75 | # celery beat schedule file 76 | celerybeat-schedule 77 | 78 | # dotenv 79 | .env 80 | 81 | # virtualenv 82 | venv/ 83 | ENV/ 84 | 85 | # Spyder project settings 86 | .spyderproject 87 | 88 | # Rope project settings 89 | .ropeproject 90 | 91 | # Memory samples 92 | *.mem 93 | *.raw 94 | *.img 95 | *.vmem 96 | samples/ 97 | -------------------------------------------------------------------------------- /vol2/usbstor/README.md: -------------------------------------------------------------------------------- 1 | # USBSTOR 2 | 3 | ## Descriptions 4 | Scans registries for values relating to USB devices plugged in to the system. 5 | 6 | ## Authors: 7 | 8 | - Kevin Breen 9 | - James Hall 10 | 11 | ## Usage: 12 | 13 | ```vol.py --plugins=plugindir --profile=Win7SP1x64 -f win7mem.img usbstor``` 14 | 15 | Example output: 16 | ``` 17 | user@ubuntu:~/Desktop$ vol.py --plugins=/home/users/volatility_plugins --profile=Win7SP1x86 -f Win7-Analysis-1d23dece.vmem usbstor 18 | Volatility Foundation Volatility Framework 2.5 19 | Reading the USBSTOR Please Wait 20 | Found USB Drive: AAA6O95BT0GDMPM0&0 21 | Serial Number: AAA6O95BT0GDMPM0&0 22 | Vendor: SMI 23 | Product: USB_DISK 24 | Revision: 1100 25 | ClassGUID: USB_DISK 26 | 27 | ContainerID: {638e1754-cecf-5255-8af3-cd6f1e1d51b0} 28 | Mounted Volume: \??\Volume{ffefd27e-110e-11e6-96bb-000c29a1e376} 29 | Drive Letter: \DosDevices\E: 30 | Friendly Name: SMI USB DISK USB Device 31 | USB Name: E:\ 32 | Device Last Connected: 2016-05-10 15:01:22 UTC+0000 33 | 34 | Class: DiskDrive 35 | Service: disk 36 | DeviceDesc: @disk.inf,%disk_devdesc%;Disk drive 37 | Capabilities: 16 38 | Mfg: @disk.inf,%genmanufacturer%;(Standard disk drives) 39 | ConfigFlags: 0 40 | Driver: {4d36e967-e325-11ce-bfc1-08002be10318}\0002 41 | Compatible IDs: 42 | USBSTOR\Disk 43 | USBSTOR\RAW 44 | 45 | 46 | HardwareID: 47 | USBSTOR\DiskSMI_____USB_DISK________1100 48 | USBSTOR\DiskSMI_____USB_DISK________ 49 | USBSTOR\DiskSMI_____ 50 | USBSTOR\SMI_____USB_DISK________1 51 | SMI_____USB_DISK________1 52 | USBSTOR\GenDisk 53 | GenDisk 54 | 55 | 56 | Windows Portable Devices 57 | FriendlyName: E:\ 58 | Serial Number: 4C531001611013119473&0 59 | Last Write Time: 2016-09-24 17:03:29 UTC+0000 60 | 61 | 62 | Unified output is also available. 63 | 64 | 65 | -------------------------------------------------------------------------------- /vol2/usbstor/README.md~: -------------------------------------------------------------------------------- 1 | # USBSTOR 2 | 3 | ## Descriptions 4 | Scans registries for values relating to USB devices plugged in to the system. 5 | 6 | ## Authors: 7 | 8 | - Kevin Breen 9 | - James Hall 10 | 11 | ## Usage: 12 | 13 | ```vol.py --plugins=plugindir --profile=Win7SP1x64 -f win7mem.img usbstor``` 14 | 15 | Example output: 16 | ``` 17 | user@ubuntu:~/Desktop$ vol.py --plugins=/home/users/volatility_plugins --profile=Win7SP1x86 -f Win7-Analysis-1d23dece.vmem usbstor 18 | Volatility Foundation Volatility Framework 2.5 19 | Reading the USBSTOR Please Wait 20 | Found USB Drive: AAA6O95BT0GDMPM0&0 21 | Serial Number: AAA6O95BT0GDMPM0&0 22 | Vendor: SMI 23 | Product: USB_DISK 24 | Revision: 1100 25 | ClassGUID: USB_DISK 26 | 27 | ContainerID: {638e1754-cecf-5255-8af3-cd6f1e1d51b0} 28 | Mounted Volume: \??\Volume{ffefd27e-110e-11e6-96bb-000c29a1e376} 29 | Drive Letter: \DosDevices\E: 30 | Friendly Name: SMI USB DISK USB Device 31 | USB Name: E:\ 32 | Device Last Connected: 2016-05-10 15:01:22 UTC+0000 33 | 34 | Class: DiskDrive 35 | Service: disk 36 | DeviceDesc: @disk.inf,%disk_devdesc%;Disk drive 37 | Capabilities: 16 38 | Mfg: @disk.inf,%genmanufacturer%;(Standard disk drives) 39 | ConfigFlags: 0 40 | Driver: {4d36e967-e325-11ce-bfc1-08002be10318}\0002 41 | Compatible IDs: 42 | USBSTOR\Disk 43 | USBSTOR\RAW 44 | 45 | 46 | HardwareID: 47 | USBSTOR\DiskSMI_____USB_DISK________1100 48 | USBSTOR\DiskSMI_____USB_DISK________ 49 | USBSTOR\DiskSMI_____ 50 | USBSTOR\SMI_____USB_DISK________1 51 | SMI_____USB_DISK________1 52 | USBSTOR\GenDisk 53 | GenDisk 54 | 55 | 56 | Windows Portable Devices 57 | FriendlyName: E:\ 58 | Serial Number: 4C531001611013119473&0 59 | Last Write Time: 2016-09-24 17:03:29 UTC+0000 60 | 61 | 62 | Unified output is also available. 63 | 64 | 65 | -------------------------------------------------------------------------------- /vol2/lastpass/README.md: -------------------------------------------------------------------------------- 1 | # LastPass 2 | 3 | ## Descriptions 4 | Read browser memory space and attempt to recover any resident artefact's. 5 | 6 | **IMPORTANT** 7 | 8 | It is important to note that this process only works if a memory image was taken whilst the lastpass plugin 9 | was running in active browser. And will only find credentials for domains that are active in a tab 10 | 11 | ## Authors: 12 | 13 | - Kevin Breen 14 | 15 | ## Usage: 16 | 17 | ```vol.py --plugins=plugindir --profile=Win7SP1x64 -f win7mem.img lastpass``` 18 | 19 | Example output: 20 | ``` 21 | localadmin@tech-server:~$ vol.py --plugins=/home/localadmin/github/volatility_plugins/lastpass --profile=Win7SP1x86 -f /home/localadmin/Desktop/lastpass-mem.vmem lastpass 22 | Volatility Foundation Volatility Framework 2.5 23 | LastPass ResultsChecking Process: chrome.exe (3400) 24 | Checking Process: chrome.exe (3400) 25 | Checking Process: chrome.exe (3400) 26 | Checking Process: chrome.exe (3840) 27 | Checking Process: chrome.exe (3840) 28 | Checking Process: chrome.exe (3840) 29 | Checking Process: chrome.exe (3912) 30 | Checking Process: chrome.exe (3912) 31 | Checking Process: chrome.exe (3912) 32 | Checking Process: chrome.exe (3912) 33 | Checking Process: chrome.exe (4092) 34 | Checking Process: chrome.exe (4092) 35 | Checking Process: chrome.exe (4092) 36 | Checking Process: chrome.exe (2036) 37 | Checking Process: chrome.exe (2036) 38 | 39 | Found LastPass Entry for hackforums.net 40 | UserName: peters.lastpass 41 | Pasword: jRvTxxxxxxxxxOTcl 42 | 43 | Found LastPass Entry for facebook.com 44 | UserName: peters.lastpass@gmail.com 45 | Pasword: Unknown 46 | 47 | Found LastPass Entry for sainsburys.co.uk 48 | UserName: peters.lastpass 49 | Pasword: mt5xxxxxxxxxzBj 50 | 51 | Found LastPass Entry for leakforums.net 52 | UserName: peterslastpass 53 | Pasword: rmH6xxxxxxxx9a2 54 | 55 | Found LastPass Entry for facebook.com,facebook.com,messenger.com 56 | UserName: Unknown 57 | Pasword: O3xxxxxxxxG7hs 58 | 59 | ``` 60 | 61 | 62 | 63 | ## ToDo 64 | - Extend to other password managers. 65 | - Display more information. 66 | - Unified Output. -------------------------------------------------------------------------------- /vol3/zone-identifier/zoneid3.py: -------------------------------------------------------------------------------- 1 | import contextlib 2 | import logging 3 | import re 4 | from typing import List 5 | 6 | from volatility3.framework import exceptions, renderers, interfaces 7 | from volatility3.framework.configuration import requirements 8 | from volatility3.plugins import yarascan 9 | 10 | vollog = logging.getLogger(__name__) 11 | 12 | try: 13 | import yara 14 | except ImportError: 15 | vollog.info("Python Yara module not found, plugin (and dependent plugins) not available") 16 | raise 17 | 18 | 19 | class ZoneID3(interfaces.plugins.PluginInterface): 20 | """Scans for ZoneIdentifier 3 streams""" 21 | 22 | _required_framework_version = (2, 0, 0) 23 | _version = (1, 0, 0) 24 | 25 | @classmethod 26 | def get_requirements(cls): 27 | return [ 28 | requirements.TranslationLayerRequirement( 29 | name="primary", 30 | description="Memory layer for the kernel", 31 | architectures=["Intel32", "Intel64"], 32 | ), 33 | requirements.VersionRequirement( 34 | name="yarascanner", component=yarascan.YaraScanner, version=(2, 0, 0) 35 | ), 36 | ] 37 | 38 | 39 | def _generator(self): 40 | layer = self.context.layers[self.config["primary"]] 41 | 42 | # Yara Rule to scan for MFT Header Signatures 43 | rules = yarascan.YaraScan.process_yara_options( 44 | {"yara_rules": "[ZoneTransfer]\\r\\nZoneId=3"} 45 | ) 46 | 47 | # Scan the layer for Raw MFT records and parse the fields 48 | for offset, _rule_name, _name, _value in layer.scan( 49 | context=self.context, scanner=yarascan.YaraScanner(rules=rules) 50 | ): 51 | with contextlib.suppress(exceptions.PagedInvalidAddressException): 52 | # read 1024 bytes from the result to parse 53 | zone_data = layer.read(offset, 1024, False) 54 | 55 | host_url_match = re.search(b'HostUrl=(.*?)\r\n', zone_data) 56 | host_url = host_url_match.group(1) if host_url_match else b'NotPresent' 57 | 58 | referrer_url_match = re.search(b'ReferrerUrl=(.*?)\r\n', zone_data) 59 | referrer_url = referrer_url_match.group(1) if referrer_url_match else b'NotPresent' 60 | 61 | # Only return if we parsed at least one field 62 | if referrer_url == host_url == b'NotPresent': 63 | continue 64 | else: 65 | yield(0,( 66 | "3", 67 | host_url.decode(), 68 | referrer_url.decode() 69 | )) 70 | 71 | def run(self): 72 | return renderers.TreeGrid( 73 | [ 74 | ("ZoneID", str), 75 | ("Host URL", str), 76 | ("Referrer Url", str), 77 | ], 78 | self._generator(), 79 | ) -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # volatility_plugins 2 | A collection of plugins for the Volatility Memory Framework 3 | 4 | Please see individual folders for details. 5 | 6 | ## Vol3 7 | 8 | ### ZoneID3 9 | Scans memory for ZoneIdentifier 3 ADS streams assocaited with files downloaded from the internet 10 | 11 | ``` 12 | $ vol -r pretty -p ~/github/volatility_plugins -f Win10Dev-Snapshot1.vmem zoneid3 13 | Volatility 3 Framework 2.5.0 14 | Formatting...0.00 PDB scanning finished 15 | | ZoneID | Host URL | Referrer Url 16 | * | 3 | https://download.sysinternals.com/files/Sysmon.zip | https://learn.microsoft.com/ 17 | * | 3 | https://raw.githubusercontent.com/olafhartong/sysmon-modular/master/sysmonconfig.xml | NotPresent 18 | * | 3 | https://download.splunk.com/products/universalforwarder/release.msi | NotPresent 19 | * | 3 | https://mh-nexus.de/downloads/HxDSetup.zip | https://mh-nexus.de/en/downloads.php?product=HxD20 20 | * | 3 | NotPresent | C:\Users\User\Downloads\PE-bear_0.6.1_x64_win_vs13.zip 21 | ``` 22 | 23 | ### Cobalt Strike 24 | Scans process memory for each process to identify CobaltStrike config and prints the config elements 25 | 26 | ``` 27 | ❯ vol -r pretty -p ~/github/volatility_plugins -f Server16-CobaltStrike.raw cobaltstrike 28 | Volatility 3 Framework 2.5.0 29 | Formatting...0.00 PDB scanning finished 30 | | PID | Process | Port | Sleep | Jitter | Server | POST_PATH | x86 Install_Path | x64 Install_Path | Pipe | License ID 31 | * | 4396 | ShellExperienc | 4444 | 10000 | 0 | | | %windir%\syswow64\rundll32.exe | %windir%\sysnative\rundll32.exe | \\.\pipe\msagent_89 | 1234567890 32 | * | 4396 | ShellExperienc | 4444 | 10000 | 0 | | | %windir%\syswow64\rundll32.exe | %windir%\sysnative\rundll32.exe | \\.\pipe\msagent_89 | 1234567890 33 | * | 4604 | rundll32.exe | 443 | 5000 | 0 | 54.170.175.43,/ca | /submit.php | %windir%\syswow64\rundll32.exe | %windir%\sysnative\rundll32.exe | | 1234567890 34 | ``` 35 | 36 | ### Password Managers 37 | Extracts cached passwords from browser process memory. 38 | Supports: 39 | - Lastpass 40 | 41 | ``` 42 | $ vol -p ~/github/volatility_plugins -f Win7-Analysis-1d23dece.vmem passwordmanager 43 | Volatility 3 Framework 2.5.0 44 | Progress: 100.00 PDB scanning finished 45 | PID Process Username Password Domain 46 | 47 | 3400 chrome.exe Not found mt5JwaPvLctWFzBj https://www.demodomain.co.uk/ 48 | 3400 chrome.exe Not found Not found https://leakforums.net/ 49 | 3400 chrome.exe Not found rmH61LVBqHSVJ9a2 https://leakforums.net/ 50 | 3400 chrome.exe Not found Not found https://leakforums.net/ 51 | ``` 52 | 53 | ### Rich Header 54 | 55 | Prints the XOR Key and Rich Header Hash for all process executables. 56 | 57 | ``` 58 | $ vol -p ~/github/volatility_plugins -f Server16-CobaltStrike.raw richheader 59 | Volatility 3 Framework 2.5.0 60 | Progress: 100.00 PDB scanning finished 61 | PID Process XOR Key Rich Header Hash 62 | 63 | 380 smss.exe e8fbb614 b4da76d938693e03d2d455ef37561772 64 | 512 csrss.exe fba319c1 e4971216867bfffb7beb058dca378a84 65 | 592 csrss.exe fba319c1 e4971216867bfffb7beb058dca378a84 66 | 608 wininit.exe 75318913 f8116f1336d2c70bd16b01ad8be7bb6d 67 | 644 winlogon.exe 4bc258ac c4f0d2eedff3968a8af33cf724e22790 68 | 716 services.exe b05eb20c 75daeb432ccb73aa5349c09bd00c2945 69 | 728 lsass.exe 631ad1fb 5a2611fd92fa692a9663952ec838d57b 70 | 800 svchost.exe fdedd411 bdf4caf91c4d0776c4021998c204944a 71 | 852 svchost.exe fdedd411 bdf4caf91c4d0776c4021998c204944a 72 | 73 | ``` 74 | 75 | ## Vol2 76 | 77 | These plugins are no longer activly maintained and will be / have been ported to Volatilty V3 78 | 79 | ### USBSTOR 80 | Parses the USBSTOR and other registry values from memory to identify USB Devices connected to the system 81 | 82 | ### LastPass 83 | Read browser memory space and attempt to recover any resident artefacts 84 | 85 | -------------------------------------------------------------------------------- /vol3/richheader/richheader.py: -------------------------------------------------------------------------------- 1 | # Rich Header 2 | # Copyright (C) 2021 Kevin Breen, Immersive Labs 3 | # https://github.com/Immersive-Labs-Sec/volatility_plugins 4 | # 5 | # Permission is hereby granted, free of charge, to any person obtaining a copy 6 | # of this software and associated documentation files (the "Software"), to deal 7 | # in the Software without restriction, including without limitation the rights 8 | # to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | # copies of the Software, and to permit persons to whom the Software is 10 | # furnished to do so, subject to the following conditions: 11 | 12 | # The above copyright notice and this permission notice shall be included in all 13 | # copies or substantial portions of the Software. 14 | 15 | # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | # FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | # AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | # SOFTWARE. 22 | 23 | import logging 24 | import re 25 | import hashlib 26 | from binascii import hexlify 27 | from typing import List 28 | 29 | from volatility3.framework.symbols import intermed 30 | from volatility3.framework.symbols.windows.extensions import pe 31 | from volatility3.framework import constants, exceptions, renderers, interfaces 32 | from volatility3.framework.configuration import requirements 33 | from volatility3.framework.objects import utility 34 | from volatility3.plugins.windows import pslist 35 | 36 | vollog = logging.getLogger(__name__) 37 | 38 | 39 | class RichHeader(interfaces.plugins.PluginInterface): 40 | """Scans process memory for each process to identify CobaltStrike config""" 41 | 42 | _required_framework_version = (2, 0, 0) 43 | _version = (1, 0, 0) 44 | 45 | @classmethod 46 | def get_requirements(cls) -> List[interfaces.configuration.RequirementInterface]: 47 | # Since we're calling the plugin, make sure we have the plugin's requirements 48 | return [ 49 | requirements.ModuleRequirement(name = 'kernel', description = 'Windows kernel', 50 | architectures = ["Intel32", "Intel64"]), 51 | requirements.PluginRequirement(name = 'pslist', plugin = pslist.PsList, version = (2, 0, 0)), 52 | requirements.ListRequirement(name = 'pid', 53 | element_type = int, 54 | description = "Process IDs to include (all other processes are excluded)", 55 | optional = True) 56 | ] 57 | 58 | @classmethod 59 | def xor(self, data, key): 60 | """Multibyte XOR Decode""" 61 | l = len(key) 62 | return bytearray(( 63 | (data[i] ^ key[i % l]) for i in range(0,len(data)) 64 | )) 65 | 66 | 67 | @classmethod 68 | def parse_result(self, raw_data): 69 | """Search the bytes for the Rich Header, extract the XOR key and walk the strucutre 70 | return: XOR Key, md5sum of the decoded header 71 | """ 72 | 73 | rich_header_pattern = b'DOS mode(.*?)Rich(....)' 74 | 75 | search = re.search(rich_header_pattern, raw_data, re.DOTALL) 76 | if search: 77 | xor_key = search.group(2) 78 | header = search.group(1) 79 | 80 | decoded_header = b'' 81 | 82 | # We need to walk backwards until we find the decoded header 83 | for i in range(len(header), 0, -4): 84 | section = header[i:i+4] 85 | xor_section = self.xor(section, xor_key) 86 | decoded_header = xor_section + decoded_header 87 | vollog.debug(xor_section) 88 | if xor_section == b'DanS': 89 | result = hashlib.md5(decoded_header) 90 | return xor_key, result.hexdigest() 91 | else: 92 | return None, None 93 | 94 | 95 | def _generator(self): 96 | 97 | kernel = self.context.modules[self.config['kernel']] 98 | pe_table_name = intermed.IntermediateSymbolTable.create(self.context, 99 | self.config_path, 100 | "windows", 101 | "pe", 102 | class_types = pe.class_types) 103 | 104 | filter_func = pslist.PsList.create_pid_filter(self.config.get('pid', None)) 105 | 106 | for proc in pslist.PsList.list_processes(context = self.context, 107 | layer_name = kernel.layer_name, 108 | symbol_table = kernel.symbol_table_name, 109 | filter_func = filter_func): 110 | 111 | try: 112 | process_name = utility.array_to_string(proc.ImageFileName) 113 | vollog.debug(f'Scanning Process {process_name}\n') 114 | proc_layer_name = proc.add_process_layer() 115 | peb = self.context.object(kernel.symbol_table_name + constants.BANG + "_PEB", 116 | layer_name = proc_layer_name, 117 | offset = proc.Peb) 118 | dos_header = self.context.object(pe_table_name + constants.BANG + "_IMAGE_DOS_HEADER", 119 | offset = peb.ImageBaseAddress, 120 | layer_name = proc_layer_name) 121 | 122 | for offset, data in dos_header.reconstruct(): 123 | xor_key, rich_header_hash = self.parse_result(data) 124 | if rich_header_hash: 125 | yield (0, ( 126 | proc.UniqueProcessId, 127 | process_name, 128 | hexlify(xor_key).decode(), 129 | rich_header_hash, 130 | )) 131 | break 132 | 133 | except Exception as err: 134 | vollog.info(f'Unable to read proc for pid {proc.UniqueProcessId} {err}') 135 | 136 | def run(self): 137 | 138 | return renderers.TreeGrid([ 139 | ("PID", int), 140 | ("Process", str), 141 | ("XOR Key", str), 142 | ("Rich Header Hash", str) 143 | ], 144 | self._generator( 145 | )) 146 | -------------------------------------------------------------------------------- /vol3/passwordmanagers/passwordmanagers.py: -------------------------------------------------------------------------------- 1 | import logging 2 | import re 3 | from typing import List 4 | 5 | from volatility3.framework import constants, exceptions, renderers, interfaces 6 | from volatility3.framework.configuration import requirements 7 | from volatility3.framework.objects import utility 8 | from volatility3.plugins import yarascan 9 | from volatility3.plugins.windows import pslist, vadyarascan 10 | 11 | vollog = logging.getLogger(__name__) 12 | 13 | try: 14 | import yara 15 | except ImportError: 16 | vollog.info("Python Yara module not found, plugin (and dependent plugins) not available") 17 | raise 18 | 19 | browser_procs = ['chrome.exe', 'firefox.exe', 'iexplore.exe'] 20 | 21 | signatures = { 22 | 'lastpass_struct_a': 'rule lastpass_strcuta {strings: $a = /{"reqinfo":.*"lplanguage":""}/ condition: $a}\n' 23 | 'rule lastpass_strcutb {strings: $a = /"tld":".*?","unencryptedUsername":".*?","realmmatch"/ condition: $a}\n' 24 | 'rule lastpass_strcutc {strings: $a = /{"cmd":"save"(.*?)"tld":"(.*?)"}/ condition: $a}\n' 25 | 'rule lastpass_strcutd {strings: $a = /"realurl":"(.*?)"domains":"(.*?)"/ condition: $a}\n' 26 | 'rule lastpass_strcute {strings: $a = /{"cmd":"save"(.*?)"formdata"(.*?)}/ condition: $a}\n' 27 | 'rule lastpass_priv1 {strings: $a = /LastPassPrivateKey<(.*?)>LastPassPrivateKey/ condition: $a}' 28 | } 29 | 30 | class PasswordManager(interfaces.plugins.PluginInterface): 31 | """Scan all processs for browsers and then scan the process memory for any password manager fragments""" 32 | 33 | _required_framework_version = (2, 0, 0) 34 | _version = (1, 0, 0) 35 | 36 | @classmethod 37 | def get_requirements(cls) -> List[interfaces.configuration.RequirementInterface]: 38 | return [ 39 | requirements.ModuleRequirement(name = 'kernel', description = 'Windows kernel', 40 | architectures = ["Intel32", "Intel64"]), 41 | requirements.PluginRequirement(name = 'pslist', plugin = pslist.PsList, version = (2, 0, 0)), 42 | requirements.PluginRequirement(name = 'vadyarascan', plugin = vadyarascan.VadYaraScan, version = (1, 0, 0)), 43 | requirements.ListRequirement(name = 'pid', 44 | element_type = int, 45 | description = "Process IDs to include (all other processes are excluded)", 46 | optional = True) 47 | ] 48 | 49 | 50 | @classmethod 51 | def parse_result(self, rule, value): 52 | """Takes a rule name and the raw data from the process and attempts to extract 3 values""" 53 | vollog.debug(rule) 54 | username = password = domain = None 55 | 56 | if rule.startswith('lastpass'): 57 | 58 | # RE Patterns for LastPass 59 | password_pattern = b'"logonPassword","value":"(.*?)"|"password","value":"(.*?)"' 60 | username_pattern = b'"unencryptedUsername":"(.*?)"' 61 | domain_pattern = b'"tld":"(.*?)"|"topurl":"(.*?)"' 62 | 63 | # Find all password elements and read first non empty field 64 | password_search = re.findall(password_pattern, value) 65 | try: 66 | password = next(item for item in password_search[0] if item != b'') 67 | except: 68 | password = b'Not found' 69 | 70 | # Find all username elements and read first non empty field 71 | username_search = re.findall(username_pattern, value) 72 | try: 73 | username = next(item for item in username_search if item != b'') 74 | except: 75 | username = b'Not found' 76 | 77 | # Find all domain elements and read first non empty field 78 | domain_search = re.findall(domain_pattern, value) 79 | try: 80 | domain = next(item for item in domain_search[0] if item != b'') 81 | except: 82 | domain = b'Not found' 83 | 84 | vollog.debug(f'\n\n------->{[username, password, domain]}\n\n') 85 | 86 | # From bytes to ascii as the GridTree will print hex otherwise 87 | return username.decode(), password.decode(), domain.decode() 88 | 89 | 90 | def _generator(self, procs): 91 | 92 | # Compile the list of rules 93 | rules = yara.compile(sources = signatures) 94 | 95 | for proc in procs: 96 | process_name = utility.array_to_string(proc.ImageFileName) 97 | 98 | # continue to next loop if we are not in a supported browser process. 99 | if not process_name in browser_procs: 100 | continue 101 | 102 | vollog.debug(f'Found Browser Process {process_name}\n') 103 | 104 | try: 105 | proc_id = proc.UniqueProcessId 106 | proc_layer_name = proc.add_process_layer() 107 | except exceptions.InvalidAddressException as excp: 108 | vollog.debug("Process {}: invalid address {} in layer {}".format(proc_id, excp.invalid_address, 109 | excp.layer_name)) 110 | continue 111 | 112 | layer = self.context.layers[proc_layer_name] 113 | 114 | # Run the yara scan with our collection of rules. The offset is the important part here. 115 | for offset, rule_name, name, value in layer.scan(context = self.context, 116 | scanner = yarascan.YaraScanner(rules = rules), 117 | sections = vadyarascan.VadYaraScan.get_vad_maps(proc)): 118 | 119 | # Read 1024 bytes from the layer at the offset and try to parse out some values. 120 | username, password, domain = self.parse_result(rule_name, layer.read(offset, 1024, False)) 121 | 122 | # Only Yield if we have at least one value 123 | if username == password == domain == 'Not found': 124 | pass 125 | else: 126 | yield (0, (proc.UniqueProcessId, process_name, username, password, domain)) 127 | 128 | def run(self): 129 | kernel = self.context.modules[self.config['kernel']] 130 | filter_func = pslist.PsList.create_pid_filter(self.config.get('pid', None)) 131 | 132 | return renderers.TreeGrid([("PID", int), ("Process", str), ("Username", str), ("Password", str), ("Domain", str)], 133 | self._generator( 134 | pslist.PsList.list_processes(context = self.context, 135 | layer_name = kernel.layer_name, 136 | symbol_table = kernel.symbol_table_name, 137 | filter_func = filter_func))) 138 | -------------------------------------------------------------------------------- /vol3/cobaltstrike/cobaltstrike.py: -------------------------------------------------------------------------------- 1 | import logging 2 | import re 3 | import struct 4 | from typing import List 5 | 6 | from volatility3.framework import constants, exceptions, renderers, interfaces 7 | from volatility3.framework.configuration import requirements 8 | from volatility3.framework.objects import utility 9 | from volatility3.plugins import yarascan 10 | from volatility3.plugins.windows import pslist, vadyarascan 11 | 12 | vollog = logging.getLogger(__name__) 13 | 14 | try: 15 | import yara 16 | except ImportError: 17 | vollog.info("Python Yara module not found, plugin (and dependent plugins) not available") 18 | raise 19 | 20 | 21 | signatures = { 22 | 'cs_config_start': """rule cobaltstrike_config 23 | { 24 | strings: 25 | $a = {2E 2F 2E 2F 2E 2C ?? ?? 2E 2C 2E 2F 2E 2C} 26 | $b = {69 68 69 68 69 6B ?? ?? 69 6B 69 68 69 6B} 27 | //$c = {?? 00 01 00 01 00 02 ?? ?? 00 02 00 01 00 02} 28 | condition: 29 | any of them 30 | }""" 31 | } 32 | 33 | # Config Struct from 34 | # https://blog.didierstevens.com/2021/11/21/update-1768-py-version-0-0-10/ 35 | # We incluide the full mapping even though we dont need it for reference 36 | CONFIG_STRUCT = { 37 | 0x0001: 'payload type', 38 | 0x0002: 'port', 39 | 0x0003: 'sleeptime', 40 | 0x0004: 'maxgetsize', # 41 | 0x0005: 'jitter', 42 | 0x0006: 'maxdns', 43 | 0x0007: 'publickey', 44 | 0x0008: 'server,get-uri', 45 | 0x0009: 'useragent', 46 | 0x000a: 'post-uri', 47 | 0x000b: 'Malleable_C2_Instructions', # 48 | 0x000c: 'http_get_header', 49 | 0x000d: 'http_post_header', 50 | 0x000e: 'SpawnTo', # 51 | 0x000f: 'pipename', 52 | 0x0010: 'killdate_year', # 53 | 0x0011: 'killdate_month', # 54 | 0x0012: 'killdate_day', # 55 | 0x0013: 'DNS_Idle', # 56 | 0x0014: 'DNS_Sleep', # 57 | 0x0015: 'SSH_HOST', # 58 | 0x0016: 'SSH_PORT', # 59 | 0x0017: 'SSH_USER-NAME', # 60 | 0x0018: 'SSH_PASSWORD', # 61 | 0x0019: 'SSH_PUBKEY', # 62 | 0x001a: 'get-verb', 63 | 0x001b: 'post-verb', 64 | 0x001c: 'HttpPostChunk', # 65 | 0x001d: 'spawnto_x86', 66 | 0x001e: 'spawnto_x64', 67 | 0x001f: 'CryptoScheme', # 68 | 0x0020: 'proxy', 69 | 0x0021: 'proxy_username', 70 | 0x0022: 'proxy_password', 71 | 0x0023: 'proxy_type', 72 | 0x0024: 'deprecated', # 73 | 0x0025: 'license-id', 74 | 0x0026: 'bStageCleanup', # 75 | 0x0027: 'bCFGCaution', # 76 | 0x0028: 'killdate', 77 | 0x0029: 'textSectionEnd', # 78 | 0x002a: 'ObfuscateSectionsInfo', # 79 | 0x002b: 'process-inject-start-rwx', 80 | 0x002c: 'process-inject-use-rwx', 81 | 0x002d: 'process-inject-min_alloc', 82 | 0x002e: 'process-inject-transform-x86', 83 | 0x002f: 'process-inject-transform-x64', 84 | 0x0030: 'DEPRECATED_PROCINJ_ALLOWED', 85 | 0x0031: 'BIND_HOST', 86 | 0x0032: 'UsesCookies', 87 | 0x0033: 'process-inject-execute', 88 | 0x0034: 'process-inject-allocation-method', 89 | 0x0035: 'process-inject-stub', 90 | 0x0036: 'HostHeader', 91 | 0x0037: 'EXIT_FUNK', 92 | 0x0038: 'SSH_BANNER', 93 | 0x0039: 'SMB_FRAME_HEADER', 94 | 0x003a: 'TCP_FRAME_HEADER', 95 | 0x003b: 'HEADERS_TO_REMOVE', 96 | 0x003c: 'DNS_beacon', 97 | 0x003d: 'DNS_A', 98 | 0x003e: 'DNS_AAAA', 99 | 0x003f: 'DNS_TXT', 100 | 0x0040: 'DNS_metadata', 101 | 0x0041: 'DNS_output', 102 | 0x0042: 'DNS_resolver', 103 | 0x0043: 'DNS_STRATEGY', 104 | 0x0044: 'DNS_STRATEGY_ROTATE_SECONDS', 105 | 0x0045: 'DNS_STRATEGY_FAIL_X', 106 | 0x0046: 'DNS_STRATE-GY_FAIL_SEC-ONDS', 107 | } 108 | 109 | class CobaltStrike(interfaces.plugins.PluginInterface): 110 | """Scans process memory for each process to identify CobaltStrike config""" 111 | 112 | _required_framework_version = (2, 0, 0) 113 | _version = (1, 0, 0) 114 | 115 | @classmethod 116 | def get_requirements(cls) -> List[interfaces.configuration.RequirementInterface]: 117 | # Since we're calling the plugin, make sure we have the plugin's requirements 118 | return [ 119 | requirements.ModuleRequirement(name = 'kernel', description = 'Windows kernel', 120 | architectures = ["Intel32", "Intel64"]), 121 | requirements.PluginRequirement(name = 'pslist', plugin = pslist.PsList, version = (2, 0, 0)), 122 | requirements.PluginRequirement(name = 'vadyarascan', plugin = vadyarascan.VadYaraScan, version = (1, 0, 0)), 123 | requirements.ListRequirement(name = 'pid', 124 | element_type = int, 125 | description = "Process IDs to include (all other processes are excluded)", 126 | optional = True) 127 | ] 128 | 129 | 130 | @classmethod 131 | def parse_result(self, rule, value): 132 | """Parse the config from the result""" 133 | 134 | # https://github.com/Te-k/cobaltstrike/blob/master/lib.py 135 | # This helped me figure out how to walk the structs after XOR 136 | 137 | if value.startswith(b'././'): 138 | xor_key = 0x2e 139 | elif value.startswith(b'ihih'): 140 | xor_key = 105 141 | else: 142 | xor_key = None 143 | 144 | vollog.debug(f'Found XOR Key {xor_key}') 145 | if not xor_key: 146 | vollog.info("Unable to find config file") 147 | return None 148 | 149 | # XOR the raw values to get the config section 150 | data = bytearray([c ^ xor_key for c in value]) 151 | 152 | vollog.debug(data) 153 | 154 | config = {} 155 | i = 0 156 | while i < len(data) - 8: 157 | if data[i] == 0 and data[i+1] == 0: 158 | break 159 | dec = struct.unpack(">HHH", data[i:i+6]) 160 | if dec[0] == 1: 161 | v = struct.unpack(">H", data[i+6:i+8])[0] 162 | config["dns"] = ((v & 1) == 1) 163 | config["ssl"] = ((v & 8) == 8) 164 | else: 165 | if dec[0] in CONFIG_STRUCT.keys(): 166 | key = CONFIG_STRUCT[dec[0]] 167 | else: 168 | vollog.debug("Unknown config command {}".format(dec[0])) 169 | key = str(dec[0]) 170 | if dec[1] == 1 and dec[2] == 2: 171 | # Short 172 | config[key] = struct.unpack(">H", data[i+6:i+8])[0] 173 | elif dec[1] == 2 and dec[2] == 4: 174 | # Int 175 | config[key] = struct.unpack(">I", data[i+6:i+10])[0] 176 | elif dec[1] == 3: 177 | # Byte or string 178 | v = data[i+6:i+6+dec[2]] 179 | try: 180 | config[key] = v.decode('utf-8').strip('\x00') 181 | except UnicodeDecodeError: 182 | config[key] = v 183 | # Add size + header 184 | i += dec[2] + 6 185 | 186 | vollog.debug(config) 187 | return config 188 | 189 | def _generator(self, procs): 190 | 191 | # Compile the list of rules 192 | rules = yara.compile(sources = signatures) 193 | 194 | for proc in procs: 195 | process_name = utility.array_to_string(proc.ImageFileName) 196 | 197 | vollog.debug(f'Scanning Process {process_name}\n') 198 | 199 | try: 200 | proc_id = proc.UniqueProcessId 201 | proc_layer_name = proc.add_process_layer() 202 | except exceptions.InvalidAddressException as excp: 203 | vollog.debug("Process {}: invalid address {} in layer {}".format(proc_id, excp.invalid_address, 204 | excp.layer_name)) 205 | continue 206 | 207 | layer = self.context.layers[proc_layer_name] 208 | 209 | # Run the yara scan with our collection of rules. The offset is the important part here. 210 | for offset, rule_name, name, value in layer.scan(context = self.context, 211 | scanner = yarascan.YaraScanner(rules = rules), 212 | sections = vadyarascan.VadYaraScan.get_vad_maps(proc)): 213 | 214 | if rule_name == 'cobaltstrike_config': 215 | # Read 1024 bytes from the layer at the offset and try to parse out some values. 216 | config = self.parse_result(rule_name, layer.read(offset, 3096, False)) 217 | yield (0, ( 218 | proc.UniqueProcessId, 219 | process_name, 220 | config.get('port', 0), 221 | config.get('sleeptime', 0), 222 | config.get('jitter', 0), 223 | config.get('server,get-uri', ''), 224 | config.get('post-uri', ''), 225 | config.get('spawnto_x86', ''), 226 | config.get('spawnto_x64', ''), 227 | config.get('pipename', ''), 228 | config.get('license-id', 0), 229 | )) 230 | 231 | 232 | def run(self): 233 | kernel = self.context.modules[self.config['kernel']] 234 | filter_func = pslist.PsList.create_pid_filter(self.config.get('pid', None)) 235 | 236 | return renderers.TreeGrid([ 237 | ("PID", int), 238 | ("Process", str), 239 | ("Port", int), 240 | ("Sleep", int), 241 | ("Jitter", int), 242 | ("Server", str), 243 | ("POST_PATH", str), 244 | ("x86 Install_Path", str), 245 | ("x64 Install_Path", str), 246 | ('Pipe', str), 247 | ("License ID", int), 248 | ], 249 | self._generator( 250 | pslist.PsList.list_processes(context = self.context, 251 | layer_name = kernel.layer_name, 252 | symbol_table = kernel.symbol_table_name, 253 | filter_func = filter_func))) 254 | -------------------------------------------------------------------------------- /vol2/lastpass/lastpass.py: -------------------------------------------------------------------------------- 1 | # Donated under Volatility Foundation, Inc. Individual Contributor Licensing Agreement 2 | # LastPass - Recover memory resident account information. 3 | # Author: Kevin Breen 4 | # Thanks to the guide on http://www.ghettoforensics.com/2013/10/dumping-malware-configuration-data-from.html 5 | 6 | import volatility.plugins.taskmods as taskmods 7 | import volatility.win32.tasks as tasks 8 | import volatility.utils as utils 9 | import volatility.debug as debug 10 | import volatility.plugins.malware.malfind as malfind 11 | import volatility.conf as conf 12 | import re 13 | import json 14 | import string 15 | 16 | try: 17 | import yara 18 | has_yara = True 19 | except ImportError: 20 | has_yara = False 21 | 22 | 23 | signatures = { 24 | 'lastpass_struct_a': 'rule lastpass_strcuta {strings: $a = /{"reqinfo":.*"lplanguage":""}/ condition: $a}\n' 25 | 'rule lastpass_strcutb {strings: $a = /"tld":".*?","unencryptedUsername":".*?","realmmatch"/ condition: $a}\n' 26 | 'rule lastpass_strcutc {strings: $a = /{"cmd":"save"(.*?)"tld":"(.*?)"}/ condition: $a}\n' 27 | 'rule lastpass_strcutd {strings: $a = /"realurl":"(.*?)"domains":"(.*?)"/ condition: $a}\n' 28 | 'rule lastpass_strcute {strings: $a = /{"cmd":"save"(.*?)"formdata"(.*?)}/ condition: $a}\n' 29 | 'rule lastpass_priv1 {strings: $a = /LastPassPrivateKey<(.*?)>LastPassPrivateKey/ condition: $a}' 30 | } 31 | 32 | config = conf.ConfObject() 33 | config.add_option('CONFSIZE', short_option='C', default=4000, 34 | help ='Config data size', 35 | action ='store', type='int') 36 | config.add_option('YARAOFFSET', short_option='Y', default=0, 37 | help ='YARA start offset', 38 | action ='store', type='int') 39 | 40 | class LastPass(taskmods.PSList): 41 | """ Extract lastpass data from process. """ 42 | 43 | def calculate(self): 44 | """ Required: Runs YARA search to find hits """ 45 | if not has_yara: 46 | debug.error('Yara must be installed for this plugin') 47 | 48 | addr_space = utils.load_as(self._config) 49 | rules = yara.compile(sources = signatures) 50 | for task in self.filter_tasks(tasks.pslist(addr_space)): 51 | if not task.ImageFileName.lower() in ['chrome.exe', 'firefox.exe', 'iexplore.exe']: 52 | continue 53 | scanner = malfind.VadYaraScanner(task=task, rules=rules) 54 | for hit, address in scanner.scan(): 55 | yield task, address 56 | 57 | def string_clean_hex(self, line): 58 | line = str(line) 59 | new_line = '' 60 | for c in line: 61 | if c in string.printable: 62 | new_line += c 63 | else: 64 | new_line += '\\x' + c.encode('hex') 65 | return new_line 66 | 67 | 68 | def clean_json(self, raw_data): 69 | # We deliberately pull in too much data to make sure we get it all. 70 | # Now parse it out again 71 | 72 | if raw_data.startswith('LastPassPrivate'): 73 | pattern = 'LastPassPrivateKey<(.*?)>LastPassPrivateKey' 74 | key_data = re.search(pattern, raw_data).group(0) 75 | if not any(substring in key_data for substring in ['"+a+"', 'indexOf']): 76 | return self.string_clean_hex(key_data) 77 | 78 | else: 79 | 80 | if raw_data.startswith("{\"cmd"): 81 | if 'formdata' in raw_data: 82 | pattern = '{"cmd":"save"(.*?)"formdata"(.*?)}' 83 | val_type = 'mixedform' 84 | else: 85 | pattern = '{"cmd":"save"(.*?)"tld":"(.*?)"}' 86 | val_type = 'mixed' 87 | 88 | elif raw_data.startswith("\"realurl"): 89 | pattern = '"realurl":"(.*?)"domains":"(.*?)"' 90 | val_type = 'mixed' 91 | 92 | elif raw_data.startswith("\"tld"): 93 | pattern = '"tld":".*?","unencryptedUsername":".*?","realmmatch"' 94 | val_type = 'username' 95 | 96 | elif raw_data.startswith('{"reqinfo"'): 97 | pattern = '{"reqinfo":.*?"lplanguage":""}' 98 | val_type = 'password' 99 | else: 100 | pass 101 | 102 | match = re.search(pattern, raw_data) 103 | real_data = self.string_clean_hex(match.group(0)) 104 | 105 | if val_type == 'username': 106 | vars = real_data.split(',') 107 | tld = vars[0].split(':')[1].strip('"') 108 | username = vars[1].split(':')[1].strip('"') 109 | return {'type': 'username', 'username': username, 'tld': tld} 110 | 111 | elif val_type == 'mixedform': 112 | if '"tld":"' in real_data: 113 | # Try to parse as json 114 | try: 115 | json_data = json.loads(real_data) 116 | tld = json_data['tld'] 117 | password = json_data['password'] 118 | username = json_data['username'] 119 | clean_data = {'type': 'mixed', 'username': username, 'tld': tld, 'password': password} 120 | except ValueError: 121 | # Json fails manual parse 122 | tld = re.search('"tld":"(.*?)"', real_data) 123 | if not tld: 124 | tld = re.search('"domains":"(.*?)"', real_data) 125 | if tld: 126 | tld = tld.group(0).split(':')[-1].strip('"') 127 | else: 128 | tld = 'unknown' 129 | try: 130 | password = re.search('"password":"(.*?)"', real_data).group(0).split(':')[-1].strip('"') 131 | except: 132 | password = 'Unknown' 133 | try: 134 | username = re.search('"username":"(.*?)"', real_data).group(0).split(':')[-1].strip('"') 135 | except: 136 | username = 'Unknown' 137 | clean_data = {'type': 'mixed', 'username': username, 'tld': tld, 'password': password} 138 | 139 | else: 140 | tld = re.search('"url":"(.*?)"', real_data).group(0).split(':')[-1].strip('"') 141 | username = re.search('login(.*?)text', real_data).group(0).split('\\t')[1] 142 | password = re.search('password(.*?)password', real_data).group(0).split('\\t')[1] 143 | clean_data = {'type': 'mixed', 'username': username, 'tld': tld, 'password': password} 144 | 145 | return clean_data 146 | 147 | elif val_type == 'mixed': 148 | # Try to parse as json 149 | try: 150 | json_data = json.loads(real_data) 151 | tld = json_data['tld'] 152 | password = json_data['password'] 153 | username = json_data['username'] 154 | clean_data = {'type': 'mixed', 'username': username, 'tld': tld, 'password': password} 155 | except ValueError: 156 | # Json fails manual parse 157 | tld = re.search('"tld":"(.*?)"', real_data) 158 | if not tld: 159 | tld = re.search('"domains":"(.*?)"', real_data) 160 | 161 | if tld: 162 | tld = tld.group(0).split(':')[-1].strip('"') 163 | else: 164 | tld = 'unknown' 165 | 166 | try: 167 | password = re.search('"password":"(.*?)"', real_data).group(0).split(':')[-1].strip('"') 168 | except: 169 | password = 'Unknown' 170 | try: 171 | username = re.search('"username":"(.*?)"', real_data).group(0).split(':')[-1].strip('"') 172 | except: 173 | username = 'Unknown' 174 | 175 | clean_data = {'type': 'mixed', 'username': username, 'tld': tld, 'password': password} 176 | else: 177 | # Try to parse as json 178 | try: 179 | json_data = json.loads(real_data) 180 | tld = json_data['domains'] 181 | password = json_data['value'] 182 | clean_data = {'type': 'password', 'password': password, 'tld': tld} 183 | except ValueError: 184 | # Json fails manual parse 185 | password = re.search('"value":"(.*?)"', real_data).group(0).split(':')[-1].strip('"') 186 | tld = re.search('"domains":"(.*?)"', real_data).group(0).split(':')[-1].strip('"') 187 | clean_data = {'type': 'password', 'password': password, 'tld': tld} 188 | 189 | return clean_data 190 | 191 | def render_text(self, outfd, data): 192 | """ Required: Parse data and display """ 193 | outfd.write("Searching for LastPass Signatures\n") 194 | 195 | rules = yara.compile(sources=signatures) 196 | 197 | results = {} 198 | priv_keys = [] 199 | 200 | for task, address in data: # iterate the yield values from calculate() 201 | outfd.write('Found pattern in Process: {0} ({1})\n'.format(task.ImageFileName, task.UniqueProcessId)) 202 | proc_addr_space = task.get_process_address_space() 203 | raw_data = proc_addr_space.read(address + self._config.YARAOFFSET, self._config.CONFSIZE) 204 | if raw_data: 205 | clean_data = self.clean_json(raw_data) 206 | if not clean_data: 207 | continue 208 | if 'PrivateKey' in clean_data: 209 | priv_keys.append(clean_data) 210 | else: 211 | # If we already created the dict 212 | if clean_data['tld'] in results: 213 | if 'username' in clean_data: 214 | if results[clean_data['tld']]['username'] == 'Unknown': 215 | results[clean_data['tld']]['username'] = clean_data['username'] 216 | if 'password' in clean_data: 217 | if results[clean_data['tld']]['password'] == 'Unknown': 218 | results[clean_data['tld']]['password'] = clean_data['password'] 219 | # Else create the dict 220 | else: 221 | if 'username' in clean_data: 222 | username = clean_data['username'] 223 | else: 224 | username = 'Unknown' 225 | if 'password' in clean_data: 226 | password = clean_data['password'] 227 | else: 228 | password = 'Unknown' 229 | results[clean_data['tld']] = {'username': username, 'password': password} 230 | 231 | for k, v in results.iteritems(): 232 | outfd.write("\nFound LastPass Entry for {0}\n".format(k)) 233 | outfd.write('UserName: {0}\n'.format(v['username'])) 234 | outfd.write('Pasword: {0}\n'.format(v['password'])) 235 | outfd.write('\n') 236 | 237 | for key in priv_keys: 238 | outfd.write('\nFound Private Key\n') 239 | outfd.write('{0}\n'.format(key)) 240 | -------------------------------------------------------------------------------- /vol2/usbstor/usbstor.py: -------------------------------------------------------------------------------- 1 | # Donated under Volatility Foundation, Inc. Individual Contributor Licensing Agreement 2 | # 3 | # Authors: James Hall And Kevin Breen 4 | 5 | import string 6 | import volatility.debug as debug 7 | import volatility.plugins.common as common 8 | import volatility.plugins.registry.registryapi as registryapi 9 | from volatility.renderers import TreeGrid 10 | 11 | 12 | class USBSTOR(common.AbstractWindowsCommand): 13 | "Parse USB Data from the Registry" 14 | 15 | def __init__(self, config, *args, **kwargs): 16 | common.AbstractWindowsCommand.__init__(self, config, *args, **kwargs) 17 | self.regapi = None 18 | 19 | def string_clean_hex(self, line): 20 | line = str(line) 21 | new_line = '' 22 | for c in line: 23 | if c in string.printable: 24 | new_line += c 25 | else: 26 | new_line += '\\x' + c.encode('hex') 27 | return new_line 28 | 29 | def calculate(self): 30 | # Store teh results in a dict 31 | results = {} 32 | results['Windows Portable Devices'] = [] 33 | results['subkeys'] = [] 34 | results['USB_DEVICES'] = [] 35 | 36 | # Grab the software HIVE 37 | debug.debug("Reading SOFTWARE Hive") 38 | self.regapi = registryapi.RegistryApi(self._config) 39 | self.regapi.set_current("SOFTWARE") 40 | 41 | 42 | # Windows version will be useful later on. 43 | WIN_VERSION_PATH = "Microsoft\\Windows NT\\CurrentVersion" 44 | win_ver = self.regapi.reg_get_value(hive_name="software", key=WIN_VERSION_PATH, value="CurrentVersion") 45 | win_ver = float(win_ver.replace('\x00', '')) 46 | 47 | # Windows Portable devices for things like phones 48 | PORTABLE_DEVICES = "Microsoft\\Windows Portable Devices\\Devices" 49 | portable_devices_key = self.regapi.reg_get_key('SOFTWARE', PORTABLE_DEVICES) 50 | portable_devices = self.regapi.reg_get_all_subkeys('SOFTWARE', PORTABLE_DEVICES, 51 | given_root=portable_devices_key) 52 | 53 | for device in portable_devices: 54 | portable_dict = {'Serial Number': '', 'FriendlyName': ''} 55 | values = self.regapi.reg_yield_values('SOFTWARE', device, given_root=device) 56 | for val in values: 57 | device_name = str(device.Name) 58 | portable_dict['Last Write Time'] = str(device.LastWriteTime) 59 | portable_dict['Serial Number'] = device_name.split('#')[-2] 60 | portable_dict['FriendlyName'] = str(val[1].replace('\x00', '')) 61 | results['Windows Portable Devices'].append(portable_dict) 62 | 63 | # Now Jump in to the SYSTEM Hive 64 | self.regapi.reset_current() 65 | self.regapi.set_current("SYSTEM") 66 | debug.debug("Reading SYSTEM Hive") 67 | 68 | # Get the CurrentControlSet 69 | currentcs = self.regapi.reg_get_currentcontrolset() 70 | 71 | if currentcs == None: 72 | currentcs = "ControlSet001" 73 | 74 | # Get list of devices form USBSTOR 75 | USB_PATH = '{0}\\Enum\\USB'.format(currentcs) 76 | USB_STOR_PATH = '{0}\\Enum\\USBSTOR'.format(currentcs) 77 | MOUNTED_DEVICES = 'MountedDevices' 78 | 79 | usb_key = self.regapi.reg_get_key('SYSTEM', USB_PATH) 80 | usb_stor_key = self.regapi.reg_get_key('SYSTEM', USB_STOR_PATH) 81 | mounted_devices_key = self.regapi.reg_get_key('SYSTEM', MOUNTED_DEVICES) 82 | 83 | # Is there something there? 84 | if not usb_stor_key: 85 | results['exists'] = False 86 | yield results 87 | else: 88 | results['exists'] = True 89 | 90 | # Only run if we have something to do 91 | if results['exists']: 92 | sub_keys = self.regapi.reg_get_all_subkeys('SYSTEM', USB_STOR_PATH, given_root=usb_stor_key) 93 | for k in sub_keys: 94 | # Theses are grouped by vender 95 | disk, vendor, product, rev = str(k.Name).split('&') 96 | vendor = vendor.split('_', 1)[-1] 97 | product = product.split('_', 1)[-1] 98 | rev = rev.split('_', 1)[-1] 99 | 100 | results['subkeys'].append(str(k.Name)) 101 | usb_devs = self.regapi.reg_get_all_subkeys('SYSTEM', k, given_root=k) 102 | for dev in usb_devs: 103 | # These are individual devices 104 | # This is what we use to map in to the USB_DEVICES 105 | usb_info_dict = {'Serial Number': str(dev.Name), 106 | 'Vendor': vendor, 107 | 'Product': product, 108 | 'Revision': rev} 109 | 110 | # Get all the sub values 111 | values = self.regapi.reg_yield_values('SYSTEM', dev, given_root=dev) 112 | for val in values: 113 | try: 114 | key_name = val[0].replace('\x00', '') 115 | key_data = val[1].replace('\x00', '') 116 | usb_info_dict[str(key_name)] = key_data 117 | except AttributeError: 118 | key_name = val[0].replace('\x00', '') 119 | try: 120 | key_data = val[1].replace('\x00', '') 121 | except: 122 | key_data = val[1] 123 | usb_info_dict[str(key_name)] = key_data 124 | 125 | # Get the last written key for each device 126 | serial_number = usb_info_dict['Serial Number'] 127 | usb_info_dict['Device Last Plugged In'] = 'Unknown' 128 | usb_subkeys = self.regapi.reg_get_all_subkeys('SYSTEM', USB_PATH, given_root=usb_key) 129 | for a in usb_subkeys: 130 | subs = self.regapi.reg_get_all_subkeys('SYSTEM', a, given_root=a) 131 | for s in subs: 132 | if serial_number.split('&')[0] == s.Name: 133 | usb_info_dict['Device Last Plugged In'] = str(s.LastWriteTime) 134 | 135 | # Now get the Drive letters if we can 136 | if win_ver >= 6.0: 137 | # Win > 7, Server > 2012 138 | # Mounted Devices Key 139 | serial_number = usb_info_dict['Serial Number'] 140 | usb_info_dict['Drive Letter'] = "Unknown" 141 | usb_info_dict['Mounted Volume'] = "Unknown" 142 | usb_info_dict['USB Name'] = "Unknown" 143 | values = self.regapi.reg_yield_values('SYSTEM', mounted_devices_key, 144 | given_root=mounted_devices_key) 145 | 146 | for val in values: 147 | key_name = str(val[0]) 148 | key_data = val[1] 149 | key_data = str(key_data.replace('\x00', '')) 150 | key_data = self.string_clean_hex(key_data) 151 | if serial_number in str(key_data): 152 | if 'Device' in str(key_name): 153 | usb_info_dict['Drive Letter'] = str(key_name) 154 | elif 'Volume' in str(key_name): 155 | usb_info_dict['Mounted Volume'] = str(key_name) 156 | for portable_dict in results['Windows Portable Devices']: 157 | if serial_number in portable_dict['Serial Number']: 158 | usb_info_dict['USB Name'] = str(portable_dict['FriendlyName']) 159 | debug.debug(type(usb_info_dict['USB Name'])) 160 | 161 | if win_ver < 6.0: 162 | # Win XP 163 | ParentID = usb_info_dict['ParentIdPrefix'] 164 | usb_info_dict['Drive Letter'] = "Unknown" 165 | usb_info_dict['Mounted Volume'] = "Unknown" 166 | values = self.regapi.reg_yield_values('SYSTEM', mounted_devices_key, 167 | given_root=mounted_devices_key) 168 | for val in values: 169 | key_name = val[0] 170 | key_data = val[1] 171 | key_data = key_data.replace('\x00', '') 172 | key_data = self.string_clean_hex(key_data) 173 | # debug.info(key_data) 174 | if ParentID in key_data: 175 | if 'Device' in str(key_name): 176 | usb_info_dict['Drive Letter'] = key_name 177 | elif 'Volume' in str(key_name): 178 | usb_info_dict['Mounted Volume'] = key_name 179 | 180 | # ToDo: Check if the current NTUSER.dat file contains the MountPoints2 entry 181 | # If yes user = this one else user = unknown 182 | results['USB_DEVICES'].append(usb_info_dict) 183 | # Return the results dict 184 | yield results 185 | 186 | def unified_output(self, data): 187 | 188 | return TreeGrid([("Serial Number", str), 189 | ("Vendor", str), 190 | ("Product", str), 191 | ("Revision", str), 192 | ("ClassGUID", str), 193 | ("ContainerID", str), 194 | ("Mounted Volume", str), 195 | ("FriendlyName", str), 196 | ("USB Name", str), 197 | ("Drive Letter", str), 198 | ("Device Last Plugged In", str), 199 | ("Device Class", str), 200 | ("Service", str), 201 | ("DeviceDesc", str), 202 | ("Capabilities", str), 203 | ("Mfg", str), 204 | ("ConfigFlags", str), 205 | ("Driver", str), 206 | ("CompatibleIDs", str), 207 | ("HardwareID", str), 208 | ("Location", str) 209 | ], self.generator(data) 210 | ) 211 | 212 | def generator(self, data): 213 | for result in data: 214 | if not result['exists']: 215 | pass 216 | else: 217 | for usbdev in result['USB_DEVICES']: 218 | yield (0, [ 219 | str(usbdev['Serial Number']), 220 | str(usbdev['Vendor']), 221 | str(usbdev['Product']), 222 | str(usbdev['Revision']), 223 | str(usbdev['ClassGUID']), 224 | str(usbdev['ContainerID']), 225 | str(usbdev['Mounted Volume']), 226 | str(usbdev['FriendlyName']), 227 | str(usbdev['USB Name']), 228 | str(usbdev['Drive Letter']), 229 | str(usbdev['Device Last Plugged In']), 230 | str(usbdev['Class']), 231 | str(usbdev['Service']), 232 | str(usbdev['DeviceDesc']), 233 | str(usbdev['Capabilities']), 234 | str(usbdev['Mfg']), 235 | str(usbdev['ConfigFlags']), 236 | str(usbdev['Driver']), 237 | str(usbdev['CompatibleIDs']), 238 | str(usbdev['HardwareID']), 239 | 'USBSTOR' 240 | ]) 241 | for portable in result['Windows Portable Devices']: 242 | yield (0, [portable['Serial Number'], '', '', '', '', '', '', '', portable['FriendlyName'], '', '', 243 | portable['Last Write Time'], '', '', '', '', '', '', '', '', 'Windows Portable Devices']) 244 | 245 | # Print to screen 246 | # I wanted to make it look a little ordered rather than just write each key / val 247 | def render_text(self, outfd, data): 248 | outfd.write('Reading the USBSTOR Please Wait\n') 249 | for result in data: 250 | if not result['exists']: 251 | outfd.write('USBSTOR Not found in SYSTEM Hive\n') 252 | else: 253 | for usbdev in result['USB_DEVICES']: 254 | outfd.write('Found USB Drive: {0}\n'.format(usbdev['Serial Number'])) 255 | 256 | outfd.write('\tSerial Number:\t{0}\n'.format(usbdev['Serial Number'])) 257 | outfd.write('\tVendor:\t{0}\n'.format(usbdev['Vendor'])) 258 | outfd.write('\tProduct:\t{0}\n'.format(usbdev['Product'])) 259 | outfd.write('\tRevision:\t{0}\n'.format(usbdev['Revision'])) 260 | outfd.write('\tClassGUID:\t{0}\n'.format(usbdev['Product'])) 261 | outfd.write('\n') 262 | outfd.write('\tContainerID:\t{0}\n'.format(usbdev['ContainerID'])) 263 | outfd.write('\tMounted Volume:\t{0}\n'.format(usbdev['Mounted Volume'])) 264 | outfd.write('\tDrive Letter:\t{0}\n'.format(usbdev['Drive Letter'])) 265 | outfd.write('\tFriendly Name:\t{0}\n'.format(usbdev['FriendlyName'])) 266 | outfd.write('\tUSB Name:\t{0}\n'.format(usbdev['USB Name'])) 267 | outfd.write('\tDevice Last Connected:\t{0}\n'.format(usbdev['Device Last Plugged In'])) 268 | outfd.write('\n') 269 | outfd.write('\tClass:\t{0}\n'.format(usbdev['Class'])) 270 | outfd.write('\tService:\t{0}\n'.format(usbdev['Service'])) 271 | outfd.write('\tDeviceDesc:\t{0}\n'.format(usbdev['DeviceDesc'])) 272 | outfd.write('\tCapabilities:\t{0}\n'.format(usbdev['Capabilities'])) 273 | outfd.write('\tMfg:\t{0}\n'.format(usbdev['Mfg'])) 274 | outfd.write('\tConfigFlags:\t{0}\n'.format(usbdev['ConfigFlags'])) 275 | outfd.write('\tDriver:\t{0}\n'.format(usbdev['Driver'])) 276 | outfd.write('\tCompatible IDs:\n') 277 | for compat in usbdev['CompatibleIDs']: 278 | outfd.write('\t\t{0}'.format(compat)) 279 | outfd.write('\n') 280 | 281 | outfd.write('\tHardwareID:\n') 282 | for hdid in usbdev['HardwareID']: 283 | outfd.write('\t\t{0}'.format(hdid)) 284 | outfd.write('\n') 285 | 286 | outfd.write('Windows Portable Devices\n') 287 | for portable in result['Windows Portable Devices']: 288 | outfd.write('\t--\n') 289 | for k, v in portable.iteritems(): 290 | outfd.write('\t{0}:\t{1}\n'.format(k, v)) 291 | 292 | outfd.write('\n') 293 | --------------------------------------------------------------------------------