- 
                Notifications
    You must be signed in to change notification settings 
- Fork 591
Add linux hidden_modules plugin #1283
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
          
     Merged
      
      
            ikelos
  merged 19 commits into
  volatilityfoundation:develop
from
gcmoreira:linux_hidden_modules
  
      
      
   
  Oct 30, 2024 
      
    
  
     Merged
                    Changes from all commits
      Commits
    
    
            Show all changes
          
          
            19 commits
          
        
        Select commit
          Hold shift + click to select a range
      
      6cd39c0
              
                Refactor of module object. Adding function helpers to simplify the co…
              
              
                gcmoreira 5dee3ae
              
                Add linux.hidden_modules plugin
              
              
                gcmoreira d5e6e7c
              
                Allow any module state value in both traditional and fast scan methods
              
              
                gcmoreira 590aa9c
              
                Make it callable from other plugins.
              
              
                gcmoreira 8d925bd
              
                Added the --heuristic-mode option, which relaxes constraints to impro…
              
              
                gcmoreira e8754fa
              
                Fix typo in usage help
              
              
                gcmoreira b5948d7
              
                Linux: hidden_modules: Add @Abyss-W4tcher suggestion to optimize the …
              
              
                gcmoreira f455c30
              
                Linux: hidden_modules: remove missed optional heuristic_mode argument
              
              
                gcmoreira d98c7eb
              
                linux: hidden_modules: Make the fast method the default. Remove vol2 …
              
              
                gcmoreira 0ddd921
              
                linux: hidden_modules: Remove unused module imports
              
              
                gcmoreira 526007f
              
                Linux: hidden_modules: Use child_template
              
              
                gcmoreira cbe071f
              
                Linux: hidden_modules: Import the whole architectures module
              
              
                gcmoreira dfd8a1f
              
                Linux: hidden_modules: Include kernel version and commit details
              
              
                gcmoreira 1c6a548
              
                Linux: hidden_modules: Simplify symbols type checks
              
              
                gcmoreira 8960bda
              
                Linux: hidden_modules: Add a symbol table check for a recent dwarf2js…
              
              
                gcmoreira f537c4a
              
                Merge branch 'develop' into linux_hidden_modules
              
              
                gcmoreira 4b76b69
              
                Linux: hidden_modules: Add docstrings and comments to enhance the doc…
              
              
                gcmoreira 722ccd5
              
                Linux: Extensions: Clean up the Linux constants imports in the object…
              
              
                gcmoreira 4f86b3f
              
                Merge branch 'develop' into linux_hidden_modules
              
              
                gcmoreira File filter
Filter by extension
Conversations
          Failed to load comments.   
        
        
          
      Loading
        
  Jump to
        
          Jump to file
        
      
      
          Failed to load files.   
        
        
          
      Loading
        
  Diff view
Diff view
There are no files selected for viewing
  
    
      This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
      Learn more about bidirectional Unicode characters
    
  
  
    
              
  
    
      This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
      Learn more about bidirectional Unicode characters
    
  
  
    
              | Original file line number | Diff line number | Diff line change | 
