From 867edc95012bd0fb02f09aafc4ae59980abe9d99 Mon Sep 17 00:00:00 2001 From: Mike Auty Date: Thu, 18 Apr 2019 01:39:37 +0100 Subject: [PATCH 1/4] Add in the os-distinguisher code. --- .../framework/plugins/windows/poolscanner.py | 54 ++++++++++++------- 1 file changed, 35 insertions(+), 19 deletions(-) diff --git a/volatility/framework/plugins/windows/poolscanner.py b/volatility/framework/plugins/windows/poolscanner.py index e4df4b30a..3c2811c8e 100644 --- a/volatility/framework/plugins/windows/poolscanner.py +++ b/volatility/framework/plugins/windows/poolscanner.py @@ -20,7 +20,7 @@ import enum import logging -from typing import Dict, Generator, List, Optional, Tuple +from typing import Dict, Generator, List, Optional, Tuple, Callable import volatility.plugins.windows.handles as handles @@ -72,6 +72,39 @@ class PoolConstraint: self.alignment = alignment +def os_distinguisher(version_check: Tuple[int, ...], + symbol_name: Optional[str] = None, + type_name: Optional[str] = None, + type_member: Optional[str] = None) -> Callable[[interfaces.context.ContextInterface, str], bool]: + """Distinguishes an operating system based on the metadata and falling back to check whether a structure exists""" + # try the primary method based on the pe version in the ISF + if not symbol_name and not type_name: + raise ValueError("OS Distinguisher must have at least one fallback method (symbol or type/member)") + + def method(context: interfaces.context.ContextInterface, symbol_table: str) -> bool: + + try: + pe_version = context.symbol_space[symbol_table].metadata.pe_version + major, minor, revision, build = pe_version + return (major, minor, revision, build) >= version_check + except (AttributeError, ValueError, TypeError): + vollog.log(constants.LOGLEVEL_VVV, "Windows PE version data is not available") + + # fall back to the backup method, if necessary + try: + if symbol_name: + _symbol = context.symbol_space.get_symbol(symbol_table + constants.BANG + symbol_name) + else: + type_class = context.symbol_space.get_type(symbol_table + constants.BANG + type_name) + if type_member: + return type_class.has_member(type_member) + return True + except exceptions.SymbolError: + return False + + return method + + class PoolScanner(plugins.PluginInterface): """A generic pool scanner plugin""" @@ -83,24 +116,7 @@ class PoolScanner(plugins.PluginInterface): requirements.SymbolTableRequirement(name = "nt_symbols", description = "Windows kernel symbols") ] - @staticmethod - def is_windows_10(context: interfaces.context.ContextInterface, symbol_table: str) -> bool: - """Determine if the analyzed sample is Windows 10""" - - # try the primary method based on the pe version in the ISF - try: - pe_version = context.symbol_space[symbol_table].metadata.pe_version - major, minor, _revision, _build = pe_version - return (major, minor) >= (10, 0) - except (AttributeError, ValueError, TypeError): - vollog.log(constants.LOGLEVEL_VVV, "Windows PE version data is not available") - - # fall back to the backup method, if necessary - try: - _symbol = context.symbol_space.get_symbol(symbol_table + constants.BANG + "ObHeaderCookie") - return True - except exceptions.SymbolError: - return False + is_windows_10 = os_distinguisher(version_check = (10, 0), symbol_name = "ObHeaderCookie") @staticmethod def is_windows_8_or_later(context: interfaces.context.ContextInterface, layer_name: str, symbol_table: str) -> bool: From 38f249aef26b254d2bf2262f31795dc27d8e2a00 Mon Sep 17 00:00:00 2001 From: Mike Auty Date: Fri, 3 May 2019 21:00:01 +0100 Subject: [PATCH 2/4] Change the os-distguisher to make it more flexible. --- .../framework/plugins/windows/poolscanner.py | 100 ++++++++---------- 1 file changed, 44 insertions(+), 56 deletions(-) diff --git a/volatility/framework/plugins/windows/poolscanner.py b/volatility/framework/plugins/windows/poolscanner.py index 3c2811c8e..df1b5801c 100644 --- a/volatility/framework/plugins/windows/poolscanner.py +++ b/volatility/framework/plugins/windows/poolscanner.py @@ -72,35 +72,53 @@ class PoolConstraint: self.alignment = alignment -def os_distinguisher(version_check: Tuple[int, ...], - symbol_name: Optional[str] = None, - type_name: Optional[str] = None, - type_member: Optional[str] = None) -> Callable[[interfaces.context.ContextInterface, str], bool]: - """Distinguishes an operating system based on the metadata and falling back to check whether a structure exists""" - # try the primary method based on the pe version in the ISF - if not symbol_name and not type_name: - raise ValueError("OS Distinguisher must have at least one fallback method (symbol or type/member)") +def os_distinguisher(version_check: Callable[[Tuple[int, ...]], bool], + fallback_checks: List[Tuple[str, Optional[str], bool]], + invert: bool = False) -> Callable[[interfaces.context.ContextInterface, str], bool]: + """Distinguishes a symbol table as being above a particular version or point + This will primarily check the version metadata first and foremost. + If that metadata isn't available then each item in the fallback_checks is tested. + If invert is specified then the result will be true if the version is less than that specified, or in the case of + fallback, if any of the fallback checks is successful. + + A fallback check is made up of: + * a symbol or type name + * a member name (implying that the value before was a type name) + * whether that symbol, type or member must be present or absent for the symbol table to be more above the required point + + Note: Specifying that a member must not be present includes the whole type not being present too (ie, either will pass the test) + """ + + # try the primary method based on the pe version in the ISF def method(context: interfaces.context.ContextInterface, symbol_table: str) -> bool: try: pe_version = context.symbol_space[symbol_table].metadata.pe_version major, minor, revision, build = pe_version - return (major, minor, revision, build) >= version_check + return version_check((major, minor, revision, build)) except (AttributeError, ValueError, TypeError): vollog.log(constants.LOGLEVEL_VVV, "Windows PE version data is not available") + if not fallback_checks: + raise ValueError("No fallback methods for os_distinguishing provided") + # fall back to the backup method, if necessary - try: - if symbol_name: - _symbol = context.symbol_space.get_symbol(symbol_table + constants.BANG + symbol_name) + for name, member, response in fallback_checks: + if member is None: + if (context.symbol_space.has_symbol(symbol_table + constants.BANG + name) + or context.symbol_space.has_type(symbol_table + constants.BANG + name)) != response: + return False else: - type_class = context.symbol_space.get_type(symbol_table + constants.BANG + type_name) - if type_member: - return type_class.has_member(type_member) - return True - except exceptions.SymbolError: - return False + try: + symbol_type = context.symbol_space.get_type(symbol_table + constants.BANG + name) + if symbol_type.has_member(member) != response: + return False + except exceptions.SymbolError: + if not response: + return False + + return True return method @@ -116,44 +134,14 @@ class PoolScanner(plugins.PluginInterface): requirements.SymbolTableRequirement(name = "nt_symbols", description = "Windows kernel symbols") ] - is_windows_10 = os_distinguisher(version_check = (10, 0), symbol_name = "ObHeaderCookie") - - @staticmethod - def is_windows_8_or_later(context: interfaces.context.ContextInterface, layer_name: str, symbol_table: str) -> bool: - """Determine if the analyzed sample is Windows 8 or later""" - - # try the primary method based on the pe version in the ISF - try: - pe_version = context.symbol_space[symbol_table].metadata.pe_version - major, minor, _revision, _build = pe_version - return (major, minor) >= (6, 2) - except (AttributeError, ValueError, TypeError): - vollog.log(constants.LOGLEVEL_VVV, "Windows PE version data is not available") - - # fall back to the backup method, if necessary - kvo = context.memory[layer_name].config['kernel_virtual_offset'] - ntkrnlmp = context.module(symbol_table, layer_name = layer_name, offset = kvo) - handle_table_type = ntkrnlmp.get_type("_HANDLE_TABLE") - return not handle_table_type.has_member("HandleCount") - - @staticmethod - def is_windows_7(context: interfaces.context.ContextInterface, layer_name: str, symbol_table: str) -> bool: - """Determine if the analyzed sample is Windows 7""" - - # try the primary method based on the pe version in the ISF - try: - pe_version = context.symbol_space[symbol_table].metadata.pe_version - major, minor, _revision, _build = pe_version - return (major, minor) == (6, 1) - except (AttributeError, ValueError): - vollog.log(constants.LOGLEVEL_VVV, "Windows PE version data is not available") - - # fall back to the backup method, if necessary - kvo = context.memory[layer_name].config['kernel_virtual_offset'] - ntkrnlmp = context.module(symbol_table, layer_name = layer_name, offset = kvo) - handle_table_type = ntkrnlmp.get_type("_OBJECT_HEADER") - return (handle_table_type.has_member("TypeIndex") - and not PoolScanner.is_windows_8_or_later(context, layer_name, symbol_table)) + is_windows_10 = os_distinguisher( + version_check = lambda x: x >= (10, 0), fallback_checks = [("ObHeaderCookie", None, True)]) + is_windows_8_or_later = os_distinguisher( + version_check = lambda x: x >= (6, 2), fallback_checks = [("_HANDLE_TABLE", "HandleCount", False)]) + # Technically, this is win7 or less + is_windows_7 = os_distinguisher( + version_check = lambda x: x == (6, 1), + fallback_checks = [("_OBJECT_HEADER", "TypeIndex", True), ("_HANDLE_TABLE", "HandleCount", True)]) def _generator(self): From 4ff137bc18130a741674513569f73ad112fd141a Mon Sep 17 00:00:00 2001 From: Mike Auty Date: Fri, 3 May 2019 21:12:46 +0100 Subject: [PATCH 3/4] Fix up a is_windows_8_or_later call. --- volatility/framework/plugins/windows/poolscanner.py | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/volatility/framework/plugins/windows/poolscanner.py b/volatility/framework/plugins/windows/poolscanner.py index df1b5801c..d87fda89d 100644 --- a/volatility/framework/plugins/windows/poolscanner.py +++ b/volatility/framework/plugins/windows/poolscanner.py @@ -73,8 +73,8 @@ class PoolConstraint: def os_distinguisher(version_check: Callable[[Tuple[int, ...]], bool], - fallback_checks: List[Tuple[str, Optional[str], bool]], - invert: bool = False) -> Callable[[interfaces.context.ContextInterface, str], bool]: + fallback_checks: List[Tuple[str, Optional[str], bool]] + ) -> Callable[[interfaces.context.ContextInterface, str], bool]: """Distinguishes a symbol table as being above a particular version or point This will primarily check the version metadata first and foremost. @@ -285,8 +285,7 @@ class PoolScanner(plugins.PluginInterface): cookie = handles.Handles.find_cookie(context = context, layer_name = layer_name, symbol_table = symbol_table) is_windows_10 = cls.is_windows_10(context = context, symbol_table = symbol_table) - is_windows_8_or_later = cls.is_windows_8_or_later( - context = context, layer_name = layer_name, symbol_table = symbol_table) + is_windows_8_or_later = cls.is_windows_8_or_later(context = context, symbol_table = symbol_table) # start off with the primary virtual layer scan_layer = layer_name From a90f3ed1a7ad6d241a27419514fdc08318c69683 Mon Sep 17 00:00:00 2001 From: Analyst Date: Sun, 26 May 2019 11:07:07 -0500 Subject: [PATCH 4/4] fixup an instance of is_windows_7 in poolscanner --- volatility/framework/plugins/windows/poolscanner.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/volatility/framework/plugins/windows/poolscanner.py b/volatility/framework/plugins/windows/poolscanner.py index d87fda89d..50f931d6a 100644 --- a/volatility/framework/plugins/windows/poolscanner.py +++ b/volatility/framework/plugins/windows/poolscanner.py @@ -335,7 +335,7 @@ class PoolScanner(plugins.PluginInterface): # We have to manually load a symbol table if symbols.symbol_table_is_64bit(context, symbol_table): - is_win_7 = PoolScanner.is_windows_7(context, 'primary', symbol_table) + is_win_7 = cls.is_windows_7(context = context, symbol_table = symbol_table) if is_win_7: pool_header_json_filename = "poolheader-x64-win7" else: