# This file is Copyright 2022 Volatility Foundation and licensed under the Volatility Software License 1.0
# which is available at https://www.volatilityfoundation.org/license/vsl-v1.0
#
import datetime
import logging
from volatility3.framework import renderers, interfaces
from volatility3.framework.configuration import requirements
from volatility3.framework.objects import utility
from volatility3.plugins.windows import pslist
from volatility3.plugins import timeliner
vollog = logging.getLogger(__name__)
[docs]
class Sessions(interfaces.plugins.PluginInterface, timeliner.TimeLinerInterface):
"""lists Processes with Session information extracted from Environmental Variables"""
_required_framework_version = (2, 0, 0)
[docs]
@classmethod
def get_requirements(cls):
return [
requirements.ModuleRequirement(
name="kernel",
description="Windows kernel",
architectures=["Intel32", "Intel64"],
),
requirements.PluginRequirement(
name="pslist", plugin=pslist.PsList, version=(2, 0, 0)
),
requirements.ListRequirement(
name="pid",
element_type=int,
description="Process IDs to include (all other processes are excluded)",
optional=True,
),
]
def _generator(self):
kernel = self.context.modules[self.config["kernel"]]
filter_func = pslist.PsList.create_pid_filter(self.config.get("pid", None))
# Collect all the values as we will want to group them later
sessions = {}
for proc in pslist.PsList.list_processes(
self.context,
kernel.layer_name,
kernel.symbol_table_name,
filter_func=filter_func,
):
session_id = proc.get_session_id()
# Detect RDP, Console or set default value
session_type = renderers.NotAvailableValue()
# Construct Username from Process Env
user_domain = ""
user_name = ""
for var, val in proc.environment_variables():
if var.lower() == "username":
user_name = val
elif var.lower() == "userdomain":
user_domain = val
if var.lower() == "sessionname":
session_type = val
# Concat Domain and User
full_user = f"{user_domain}/{user_name}"
if full_user == "/":
full_user = renderers.NotAvailableValue()
# Collect all the values in to a row we can yield after sorting.
row = {
"session_id": session_id,
"process_id": proc.UniqueProcessId,
"process_name": utility.array_to_string(proc.ImageFileName),
"user_name": full_user,
"process_start": proc.get_create_time(),
"session_type": session_type,
}
# Add row to correct session so we can sort it later
if session_id in sessions:
sessions[session_id].append(row)
else:
sessions[session_id] = [row]
# Group and yield each row
for rows in sessions.values():
for row in rows:
yield 0, (
row.get("session_id"),
row.get("session_type"),
row.get("process_id"),
row.get("process_name"),
row.get("user_name"),
row.get("process_start"),
)
[docs]
def generate_timeline(self):
for row in self._generator():
_depth, row_data = row
# Only add to timeline if we have the username
# Without the user context PSList output is identical
if isinstance(row_data[4], str):
description = f"Process: {row_data[2]} {row_data[3]} started by user {row_data[4]}"
yield (description, timeliner.TimeLinerType.CREATED, row_data[5])
[docs]
def run(self):
return renderers.TreeGrid(
[
("Session ID", int),
("Session Type", str),
("Process ID", int),
("Process", str),
("User Name", str),
("Create Time", datetime.datetime),
],
self._generator(),
)