|---|---|---|
| @@ -0,0 +1,246 @@ | ||
| # This file is Copyright 2024 Volatility Foundation and licensed under the Volatility Software License 1.0 | ||
| # which is available at https://www.volatilityfoundation.org/license/vsl-v1.0 | ||
| # | ||
| import logging | ||
| from typing import List, Set, Tuple, Iterable | ||
| from volatility3.framework import renderers, interfaces, exceptions, objects | ||
| from volatility3.framework.constants import architectures | ||
| from volatility3.framework.renderers import format_hints | ||
| from volatility3.framework.configuration import requirements | ||
| from volatility3.plugins.linux import lsmod | ||
|  | ||
| vollog = logging.getLogger(__name__) | ||
|  | ||
|  | ||
| class Hidden_modules(interfaces.plugins.PluginInterface): | ||
| """Carves memory to find hidden kernel modules""" | ||
|  | ||
| _required_framework_version = (2, 10, 0) | ||
|  | ||
| _version = (1, 0, 0) | ||
|  | ||
| @classmethod | ||
| def get_requirements(cls) -> List[interfaces.configuration.RequirementInterface]: | ||
| return [ | ||
| requirements.ModuleRequirement( | ||
| name="kernel", | ||
| description="Linux kernel", | ||
| architectures=architectures.LINUX_ARCHS, | ||
| ), | ||
| requirements.PluginRequirement( | ||
| name="lsmod", plugin=lsmod.Lsmod, version=(2, 0, 0) | ||
| ), | ||
| ] | ||
|  | ||
| @staticmethod | ||
| def get_modules_memory_boundaries( | ||
| context: interfaces.context.ContextInterface, | ||
| vmlinux_module_name: str, | ||
| ) -> Tuple[int]: | ||
| """Determine the boundaries of the module allocation area | ||
|  | ||
| Args: | ||
| context: The context to retrieve required elements (layers, symbol tables) from | ||
| vmlinux_module_name: The name of the kernel module on which to operate | ||
|  | ||
| Returns: | ||
| A tuple containing the minimum and maximum addresses for the module allocation area. | ||
| """ | ||
| vmlinux = context.modules[vmlinux_module_name] | ||
| if vmlinux.has_symbol("mod_tree"): | ||
| # Kernel >= 5.19 58d208de3e8d87dbe196caf0b57cc58c7a3836ca | ||
| mod_tree = vmlinux.object_from_symbol("mod_tree") | ||
| modules_addr_min = mod_tree.addr_min | ||
| modules_addr_max = mod_tree.addr_max | ||
| elif vmlinux.has_symbol("module_addr_min"): | ||
| # 2.6.27 <= kernel < 5.19 3a642e99babe0617febb6f402e1e063479f489db | ||
| modules_addr_min = vmlinux.object_from_symbol("module_addr_min") | ||
| modules_addr_max = vmlinux.object_from_symbol("module_addr_max") | ||
|  | ||
| if isinstance(modules_addr_min, objects.Void): | ||
| raise exceptions.VolatilityException( | ||
| "Your ISF symbols lack type information. You may need to update the" | ||
| "ISF using the latest version of dwarf2json" | ||
| ) | ||
| else: | ||
| raise exceptions.VolatilityException( | ||
| "Cannot find the module memory allocation area. Unsupported kernel" | ||
| ) | ||
|  | ||
| return modules_addr_min, modules_addr_max | ||
|  | ||
| @classmethod | ||
| def _get_module_address_alignment( | ||
| cls, | ||
| context: interfaces.context.ContextInterface, | ||
| vmlinux_module_name: str, | ||
| ) -> int: | ||
| """Obtain the module memory address alignment. | ||
|  | ||
| struct module is aligned to the L1 cache line, which is typically 64 bytes for most | ||
| common i386/AMD64/ARM64 configurations. In some cases, it can be 128 bytes, but this | ||
| will still work. | ||
|  | ||
| Args: | ||
| context: The context to retrieve required elements (layers, symbol tables) from | ||
| vmlinux_module_name: The name of the kernel module on which to operate | ||
|  | ||
| Returns: | ||
| The struct module alignment | ||
| """ | ||
| # FIXME: When dwarf2json/ISF supports type alignments. Read it directly from the type metadata | ||
| # Additionally, while 'context' and 'vmlinux_module_name' are currently unused, they will be | ||
| # essential for retrieving type metadata in the future. | ||
| return 64 | ||
|  | ||
| @staticmethod | ||
| def _validate_alignment_patterns( | ||
| addresses: Iterable[int], | ||
| address_alignment: int, | ||
| ) -> bool: | ||
| """Check if the memory addresses meet our alignments patterns | ||
|  | ||
| Args: | ||
| addresses: Iterable with the address values | ||
| address_alignment: Number of bytes for alignment validation | ||
|  | ||
| Returns: | ||
| True if all the addresses meet the alignment | ||
| """ | ||
| return all(addr % address_alignment == 0 for addr in addresses) | ||
|  | ||
| @classmethod | ||
| def get_hidden_modules( | ||
| cls, | ||
| context: interfaces.context.ContextInterface, | ||
| vmlinux_module_name: str, | ||
| known_module_addresses: Set[int], | ||
| modules_memory_boundaries: Tuple, | ||
| ) -> Iterable[interfaces.objects.ObjectInterface]: | ||
| """Enumerate hidden modules by taking advantage of memory address alignment patterns | ||
|  | ||
| This technique is much faster and uses less memory than the traditional scan method | ||
| in Volatility2, but it doesn't work with older kernels. | ||
|  | ||
| From kernels 4.2 struct module allocation are aligned to the L1 cache line size. | ||
| In i386/amd64/arm64 this is typically 64 bytes. However, this can be changed in | ||
| the Linux kernel configuration via CONFIG_X86_L1_CACHE_SHIFT. The alignment can | ||
| also be obtained from the DWARF info i.e. DW_AT_alignment<64>, but dwarf2json | ||
| doesn't support this feature yet. | ||
| In kernels < 4.2, alignment attributes are absent in the struct module, meaning | ||
| alignment cannot be guaranteed. Therefore, for older kernels, it's better to use | ||
| the traditional scan technique. | ||
|  | ||
| Args: | ||
| context: The context to retrieve required elements (layers, symbol tables) from | ||
| vmlinux_module_name: The name of the kernel module on which to operate | ||
| known_module_addresses: Set with known module addresses | ||
| modules_memory_boundaries: Minimum and maximum address boundaries for module allocation. | ||
| Yields: | ||
| module objects | ||
| """ | ||
| vmlinux = context.modules[vmlinux_module_name] | ||
| vmlinux_layer = context.layers[vmlinux.layer_name] | ||
|  | ||
| module_addr_min, module_addr_max = modules_memory_boundaries | ||
| module_address_alignment = cls._get_module_address_alignment( | ||
| context, vmlinux_module_name | ||
| ) | ||
| if not cls._validate_alignment_patterns( | ||
| known_module_addresses, module_address_alignment | ||
| ): | ||
| vollog.warning( | ||
| f"Module addresses aren't aligned to {module_address_alignment} bytes. " | ||
| "Switching to 1 byte aligment scan method." | ||
| ) | ||
| module_address_alignment = 1 | ||
|  | ||
| mkobj_offset = vmlinux.get_type("module").relative_child_offset("mkobj") | ||
| mod_offset = vmlinux.get_type("module_kobject").relative_child_offset("mod") | ||
| offset_to_mkobj_mod = mkobj_offset + mod_offset | ||
| mod_member_template = vmlinux.get_type("module_kobject").child_template("mod") | ||
| mod_size = mod_member_template.size | ||
| mod_member_data_format = mod_member_template.data_format | ||
|  | ||
| for module_addr in range( | ||
| module_addr_min, module_addr_max, module_address_alignment | ||
| ): | ||
| if module_addr in known_module_addresses: | ||
| continue | ||
|  | ||
| try: | ||
| # This is just a pre-filter. Module readability and consistency are verified in module.is_valid() | ||
| self_referential_bytes = vmlinux_layer.read( | ||
| module_addr + offset_to_mkobj_mod, mod_size | ||
| ) | ||
| self_referential = objects.convert_data_to_value( | ||
| self_referential_bytes, int, mod_member_data_format | ||
| ) | ||
| if self_referential != module_addr: | ||
| continue | ||
| except ( | ||
| exceptions.PagedInvalidAddressException, | ||
| exceptions.InvalidAddressException, | ||
| ): | ||
| continue | ||
|  | ||
| module = vmlinux.object("module", offset=module_addr, absolute=True) | ||
| if module and module.is_valid(): | ||
| yield module | ||
|  | ||
| @classmethod | ||
| def get_lsmod_module_addresses( | ||
| cls, | ||
| context: interfaces.context.ContextInterface, | ||
| vmlinux_module_name: str, | ||
| ) -> Set[int]: | ||
| """Obtain a set the known module addresses from linux.lsmod plugin | ||
|  | ||
| Args: | ||
| context: The context to retrieve required elements (layers, symbol tables) from | ||
| vmlinux_module_name: The name of the kernel module on which to operate | ||
|  | ||
| Returns: | ||
| A set containing known kernel module addresses | ||
| """ | ||
| vmlinux = context.modules[vmlinux_module_name] | ||
| vmlinux_layer = context.layers[vmlinux.layer_name] | ||
|  | ||
| known_module_addresses = { | ||
| vmlinux_layer.canonicalize(module.vol.offset) | ||
| for module in lsmod.Lsmod.list_modules(context, vmlinux_module_name) | ||
| } | ||
| return known_module_addresses | ||
|  | ||
| def _generator(self): | ||
| vmlinux_module_name = self.config["kernel"] | ||
| known_module_addresses = self.get_lsmod_module_addresses( | ||
| self.context, vmlinux_module_name | ||
| ) | ||
| modules_memory_boundaries = self.get_modules_memory_boundaries( | ||
| self.context, vmlinux_module_name | ||
| ) | ||
| for module in self.get_hidden_modules( | ||
| self.context, | ||
| vmlinux_module_name, | ||
| known_module_addresses, | ||
| modules_memory_boundaries, | ||
| ): | ||
| module_addr = module.vol.offset | ||
| module_name = module.get_name() or renderers.NotAvailableValue() | ||
| fields = (format_hints.Hex(module_addr), module_name) | ||
| yield (0, fields) | ||
|  | ||
| def run(self): | ||
| if self.context.symbol_space.verify_table_versions( | ||
| "dwarf2json", lambda version, _: (not version) or version < (0, 8, 0) | ||
| ): | ||
| raise exceptions.SymbolSpaceError( | ||
| "Invalid symbol table, please ensure the ISF table produced by dwarf2json was created with version 0.8.0 or later" | ||
| ) | ||
|  | ||
| headers = [ | ||
| ("Address", format_hints.Hex), | ||
| ("Name", str), | ||
| ] | ||
| return renderers.TreeGrid(headers, self._generator()) | ||
      
      Oops, something went wrong.
        
    
  
      
      Oops, something went wrong.
        
    
  
  Add this suggestion to a batch that can be applied as a single commit.
  This suggestion is invalid because no changes were made to the code.
  Suggestions cannot be applied while the pull request is closed.
  Suggestions cannot be applied while viewing a subset of changes.
  Only one suggestion per line can be applied in a batch.
  Add this suggestion to a batch that can be applied as a single commit.
  Applying suggestions on deleted lines is not supported.
  You must change the existing code in this line in order to create a valid suggestion.
  Outdated suggestions cannot be applied.
  This suggestion has been applied or marked resolved.
  Suggestions cannot be applied from pending reviews.
  Suggestions cannot be applied on multi-line comments.
  Suggestions cannot be applied while the pull request is queued to merge.
  Suggestion cannot be applied right now. Please check back later.
  
    
  
    
Uh oh!
There was an error while loading. Please reload this page.