-
Notifications
You must be signed in to change notification settings - Fork 640
Volshell: Add Dedicated Method to retrieve EPROCESS/Task object #1381
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
Changes from 1 commit
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -3,13 +3,24 @@ | |
| # | ||
|
|
||
| from typing import Any, List, Optional, Tuple, Union | ||
| from enum import Enum | ||
|
|
||
| from volatility3.cli.volshell import generic | ||
| from volatility3.framework import constants, interfaces | ||
| from volatility3.framework.configuration import requirements | ||
| from volatility3.plugins.linux import pslist | ||
|
|
||
|
|
||
| # Could import the enum from psscan.py to avoid code duplication | ||
| class DescExitStateEnum(Enum): | ||
| """Enum for linux task exit_state as defined in include/linux/sched.h""" | ||
|
|
||
| TASK_RUNNING = 0x00000000 | ||
| EXIT_DEAD = 0x00000010 | ||
| EXIT_ZOMBIE = 0x00000020 | ||
| EXIT_TRACE = EXIT_ZOMBIE | EXIT_DEAD | ||
|
|
||
|
|
||
| class Volshell(generic.Volshell): | ||
| """Shell environment to directly interact with a linux memory image.""" | ||
|
|
||
|
|
@@ -40,6 +51,52 @@ def change_task(self, pid=None): | |
| return None | ||
| print(f"No task with task ID {pid} found") | ||
|
|
||
| def get_process(self, pid=None, offset=None): | ||
| """Get Task based on a process ID. Does not retrieve the layer, to change layer use the .pid attribute. The offset argument can be used both for physical or virtual offsets""" | ||
the-rectifier marked this conversation as resolved.
Outdated
Show resolved
Hide resolved
|
||
|
|
||
| if pid is not None and offset is not None: | ||
| print("Only one parameter is accepted") | ||
| return None | ||
|
|
||
| if offset is not None: | ||
| vmlinux_module_name = self.config["kernel"] | ||
| vmlinux = self.context.modules[vmlinux_module_name] | ||
|
|
||
| kernel_layer_name = vmlinux.layer_name | ||
| kernel_layer = self.context.layers[kernel_layer_name] | ||
|
|
||
| memory_layer_name = kernel_layer.dependencies[0] | ||
|
|
||
| ptask = self.context.object( | ||
| vmlinux.symbol_table_name + constants.BANG + "task_struct", | ||
| layer_name=memory_layer_name, | ||
| offset=offset, | ||
| native_layer_name=kernel_layer_name, | ||
| ) | ||
|
|
||
| try: | ||
| DescExitStateEnum(ptask.exit_state) | ||
|
||
| except ValueError: | ||
| print( | ||
| f"task_struct @ {hex(ptask.vol.offset)} as exit_state {ptask.exit_state} is likely not valid" | ||
| ) | ||
|
|
||
| if not (0 < ptask.pid < 65535): | ||
| print( | ||
| f"task_struct @ {hex(ptask.vol.offset)} as pid {ptask.pid} is likely not valid" | ||
| ) | ||
|
|
||
| return ptask | ||
|
|
||
| if pid is not None: | ||
| tasks = self.list_tasks() | ||
| for task in tasks: | ||
| if task.pid == pid: | ||
| return task | ||
| print(f"No task with task ID {pid} found") | ||
|
|
||
| return None | ||
|
|
||
| def list_tasks(self): | ||
| """Returns a list of task objects from the primary layer""" | ||
| # We always use the main kernel memory and associated symbols | ||
|
|
@@ -50,6 +107,7 @@ def construct_locals(self) -> List[Tuple[List[str], Any]]: | |
| result += [ | ||
| (["ct", "change_task", "cp"], self.change_task), | ||
| (["lt", "list_tasks", "ps"], self.list_tasks), | ||
| (["gp", "get_process"], self.get_process), | ||
|
||
| (["symbols"], self.context.symbol_space[self.current_symbol_table]), | ||
| ] | ||
| if self.config.get("pid", None) is not None: | ||
|
|
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -44,11 +44,58 @@ def list_processes(self): | |
| ) | ||
| ) | ||
|
|
||
| def get_process(self, pid=None, v_offset=None, p_offset=None): | ||
| """Returns the EPROCESS object that matches the pid. If v_offset/p_offset is provided, construct the EPROCESS object at the provided address. Only one parameter is allowed.""" | ||
|
||
|
|
||
| if sum(1 if x is not None else 0 for x in [pid, v_offset, p_offset]) != 1: | ||
| print("Only one parameter is accepted") | ||
| return None | ||
|
|
||
| kernel_name = self.config["kernel"] | ||
| kernel = self.context.modules[kernel_name] | ||
|
|
||
| kernel_layer_name = kernel.layer_name | ||
|
|
||
| kernel_layer = self.context.layers[kernel_layer_name] | ||
| memory_layer_name = kernel_layer.dependencies[0] | ||
ikelos marked this conversation as resolved.
Show resolved
Hide resolved
|
||
|
|
||
| eprocess_symbol = kernel.symbol_table_name + constants.BANG + "_EPROCESS" | ||
|
|
||
| if v_offset is not None: | ||
| eproc = self.context.object( | ||
| eprocess_symbol, | ||
| layer_name=kernel_layer_name, | ||
| offset=v_offset, | ||
| ) | ||
|
|
||
| return eproc | ||
|
|
||
| if p_offset is not None: | ||
| eproc = self.context.object( | ||
| eprocess_symbol, | ||
| layer_name=memory_layer_name, | ||
| offset=p_offset, | ||
| native_layer_name=kernel_layer_name, | ||
| ) | ||
|
|
||
| return eproc | ||
|
|
||
| if pid is not None: | ||
| processes = self.list_processes() | ||
| for process in processes: | ||
| if process.UniqueProcessId == pid: | ||
| return process | ||
| print(f"No process with process ID {pid} found") | ||
| return None | ||
|
|
||
| return None | ||
|
|
||
| def construct_locals(self) -> List[Tuple[List[str], Any]]: | ||
| result = super().construct_locals() | ||
| result += [ | ||
| (["cp", "change_process"], self.change_process), | ||
| (["lp", "list_processes", "ps"], self.list_processes), | ||
| (["gp", "get_process"], self.get_process), | ||
| (["symbols"], self.context.symbol_space[self.current_symbol_table]), | ||
| ] | ||
| if self.config.get("pid", None) is not None: | ||
|
|
||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This implies offset is always a physical offset. Were you going to split that into
physical_offsetandvirtual_offsetor something?There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I did split the code, basically the same as the windows side, but wanted to get your input on the
is_valid()thing before committing