Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
30 changes: 30 additions & 0 deletions test/plugins/windows/windows.py
Original file line number Diff line number Diff line change
Expand Up @@ -1471,3 +1471,33 @@ def test_windows_specific_virtmap(self, volatility, python):
)
for expected_row in expected_rows:
assert test_volatility.match_output_row(expected_row, json_out)


class TestWindowsShutdown:
def test_windows_lastshutdown(self, volatility, python, image):
image = WindowsSamples.WINDOWS10_GENERIC.value.path

rc, out, _err = test_volatility.runvol_plugin(
"windows.registry.shutdown",
image,
volatility,
python,
globalargs=("-r", "json"),
)


assert rc == 0
json_out = json.loads(out)
assert isinstance(json_out, list)
assert len(json_out) > 0
expected_keys = {"Registry Key", "Last Shutdown Time"}

#Here I controll for every row in the output if there are
#the registry key and the value other than controlling
#if the output actually exists and is the right output to shutdown plugin
for row in json_out:
assert isinstance(row, dict)
assert expected_keys.issubset(row.keys())
assert row["Registry Key"]
assert row["Last Shutdown Time"]
assert "ControlSet" in row["Registry Key"]
101 changes: 101 additions & 0 deletions volatility3/framework/plugins/windows/registry/shutdown.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,101 @@
import logging
import struct
import datetime
from typing import Iterable, Optional

from volatility3.framework import interfaces, renderers, exceptions
from volatility3.framework.configuration import requirements
from volatility3.framework.layers import registry as registry_layer
from volatility3.framework.symbols.windows.extensions import registry
from volatility3.plugins.windows.registry import hivelist
from volatility3.framework.renderers import conversion

vollog = logging.getLogger(__name__)


class LastShutdown(interfaces.plugins.PluginInterface):
"""Extract last Windows shutdown time from registry"""

_required_framework_version = (2, 0, 0)
_version = (1, 0, 0)

@classmethod
def get_requirements(cls):
return [
requirements.ModuleRequirement(
name="kernel",
description="Windows kernel",
architectures=["Intel32", "Intel64"],
),
requirements.VersionRequirement(
name="hivelist",
component=hivelist.HiveList,
version=(2, 0, 0),
),
]

@classmethod
def get_key(
cls, hive: registry_layer.RegistryHive, key_path: str
) -> Optional["registry.CM_KEY_NODE"]:
try:
return hive.get_key(key_path)
except Exception:
return None

def _generator(self, syshive: registry_layer.RegistryHive) -> Iterable:

if not syshive:
return

# Windows systems typically maintain a small number of ControlSets
# (commonly 1–3, but occasionally more in recovery scenarios).
# We iterate over a bounded range to ensure we can recover shutdown
# time even if the active ControlSet cannot be determined via
# Select\\Current or if registry data is partially corrupted.
for i in range(1, 5):

key_path = f"ControlSet{i:03}\\Control\\Windows"

key = self.get_key(syshive, key_path)

if not key:
continue

for v in key.get_values():

if v.get_name() == "ShutdownTime":

try:
data = syshive.read(v.Data + 4, v.DataLength)
except exceptions.InvalidAddressException:
continue

if not data or len(data) < 8:
continue

filetime = struct.unpack("<Q", data[:8])[0]
shutdown_time = conversion.wintime_to_datetime(filetime)

yield (0, (key_path, str(shutdown_time)))

def run(self):

syshive = None

for hive in hivelist.HiveList.list_hives(
context=self.context,
base_config_path=self.config_path,
kernel_module_name=self.config["kernel"],
):

if hive.get_name().split("\\")[-1].upper() == "SYSTEM":
syshive = hive

return renderers.TreeGrid(
[
("Registry Key", str),
("Last Shutdown Time", str),
],
self._generator(syshive),
)