diff --git a/README.txt b/README.txt index 7fcc6262b..10dd59a5f 100644 --- a/README.txt +++ b/README.txt @@ -1,4 +1,135 @@ ============================================================================ +Volatility Framework with Windows 10 Memory Compression +============================================================================ + +This repository contains Volatility with additions made to support Windows 10 +memory compression. If a supported Windows 10 profile is used, it will attempt +to apply the corresponding address space that enables memory decompression. +This allows plugins to read previously unreadable, compressed data. If a +compressed page is unable to be read, it has likely been paged-out and cannot +be recovered. + +Requirements +============ +- Python 2.6 or later, but not 3.0. http://www.python.org +- Yara (https://github.com/VirusTotal/yara/releases) +- Distorm3 (https://github.com/gdabah/distorm/releases) +- Download/clone this repository (https://github.com/fireeye/win10_volatility) + +Follow any installation instructions available at: + + https://github.com/volatilityfoundation/volatility/wiki/Installation + +Supported Windows 10 Versions +============================= + +OS | Build | Arch | Profile +------ | ------ | ---- | ------- +Win 10 | 1607 | x86 | Win10_x86_14393 +Win 10 | 1607 | x64 | Win10_x64_14393 +Win 10 | 1703 | x86 | Win10_x86_15063 +Win 10 | 1703 | x64 | Win10_x64_15063 +Win 10 | 1709 | x86 | Win10_x86_16299 +Win 10 | 1709 | x64 | Win10_x64_16299 +Win 10 | 1803 | x86 | Win10_x86_17134 +Win 10 | 1803 | x64 | Win10_x64_17134 +Win 10 | 1809 | x86 | Win10_x86_17763 +Win 10 | 1809 | x64 | Win10_x64_17763 + +Address Space Details +===================== + +Below are the new address spaces that support Windows 10 memory compression: +* Win10CompressedIA32PagedMemoryPae +* Win10CompressedIA32PagedMemory +* Win10CompressedAMD64PagedMemory + +To verify one of the new Windows 10 memory compression address spaces loads, +run the 'imageinfo' plugin against a supported Windows 10 memory capture. + +"AS Layer 1" should report one of the following values: +* Win10CompressedIA32PagedMemoryPae +* Win10CompressedIA32PagedMemory +* Win10CompressedAMD64PagedMemory + +If not reported, rerun imageinfo and specify a profile using the command line +option “--profile=”. If the profile is not known ahead of time, you +may need to iterate through the suggested profiles until the correct Windows 10 +memory compression address space is reported. + +Windows 10 memory decompression relies on the address of nt!SmGlobals. Before +applying the new address space, we utilize Yara to scan the memory image using +a byte-sequence regular expression to find nt!SmGlobals. If not found, the +address space aborts loading. Users can manually supply this address, if known, +via the command line option: + + --smglobals= + +The decompression algorithms also rely on the value of the Virtual Store page +file number. On default Windows 10 configurations this value is two. This is +also the default value used in our address spaces. To override this value, users +may supply a different value via the command line option: + + --vspagefilenumber= + +New Files +========= + +Below are the files added to support Windows 10 memory compression: +* volatility/plugins/addrspaces/win10_memcompression.py +* volatility/plugins/overlays/windows/win10_memcompression.py +* volatility/plugins/win10deflate.py +* volatility/plugins/win10smglobals.py + +If you already have Volatility 2.6.1 (at least commit 8769579), you can copy +these files into their respective locations and begin using our new address +spaces. + +New Plugins +=========== + +We added two new plugins. + +Plugins +------- +win10deflate - Takes a virtual address of a known compressed page and a process id, and returns the decompressed data +win10smglobals - Prints the address of nt!SmGlobals if found via Yara + + +Side Note +========= + +With the addition of the new address spaces, scanning plugins such as 'psscan' +may take longer than expected due to the decompression of many pages. +However, without the new address spaces that support Windows 10 memory +compression, most scanning plugins will fail due to not finding the +nt!ObHeaderCookie (the value found via the plugin 'win10cookie'). To aid in the +need for speed, we added an additional command line flag: + + --disablewin10memcompress + +This flag prevents the loading of our new address spaces. + +To demonstrate how this can be useful, let's say you have a Windows 10 memory +image with a high load of compressed pages and want to run the plugin 'psscan'. +To speed up scanning, the user disables our new address space via the flag +above. However, now the user faces an error message: + + "Cannot find nt!ObGetObjectType" + +This error indicates that the address where nt!ObHeaderCookie is found is likely +within a compressed page and cannot be read. To get around this, users can first +enable a new Windows 10 memory compression address space and find the address +of nt!ObHeaderCookie by running the plugin 'win10cookie'. Then the user can +supply this value via the command line while disabling our new address space to +get the speed up for the 'psscan' plugin. Below is the command line option to +provide a custom nt!ObHeaderCookie value: + + --cookie= + + +(Original REDADME below) +============================================================================ Volatility Framework - Volatile memory extraction utility framework ============================================================================ diff --git a/volatility/plugins/addrspaces/win10_memcompression.py b/volatility/plugins/addrspaces/win10_memcompression.py new file mode 100644 index 000000000..6c6fb87cb --- /dev/null +++ b/volatility/plugins/addrspaces/win10_memcompression.py @@ -0,0 +1,689 @@ +# Copyright (C) 2019 FireEye, Inc. +# +# This program is free software; you can redistribute it and/or +# modify it under the terms of the GNU General Public License +# as published by the Free Software Foundation; either version 2 +# of the License, or (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program; if not, write to the Free Software +# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. +# +# Authors: +# Blaine Stancill +# +# Donated under Volatility Foundation, Inc. Individual Contributor Licensing Agreement + +import volatility.debug as debug +import volatility.obj as obj +import volatility.plugins.addrspaces.amd64 as amd64 +import volatility.plugins.addrspaces.intel as intel +import volatility.win32 as win32 + +PAGE_SIZE = 0x1000 +PAGE_MASK = PAGE_SIZE - 1 +XPRESS_ALGO = 3 +INVALIDPTEMASK = 0x2000 + + +############################################################################### +# Handles all Win10 memory decompression +############################################################################### +class WindowsMemoryCompression(object): + """Handles compressed pages on Windows 10.""" + + def __init__(self, addrspace, sm_globals, page_file_number): + self.addrspace = addrspace + + self.sm_globals = obj.Object("_SM_GLOBALS", offset = sm_globals, + vm = self.addrspace) + + self.page_file_number = page_file_number + + self._store_tree_root = self.sm_globals.SmkmStoreMgr.KeyToStoreTree + + self._smkm = self.sm_globals.SmkmStoreMgr.Smkm + + # Simplex caching scheme + self._page_cache = {} + self._page_data_cache = {} + + # Store data about the type of OS and architecture + self._size_of_pointer = self.addrspace.profile.get_obj_size("address") + self._build_id = self.addrspace.profile.metadata.get('build', 0) + self._mem_type = self.addrspace.profile.metadata.get('memory_model', + '32bit') + # Value to shift PTE to retrieve page file low. The bit position + # changed around 1803 from bit positions 1-4 to 12-16. + if self._build_id >= 17134: + self._pfl_shift = 12 + else: + self._pfl_shift = 1 + + def get_pte(self, vaddr, addrspace): + ''' + We do this manually rather than at the AS level because the AS we are + dealing with will not have an entry present for VAs that are in a + compressed pages. + ''' + vaddr = long(vaddr) + pml4e = None + pdpe = None + + if (not addrspace.pae + and addrspace.profile.metadata.get( + 'memory_model', '32bit') == '32bit'): + pgd = addrspace.get_pgd(vaddr) + else: + if addrspace.profile.metadata.get('memory_model', + '32bit') == '32bit': + pdpe = addrspace.get_pdpi(vaddr) + else: + pml4e = addrspace.get_pml4e(vaddr) + if not addrspace.entry_present(pml4e): + return None + + pdpe = addrspace.get_pdpi(vaddr, pml4e) + + if not addrspace.entry_present(pdpe): + return None + + # Quit early if it is a large page + if addrspace.page_size_flag(pdpe): + return None + + pgd = addrspace.get_pgd(vaddr, pdpe) + + if addrspace.entry_present(pgd): + + # Continue if it is not a large page + if not addrspace.page_size_flag(pgd): + pte = addrspace.get_pte(vaddr, pgd) + + if pml4e: + debug.debug("pml4e: {0:#x}".format(pml4e)) + if pdpe: + debug.debug("pdpe: {0:#x}".format(pdpe)) + debug.debug("pgd: {0:#x}".format(pgd)) + debug.debug("pte: {0:#x}".format(pte)) + + return pte + + return None + + def is_vaddr_present(self, vaddr, addrspace): + pte = self.get_pte(vaddr, addrspace) + if pte: + return addrspace.entry_present(pte), pte + + return False, None + + def get_page_key(self, vaddr): + is_present, pte = self.is_vaddr_present(vaddr, self.addrspace) + + if is_present or not pte: + return None + + # Calculate the page key based on the page file number + page_file_number = (pte >> self._pfl_shift) & 0x0F + if page_file_number == self.page_file_number: + if self._build_id >= 17134: + # Check if the SwizzleBit is set + if pte & (1 << 4): + return (page_file_number << 28) | (pte >> 32) + else: + # Flip bit using InvalidPteMask from MiState.Hardware + return (page_file_number << 28) | ( + (pte >> 32) & ~INVALIDPTEMASK) + else: + return (page_file_number << 28) | (pte >> 32) + + return None + + def bisect_right(self, root, key): + """Custom bisect right to avoid list copies""" + lo = 0 + hi = root.Elements + + while lo < hi: + mid = (lo + hi) // 2 + if key < root.Nodes[mid].Key.v(): + hi = mid + else: + lo = mid + 1 + return lo + + def b_tree_search(self, root, key): + if not root: + return None + + debug.debug("Root: {0:#x}, Key: {1:#x}".format(root, key)) + + leaf = bool(root.Leaf.v()) + debug.debug("\tLeaf? {0}".format(leaf)) + + # Need to cast as leaf child nodes can be a different size + if leaf: + root = root.cast("_B_TREE_LEAF") + + index = self.bisect_right(root, key) + debug.debug("\tNode Index: {0:#x}".format(index)) + + if index: + node = root.Nodes[index - 1] + debug.debug("\tNode: {0:#x}".format(node)) + + if not leaf: + return self.b_tree_search(node.Child.dereference(), key) + + # Correct key found from leaf + debug.debug("\tNode Key: {0:#x}".format(node.Key.v())) + if node.Key.v() == key: + debug.debug("\tNode Value: {0:#x}".format(node.Value.v())) + return node.Value.v() + + # Worst case, no key found and we're at a leaf + return None + else: + # If it's less than all the keys, use the root's left-most child + return self.b_tree_search(root.LeftChild.dereference(), key) + + def get_smkm_store_index(self, page_key): + index = self.b_tree_search(self._store_tree_root, page_key) + + if not index: + raise KeyError("Could not find Smkm store index for " + "page key: {0:#x}".format(page_key)) + + if (index >> 24) & 0xFF == 1: + raise KeyError("Smkm store index is not valid for " + "page key: {0:#x}".format(page_key)) + + return index & 0x3FF + + def get_smkm_store(self, smkm_store_index): + meta_index = smkm_store_index >> 5 + smkm_meta_ptr = self._smkm.StoreMetaDataPtrArray[meta_index] + + store_index = smkm_store_index & 0x1F + smkm_meta = smkm_meta_ptr.dereference()[store_index] + + debug.debug("Smkm Metadata: {0:#x}".format(smkm_meta)) + debug.debug("Smkm Store: {0:#x}".format(smkm_meta.SmkmStore)) + return smkm_meta.SmkmStore + + def get_region_key(self, smkm_store, page_key): + st_data_mgr = smkm_store.StStore.StDataMgr + debug.debug("StDataMgr: {0:#x}".format(st_data_mgr)) + + root = st_data_mgr.PagesTree + debug.debug("Pages Tree: {0:#x}".format(root)) + + region_key = self.b_tree_search(root, page_key) + if not region_key: + raise KeyError("Could not find region key for " + "page key: {0:#x}".format(page_key)) + + debug.debug("Region Key: {0:#x}".format(region_key)) + return region_key + + def get_page_record(self, smkm_store, region_key): + chunk_metadata = smkm_store.StStore.StDataMgr.ChunkMetaData + debug.debug("Chunk Metadata: {0:#x}".format(chunk_metadata)) + + # Get the encoded metadata about the chunk + encoded_metadata = region_key >> (chunk_metadata.BitValue.v() & 0xFF) + debug.debug("Encoded Metadata: {0:#x}".format(encoded_metadata)) + + # Highest non-zero bit position of encoded_metadata is the index into + # an array of pointers where each pointer points to an array of chunks + ptr_index = int(encoded_metadata).bit_length() - 1 + debug.debug("Chunk Ptr Index: {0:#x}".format(ptr_index)) + + chunk_array = chunk_metadata.ChunkPtrArray[ptr_index] + debug.debug("Chunk Array: {0:#x}".format(chunk_array)) + + # Flip the highest bit to get an index into the chunk array + chunk_array_index = ((1 << (ptr_index & 0xFF)) ^ encoded_metadata) + debug.debug("Chunk Array Index: {0:#x}".format(chunk_array_index)) + + # Treat chunk size differently based on OS type and architecture + if self._build_id >= 15063 and self._mem_type == '32bit': + chunk_size = 3 * self._size_of_pointer + else: + chunk_size = 2 * self._size_of_pointer + + debug.debug("Chunk Size: {0:#x}".format(chunk_size)) + + # Get the chunk from the chunk array + chunk_offset = chunk_array + (chunk_array_index * chunk_size) + debug.debug("Chunk Offset: {0:#x}".format(chunk_offset)) + + # The first field in the chunk points to the chunk's page record data + page_record_ptr = obj.Object("Pointer", + offset = chunk_offset, + vm = self.addrspace) + debug.debug("Page Record Ptr: {0:#x}".format(page_record_ptr)) + + # Page record data begins with a header followed by a page record array + page_record_array = (page_record_ptr + + chunk_metadata.ChunkPageHeaderSize.v()) + debug.debug("Page Record Array: {0:#x}".format(page_record_array)) + + # Calculate page record index based on page record size + page_record_index = (((region_key & + chunk_metadata.PageRecordsPerChunkMask.v()) * + chunk_metadata.PageRecordSize.v()) & 0xFFFFFFFF) + debug.debug("Page Record Index: {0:#x}".format(page_record_index)) + + # Get the page record from the page record array + page_record_offset = page_record_array + page_record_index + debug.debug("Page Record Offset: {0:#x}".format(page_record_offset)) + + # Cast to our defined type + st_page_record = obj.Object("_ST_PAGE_RECORD", + offset = page_record_offset, + vm = self.addrspace) + debug.debug("StPage Record: {0:#x}".format(st_page_record)) + + # If page record is not valid, follow next record + if st_page_record.Key.v() == 0xFFFFFFFF: + return self.get_page_record(smkm_store, st_page_record.NextKey.v()) + + return st_page_record + + def get_page_address(self, smkm_store, page_record): + st_data_mgr = smkm_store.StStore.StDataMgr + + # Calculate encoded region array index + key = page_record.Key.v() + debug.debug("Record Key: {0:#x}".format(key)) + + region_index = (key >> (st_data_mgr.RegionIndexMask.v() & 0xFF)) + debug.debug("Region Index: {0:#x}".format(region_index)) + + # Get a pointer to the region + region_index *= self._size_of_pointer + debug.debug("Region Index x sizeof(ptr): {0:#x}".format(region_index)) + + region_offset = smkm_store.CompressedRegionPtrArray.v() + region_index + debug.debug("Region Offset: {0:#x}".format(region_offset)) + + region_ptr = obj.Object("Pointer", + offset = region_offset, + vm = self.addrspace) + + # Align the base + if self._mem_type == '32bit': + region_ptr &= 0x7FFF0000 + else: + region_ptr &= 0x7FFFFFFFFFFF0000 + debug.debug("Region Base: {0:#x}".format(region_ptr)) + + # Calculate encoded offset to the compressed page address in the region + page_offset = (key & st_data_mgr.RegionSizeMask.v()) << 4 + debug.debug("Page Offset: {0:#x}".format(page_offset)) + + # Get the address of the compressed page + page_address = region_ptr + page_offset + debug.debug("Resolved Page Addr: {0:#x}".format(page_address)) + + return page_address + + def get_page_data_cache(self, page_key): + if page_key not in self._page_data_cache: + page_record, proc_space, page_addr = self.get_page_cache(page_key) + + # Cache failure if the compressed page is not available + if not page_record: + self._page_data_cache[page_key] = None + return None + + # If the compressed size is the size of a page, return as is + comp_size = page_record.CompressedSize.v() + if comp_size == PAGE_SIZE: + comp_data = proc_space.read(page_addr, comp_size) + self._page_data_cache[page_key] = comp_data + return comp_data + + # Mask the size to get the actual compressed size + comp_size &= PAGE_MASK + + # Read in only what is needed + comp_data = proc_space.read(page_addr, comp_size) + if not comp_data: + self._page_data_cache[page_key] = None + return None + + try: + decompressed_data = win32.xpress.xpress_decode(comp_data) + except Exception as e: + debug.debug("Error decompressing: {0}".format(str(e))) + self._page_data_cache[page_key] = None + return None + + len_decompressed = len(decompressed_data) + if len_decompressed != PAGE_SIZE: + debug.debug("Decompressed data is not the " + "size of a page: {0:#x}".format(len_decompressed)) + self._page_data_cache[page_key] = None + return None + + # Cache the decompressed page data + self._page_data_cache[page_key] = decompressed_data + + return self._page_data_cache[page_key] + + def get_page_cache(self, page_key): + if page_key not in self._page_cache: + debug.debug("Page Key: {0:#x}".format(page_key)) + + try: + smkm_store_index = self.get_smkm_store_index(page_key) + debug.debug("Smkm Store Index: {0}".format(smkm_store_index)) + except KeyError as e: + debug.debug("Tree Error: {0}".format(str(e))) + self._page_cache[page_key] = (None, None, None) + return None, None, None + + smkm_store = self.get_smkm_store(smkm_store_index) + + # Stop early if it's a non-supported compression algorithm + comp_algo = smkm_store.StStore.StDataMgr.CompressionAlgorithm.v() + if comp_algo != XPRESS_ALGO: + debug.debug("Unsupported decompression " + "algorithm: {0}".format(comp_algo)) + return None, None, None + + # Walk the data-structures to get the compressed page address + try: + region_key = self.get_region_key(smkm_store, page_key) + except KeyError as e: + debug.debug("Tree Error: {0}".format(str(e))) + self._page_cache[page_key] = (None, None, None) + return None, None, None + + page_record = self.get_page_record(smkm_store, region_key) + page_addr = self.get_page_address(smkm_store, page_record) + + # Check if the address is valid in the context of it's owner + debug.debug("Owner Process: {0:#x} -> {1}".format( + smkm_store.OwnerProcess.UniqueProcessId, + smkm_store.OwnerProcess.ImageFileName)) + proc_space = smkm_store.OwnerProcess.get_process_address_space() + comp_data_is_present, pte = self.is_vaddr_present(page_addr, + proc_space) + + # If not present, the compressed page may be paged to disk + if not comp_data_is_present: + debug.debug("Compressed data is likely paged out") + self._page_cache[page_key] = (None, None, None) + return None, None, None + + self._page_cache[page_key] = (page_record, proc_space, page_addr) + + return self._page_cache[page_key] + + def get_compressed_page_paddr(self, vaddr): + debug.debug("Target Addr: {0:#x}".format(vaddr)) + + page_key = self.get_page_key(vaddr) + if page_key: + _, proc_space, page_addr = self.get_page_cache(page_key) + if proc_space: + return proc_space.vtop(page_addr) + + return None + + def decompress_page_data(self, vaddr, offset, length): + debug.debug("Target Addr: {0:#x}".format(vaddr)) + + page_key = self.get_page_key(vaddr) + if page_key: + page_data = self.get_page_data_cache(page_key) + if page_data: + return page_data[offset:offset + length] + + return None + + +############################################################################### +# Address spaces to transparently deal with Win10 memory +############################################################################### +class Win10CompressedPagedMemory(object): + def __init__(self, paged_as, memcompress): + # Initialize a local copy of the paged address space to use + self.paged_as = paged_as + + # Object to decompress Windows 10 compressed memory + self.memcompress = memcompress + + @property + def sm_globals(self): + # May raise an exception which causes the AS to fail loading + if not hasattr(self, "_sm_globals"): + sm_globals = obj.VolMagic(self.paged_as).SmGlobals.v() + + if not sm_globals: + raise Exception("Invalid nt!SmGlobals value") + + self._sm_globals = sm_globals + + return self._sm_globals + + @property + def page_file_number(self): + if not hasattr(self, "_page_file_number"): + page_file_number = obj.VolMagic(self.paged_as).VSPageFileNumber.v() + + if not page_file_number: + raise Exception("Invalid Virtual Store page file number value") + + self._page_file_number = page_file_number + + return self._page_file_number + + @property + def pfl_shift(self): + ''' + Value to shift PTE to retrieve page file low. The bit position changed + around 1803 from 1-4 to 12-16. + ''' + if not hasattr(self, "_pfl_shift"): + build = self.profile.metadata.get('build', 14393) + + if build >= 17134: + self._pfl_shift = 12 + else: + self._pfl_shift = 1 + + return self._pfl_shift + + def is_valid_profile(self, profile): + ''' + This address space should only be used with recent Win 10 profiles + ''' + valid = self.paged_as.is_valid_profile(profile) + os = profile.metadata.get('os', '') + major = profile.metadata.get('major', 0) + minor = profile.metadata.get('minor', 0) + build = profile.metadata.get('build', 0) + return (valid + and major >= 6 + and minor >= 4 + and os == 'windows' + and build in [14393, 15063, 16299, 17134, 17763, 18362]) + + def entry_present(self, entry): + present = self.paged_as.entry_present(entry) + in_virtual_store = self.entry_in_virtual_store(entry, present) + return present or in_virtual_store + + def entry_in_virtual_store(self, entry, present = None): + if not present: + present = self.paged_as.entry_present(entry) + + return (not present and ( + # In virtual store + (((entry >> self.pfl_shift) & 0x0F) == self.page_file_number) and + # Not prototype + not (entry & (1 << 10)) and + # Not VAD + not ((entry >> 32) == 0xFFFFFFFF) and + # Not demand zero + not ((entry >> 32) == 0) + )) + + def is_vaddr_compressed(self, vaddr): + paddr = self.paged_as.vtop(vaddr) + + if not paddr: + pte = self.memcompress.get_pte(vaddr, self.paged_as) + if pte: + return self.entry_in_virtual_store(pte) + + return False + + def vtop(self, vaddr): + # If the virual address already maps to a physical address, return it + paddr = self.paged_as.vtop(vaddr) + + # Attempt to return a physical address for compressed pages. + # Mainly to allow plugins to work if using vtop() to check if + # an address is valid. + if not paddr: + if self.is_vaddr_compressed(vaddr): + paddr = self.memcompress.get_compressed_page_paddr(vaddr) + + return paddr + + def _partial_read(self, vaddr, length): + # The offset within the page where we start reading + page_offset = vaddr & PAGE_MASK + + # How much data can we satisfy? + available = min(PAGE_SIZE - page_offset, length) + + if self.is_vaddr_compressed(vaddr): + return self.memcompress.decompress_page_data(vaddr, page_offset, + available) + else: + return self.paged_as.read(vaddr, available) + + def read(self, addr, length, zread = False): + result = '' + + while length > 0: + buf = self._partial_read(addr, length) + if not buf: + break + + addr += len(buf) + length -= len(buf) + result += buf + + if result == '': + if zread: + return '\0' * length + + result = obj.NoneObject("Unable to read data at {0:#x} " + "for length {1:#x}".format(addr, length)) + else: + if zread: + result += ('\0' * length) + + return result + + def zread(self, addr, length): + return self.read(addr, length, zread = True) + + +class Win10CompressedAMD64PagedMemory(Win10CompressedPagedMemory, + amd64.SkipDuplicatesAMD64PagedMemory): + order = 50 + + def __init__(self, base, config, *args, **kwargs): + # We must be stacked on someone else + self.as_assert(base, "No base Address Space") + + # Initialize a local copy of the paged address space to use + self.paged_as = amd64.SkipDuplicatesAMD64PagedMemory(base, config, + *args, **kwargs) + + if obj.VolMagic(self.paged_as).DisableWin10MemCompress.v(): + raise Exception("Disabling Win10 memory decompression AS") + + # Object to decompress Windows 10 compressed memory + self.memcompress = WindowsMemoryCompression( + addrspace = self.paged_as, + sm_globals = self.sm_globals, + page_file_number = self.page_file_number) + + # Initialize our memory compression parent address space + Win10CompressedPagedMemory.__init__(self, paged_as = self.paged_as, + memcompress = self.memcompress) + + # Initialize our paged parent address space + amd64.SkipDuplicatesAMD64PagedMemory.__init__(self, base, config, + *args, **kwargs) + + +class Win10CompressedIA32PagedMemory(Win10CompressedPagedMemory, + intel.IA32PagedMemory): + order = 55 + + def __init__(self, base, config, *args, **kwargs): + # We must be stacked on someone else + self.as_assert(base, "No base Address Space") + + # Initialize a local copy of the paged address space to use + self.paged_as = intel.IA32PagedMemory(base, config, *args, **kwargs) + + if obj.VolMagic(self.paged_as).DisableWin10MemCompress.v(): + raise Exception("Disabling Win10 memory decompression AS") + + # Object to decompress Windows 10 compressed memory + self.memcompress = WindowsMemoryCompression( + addrspace = self.paged_as, + sm_globals = self.sm_globals, + page_file_number = self.page_file_number) + + # Initialize our memory compression parent address space + Win10CompressedPagedMemory.__init__(self, paged_as = self.paged_as, + memcompress = self.memcompress) + + # Initialize our parent address space + intel.IA32PagedMemory.__init__(self, base, config, *args, **kwargs) + + +class Win10CompressedIA32PagedMemoryPae(Win10CompressedPagedMemory, + intel.IA32PagedMemoryPae): + order = 50 + + def __init__(self, base, config, *args, **kwargs): + # We must be stacked on someone else + self.as_assert(base, "No base Address Space") + + # Initialize a local copy of the paged address space to use + self.paged_as = intel.IA32PagedMemoryPae(base, config, *args, **kwargs) + + if obj.VolMagic(self.paged_as).DisableWin10MemCompress.v(): + raise Exception("Disabling Win10 memory decompression AS") + + # Object to decompress Windows 10 compressed memory + self.memcompress = WindowsMemoryCompression( + addrspace = self.paged_as, + sm_globals = self.sm_globals, + page_file_number = self.page_file_number) + + # Initialize our memory compression parent address space + Win10CompressedPagedMemory.__init__(self, paged_as = self.paged_as, + memcompress = self.memcompress) + + # Initialize our parent address space + intel.IA32PagedMemoryPae.__init__(self, base, config, *args, **kwargs) diff --git a/volatility/plugins/overlays/windows/win10_memcompression.py b/volatility/plugins/overlays/windows/win10_memcompression.py new file mode 100644 index 000000000..8355a5e99 --- /dev/null +++ b/volatility/plugins/overlays/windows/win10_memcompression.py @@ -0,0 +1,842 @@ +# Copyright (C) 2019 FireEye, Inc. +# +# This program is free software; you can redistribute it and/or +# modify it under the terms of the GNU General Public License +# as published by the Free Software Foundation; either version 2 +# of the License, or (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program; if not, write to the Free Software +# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. +# +# Authors: +# Blaine Stancill +# +# Donated under Volatility Foundation, Inc. Individual Contributor Licensing Agreement + +import struct + +import volatility.debug as debug +import volatility.obj as obj +import volatility.plugins.malware.malfind as malfind +import volatility.win32.tasks as tasks + +try: + import yara + + has_yara = True +except ImportError: + has_yara = False + +############################################################################### +# General V-Types +############################################################################### +win10_mem_comp_general_vtypes = { + '_ST_PAGE_RECORD': [None, { + 'Key': [0x0, ['unsigned int']], + 'CompressedSize': [0x4, ['unsigned short int']], + 'NextKey': [0x4, ['unsigned int']] + }], + '_SMKM': [None, { + 'StoreMetaDataPtrArray': [0x0, ['array', 32, + ['pointer', ['array', 32, [ + "_SMKM_STORE_METADATA"]]]]] + }], + '_SM_GLOBALS': [None, { + 'SmkmStoreMgr': [0x0, ['_SMKM_STORE_MGR']] + }] +} + +############################################################################### +# x86 Specific V-Types +############################################################################### +win10_mem_comp_general_x86_vtypes = { + '_B_TREE_LEAF_NODE': [0x8, { + 'Key': [0x0, ['unsigned int']], + 'Value': [0x4, ['unsigned int']], + }], + '_B_TREE_LEAF': [None, { + 'Elements': [0x0, ['unsigned short int']], + 'Level': [0x2, ['unsigned char']], + 'Leaf': [0x3, ['unsigned char']], + 'LeftChild': [0x4, ['pointer', ['_B_TREE']]], + 'Nodes': [0x8, ['array', lambda x: x.Elements, ['_B_TREE_LEAF_NODE']]] + }], + '_B_TREE_NODE': [0x8, { + 'Key': [0x0, ['unsigned int']], + 'Child': [0x4, ['pointer', ['_B_TREE']]] + }], + '_B_TREE': [None, { + 'Elements': [0x0, ['unsigned short int']], + 'Level': [0x2, ['unsigned char']], + 'Leaf': [0x3, ['unsigned char']], + 'LeftChild': [0x4, ['pointer', ['_B_TREE']]], + 'Nodes': [0x8, ['array', lambda x: x.Elements, ['_B_TREE_NODE']]] + }], + '_SMHP_CHUNK_METADATA': [None, { + 'ChunkPtrArray': [0x0, ['array', 32, ['pointer', ['void']]]], + 'BitValue': [0x88, ['unsigned int']], + 'PageRecordsPerChunkMask': [0x8C, ['unsigned int']], + 'PageRecordSize': [0x90, ['unsigned int']], + 'ChunkPageHeaderSize': [0x98, ['unsigned int']], + }], + '_ST_STORE': [None, { + 'StDataMgr': [0x38, ['_ST_DATA_MGR']] + }], + '_SMKM_STORE_METADATA': [0x14, { + 'SmkmStore': [0x0, ['pointer', ["_SMKM_STORE"]]], + }], + '_SMKM_STORE_MGR': [None, { + 'Smkm': [0x0, ['_SMKM']], + 'KeyToStoreTree': [0xF4, ['pointer', ['_B_TREE']]] + }] +} + +############################################################################### +# x64 Specific V-Types +############################################################################### +win10_mem_comp_general_x64_vtypes = { + '_B_TREE_LEAF_NODE': [0x8, { + 'Key': [0x0, ['unsigned int']], + 'Value': [0x4, ['unsigned int']], + }], + '_B_TREE_LEAF': [None, { + 'Elements': [0x0, ['unsigned short int']], + 'Level': [0x2, ['unsigned char']], + 'Leaf': [0x3, ['unsigned char']], + 'LeftChild': [0x8, ['pointer', ['_B_TREE']]], + 'Nodes': [0x10, ['array', lambda x: x.Elements, ['_B_TREE_LEAF_NODE']]] + }], + '_B_TREE_NODE': [0x10, { + 'Key': [0x0, ['unsigned int']], + 'Child': [0x8, ['pointer', ['_B_TREE']]] + }], + '_B_TREE': [None, { + 'Elements': [0x0, ['unsigned short int']], + 'Level': [0x2, ['unsigned char']], + 'Leaf': [0x3, ['unsigned char']], + 'LeftChild': [0x8, ['pointer', ['_B_TREE']]], + 'Nodes': [0x10, ['array', lambda x: x.Elements, ['_B_TREE_NODE']]] + }], + '_SMHP_CHUNK_METADATA': [None, { + 'ChunkPtrArray': [0x0, ['array', 32, ['pointer', ['void']]]], + 'BitValue': [0x108, ['unsigned int']], + 'PageRecordsPerChunkMask': [0x10C, ['unsigned int']], + 'PageRecordSize': [0x110, ['unsigned int']], + 'ChunkPageHeaderSize': [0x118, ['unsigned int']], + }], + '_ST_STORE': [None, { + 'StDataMgr': [0x50, ['_ST_DATA_MGR']] + }], + '_SMKM_STORE_METADATA': [0x28, { + 'SmkmStore': [0x0, ['pointer', ["_SMKM_STORE"]]], + }], + '_SMKM_STORE_MGR': [None, { + 'Smkm': [0x0, ['_SMKM']], + 'KeyToStoreTree': [0x1C0, ['pointer', ['_B_TREE']]] + }] +} + +############################################################################### +# x64 1903 Specific V-Types +############################################################################### +win10_mem_comp_x64_1903 = { + '_ST_DATA_MGR': [None, { + 'PagesTree': [0x0, ['pointer', ['_B_TREE']]], + 'ChunkMetaData': [0xC0, ['_SMHP_CHUNK_METADATA']], + 'SmkmStore': [0x320, ['pointer', ["_SMKM_STORE"]]], + 'RegionSizeMask': [0x328, ['unsigned int']], + 'RegionIndexMask': [0x32C, ['unsigned int']], + 'CompressionAlgorithm': [0x3E0, ['unsigned short int']], + }], + '_SMKM_STORE': [None, { + 'StStore': [0x0, ['_ST_STORE']], + 'CompressedRegionPtrArray': [0x1848, ['pointer', ['void']]], + 'OwnerProcess': [0x19A8, ['pointer', ["_EPROCESS"]]] + }] +} + +############################################################################### +# x86 1903 Specific V-Types +############################################################################### +win10_mem_comp_x86_1903 = { + '_ST_DATA_MGR': [None, { + 'PagesTree': [0x0, ['pointer', ['_B_TREE']]], + 'ChunkMetaData': [0x6C, ['_SMHP_CHUNK_METADATA']], + 'SmkmStore': [0x1C0, ['pointer', ["_SMKM_STORE"]]], + 'RegionSizeMask': [0x1C4, ['unsigned int']], + 'RegionIndexMask': [0x1C8, ['unsigned int']], + 'CompressionAlgorithm': [0x224, ['unsigned short int']], + }], + '_SMKM_STORE': [None, { + 'StStore': [0x0, ['_ST_STORE']], + 'CompressedRegionPtrArray': [0x1184, ['pointer', ['void']]], + 'OwnerProcess': [0x1254, ['pointer', ["_EPROCESS"]]] + }] +} + +############################################################################### +# x64 1809 Specific V-Types +############################################################################### +win10_mem_comp_x64_1809 = { + '_ST_DATA_MGR': [None, { + 'PagesTree': [0x0, ['pointer', ['_B_TREE']]], + 'ChunkMetaData': [0xC0, ['_SMHP_CHUNK_METADATA']], + 'SmkmStore': [0x320, ['pointer', ["_SMKM_STORE"]]], + 'RegionSizeMask': [0x328, ['unsigned int']], + 'RegionIndexMask': [0x32C, ['unsigned int']], + 'CompressionAlgorithm': [0x3E0, ['unsigned short int']], + }], + '_SMKM_STORE': [None, { + 'StStore': [0x0, ['_ST_STORE']], + 'CompressedRegionPtrArray': [0x1848, ['pointer', ['void']]], + 'OwnerProcess': [0x19A8, ['pointer', ["_EPROCESS"]]] + }] +} + +############################################################################### +# x86 1809 Specific V-Types +############################################################################### +win10_mem_comp_x86_1809 = { + '_ST_DATA_MGR': [None, { + 'PagesTree': [0x0, ['pointer', ['_B_TREE']]], + 'ChunkMetaData': [0x6C, ['_SMHP_CHUNK_METADATA']], + 'SmkmStore': [0x1C0, ['pointer', ["_SMKM_STORE"]]], + 'RegionSizeMask': [0x1C4, ['unsigned int']], + 'RegionIndexMask': [0x1C8, ['unsigned int']], + 'CompressionAlgorithm': [0x224, ['unsigned short int']], + }], + '_SMKM_STORE': [None, { + 'StStore': [0x0, ['_ST_STORE']], + 'CompressedRegionPtrArray': [0x1184, ['pointer', ['void']]], + 'OwnerProcess': [0x1254, ['pointer', ["_EPROCESS"]]] + }] +} + +############################################################################### +# x64 1803 Specific V-Types +############################################################################### +win10_mem_comp_x64_1803 = { + '_ST_DATA_MGR': [None, { + 'PagesTree': [0x0, ['pointer', ['_B_TREE']]], + 'ChunkMetaData': [0xC0, ['_SMHP_CHUNK_METADATA']], + 'SmkmStore': [0x320, ['pointer', ["_SMKM_STORE"]]], + 'RegionSizeMask': [0x328, ['unsigned int']], + 'RegionIndexMask': [0x32C, ['unsigned int']], + 'CompressionAlgorithm': [0x3E0, ['unsigned short int']], + }], + '_SMKM_STORE': [None, { + 'StStore': [0x0, ['_ST_STORE']], + 'CompressedRegionPtrArray': [0x1848, ['pointer', ['void']]], + 'OwnerProcess': [0x19A8, ['pointer', ["_EPROCESS"]]] + }] +} + +############################################################################### +# x86 1803 Specific V-Types +############################################################################### +win10_mem_comp_x86_1803 = { + '_ST_DATA_MGR': [None, { + 'PagesTree': [0x0, ['pointer', ['_B_TREE']]], + 'ChunkMetaData': [0x6C, ['_SMHP_CHUNK_METADATA']], + 'SmkmStore': [0x1C0, ['pointer', ["_SMKM_STORE"]]], + 'RegionSizeMask': [0x1C4, ['unsigned int']], + 'RegionIndexMask': [0x1C8, ['unsigned int']], + 'CompressionAlgorithm': [0x224, ['unsigned short int']], + }], + '_SMKM_STORE': [None, { + 'StStore': [0x0, ['_ST_STORE']], + 'CompressedRegionPtrArray': [0x1184, ['pointer', ['void']]], + 'OwnerProcess': [0x1254, ['pointer', ["_EPROCESS"]]] + }] +} + +############################################################################### +# x64 1709 Specific V-Types +############################################################################### +win10_mem_comp_x64_1709 = { + '_ST_DATA_MGR': [None, { + 'PagesTree': [0x0, ['pointer', ['_B_TREE']]], + 'ChunkMetaData': [0xC0, ['_SMHP_CHUNK_METADATA']], + 'SmkmStore': [0x320, ['pointer', ["_SMKM_STORE"]]], + 'RegionSizeMask': [0x328, ['unsigned int']], + 'RegionIndexMask': [0x32C, ['unsigned int']], + 'CompressionAlgorithm': [0x3E0, ['unsigned short int']], + }], + '_SMKM_STORE': [None, { + 'StStore': [0x0, ['_ST_STORE']], + 'CompressedRegionPtrArray': [0x1848, ['pointer', ['void']]], + 'OwnerProcess': [0x19A8, ['pointer', ["_EPROCESS"]]] + }] +} + +############################################################################### +# x86 1709 Specific V-Types +############################################################################### +win10_mem_comp_x86_1709 = { + '_ST_DATA_MGR': [None, { + 'PagesTree': [0x0, ['pointer', ['_B_TREE']]], + 'ChunkMetaData': [0x6C, ['_SMHP_CHUNK_METADATA']], + 'SmkmStore': [0x1C0, ['pointer', ["_SMKM_STORE"]]], + 'RegionSizeMask': [0x1C4, ['unsigned int']], + 'RegionIndexMask': [0x1C8, ['unsigned int']], + 'CompressionAlgorithm': [0x224, ['unsigned short int']], + }], + '_SMKM_STORE': [None, { + 'StStore': [0x0, ['_ST_STORE']], + 'CompressedRegionPtrArray': [0x1184, ['pointer', ['void']]], + 'OwnerProcess': [0x1254, ['pointer', ["_EPROCESS"]]] + }] +} + +############################################################################### +# x64 1703 Specific V-Types +############################################################################### +win10_mem_comp_x64_1703 = { + '_ST_DATA_MGR': [None, { + 'PagesTree': [0x0, ['pointer', ['_B_TREE']]], + 'ChunkMetaData': [0xC0, ['_SMHP_CHUNK_METADATA']], + 'SmkmStore': [0x320, ['pointer', ["_SMKM_STORE"]]], + 'RegionSizeMask': [0x328, ['unsigned int']], + 'RegionIndexMask': [0x32C, ['unsigned int']], + 'CompressionAlgorithm': [0x3D0, ['unsigned short int']], + }], + '_SMKM_STORE': [None, { + 'StStore': [0x0, ['_ST_STORE']], + 'CompressedRegionPtrArray': [0x1828, ['pointer', ['void']]], + 'OwnerProcess': [0x1988, ['pointer', ["_EPROCESS"]]] + }] +} + +############################################################################### +# x86 1703 Specific V-Types +############################################################################### +win10_mem_comp_x86_1703 = { + '_ST_DATA_MGR': [None, { + 'PagesTree': [0x0, ['pointer', ['_B_TREE']]], + 'ChunkMetaData': [0x6C, ['_SMHP_CHUNK_METADATA']], + 'SmkmStore': [0x1C0, ['pointer', ["_SMKM_STORE"]]], + 'RegionSizeMask': [0x1C4, ['unsigned int']], + 'RegionIndexMask': [0x1C8, ['unsigned int']], + 'CompressionAlgorithm': [0x220, ['unsigned short int']], + }], + '_SMKM_STORE': [None, { + 'StStore': [0x0, ['_ST_STORE']], + 'CompressedRegionPtrArray': [0x1174, ['pointer', ['void']]], + 'OwnerProcess': [0x1244, ['pointer', ["_EPROCESS"]]] + }] +} + +############################################################################### +# x64 1607 Specific V-Types +############################################################################### +win10_mem_comp_x64_1607 = { + '_ST_DATA_MGR': [None, { + 'PagesTree': [0x0, ['pointer', ['_B_TREE']]], + 'ChunkMetaData': [0xC0, ['_SMHP_CHUNK_METADATA']], + 'SmkmStore': [0x320, ['pointer', ["_SMKM_STORE"]]], + 'RegionSizeMask': [0x328, ['unsigned int']], + 'RegionIndexMask': [0x32C, ['unsigned int']], + 'CompressionAlgorithm': [0x3D0, ['unsigned short int']], + }], + '_SMKM_STORE': [None, { + 'StStore': [0x0, ['_ST_STORE']], + 'CompressedRegionPtrArray': [0x17A8, ['pointer', ['void']]], + 'OwnerProcess': [0x1918, ['pointer', ["_EPROCESS"]]] + }] +} + +############################################################################### +# x86 1607 Specific V-Types +############################################################################### +win10_mem_comp_x86_1607 = { + '_ST_DATA_MGR': [None, { + 'PagesTree': [0x0, ['pointer', ['_B_TREE']]], + 'ChunkMetaData': [0x6C, ['_SMHP_CHUNK_METADATA']], + 'SmkmStore': [0x1C0, ['pointer', ["_SMKM_STORE"]]], + 'RegionSizeMask': [0x1C4, ['unsigned int']], + 'RegionIndexMask': [0x1C8, ['unsigned int']], + 'CompressionAlgorithm': [0x220, ['unsigned short int']], + }], + '_SMKM_STORE': [None, { + 'StStore': [0x0, ['_ST_STORE']], + 'CompressedRegionPtrArray': [0x1124, ['pointer', ['void']]], + 'OwnerProcess': [0x1204, ['pointer', ["_EPROCESS"]]] + }] +} + + +############################################################################### +# Add V-Types Based on Conditions +############################################################################### +class Win10MemCompressWin10x641903(obj.ProfileModification): + before = ['WindowsObjectClasses'] + conditions = {'os': lambda x: x == 'windows', + 'major': lambda x: x == 6, + 'minor': lambda x: x == 4, + 'build': lambda x: x == 18362, + 'memory_model': lambda x: x == '64bit'} + + def modification(self, profile): + profile.vtypes.update(win10_mem_comp_general_vtypes) + profile.vtypes.update(win10_mem_comp_general_x64_vtypes) + profile.vtypes.update(win10_mem_comp_x64_1903) + + +class Win10MemCompressWin10x861903(obj.ProfileModification): + before = ['WindowsObjectClasses'] + conditions = {'os': lambda x: x == 'windows', + 'major': lambda x: x == 6, + 'minor': lambda x: x == 4, + 'build': lambda x: x == 18362, + 'memory_model': lambda x: x == '32bit'} + + def modification(self, profile): + profile.vtypes.update(win10_mem_comp_general_vtypes) + profile.vtypes.update(win10_mem_comp_general_x86_vtypes) + profile.vtypes.update(win10_mem_comp_x86_1903) + + +class Win10MemCompressWin10x641809(obj.ProfileModification): + before = ['WindowsObjectClasses'] + conditions = {'os': lambda x: x == 'windows', + 'major': lambda x: x == 6, + 'minor': lambda x: x == 4, + 'build': lambda x: x == 17763, + 'memory_model': lambda x: x == '64bit'} + + def modification(self, profile): + profile.vtypes.update(win10_mem_comp_general_vtypes) + profile.vtypes.update(win10_mem_comp_general_x64_vtypes) + profile.vtypes.update(win10_mem_comp_x64_1809) + + +class Win10MemCompressWin10x861809(obj.ProfileModification): + before = ['WindowsObjectClasses'] + conditions = {'os': lambda x: x == 'windows', + 'major': lambda x: x == 6, + 'minor': lambda x: x == 4, + 'build': lambda x: x == 17763, + 'memory_model': lambda x: x == '32bit'} + + def modification(self, profile): + profile.vtypes.update(win10_mem_comp_general_vtypes) + profile.vtypes.update(win10_mem_comp_general_x86_vtypes) + profile.vtypes.update(win10_mem_comp_x86_1809) + + +class Win10MemCompressWin10x641803(obj.ProfileModification): + before = ['WindowsObjectClasses'] + conditions = {'os': lambda x: x == 'windows', + 'major': lambda x: x == 6, + 'minor': lambda x: x == 4, + 'build': lambda x: x == 17134, + 'memory_model': lambda x: x == '64bit'} + + def modification(self, profile): + profile.vtypes.update(win10_mem_comp_general_vtypes) + profile.vtypes.update(win10_mem_comp_general_x64_vtypes) + profile.vtypes.update(win10_mem_comp_x64_1803) + + +class Win10MemCompressWin10x861803(obj.ProfileModification): + before = ['WindowsObjectClasses'] + conditions = {'os': lambda x: x == 'windows', + 'major': lambda x: x == 6, + 'minor': lambda x: x == 4, + 'build': lambda x: x == 17134, + 'memory_model': lambda x: x == '32bit'} + + def modification(self, profile): + profile.vtypes.update(win10_mem_comp_general_vtypes) + profile.vtypes.update(win10_mem_comp_general_x86_vtypes) + profile.vtypes.update(win10_mem_comp_x86_1803) + + +class Win10MemCompressWin10x641709(obj.ProfileModification): + before = ['WindowsObjectClasses'] + conditions = {'os': lambda x: x == 'windows', + 'major': lambda x: x == 6, + 'minor': lambda x: x == 4, + 'build': lambda x: x == 16299, + 'memory_model': lambda x: x == '64bit'} + + def modification(self, profile): + profile.vtypes.update(win10_mem_comp_general_vtypes) + profile.vtypes.update(win10_mem_comp_general_x64_vtypes) + profile.vtypes.update(win10_mem_comp_x64_1709) + + +class Win10MemCompressWin10x861709(obj.ProfileModification): + before = ['WindowsObjectClasses'] + conditions = {'os': lambda x: x == 'windows', + 'major': lambda x: x == 6, + 'minor': lambda x: x == 4, + 'build': lambda x: x == 16299, + 'memory_model': lambda x: x == '32bit'} + + def modification(self, profile): + profile.vtypes.update(win10_mem_comp_general_vtypes) + profile.vtypes.update(win10_mem_comp_general_x86_vtypes) + profile.vtypes.update(win10_mem_comp_x86_1709) + + +class Win10MemCompressWin10x641703(obj.ProfileModification): + before = ['WindowsObjectClasses'] + conditions = {'os': lambda x: x == 'windows', + 'major': lambda x: x == 6, + 'minor': lambda x: x == 4, + 'build': lambda x: x == 15063, + 'memory_model': lambda x: x == '64bit'} + + def modification(self, profile): + profile.vtypes.update(win10_mem_comp_general_vtypes) + profile.vtypes.update(win10_mem_comp_general_x64_vtypes) + profile.vtypes.update(win10_mem_comp_x64_1703) + + +class Win10MemCompressWin10x861703(obj.ProfileModification): + before = ['WindowsObjectClasses'] + conditions = {'os': lambda x: x == 'windows', + 'major': lambda x: x == 6, + 'minor': lambda x: x == 4, + 'build': lambda x: x == 15063, + 'memory_model': lambda x: x == '32bit'} + + def modification(self, profile): + profile.vtypes.update(win10_mem_comp_general_vtypes) + profile.vtypes.update(win10_mem_comp_general_x86_vtypes) + profile.vtypes.update(win10_mem_comp_x86_1703) + + +class Win10MemCompressWin10x641607(obj.ProfileModification): + before = ['WindowsObjectClasses'] + conditions = {'os': lambda x: x == 'windows', + 'major': lambda x: x == 6, + 'minor': lambda x: x == 4, + 'build': lambda x: x == 14393, + 'memory_model': lambda x: x == '64bit'} + + def modification(self, profile): + profile.vtypes.update(win10_mem_comp_general_vtypes) + profile.vtypes.update(win10_mem_comp_general_x64_vtypes) + profile.vtypes.update(win10_mem_comp_x64_1607) + + +class Win10MemCompressWin10x861607(obj.ProfileModification): + before = ['WindowsObjectClasses'] + conditions = {'os': lambda x: x == 'windows', + 'major': lambda x: x == 6, + 'minor': lambda x: x == 4, + 'build': lambda x: x == 14393, + 'memory_model': lambda x: x == '32bit'} + + def modification(self, profile): + profile.vtypes.update(win10_mem_comp_general_vtypes) + profile.vtypes.update(win10_mem_comp_general_x86_vtypes) + profile.vtypes.update(win10_mem_comp_x86_1607) + + +############################################################################### +# Find nt!SmGlobals +############################################################################### +class SmGlobalsStore(object): + """A class for finding and storing the nt!SmGlobals value""" + + _instance = None + + """ + Below is information about the regexs used to locate nt!SmGlobals: + + 1607-1709.x86 + 8B D1 B9 ?? ?? ?? ?? E8 ?? ?? ?? ?? EB + SmUpdateMemoryCondition(x,x)+3E 8B D1 mov edx, ecx + SmUpdateMemoryCondition(x,x)+40 B9 C0 FA 6A 00 mov ecx, offset ?SmGlobals@@3U_SM_GLOBALS@@A + SmUpdateMemoryCondition(x,x)+45 E8 2C 42 02 00 call ?SmUpdateMemoryConditions@?$SMKM_STORE_MGR@USM_TRAITS@@@@SGXPAU1@W4_SMP_MEMORY_CONDITION@@K@Z + SmUpdateMemoryCondition(x,x)+4A EB D5 jmp short loc_4F2D3D + SmUpdateMemoryCondition(x,x)+4A _SmUpdateMemoryCondition@8 endp + + 1803-1809.x86 + 8B D1 B9 ?? ?? ?? ?? E8 ?? ?? ?? ?? ?? EB + SmUpdateMemoryCondition(x,x)+3D 8B D1 mov edx, ecx + SmUpdateMemoryCondition(x,x)+3F B9 00 EB 6E 00 mov ecx, offset ?SmGlobals@@3U_SM_GLOBALS@@A ; _SM_GLOBALS SmGlobals + SmUpdateMemoryCondition(x,x)+44 E8 49 4E 09 00 call ?SmUpdateMemoryConditions@?$SMKM_STORE_MGR@USM_TRAITS@@@@SGXPAU1@W4_SMP_MEMORY_CONDITION@@K@Z + SmUpdateMemoryCondition(x,x)+49 5E pop esi + SmUpdateMemoryCondition(x,x)+4A EB D4 jmp short loc_4306F8 + SmUpdateMemoryCondition(x,x)+4A _SmUpdateMem + """ + _x86_smglobals = { + 'rules': { + 14393: { + 'sig': "{8B D1 B9 ?? ?? ?? ?? E8 ?? ?? ?? ?? EB}", + 'addr_start': 3, + 'addr_end': 7, + }, + 15063: { + 'sig': "{8B D1 B9 ?? ?? ?? ?? E8 ?? ?? ?? ?? EB}", + 'addr_start': 3, + 'addr_end': 7, + }, + 16299: { + 'sig': "{8B D1 B9 ?? ?? ?? ?? E8 ?? ?? ?? ?? EB}", + 'addr_start': 3, + 'addr_end': 7, + }, + 17134: { + 'sig': "{8B D1 B9 ?? ?? ?? ?? E8 ?? ?? ?? ?? EB}", + 'addr_start': 3, + 'addr_end': 7, + }, + 17763: { + 'sig': "{8B D1 B9 ?? ?? ?? ?? E8 ?? ?? ?? ?? ?? EB}", + 'addr_start': 3, + 'addr_end': 7, + }, + 18362: { + 'sig': "{8B D1 B9 ?? ?? ?? ?? E8 ?? ?? ?? ?? ?? EB}", + 'addr_start': 3, + 'addr_end': 7, + } + } + } + + """ + Below is information about the regexs used to locate nt!SmGlobals: + + *.x64 + 4C 8B 02 48 8D ?? ?? ?? ?? ?? 48 8D 15 ?? ?? ?? ?? E8 ?? ?? ?? ?? 8B + SmpPageWrite+32 058 4C 8B 02 mov r8, [rdx] + SmpPageWrite+35 058 48 8D 0D 10 06 32 00 lea rcx, qword_1403F71A8 + SmpPageWrite+3C 058 48 8D 15 61 FE 31 00 lea rdx, ?SmGlobals@@3U_SM_GLOBALS@@A + SmpPageWrite+43 058 E8 5C 15 FB FF call SmpKeyedStoreReference + SmpPageWrite+48 058 8B F8 mov edi, eax + """ + _x64_smglobals = { + 'rules': { + 14393: { + 'sig': ("{4C 8B 02 48 8D ?? ?? ?? ?? ?? 48 8D 15 ?? ?? ?? ?? " + "E8 ?? ?? ?? ?? 8B}"), + 'addr_start': 13, + 'addr_end': 17 + }, + 15063: { + 'sig': ("{4C 8B 02 48 8D ?? ?? ?? ?? ?? 48 8D 15 ?? ?? ?? ?? " + "E8 ?? ?? ?? ?? 8B}"), + 'addr_start': 13, + 'addr_end': 17 + }, + 16299: { + 'sig': ("{4C 8B 02 48 8D ?? ?? ?? ?? ?? 48 8D 15 ?? ?? ?? ?? " + "E8 ?? ?? ?? ?? 8B}"), + 'addr_start': 13, + 'addr_end': 17 + }, + 17134: { + 'sig': ("{4C 8B 02 48 8D ?? ?? ?? ?? ?? 48 8D 15 ?? ?? ?? ?? " + "E8 ?? ?? ?? ?? 8B}"), + 'addr_start': 13, + 'addr_end': 17 + }, + 17763: { + 'sig': ("{4C 8B 02 48 8D ?? ?? ?? ?? ?? 48 8D 15 ?? ?? ?? ?? " + "E8 ?? ?? ?? ?? 8B}"), + 'addr_start': 13, + 'addr_end': 17 + }, + 18362: { + 'sig': ("{4C 8B 02 48 8D ?? ?? ?? ?? ?? 48 8D 15 ?? ?? ?? ?? " + "E8 ?? ?? ?? ?? 8B}"), + 'addr_start': 13, + 'addr_end': 17 + } + } + } + + def __init__(self): + self._smglobals = None + + def smglobals(self): + return self._smglobals + + def _yara_scan(self, addrspace, rule, start, end): + rules = yara.compile(sources = { + 'n': 'rule r1 {strings: $a = ' + rule + ' condition: $a}' + }) + + scanner = malfind.DiscontigYaraScanner(address_space = addrspace, + rules = rules) + + scan_results = scanner.scan(start_offset = start, maxlen = end) + + try: + hit, addr = next(scan_results) + except StopIteration: + return None, None + + return hit, addr + + def _to_signed(self, x, b = 32): + if x >> (b - 1): # is the highest bit set? + return x - (1 << b) # 2's complement + return x + + def _get_smglobals(self, addrspace, nt_mod, meta): + # Define range for finding SmGlobals with Yara + nt_start = nt_mod.DllBase + nt_end = nt_mod.SizeOfImage + + build = addrspace.profile.metadata.get('build', 14393) + rule = meta['rules'][build] + + hit, addr = self._yara_scan(addrspace, rule['sig'], nt_start, nt_end) + if not addr or not nt_mod.obj_vm.is_valid_address(addr): + return None + + # Extract the SmGlobals address from the regex hit + start = rule['addr_start'] + end = rule['addr_end'] + sm_addr = struct.unpack("> addr.bit_length()) << addr.bit_length() + + # Use RIP-relative addressing + smglobals = addr + end + self._to_signed(sm_addr) + else: + smglobals = sm_addr + + # Check if SmGlobals is valid and within bounds of ntoskrnl + if (not nt_mod.obj_vm.is_valid_address(smglobals) + or smglobals > (nt_start + nt_end) + or smglobals < nt_start): + return None + + return smglobals + + def find_smglobals(self, addrspace): + """Find and read the nt!SmGlobals value. + + On success, return True and save the SmGlobals value in + self._smglobals. On Failure, return False. + + This method must be called before performing any tasks that require + SmGlobals. Otherwise reading compressed pages is out of the question. + """ + meta = addrspace.profile.metadata + vers = (meta.get("major", 0), meta.get("minor", 0)) + + # This only applies to Windows 10 or greater + if vers < (6, 4): + return False + + # Prevent subsequent attempts from recalculating the existing value + if self._smglobals: + return True + + if not has_yara: + debug.warning("Yara module is not installed") + return False + + kdbg = tasks.get_kdbg(addrspace) + if not kdbg: + debug.warning("Cannot find KDBG") + return False + + # First module should be ntoskrnl + try: + nt_mod = next(kdbg.modules()) + except StopIteration: + debug.warning("Cannot find ntoskrnl") + return False + + if not nt_mod: + debug.warning("Cannot find ntoskrnl") + return False + + if meta.get('memory_model', '32bit') == '32bit': + smglobals = self._get_smglobals(addrspace, nt_mod, + self._x86_smglobals) + else: + smglobals = self._get_smglobals(addrspace, nt_mod, + self._x64_smglobals) + + self._smglobals = smglobals + return True + + @staticmethod + def instance(): + if not SmGlobalsStore._instance: + SmGlobalsStore._instance = SmGlobalsStore() + + return SmGlobalsStore._instance + + +class VolatilitySmGlobals(obj.VolatilityMagic): + """The Windows 10 SmGlobals Finder""" + + def generate_suggestions(self): + store = SmGlobalsStore.instance() + store.find_smglobals(self.obj_vm) + yield store.smglobals() + + +class Win10SmGlobals(obj.ProfileModification): + """The Windows 10 SmGlobals Finder""" + + before = ['WindowsOverlay'] + + conditions = {'os': lambda x: x == 'windows', + 'major': lambda x: x == 6, + 'minor': lambda x: x == 4, + 'build': lambda x: x in [14393, 15063, 16299, 17134, 17763, + 18362]} + + def modification(self, profile): + profile.merge_overlay( + {'VOLATILITY_MAGIC': [None, {'SmGlobals': [0x0, [ + 'VolatilitySmGlobals', dict(configname = "SMGLOBALS")]]}]}) + profile.object_classes.update( + {'VolatilitySmGlobals': VolatilitySmGlobals}) + + +class Win10VSPageFileNumber(obj.ProfileModification): + """The Windows 10 Virtual Store Page File Number""" + + before = ['WindowsOverlay'] + + conditions = {'os': lambda x: x == 'windows', + 'major': lambda x: x == 6, + 'minor': lambda x: x == 4, + 'build': lambda x: x in [14393, 15063, 16299, 17134, 17763, + 18362]} + + def modification(self, profile): + profile.merge_overlay( + {'VOLATILITY_MAGIC': [None, {'VSPageFileNumber': [0x0, [ + 'VolatilityMagic', + dict(configname = "VSPAGEFILENUMBER", value = 2)]]}]}) + + +class Win10DisableMemCompression(obj.ProfileModification): + """Disables Windows 10 memory decompression address spaces if set""" + + before = ['WindowsOverlay'] + + conditions = {'os': lambda x: x == 'windows', + 'major': lambda x: x == 6, + 'minor': lambda x: x == 4, + 'build': lambda x: x in [14393, 15063, 16299, 17134, 17763, + 18362]} + + def modification(self, profile): + profile.merge_overlay( + {'VOLATILITY_MAGIC': [None, {'DisableWin10MemCompress': [0x0, [ + 'VolatilityMagic', + dict(configname = "DISABLEWIN10MEMCOMPRESS", value = False) + ]]}]}) diff --git a/volatility/plugins/win10deflate.py b/volatility/plugins/win10deflate.py new file mode 100644 index 000000000..02bae4068 --- /dev/null +++ b/volatility/plugins/win10deflate.py @@ -0,0 +1,136 @@ +# Copyright (C) 2019 FireEye, Inc. +# +# This program is free software; you can redistribute it and/or +# modify it under the terms of the GNU General Public License +# as published by the Free Software Foundation; either version 2 +# of the License, or (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program; if not, write to the Free Software +# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. +# +# Authors: +# Blaine Stancill +# +# Donated under Volatility Foundation, Inc. Individual Contributor Licensing Agreement + +import volatility.debug as debug +import volatility.plugins.common as common +import volatility.win32 as win32 +import volatility.utils as utils +import volatility.obj as obj + +from volatility.plugins.addrspaces.win10_memcompression import \ + Win10CompressedPagedMemory + +PAGE_SIZE = 0x1000 + + +def to_hexdump(base_address, data): + hexdump = [] + for o, h, c in utils.Hexdump(data): + hexdump.append( + "{0:#010x} {1:<48} {2}".format(base_address + o, h, ''.join(c))) + return "{0}\n".format("\n".join(hexdump)) + + +class Win10Deflate(common.AbstractWindowsCommand): + """Windows 10 page decompression plugin, decompresses a single page""" + + def __init__(self, config, *args, **kwargs): + common.AbstractWindowsCommand.__init__(self, config, *args, **kwargs) + config.add_option('PID', short_option = 'p', default = None, + help = 'PID of process', action = 'store', + type = 'int') + config.add_option('OFFSET', short_option = 'o', default = None, + help = ('EPROCESS offset (in hex) in' + ' kernel address space'), + action = 'store', type = 'int') + config.add_option('VA', default = None, + help = 'VA of page to decompress', + action = 'store', type = 'int') + + @staticmethod + def register_options(config): + config.add_option('VSPAGEFILENUMBER', default = 2, + help = ('Specify the page file number corresponding' + ' to the Virtual Store (default is 2)' + ' (valid for Windows 10 only)'), + action = 'store', + type = 'int') + config.add_option('DISABLEWIN10MEMCOMPRESS', default = False, + help = ('Disables Win10 memory decompression address' + ' spaces (valid for Windows 10 only)'), + action = 'store_true') + + @staticmethod + def is_valid_profile(profile): + os = profile.metadata.get('os', '') + major = profile.metadata.get('major', 0) + minor = profile.metadata.get('minor', 0) + build = profile.metadata.get('build', 0) + return (major >= 6 + and minor >= 4 + and os == 'windows' + and build in [14393, 15063, 16299, 17134, 17763, 18362]) + + def valid_arguments(self): + if not (self._config.OFFSET or self._config.PID): + debug.warning( + "Provide either an EPROCESS offset or PID as the argument") + return False + + if self._config.OFFSET and self._config.PID: + debug.warning( + "Provide an EPROCESS offset or PID as the argument, not both") + return False + + if not self._config.VA: + debug.warning("Provide a Virtual Address to decompress") + return False + + return True + + def calculate(self): + if not self.valid_arguments(): + return None + + address_space = utils.load_as(self._config) + if not isinstance(address_space, Win10CompressedPagedMemory): + debug.warning( + "Address space not compatible with Win10 memory compression") + return None + + # Get _EPROCESS for the supplied PID + offset = self._config.OFFSET + if self._config.PID: + for p in win32.tasks.pslist(address_space): + if p.UniqueProcessId.v() == self._config.PID: + offset = p.v() + break + + if not offset: + debug.warning("Could not find the specified process") + return None + + proc = obj.Object("_EPROCESS", offset = offset, vm = address_space) + + proc_address_space = proc.get_process_address_space() + if not isinstance(proc_address_space, Win10CompressedPagedMemory): + debug.warning( + "Address space not compatible with Win10 memory compression") + return None + + data = proc_address_space.read(self._config.VA, PAGE_SIZE) + return data + + def render_text(self, outfd, data): + if data: + outfd.write(to_hexdump(self._config.VA, data)) + else: + outfd.write("Failed to retrieve and decompress data") diff --git a/volatility/plugins/win10smglobals.py b/volatility/plugins/win10smglobals.py new file mode 100644 index 000000000..d78364b44 --- /dev/null +++ b/volatility/plugins/win10smglobals.py @@ -0,0 +1,59 @@ +# Copyright (C) 2019 FireEye, Inc. +# +# This program is free software; you can redistribute it and/or +# modify it under the terms of the GNU General Public License +# as published by the Free Software Foundation; either version 2 +# of the License, or (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program; if not, write to the Free Software +# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. +# +# Authors: +# Blaine Stancill +# +# Donated under Volatility Foundation, Inc. Individual Contributor Licensing Agreement + +import volatility.plugins.common as common +import volatility.utils as utils +import volatility.obj as obj + + +class Win10SmGlobals(common.AbstractWindowsCommand): + """Find the SmGlobals value for Windows 10""" + + def __init__(self, config, *args, **kwargs): + common.AbstractWindowsCommand.__init__(self, config, *args, **kwargs) + + @staticmethod + def register_options(config): + config.add_option('SMGLOBALS', default = None, type = 'int', + help = ("Specify the virtual address of nt!SmGlobals" + " (valid for Windows 10 only)")) + + @staticmethod + def is_valid_profile(profile): + os = profile.metadata.get('os', '') + major = profile.metadata.get('major', 0) + minor = profile.metadata.get('minor', 0) + build = profile.metadata.get('build', 0) + return (major >= 6 + and minor >= 4 + and os == 'windows' + and build in [14393, 15063, 16299, 17134, 17763, 18362]) + + def calculate(self): + address_space = utils.load_as(self._config) + + return obj.VolMagic(address_space).SmGlobals.v() + + def render_text(self, outfd, data): + if data: + outfd.write("{0:#x}".format(data)) + else: + outfd.write("Unable to find nt!SmGlobals")