diff --git a/.github/workflows/ruff.yaml b/.github/workflows/ruff.yaml index 77e3aa864..98a05a616 100644 --- a/.github/workflows/ruff.yaml +++ b/.github/workflows/ruff.yaml @@ -9,7 +9,7 @@ jobs: steps: - uses: actions/checkout@v4 - - uses: astral-sh/ruff-action@v1 + - uses: astral-sh/ruff-action@v3.2.1 with: args: check src: "." diff --git a/.github/workflows/test.yaml b/.github/workflows/test.yaml index 5fdc83327..56d671102 100644 --- a/.github/workflows/test.yaml +++ b/.github/workflows/test.yaml @@ -31,13 +31,17 @@ jobs: gunzip linux-sample-1.bin.gz curl -sLO "https://downloads.volatilityfoundation.org/volatility3/images/win-xp-laptop-2005-06-25.img.gz" gunzip win-xp-laptop-2005-06-25.img.gz + curl -sLO "https://downloads.volatilityfoundation.org/volatility3/images/win-10_19041-2025_03.dmp.gz" + gunzip win-10_19041-2025_03.dmp.gz cd - - name: Download and Extract symbols run: | cd ./volatility3/symbols curl -sLO https://downloads.volatilityfoundation.org/volatility3/symbols/linux.zip + curl -sLO https://downloads.volatilityfoundation.org/volatility3/symbols/symbols_win-10_19041-2025_03.zip unzip linux.zip + unzip symbols_win-10_19041-2025_03.zip cd - - name: Testing... @@ -47,8 +51,8 @@ jobs: pytest ./test/plugins/linux/linux.py --volatility=volshell.py --image-dir=./test_images -k test_linux_volshell -v # Volatility - pytest ./test/plugins/windows/windows.py --volatility=vol.py --image-dir=./test_images -k "test_windows and not test_windows_volshell" -v - pytest ./test/plugins/linux/linux.py --volatility=vol.py --image-dir=./test_images -k "test_linux and not test_linux_volshell" -v + pytest ./test/plugins/windows/windows.py --volatility=vol.py --image=./test_images/win-10_19041-2025_03.dmp -k "test_windows and not test_windows_volshell" -v --durations=0 + pytest ./test/plugins/linux/linux.py --volatility=vol.py --image-dir=./test_images -k "test_linux and not test_linux_volshell" -v --durations=0 - name: Clean up post-test run: | diff --git a/doc/source/simple-plugin.rst b/doc/source/simple-plugin.rst index a6916a027..d855e319d 100644 --- a/doc/source/simple-plugin.rst +++ b/doc/source/simple-plugin.rst @@ -53,9 +53,9 @@ to be able to run properly. Any that are defined as optional need not necessari description = "Process IDs to include (all other processes are excluded)", optional = True ), - requirements.PluginRequirement( + requirements.VersionRequirement( name = 'pslist', - plugin = pslist.PsList, + component = pslist.PsList, version = (2, 0, 0) ), ] diff --git a/test/__init__.py b/test/__init__.py index 2b59ef5ca..b2cfa3139 100644 --- a/test/__init__.py +++ b/test/__init__.py @@ -1,4 +1,8 @@ from enum import Enum +from pathlib import Path + +TESTS_ROOT_DIR = Path(__file__).parent +WINDOWS_TESTS_DATA_DIR = TESTS_ROOT_DIR / "plugins" / "windows" / "test_data" class Sample: @@ -9,6 +13,8 @@ class Sample: class WindowsSamples(Enum): WINDOWSXP_GENERIC = Sample("./test_images/win-xp-laptop-2005-06-25.img") """WindowsXP sample from early Volatility training.""" + WINDOWS10_GENERIC = Sample("./test_images/win-10_19041-2025_03.dmp") + """Windows10 CrashDump sample.""" class LinuxSamples(Enum): diff --git a/test/plugins/linux/linux.py b/test/plugins/linux/linux.py index 8ec485735..e39c1d15d 100644 --- a/test/plugins/linux/linux.py +++ b/test/plugins/linux/linux.py @@ -8,7 +8,7 @@ from test import test_volatility, LinuxSamples class TestLinuxVolshell: def test_linux_volshell(self, image, volatility, python): out = test_volatility.basic_volshell_test( - image, volatility, python, globalargs=("-l",) + image, volatility, python, volshellargs=("-l",) ) assert out.count(b" 100 diff --git a/test/plugins/windows/test_data/windows.driverirp.DriverIrp.json b/test/plugins/windows/test_data/windows.driverirp.DriverIrp.json new file mode 100644 index 000000000..f026e54a2 --- /dev/null +++ b/test/plugins/windows/test_data/windows.driverirp.DriverIrp.json @@ -0,0 +1,32 @@ +{ + "GENERIC": [ + "IRP_MJ_CREATE", + "IRP_MJ_CREATE_NAMED_PIPE", + "IRP_MJ_CLOSE", + "IRP_MJ_READ", + "IRP_MJ_WRITE", + "IRP_MJ_QUERY_INFORMATION", + "IRP_MJ_SET_INFORMATION", + "IRP_MJ_QUERY_EA", + "IRP_MJ_SET_EA", + "IRP_MJ_FLUSH_BUFFERS", + "IRP_MJ_QUERY_VOLUME_INFORMATION", + "IRP_MJ_SET_VOLUME_INFORMATION", + "IRP_MJ_DIRECTORY_CONTROL", + "IRP_MJ_FILE_SYSTEM_CONTROL", + "IRP_MJ_DEVICE_CONTROL", + "IRP_MJ_INTERNAL_DEVICE_CONTROL", + "IRP_MJ_SHUTDOWN", + "IRP_MJ_LOCK_CONTROL", + "IRP_MJ_CLEANUP", + "IRP_MJ_CREATE_MAILSLOT", + "IRP_MJ_QUERY_SECURITY", + "IRP_MJ_SET_SECURITY", + "IRP_MJ_POWER", + "IRP_MJ_SYSTEM_CONTROL", + "IRP_MJ_DEVICE_CHANGE", + "IRP_MJ_QUERY_QUOTA", + "IRP_MJ_SET_QUOTA", + "IRP_MJ_PNP" + ] +} \ No newline at end of file diff --git a/test/plugins/windows/test_data/windows.info.Info.json b/test/plugins/windows/test_data/windows.info.Info.json new file mode 100644 index 000000000..ef350389f --- /dev/null +++ b/test/plugins/windows/test_data/windows.info.Info.json @@ -0,0 +1,96 @@ +{ + "WINDOWS10_GENERIC": [ + { + "Value": "0xf8043601f000", + "Variable": "Kernel Base" + }, + { + "Value": "0x6d4000", + "Variable": "DTB" + }, + { + "Value": "True", + "Variable": "Is64Bit" + }, + { + "Value": "False", + "Variable": "IsPAE" + }, + { + "Value": "0 WindowsIntel32e", + "Variable": "layer_name" + }, + { + "Value": "1 WindowsCrashDump64Layer", + "Variable": "memory_layer" + }, + { + "Value": "2 FileLayer", + "Variable": "base_layer" + }, + { + "Value": "0xf80436c1fb20", + "Variable": "KdDebuggerDataBlock" + }, + { + "Value": "19041.1.amd64fre.vb_release.1912", + "Variable": "NTBuildLab" + }, + { + "Value": "0", + "Variable": "CSDVersion" + }, + { + "Value": "0xf80436c2e420", + "Variable": "KdVersionBlock" + }, + { + "Value": "15.19041", + "Variable": "Major/Minor" + }, + { + "Value": "34404", + "Variable": "MachineType" + }, + { + "Value": "1", + "Variable": "KeNumberProcessors" + }, + { + "Value": "2025-03-06 17:59:20+00:00", + "Variable": "SystemTime" + }, + { + "Value": "C:\\Windows", + "Variable": "NtSystemRoot" + }, + { + "Value": "NtProductWinNt", + "Variable": "NtProductType" + }, + { + "Value": "10", + "Variable": "NtMajorVersion" + }, + { + "Value": "0", + "Variable": "NtMinorVersion" + }, + { + "Value": "10", + "Variable": "PE MajorOperatingSystemVersion" + }, + { + "Value": "0", + "Variable": "PE MinorOperatingSystemVersion" + }, + { + "Value": "34404", + "Variable": "PE Machine" + }, + { + "Value": "Tue Sep 26 06:53:33 2023", + "Variable": "PE TimeDateStamp" + } + ] +} \ No newline at end of file diff --git a/test/plugins/windows/test_data/windows.pstree.PsTree.json b/test/plugins/windows/test_data/windows.pstree.PsTree.json new file mode 100644 index 000000000..139931730 --- /dev/null +++ b/test/plugins/windows/test_data/windows.pstree.PsTree.json @@ -0,0 +1,375 @@ +{ + "WINDOWS10_GENERIC": { + "Audit": "\\Device\\HarddiskVolume4\\Windows\\System32\\winlogon.exe", + "Cmd": null, + "CreateTime": "2025-03-06T17:49:34+00:00", + "ExitTime": null, + "Handles": null, + "ImageFileName": "winlogon.exe", + "Offset(V)": 145201769754752, + "PID": 3616, + "PPID": 3568, + "Path": null, + "SessionId": 2, + "Threads": 3, + "Wow64": false, + "__children": [ + { + "Audit": "\\Device\\HarddiskVolume4\\Windows\\System32\\userinit.exe", + "Cmd": null, + "CreateTime": "2025-03-06T17:50:15+00:00", + "ExitTime": "2025-03-06T17:50:32+00:00", + "Handles": null, + "ImageFileName": "userinit.exe", + "Offset(V)": 145201786712256, + "PID": 4832, + "PPID": 3616, + "Path": null, + "SessionId": 2, + "Threads": 0, + "Wow64": false, + "__children": [ + { + "Audit": "\\Device\\HarddiskVolume4\\Windows\\explorer.exe", + "Cmd": "C:\\Windows\\Explorer.EXE", + "CreateTime": "2025-03-06T17:50:17+00:00", + "ExitTime": null, + "Handles": null, + "ImageFileName": "explorer.exe", + "Offset(V)": 145201787191488, + "PID": 4912, + "PPID": 4832, + "Path": "C:\\Windows\\Explorer.EXE", + "SessionId": 2, + "Threads": 57, + "Wow64": false, + "__children": [ + { + "Audit": "\\Device\\HarddiskVolume4\\Windows\\System32\\SecurityHealthSystray.exe", + "Cmd": "\"C:\\Windows\\System32\\SecurityHealthSystray.exe\" ", + "CreateTime": "2025-03-06T17:51:05+00:00", + "ExitTime": null, + "Handles": null, + "ImageFileName": "SecurityHealth", + "Offset(V)": 145201826054336, + "PID": 1860, + "PPID": 4912, + "Path": "C:\\Windows\\System32\\SecurityHealthSystray.exe", + "SessionId": 2, + "Threads": 2, + "Wow64": false, + "__children": [] + }, + { + "Audit": "\\Device\\HarddiskVolume4\\Program Files (x86)\\Microsoft\\Edge\\Application\\msedge.exe", + "Cmd": "\"C:\\Program Files (x86)\\Microsoft\\Edge\\Application\\msedge.exe\" --no-startup-window --win-session-start", + "CreateTime": "2025-03-06T17:51:05+00:00", + "ExitTime": null, + "Handles": null, + "ImageFileName": "msedge.exe", + "Offset(V)": 145201826906304, + "PID": 3952, + "PPID": 4912, + "Path": "C:\\Program Files (x86)\\Microsoft\\Edge\\Application\\msedge.exe", + "SessionId": 2, + "Threads": 61, + "Wow64": false, + "__children": [ + { + "Audit": "\\Device\\HarddiskVolume4\\Program Files (x86)\\Microsoft\\Edge\\Application\\msedge.exe", + "Cmd": "\"C:\\Program Files (x86)\\Microsoft\\Edge\\Application\\msedge.exe\" --type=renderer --string-annotations --instant-process --pdf-upsell-enabled --video-capture-use-gpu-memory-buffer --lang=en-US --js-flags=--ms-user-locale= --device-scale-factor=1 --num-raster-threads=1 --renderer-client-id=21 --time-ticks-at-unix-epoch=-1741283277737123 --launch-time-ticks=556708795 --always-read-main-dll --field-trial-handle=3928,i,15721868202469256575,6833569417170289141,262144 --variations-seed-version --mojo-platform-channel-handle=3996 /prefetch:1", + "CreateTime": "2025-03-06T17:57:14+00:00", + "ExitTime": null, + "Handles": null, + "ImageFileName": "msedge.exe", + "Offset(V)": 145201787768960, + "PID": 5348, + "PPID": 3952, + "Path": "C:\\Program Files (x86)\\Microsoft\\Edge\\Application\\msedge.exe", + "SessionId": 2, + "Threads": 19, + "Wow64": false, + "__children": [] + }, + { + "Audit": "\\Device\\HarddiskVolume4\\Program Files (x86)\\Microsoft\\Edge\\Application\\msedge.exe", + "Cmd": "\"C:\\Program Files (x86)\\Microsoft\\Edge\\Application\\msedge.exe\" --type=utility --utility-sub-type=edge_xpay_wallet.mojom.EdgeXPayWalletService --lang=en-US --service-sandbox-type=utility --string-annotations --always-read-main-dll --field-trial-handle=6740,i,15721868202469256575,6833569417170289141,262144 --variations-seed-version --mojo-platform-channel-handle=6732 /prefetch:8", + "CreateTime": "2025-03-06T17:57:52+00:00", + "ExitTime": null, + "Handles": null, + "ImageFileName": "msedge.exe", + "Offset(V)": 145201832050880, + "PID": 3876, + "PPID": 3952, + "Path": "C:\\Program Files (x86)\\Microsoft\\Edge\\Application\\msedge.exe", + "SessionId": 2, + "Threads": 8, + "Wow64": false, + "__children": [] + }, + { + "Audit": "\\Device\\HarddiskVolume4\\Program Files (x86)\\Microsoft\\Edge\\Application\\msedge.exe", + "Cmd": "\"C:\\Program Files (x86)\\Microsoft\\Edge\\Application\\msedge.exe\" --type=utility --utility-sub-type=edge_search_indexer.mojom.SearchIndexerInterfaceBroker --lang=en-US --service-sandbox-type=search_indexer --message-loop-type-ui --string-annotations --always-read-main-dll --field-trial-handle=7016,i,15721868202469256575,6833569417170289141,262144 --variations-seed-version --mojo-platform-channel-handle=7128 /prefetch:8", + "CreateTime": "2025-03-06T17:57:59+00:00", + "ExitTime": null, + "Handles": null, + "ImageFileName": "msedge.exe", + "Offset(V)": 145201827619008, + "PID": 5604, + "PPID": 3952, + "Path": "C:\\Program Files (x86)\\Microsoft\\Edge\\Application\\msedge.exe", + "SessionId": 2, + "Threads": 14, + "Wow64": false, + "__children": [] + }, + { + "Audit": "\\Device\\HarddiskVolume4\\Program Files (x86)\\Microsoft\\Edge\\Application\\msedge.exe", + "Cmd": "\"C:\\Program Files (x86)\\Microsoft\\Edge\\Application\\msedge.exe\" --type=utility --utility-sub-type=entity_extraction_service.mojom.Extractor --lang=en-US --service-sandbox-type=entity_extraction --onnx-enabled-for-ee --string-annotations --always-read-main-dll --field-trial-handle=5520,i,15721868202469256575,6833569417170289141,262144 --variations-seed-version --mojo-platform-channel-handle=5608 /prefetch:8", + "CreateTime": "2025-03-06T17:57:16+00:00", + "ExitTime": null, + "Handles": null, + "ImageFileName": "msedge.exe", + "Offset(V)": 145201827774656, + "PID": 1000, + "PPID": 3952, + "Path": "C:\\Program Files (x86)\\Microsoft\\Edge\\Application\\msedge.exe", + "SessionId": 2, + "Threads": 9, + "Wow64": false, + "__children": [] + }, + { + "Audit": "\\Device\\HarddiskVolume4\\Program Files (x86)\\Microsoft\\Edge\\Application\\msedge.exe", + "Cmd": "\"C:\\Program Files (x86)\\Microsoft\\Edge\\Application\\msedge.exe\" --type=renderer --string-annotations --pdf-upsell-enabled --disable-gpu-compositing --video-capture-use-gpu-memory-buffer --lang=en-US --js-flags=--ms-user-locale= --device-scale-factor=1 --num-raster-threads=1 --renderer-client-id=31 --time-ticks-at-unix-epoch=-1741283277737123 --launch-time-ticks=596993509 --always-read-main-dll --field-trial-handle=6904,i,15721868202469256575,6833569417170289141,262144 --variations-seed-version --mojo-platform-channel-handle=7092 /prefetch:1", + "CreateTime": "2025-03-06T17:57:54+00:00", + "ExitTime": null, + "Handles": null, + "ImageFileName": "msedge.exe", + "Offset(V)": 145201839592000, + "PID": 7080, + "PPID": 3952, + "Path": "C:\\Program Files (x86)\\Microsoft\\Edge\\Application\\msedge.exe", + "SessionId": 2, + "Threads": 15, + "Wow64": false, + "__children": [] + }, + { + "Audit": "\\Device\\HarddiskVolume4\\Program Files (x86)\\Microsoft\\Edge\\Application\\msedge.exe", + "Cmd": "\"C:\\Program Files (x86)\\Microsoft\\Edge\\Application\\msedge.exe\" --type=renderer --string-annotations --pdf-upsell-enabled --disable-gpu-compositing --video-capture-use-gpu-memory-buffer --lang=en-US --js-flags=--ms-user-locale= --device-scale-factor=1 --num-raster-threads=1 --renderer-client-id=35 --time-ticks-at-unix-epoch=-1741283277737123 --launch-time-ticks=625057436 --always-read-main-dll --field-trial-handle=5592,i,15721868202469256575,6833569417170289141,262144 --variations-seed-version --mojo-platform-channel-handle=5688 /prefetch:1", + "CreateTime": "2025-03-06T17:58:23+00:00", + "ExitTime": null, + "Handles": null, + "ImageFileName": "msedge.exe", + "Offset(V)": 145201828201216, + "PID": 5132, + "PPID": 3952, + "Path": "C:\\Program Files (x86)\\Microsoft\\Edge\\Application\\msedge.exe", + "SessionId": 2, + "Threads": 17, + "Wow64": false, + "__children": [] + }, + { + "Audit": "\\Device\\HarddiskVolume4\\Program Files (x86)\\Microsoft\\Edge\\Application\\msedge.exe", + "Cmd": "\"C:\\Program Files (x86)\\Microsoft\\Edge\\Application\\msedge.exe\" --type=renderer --string-annotations --pdf-upsell-enabled --disable-gpu-compositing --video-capture-use-gpu-memory-buffer --lang=en-US --js-flags=--ms-user-locale= --device-scale-factor=1 --num-raster-threads=1 --renderer-client-id=36 --time-ticks-at-unix-epoch=-1741283277737123 --launch-time-ticks=625148972 --always-read-main-dll --field-trial-handle=7112,i,15721868202469256575,6833569417170289141,262144 --variations-seed-version --mojo-platform-channel-handle=6396 /prefetch:1", + "CreateTime": "2025-03-06T17:58:23+00:00", + "ExitTime": null, + "Handles": null, + "ImageFileName": "msedge.exe", + "Offset(V)": 145201839919232, + "PID": 5388, + "PPID": 3952, + "Path": "C:\\Program Files (x86)\\Microsoft\\Edge\\Application\\msedge.exe", + "SessionId": 2, + "Threads": 15, + "Wow64": false, + "__children": [] + }, + { + "Audit": "\\Device\\HarddiskVolume4\\Program Files (x86)\\Microsoft\\Edge\\Application\\msedge.exe", + "Cmd": "\"C:\\Program Files (x86)\\Microsoft\\Edge\\Application\\msedge.exe\" --type=utility --utility-sub-type=network.mojom.NetworkService --lang=en-US --service-sandbox-type=none --string-annotations --always-read-main-dll --field-trial-handle=2180,i,15721868202469256575,6833569417170289141,262144 --variations-seed-version --mojo-platform-channel-handle=2512 /prefetch:3", + "CreateTime": "2025-03-06T17:51:14+00:00", + "ExitTime": null, + "Handles": null, + "ImageFileName": "msedge.exe", + "Offset(V)": 145201835704512, + "PID": 6448, + "PPID": 3952, + "Path": "C:\\Program Files (x86)\\Microsoft\\Edge\\Application\\msedge.exe", + "SessionId": 2, + "Threads": 16, + "Wow64": false, + "__children": [] + }, + { + "Audit": "\\Device\\HarddiskVolume4\\Program Files (x86)\\Microsoft\\Edge\\Application\\msedge.exe", + "Cmd": null, + "CreateTime": "2025-03-06T17:51:16+00:00", + "ExitTime": null, + "Handles": null, + "ImageFileName": "msedge.exe", + "Offset(V)": 145201668321408, + "PID": 6672, + "PPID": 3952, + "Path": null, + "SessionId": 2, + "Threads": 9, + "Wow64": false, + "__children": [] + }, + { + "Audit": "\\Device\\HarddiskVolume4\\Program Files (x86)\\Microsoft\\Edge\\Application\\msedge.exe", + "Cmd": "\"C:\\Program Files (x86)\\Microsoft\\Edge\\Application\\msedge.exe\" --type=utility --utility-sub-type=price_comparison_service.mojom.DataProcessor --lang=en-US --service-sandbox-type=entity_extraction --string-annotations --always-read-main-dll --field-trial-handle=6188,i,15721868202469256575,6833569417170289141,262144 --variations-seed-version --mojo-platform-channel-handle=6580 /prefetch:8", + "CreateTime": "2025-03-06T17:58:44+00:00", + "ExitTime": null, + "Handles": null, + "ImageFileName": "msedge.exe", + "Offset(V)": 145201830273728, + "PID": 6064, + "PPID": 3952, + "Path": "C:\\Program Files (x86)\\Microsoft\\Edge\\Application\\msedge.exe", + "SessionId": 2, + "Threads": 9, + "Wow64": false, + "__children": [] + }, + { + "Audit": "\\Device\\HarddiskVolume4\\Program Files (x86)\\Microsoft\\Edge\\Application\\msedge.exe", + "Cmd": "\"C:\\Program Files (x86)\\Microsoft\\Edge\\Application\\msedge.exe\" --type=gpu-process --string-annotations --gpu-preferences=UAAAAAAAAADgAAAEAAAAAAAAAAAAAAAAAABgAAEAAAAAAAAAAAAAAAAAAAACAAAAAAAAAAAAAAAAAAAAAAAAABAAAAAAAAAAEAAAAAAAAAAIAAAAAAAAAAgAAAAAAAAA --always-read-main-dll --field-trial-handle=2472,i,15721868202469256575,6833569417170289141,262144 --variations-seed-version --mojo-platform-channel-handle=2468 /prefetch:2", + "CreateTime": "2025-03-06T17:51:14+00:00", + "ExitTime": null, + "Handles": null, + "ImageFileName": "msedge.exe", + "Offset(V)": 145201834049728, + "PID": 6456, + "PPID": 3952, + "Path": "C:\\Program Files (x86)\\Microsoft\\Edge\\Application\\msedge.exe", + "SessionId": 2, + "Threads": 15, + "Wow64": false, + "__children": [] + }, + { + "Audit": "\\Device\\HarddiskVolume4\\Program Files (x86)\\Microsoft\\Edge\\Application\\msedge.exe", + "Cmd": null, + "CreateTime": "2025-03-06T17:51:11+00:00", + "ExitTime": null, + "Handles": null, + "ImageFileName": "msedge.exe", + "Offset(V)": 145201786340096, + "PID": 6204, + "PPID": 3952, + "Path": null, + "SessionId": 2, + "Threads": 8, + "Wow64": false, + "__children": [] + } + ] + }, + { + "Audit": "\\Device\\HarddiskVolume4\\Users\\generic-user\\AppData\\Local\\Microsoft\\OneDrive\\OneDrive.exe", + "Cmd": "\"C:\\Users\\generic-user\\AppData\\Local\\Microsoft\\OneDrive\\OneDrive.exe\" /background", + "CreateTime": "2025-03-06T17:51:11+00:00", + "ExitTime": null, + "Handles": null, + "ImageFileName": "OneDrive.exe", + "Offset(V)": 145201834340544, + "PID": 6160, + "PPID": 4912, + "Path": "C:\\Users\\generic-user\\AppData\\Local\\Microsoft\\OneDrive\\OneDrive.exe", + "SessionId": 2, + "Threads": 22, + "Wow64": true, + "__children": [] + }, + { + "Audit": "\\Device\\HarddiskVolume4\\Windows\\System32\\cmd.exe", + "Cmd": "\"C:\\Windows\\system32\\cmd.exe\" ", + "CreateTime": "2025-03-06T17:51:44+00:00", + "ExitTime": null, + "Handles": null, + "ImageFileName": "cmd.exe", + "Offset(V)": 145201834332288, + "PID": 784, + "PPID": 4912, + "Path": "C:\\Windows\\system32\\cmd.exe", + "SessionId": 2, + "Threads": 1, + "Wow64": false, + "__children": [ + { + "Audit": "\\Device\\HarddiskVolume4\\Windows\\System32\\conhost.exe", + "Cmd": null, + "CreateTime": "2025-03-06T17:51:49+00:00", + "ExitTime": null, + "Handles": null, + "ImageFileName": "conhost.exe", + "Offset(V)": 145201834446976, + "PID": 3896, + "PPID": 784, + "Path": null, + "SessionId": 2, + "Threads": 3, + "Wow64": false, + "__children": [] + } + ] + }, + { + "Audit": "\\Device\\HarddiskVolume4\\Windows\\System32\\notepad.exe", + "Cmd": "\"C:\\Windows\\system32\\notepad.exe\" ", + "CreateTime": "2025-03-06T17:52:33+00:00", + "ExitTime": null, + "Handles": null, + "ImageFileName": "notepad.exe", + "Offset(V)": 145201839497344, + "PID": 2968, + "PPID": 4912, + "Path": "C:\\Windows\\system32\\notepad.exe", + "SessionId": 2, + "Threads": 4, + "Wow64": false, + "__children": [] + } + ] + } + ] + }, + { + "Audit": "\\Device\\HarddiskVolume4\\Windows\\System32\\fontdrvhost.exe", + "Cmd": null, + "CreateTime": "2025-03-06T17:49:42+00:00", + "ExitTime": null, + "Handles": null, + "ImageFileName": "fontdrvhost.ex", + "Offset(V)": 145201770697088, + "PID": 3812, + "PPID": 3616, + "Path": null, + "SessionId": 2, + "Threads": 5, + "Wow64": false, + "__children": [] + }, + { + "Audit": "\\Device\\HarddiskVolume4\\Windows\\System32\\dwm.exe", + "Cmd": "\"dwm.exe\"", + "CreateTime": "2025-03-06T17:49:42+00:00", + "ExitTime": null, + "Handles": null, + "ImageFileName": "dwm.exe", + "Offset(V)": 145201770352768, + "PID": 3860, + "PPID": 3616, + "Path": "C:\\Windows\\system32\\dwm.exe", + "SessionId": 2, + "Threads": 16, + "Wow64": false, + "__children": [] + } + ] + } +} diff --git a/test/plugins/windows/test_data/windows.registry.hivescan.HiveScan.json b/test/plugins/windows/test_data/windows.registry.hivescan.HiveScan.json new file mode 100644 index 000000000..67d36af04 --- /dev/null +++ b/test/plugins/windows/test_data/windows.registry.hivescan.HiveScan.json @@ -0,0 +1,100 @@ +{ + "WINDOWS10_GENERIC": [ + { + "Offset": 213323072327680, + "__children": [] + }, + { + "Offset": 213323069669376, + "__children": [] + }, + { + "Offset": 213323011252224, + "__children": [] + }, + { + "Offset": 213322964488192, + "__children": [] + }, + { + "Offset": 213323011387392, + "__children": [] + }, + { + "Offset": 213322962362368, + "__children": [] + }, + { + "Offset": 213323041546240, + "__children": [] + }, + { + "Offset": 213323013046272, + "__children": [] + }, + { + "Offset": 213323061571584, + "__children": [] + }, + { + "Offset": 213323079548928, + "__children": [] + }, + { + "Offset": 213323067502592, + "__children": [] + }, + { + "Offset": 213323081900032, + "__children": [] + }, + { + "Offset": 213322954362880, + "__children": [] + }, + { + "Offset": 213322954346496, + "__children": [] + }, + { + "Offset": 213323014123520, + "__children": [] + }, + { + "Offset": 213323067707392, + "__children": [] + }, + { + "Offset": 213323070193664, + "__children": [] + }, + { + "Offset": 213323078791168, + "__children": [] + }, + { + "Offset": 213323069112320, + "__children": [] + }, + { + "Offset": 213323047776256, + "__children": [] + }, + { + "Offset": 213323013799936, + "__children": [] + }, + { + "Offset": 213322954985472, + "__children": [] + }, + { + "Offset": 213322954969088, + "__children": [] + }, + { + "Offset": 213323048636416, + "__children": [] + } + ] +} \ No newline at end of file diff --git a/test/plugins/windows/test_data/windows.registry.printkey.PrintKey.json b/test/plugins/windows/test_data/windows.registry.printkey.PrintKey.json new file mode 100644 index 000000000..ee069ec30 --- /dev/null +++ b/test/plugins/windows/test_data/windows.registry.printkey.PrintKey.json @@ -0,0 +1,61 @@ +{ + "WINDOWS10_GENERIC": [ + { + "Data": "", + "Hive Offset": 213322954346496, + "Key": "[NONAME]", + "Last Write Time": "2025-03-06T17:59:19+00:00", + "Name": "A", + "Type": "Key", + "Volatile": false + }, + { + "Data": "", + "Hive Offset": 213322954362880, + "Key": "\\REGISTRY\\MACHINE\\SYSTEM", + "Last Write Time": "2019-12-07T09:15:07+00:00", + "Name": "ControlSet001", + "Type": "Key", + "Volatile": false + }, + { + "Data": "", + "Hive Offset": 213322964488192, + "Key": "\\SystemRoot\\System32\\Config\\SOFTWARE", + "Last Write Time": "2025-03-06T17:38:01+00:00", + "Name": "Classes", + "Type": "Key", + "Volatile": false + }, + { + "Data": "", + "Hive Offset": 213323011252224, + "Key": "\\SystemRoot\\System32\\Config\\SAM", + "Last Write Time": "2025-01-31T13:01:49+00:00", + "Name": "SAM", + "Type": "Key", + "Volatile": false, + "__children": [] + }, + { + "Data": "", + "Hive Offset": 213323048636416, + "Key": "\\??\\C:\\Users\\generic-user\\ntuser.dat", + "Last Write Time": "2025-03-06T17:50:04+00:00", + "Name": "SOFTWARE", + "Type": "Key", + "Volatile": false, + "__children": [] + }, + { + "Data": "", + "Hive Offset": 213323047776256, + "Key": "\\??\\C:\\Users\\generic-user\\AppData\\Local\\Microsoft\\Windows\\UsrClass.dat", + "Last Write Time": "2025-03-05T18:36:09+00:00", + "Name": ".eip", + "Type": "Key", + "Volatile": false, + "__children": [] + } + ] +} \ No newline at end of file diff --git a/test/plugins/windows/test_data/windows.registry.userassist.UserAssist.json b/test/plugins/windows/test_data/windows.registry.userassist.UserAssist.json new file mode 100644 index 000000000..6ae740822 --- /dev/null +++ b/test/plugins/windows/test_data/windows.registry.userassist.UserAssist.json @@ -0,0 +1,123 @@ +{ + "WINDOWS10_GENERIC": { + "Count": null, + "Focus Count": null, + "Hive Name": "\\??\\C:\\Users\\generic-user\\ntuser.dat", + "Hive Offset": 213323048636416, + "ID": null, + "Last Updated": null, + "Last Write Time": "2025-03-06T17:57:09+00:00", + "Name": null, + "Path": "ntuser.dat\\SOFTWARE\\Microsoft\\Windows\\CurrentVersion\\Explorer\\UserAssist\\{F4E57C4B-2036-45F0-A9AB-443BCFE33D9F}\\Count", + "Raw Data": "", + "Time Focused": null, + "Type": "Key", + "__children": [ + { + "Count": 7, + "Focus Count": 0, + "Hive Name": "\\??\\C:\\Users\\generic-user\\ntuser.dat", + "Hive Offset": 213323048636416, + "ID": null, + "Last Updated": "2025-03-05T18:34:13+00:00", + "Last Write Time": "2025-03-06T17:57:09+00:00", + "Name": "%ALLUSERSPROFILE%\\Microsoft\\Windows\\Start Menu\\Programs\\Accessories\\Paint.lnk", + "Path": "ntuser.dat\\SOFTWARE\\Microsoft\\Windows\\CurrentVersion\\Explorer\\UserAssist\\{F4E57C4B-2036-45F0-A9AB-443BCFE33D9F}\\Count", + "Raw Data": "\"\n00 00 00 00 07 00 00 00 00 00 00 00 07 00 00 00 ................\n00 00 80 bf 00 00 80 bf 00 00 80 bf 00 00 80 bf ................\n00 00 80 bf 00 00 80 bf 00 00 80 bf 00 00 80 bf ................\n00 00 80 bf 00 00 80 bf ff ff ff ff 90 86 6b 31 ..............k1\nfd 8d db 01 00 00 00 00 ........ \"", + "Time Focused": "0:00:00.507000", + "Type": "Value", + "__children": [] + }, + { + "Count": 1, + "Focus Count": 0, + "Hive Name": "\\??\\C:\\Users\\generic-user\\ntuser.dat", + "Hive Offset": 213323048636416, + "ID": null, + "Last Updated": "2025-03-06T12:46:34+00:00", + "Last Write Time": "2025-03-06T17:57:09+00:00", + "Name": "%ALLUSERSPROFILE%\\Microsoft\\Windows\\Start Menu\\Programs\\Administrative Tools\\Registry Editor.lnk", + "Path": "ntuser.dat\\SOFTWARE\\Microsoft\\Windows\\CurrentVersion\\Explorer\\UserAssist\\{F4E57C4B-2036-45F0-A9AB-443BCFE33D9F}\\Count", + "Raw Data": "\"\n00 00 00 00 01 00 00 00 00 00 00 00 01 00 00 00 ................\n00 00 80 bf 00 00 80 bf 00 00 80 bf 00 00 80 bf ................\n00 00 80 bf 00 00 80 bf 00 00 80 bf 00 00 80 bf ................\n00 00 80 bf 00 00 80 bf ff ff ff ff f0 82 cf ca ................\n95 8e db 01 00 00 00 00 ........ \"", + "Time Focused": "0:00:00.501000", + "Type": "Value", + "__children": [] + }, + { + "Count": 4, + "Focus Count": 0, + "Hive Name": "\\??\\C:\\Users\\generic-user\\ntuser.dat", + "Hive Offset": 213323048636416, + "ID": null, + "Last Updated": "2025-03-06T17:36:33+00:00", + "Last Write Time": "2025-03-06T17:57:09+00:00", + "Name": "%APPDATA%\\Microsoft\\Windows\\Start Menu\\Programs\\Windows PowerShell\\Windows PowerShell.lnk", + "Path": "ntuser.dat\\SOFTWARE\\Microsoft\\Windows\\CurrentVersion\\Explorer\\UserAssist\\{F4E57C4B-2036-45F0-A9AB-443BCFE33D9F}\\Count", + "Raw Data": "\"\n00 00 00 00 04 00 00 00 00 00 00 00 04 00 00 00 ................\n00 00 80 bf 00 00 80 bf 00 00 80 bf 00 00 80 bf ................\n00 00 80 bf 00 00 80 bf 00 00 80 bf 00 00 80 bf ................\n00 00 80 bf 00 00 80 bf ff ff ff ff 10 67 cf 4d .............g.M\nbe 8e db 01 00 00 00 00 ........ \"", + "Time Focused": "0:00:00.504000", + "Type": "Value", + "__children": [] + }, + { + "Count": 1, + "Focus Count": 0, + "Hive Name": "\\??\\C:\\Users\\generic-user\\ntuser.dat", + "Hive Offset": 213323048636416, + "ID": null, + "Last Updated": "2025-03-06T17:51:44+00:00", + "Last Write Time": "2025-03-06T17:57:09+00:00", + "Name": "%APPDATA%\\Microsoft\\Windows\\Start Menu\\Programs\\System Tools\\Command Prompt.lnk", + "Path": "ntuser.dat\\SOFTWARE\\Microsoft\\Windows\\CurrentVersion\\Explorer\\UserAssist\\{F4E57C4B-2036-45F0-A9AB-443BCFE33D9F}\\Count", + "Raw Data": "\"\n00 00 00 00 01 00 00 00 00 00 00 00 01 00 00 00 ................\n00 00 80 bf 00 00 80 bf 00 00 80 bf 00 00 80 bf ................\n00 00 80 bf 00 00 80 bf 00 00 80 bf 00 00 80 bf ................\n00 00 80 bf 00 00 80 bf ff ff ff ff d0 99 66 6c ..............fl\nc0 8e db 01 00 00 00 00 ........ \"", + "Time Focused": "0:00:00.501000", + "Type": "Value", + "__children": [] + }, + { + "Count": 1, + "Focus Count": 0, + "Hive Name": "\\??\\C:\\Users\\generic-user\\ntuser.dat", + "Hive Offset": 213323048636416, + "ID": null, + "Last Updated": "2025-03-06T17:52:33+00:00", + "Last Write Time": "2025-03-06T17:57:09+00:00", + "Name": "%ALLUSERSPROFILE%\\Microsoft\\Windows\\Start Menu\\Programs\\Accessories\\Notepad.lnk", + "Path": "ntuser.dat\\SOFTWARE\\Microsoft\\Windows\\CurrentVersion\\Explorer\\UserAssist\\{F4E57C4B-2036-45F0-A9AB-443BCFE33D9F}\\Count", + "Raw Data": "\"\n00 00 00 00 01 00 00 00 00 00 00 00 01 00 00 00 ................\n00 00 80 bf 00 00 80 bf 00 00 80 bf 00 00 80 bf ................\n00 00 80 bf 00 00 80 bf 00 00 80 bf 00 00 80 bf ................\n00 00 80 bf 00 00 80 bf ff ff ff ff 00 62 ba 89 .............b..\nc0 8e db 01 00 00 00 00 ........ \"", + "Time Focused": "0:00:00.501000", + "Type": "Value", + "__children": [] + }, + { + "Count": 2, + "Focus Count": 0, + "Hive Name": "\\??\\C:\\Users\\generic-user\\ntuser.dat", + "Hive Offset": 213323048636416, + "ID": null, + "Last Updated": "2025-03-06T17:56:50+00:00", + "Last Write Time": "2025-03-06T17:57:09+00:00", + "Name": "%ALLUSERSPROFILE%\\Microsoft\\Windows\\Start Menu\\Programs\\Administrative Tools\\Task Scheduler.lnk", + "Path": "ntuser.dat\\SOFTWARE\\Microsoft\\Windows\\CurrentVersion\\Explorer\\UserAssist\\{F4E57C4B-2036-45F0-A9AB-443BCFE33D9F}\\Count", + "Raw Data": "\"\n00 00 00 00 02 00 00 00 00 00 00 00 02 00 00 00 ................\n00 00 80 bf 00 00 80 bf 00 00 80 bf 00 00 80 bf ................\n00 00 80 bf 00 00 80 bf 00 00 80 bf 00 00 80 bf ................\n00 00 80 bf 00 00 80 bf ff ff ff ff b0 24 49 23 .............$I#\nc1 8e db 01 00 00 00 00 ........ \"", + "Time Focused": "0:00:00.502000", + "Type": "Value", + "__children": [] + }, + { + "Count": 1, + "Focus Count": 0, + "Hive Name": "\\??\\C:\\Users\\generic-user\\ntuser.dat", + "Hive Offset": 213323048636416, + "ID": null, + "Last Updated": "2025-03-06T17:57:09+00:00", + "Last Write Time": "2025-03-06T17:57:09+00:00", + "Name": "%ALLUSERSPROFILE%\\Microsoft\\Windows\\Start Menu\\Programs\\Microsoft Edge.lnk", + "Path": "ntuser.dat\\SOFTWARE\\Microsoft\\Windows\\CurrentVersion\\Explorer\\UserAssist\\{F4E57C4B-2036-45F0-A9AB-443BCFE33D9F}\\Count", + "Raw Data": "\"\n00 00 00 00 01 00 00 00 00 00 00 00 01 00 00 00 ................\n00 00 80 bf 00 00 80 bf 00 00 80 bf 00 00 80 bf ................\n00 00 80 bf 00 00 80 bf 00 00 80 bf 00 00 80 bf ................\n00 00 80 bf 00 00 80 bf ff ff ff ff 60 3d 89 2e ............`=..\nc1 8e db 01 00 00 00 00 ........ \"", + "Time Focused": "0:00:00.501000", + "Type": "Value", + "__children": [] + } + ] + } +} \ No newline at end of file diff --git a/test/plugins/windows/test_data/windows.sessions.Sessions.json b/test/plugins/windows/test_data/windows.sessions.Sessions.json new file mode 100644 index 000000000..30b202adb --- /dev/null +++ b/test/plugins/windows/test_data/windows.sessions.Sessions.json @@ -0,0 +1,36 @@ +{ + "WINDOWS10_GENERIC": [ + { + "Create Time": "2025-03-06T17:48:02+00:00", + "Process": "System", + "Process ID": 4 + }, + { + "Create Time": "2025-03-06T17:48:37+00:00", + "Process": "lsass.exe", + "Process ID": 696, + "Session ID": 0, + "Session Type": null, + "User Name": "/SYSTEM", + "__children": [] + }, + { + "Create Time": "2025-03-06T17:49:03+00:00", + "Process": "MsMpEng.exe", + "Process ID": 1956, + "Session ID": 0, + "Session Type": null, + "User Name": "WORKGROUP/Windows-generic$", + "__children": [] + }, + { + "Create Time": "2025-03-06T17:50:08+00:00", + "Process": "rdpclip.exe", + "Process ID": 4180, + "Session ID": 2, + "Session Type": null, + "User Name": "Windows-generic/generic-user", + "__children": [] + } + ] +} \ No newline at end of file diff --git a/test/plugins/windows/test_data/windows.shimcachemem.ShimcacheMem.json b/test/plugins/windows/test_data/windows.shimcachemem.ShimcacheMem.json new file mode 100644 index 000000000..b671f4c4b --- /dev/null +++ b/test/plugins/windows/test_data/windows.shimcachemem.ShimcacheMem.json @@ -0,0 +1,41 @@ +{ + "WINDOWS10_GENERIC": + [ + { + "Exec Flag": null, + "File Path": "C:\\Windows\\System32\\cmdl32.exe", + "File Size": null, + "Last Modified": "2019-12-07T09:09:07+00:00", + "Last Update": null, + "Order": 0, + "__children": [] + }, + { + "Exec Flag": null, + "File Path": "C:\\Windows\\System32\\cmdkey.exe", + "File Size": null, + "Last Modified": "2019-12-07T09:09:09+00:00", + "Last Update": null, + "Order": 1, + "__children": [] + }, + { + "Exec Flag": null, + "File Path": "C:\\Windows\\system32\\whoami.exe", + "File Size": null, + "Last Modified": "2019-12-07T09:09:51+00:00", + "Last Update": null, + "Order": 2, + "__children": [] + }, + { + "Exec Flag": null, + "File Path": null, + "File Size": null, + "Last Modified": "2023-12-13T16:43:28+00:00", + "Last Update": null, + "Order": 3, + "__children": [] + } + ] +} \ No newline at end of file diff --git a/test/plugins/windows/test_data/windows.timers.Timers.json b/test/plugins/windows/test_data/windows.timers.Timers.json new file mode 100644 index 000000000..88e8f339a --- /dev/null +++ b/test/plugins/windows/test_data/windows.timers.Timers.json @@ -0,0 +1,684 @@ +{ + "WINDOWSXP_GENERIC": [ + { + "DueTime": "0x00000001:0xb6912640", + "Module": "ntoskrnl", + "Offset": 2180960760, + "Period(ms)": 0, + "Routine": 2152618407, + "Signaled": "-", + "Symbol": "ExpTimerDpcRoutine", + "__children": [] + }, + { + "DueTime": "0x00000001:0xe2e5d9fc", + "Module": "ks", + "Offset": 2181100944, + "Period(ms)": 0, + "Routine": 4162614588, + "Signaled": "-", + "Symbol": null, + "__children": [] + }, + { + "DueTime": "0x80000001:0x838d66d0", + "Module": "ntoskrnl", + "Offset": 2180726816, + "Period(ms)": 0, + "Routine": 2152618407, + "Signaled": "-", + "Symbol": "ExpTimerDpcRoutine", + "__children": [] + }, + { + "DueTime": "0x00000001:0xbe54efd0", + "Module": "NDIS", + "Offset": 2182586488, + "Period(ms)": 60000, + "Routine": 4164630316, + "Signaled": "Yes", + "Symbol": null, + "__children": [] + }, + { + "DueTime": "0x80000000:0x6d915dc0", + "Module": "ntoskrnl", + "Offset": 2182771520, + "Period(ms)": 0, + "Routine": 2152618407, + "Signaled": "-", + "Symbol": "ExpTimerDpcRoutine", + "__children": [] + }, + { + "DueTime": "0x00000001:0xd5616870", + "Module": "HTTP", + "Offset": 4122586976, + "Period(ms)": 0, + "Routine": 4122527634, + "Signaled": "-", + "Symbol": null, + "__children": [] + }, + { + "DueTime": "0x00000001:0xaa92535f", + "Module": "USBPORT", + "Offset": 2179614512, + "Period(ms)": 0, + "Routine": 4163343596, + "Signaled": "-", + "Symbol": null, + "__children": [] + }, + { + "DueTime": "0x80000000:0x28ec7d80", + "Module": "ntoskrnl", + "Offset": 2167895896, + "Period(ms)": 0, + "Routine": 2152618407, + "Signaled": "-", + "Symbol": "ExpTimerDpcRoutine", + "__children": [] + }, + { + "DueTime": "0x00000001:0xd5f1ddd0", + "Module": "afd", + "Offset": 4289149728, + "Period(ms)": 0, + "Routine": 4146614848, + "Signaled": "-", + "Symbol": null, + "__children": [] + }, + { + "DueTime": "0x00000001:0xe64e9c50", + "Module": "ntoskrnl", + "Offset": 4289091536, + "Period(ms)": 0, + "Routine": 2152618407, + "Signaled": "-", + "Symbol": "ExpTimerDpcRoutine", + "__children": [] + }, + { + "DueTime": "0x00000002:0x10691f50", + "Module": "BATTC", + "Offset": 2182647568, + "Period(ms)": 0, + "Routine": 4170619626, + "Signaled": "-", + "Symbol": null, + "__children": [] + }, + { + "DueTime": "0x00000002:0x10691f50", + "Module": "BATTC", + "Offset": 2184852912, + "Period(ms)": 0, + "Routine": 4170619626, + "Signaled": "-", + "Symbol": null, + "__children": [] + }, + { + "DueTime": "0x00000001:0xe650bd40", + "Module": "ntoskrnl", + "Offset": 4289091352, + "Period(ms)": 0, + "Routine": 2152618407, + "Signaled": "-", + "Symbol": "ExpTimerDpcRoutine", + "__children": [] + }, + { + "DueTime": "0x00000001:0xaf39aa70", + "Module": "watchdog", + "Offset": 2182133296, + "Period(ms)": 10000, + "Routine": 4170290884, + "Signaled": "Yes", + "Symbol": "_imp__KdSave", + "__children": [] + }, + { + "DueTime": "0x00000001:0xaf39aa70", + "Module": "watchdog", + "Offset": 2181849992, + "Period(ms)": 10000, + "Routine": 4170290884, + "Signaled": "Yes", + "Symbol": "_imp__KdSave", + "__children": [] + }, + { + "DueTime": "0x00000008:0x61e17090", + "Module": "ntoskrnl", + "Offset": 2153125248, + "Period(ms)": 0, + "Routine": 2152824977, + "Signaled": "-", + "Symbol": "ExpTimeRefreshDpcRoutine", + "__children": [] + }, + { + "DueTime": "0x00000001:0xa9537f40", + "Module": "ntoskrnl", + "Offset": 2153083360, + "Period(ms)": 0, + "Routine": 2152814203, + "Signaled": "-", + "Symbol": "CmpLazyFlushDpcRoutine", + "__children": [] + }, + { + "DueTime": "0x00000001:0xc5addbe0", + "Module": "TDI", + "Offset": 4147138000, + "Period(ms)": 0, + "Routine": 4169831408, + "Signaled": "-", + "Symbol": null, + "__children": [] + }, + { + "DueTime": "0x00000001:0xaadb3be0", + "Module": "netbt", + "Offset": 2182155920, + "Period(ms)": 0, + "Routine": 4146697354, + "Signaled": "-", + "Symbol": null, + "__children": [] + }, + { + "DueTime": "0x00000001:0xaadb9040", + "Module": "TDI", + "Offset": 2182059040, + "Period(ms)": 0, + "Routine": 4169831408, + "Signaled": "-", + "Symbol": null, + "__children": [] + }, + { + "DueTime": "0x00000001:0xa9537f40", + "Module": "ntoskrnl", + "Offset": 2153083360, + "Period(ms)": 0, + "Routine": 2152814203, + "Signaled": "-", + "Symbol": "CmpLazyFlushDpcRoutine", + "__children": [] + }, + { + "DueTime": "0x80000000:0x19eae820", + "Module": "ntoskrnl", + "Offset": 2182169704, + "Period(ms)": 0, + "Routine": 2152618407, + "Signaled": "-", + "Symbol": "ExpTimerDpcRoutine", + "__children": [] + }, + { + "DueTime": "0x00000001:0xaa9b2a20", + "Module": "NDIS", + "Offset": 2179568032, + "Period(ms)": 0, + "Routine": 4164628447, + "Signaled": "-", + "Symbol": null, + "__children": [] + }, + { + "DueTime": "0x00000002:0x1a97a160", + "Module": "Ntfs", + "Offset": 4164841808, + "Period(ms)": 0, + "Routine": 4164732734, + "Signaled": "-", + "Symbol": null, + "__children": [] + }, + { + "DueTime": "0x80000000:0x2c5fb7e0", + "Module": "ntoskrnl", + "Offset": 2180369200, + "Period(ms)": 0, + "Routine": 2152618407, + "Signaled": "-", + "Symbol": "ExpTimerDpcRoutine", + "__children": [] + }, + { + "DueTime": "0x00000002:0x3dcf47a0", + "Module": "afd", + "Offset": 2183169664, + "Period(ms)": 0, + "Routine": 4146614848, + "Signaled": "-", + "Symbol": null, + "__children": [] + }, + { + "DueTime": "0x00000001:0xc4906e60", + "Module": "rdbss", + "Offset": 4146422176, + "Period(ms)": 0, + "Routine": 4146381701, + "Signaled": "-", + "Symbol": null, + "__children": [] + }, + { + "DueTime": "0x00000001:0xaa92535f", + "Module": "USBPORT", + "Offset": 2179614512, + "Period(ms)": 0, + "Routine": 4163343596, + "Signaled": "-", + "Symbol": null, + "__children": [] + }, + { + "DueTime": "0x00000001:0xaa8619e0", + "Module": "TDI", + "Offset": 2182064136, + "Period(ms)": 0, + "Routine": 4169831408, + "Signaled": "-", + "Symbol": null, + "__children": [] + }, + { + "DueTime": "0x00000001:0xc45ec240", + "Module": "tcpip", + "Offset": 4147157776, + "Period(ms)": 100, + "Routine": 4146861021, + "Signaled": "Yes", + "Symbol": null, + "__children": [] + }, + { + "DueTime": "0x80000000:0x35b72850", + "Module": "ntoskrnl", + "Offset": 2180437784, + "Period(ms)": 0, + "Routine": 2152618407, + "Signaled": "-", + "Symbol": "ExpTimerDpcRoutine", + "__children": [] + }, + { + "DueTime": "0x00000001:0xaad6fab0", + "Module": "NDIS", + "Offset": 2181321392, + "Period(ms)": 1000, + "Routine": 4164630316, + "Signaled": "Yes", + "Symbol": null, + "__children": [] + }, + { + "DueTime": "0x00000001:0xaadb9040", + "Module": "TDI", + "Offset": 2182059040, + "Period(ms)": 0, + "Routine": 4169831408, + "Signaled": "-", + "Symbol": null, + "__children": [] + }, + { + "DueTime": "0x00000001:0xa9537f40", + "Module": "ntoskrnl", + "Offset": 2153083360, + "Period(ms)": 0, + "Routine": 2152814203, + "Signaled": "-", + "Symbol": "CmpLazyFlushDpcRoutine", + "__children": [] + }, + { + "DueTime": "0x00000001:0xe2e5ee70", + "Module": "ipsec", + "Offset": 4147285920, + "Period(ms)": 60000, + "Routine": 4147221715, + "Signaled": "Yes", + "Symbol": null, + "__children": [] + }, + { + "DueTime": "0x00000001:0xe2e5ee70", + "Module": "ipsec", + "Offset": 4147284744, + "Period(ms)": 0, + "Routine": 4147221577, + "Signaled": "-", + "Symbol": null, + "__children": [] + }, + { + "DueTime": "0x00000001:0xe2e5d9fc", + "Module": "ks", + "Offset": 2181100944, + "Period(ms)": 0, + "Routine": 4162614588, + "Signaled": "-", + "Symbol": null, + "__children": [] + }, + { + "DueTime": "0x00000098:0xa67683e0", + "Module": "NDIS", + "Offset": 2183038120, + "Period(ms)": 0, + "Routine": 4164628447, + "Signaled": "-", + "Symbol": null, + "__children": [] + }, + { + "DueTime": "0x00000001:0xa6beb810", + "Module": "ntoskrnl", + "Offset": 2153086208, + "Period(ms)": 1000, + "Routine": 2152611575, + "Signaled": "Yes", + "Symbol": "IopTimerDispatch", + "__children": [] + }, + { + "DueTime": "0x00000001:0xe2f53650", + "Module": "ipnat", + "Offset": 4146164320, + "Period(ms)": 60000, + "Routine": 4146134936, + "Signaled": "Yes", + "Symbol": null, + "__children": [] + }, + { + "DueTime": "0x80000001:0x838d66d0", + "Module": "ntoskrnl", + "Offset": 2180726816, + "Period(ms)": 0, + "Routine": 2152618407, + "Signaled": "-", + "Symbol": "ExpTimerDpcRoutine", + "__children": [] + }, + { + "DueTime": "0x00000001:0xa6c34da0", + "Module": "ntoskrnl", + "Offset": 2153118912, + "Period(ms)": 1000, + "Routine": 2152615232, + "Signaled": "Yes", + "Symbol": "PopScanIdleList", + "__children": [] + }, + { + "DueTime": "0x00000098:0xa67683e0", + "Module": "NDIS", + "Offset": 2183038120, + "Period(ms)": 0, + "Routine": 4164628447, + "Signaled": "-", + "Symbol": null, + "__children": [] + }, + { + "DueTime": "0x00000001:0xa6d41cb0", + "Module": "ntoskrnl", + "Offset": 2153080200, + "Period(ms)": 0, + "Routine": 2152616496, + "Signaled": "-", + "Symbol": "CcScanDpc", + "__children": [] + }, + { + "DueTime": "0x80000000:0x14500b10", + "Module": "ntoskrnl", + "Offset": 2182113448, + "Period(ms)": 0, + "Routine": 2152618407, + "Signaled": "-", + "Symbol": "ExpTimerDpcRoutine", + "__children": [] + }, + { + "DueTime": "0x00000001:0xaba81b20", + "Module": "NDIS", + "Offset": 2182656416, + "Period(ms)": 0, + "Routine": 4164628447, + "Signaled": "-", + "Symbol": null, + "__children": [] + }, + { + "DueTime": "0x00000001:0xab110bd0", + "Module": "ntoskrnl", + "Offset": 2181090272, + "Period(ms)": 0, + "Routine": 2152754200, + "Signaled": "-", + "Symbol": "CcPfTraceTimerRoutine", + "__children": [] + }, + { + "DueTime": "0x00000098:0xa6ad86a0", + "Module": "NDIS", + "Offset": 2182615456, + "Period(ms)": 0, + "Routine": 4164628447, + "Signaled": "-", + "Symbol": null, + "__children": [] + }, + { + "DueTime": "0x00000001:0xd56a01e0", + "Module": "HTTP", + "Offset": 4122576520, + "Period(ms)": 60000, + "Routine": 4122481076, + "Signaled": "Yes", + "Symbol": null, + "__children": [] + }, + { + "DueTime": "0x00000001:0xd56a7ca0", + "Module": "HTTP", + "Offset": 4122577120, + "Period(ms)": 30000, + "Routine": 4122510208, + "Signaled": "Yes", + "Symbol": null, + "__children": [] + }, + { + "DueTime": "0x00000001:0xd56a7ca0", + "Module": "HTTP", + "Offset": 4122586816, + "Period(ms)": 0, + "Routine": 4122536296, + "Signaled": "-", + "Symbol": null, + "__children": [] + }, + { + "DueTime": "0x80000000:0x14500b10", + "Module": "ntoskrnl", + "Offset": 2182113448, + "Period(ms)": 0, + "Routine": 2152618407, + "Signaled": "-", + "Symbol": "ExpTimerDpcRoutine", + "__children": [] + }, + { + "DueTime": "0x00000002:0x10691f50", + "Module": "BATTC", + "Offset": 2184852912, + "Period(ms)": 0, + "Routine": 4170619626, + "Signaled": "-", + "Symbol": null, + "__children": [] + }, + { + "DueTime": "0x00000002:0x10691f50", + "Module": "BATTC", + "Offset": 2182647568, + "Period(ms)": 0, + "Routine": 4170619626, + "Signaled": "-", + "Symbol": null, + "__children": [] + }, + { + "DueTime": "0x00000001:0xaa90b010", + "Module": "sr", + "Offset": 2184659528, + "Period(ms)": 0, + "Routine": 4165384494, + "Signaled": "-", + "Symbol": null, + "__children": [] + }, + { + "DueTime": "0x00000131:0x3cf80b10", + "Module": "NDIS", + "Offset": 2181526704, + "Period(ms)": 0, + "Routine": 4164628447, + "Signaled": "-", + "Symbol": null, + "__children": [] + }, + { + "DueTime": "0x00000001:0xaa9b2a20", + "Module": "NDIS", + "Offset": 2179568032, + "Period(ms)": 0, + "Routine": 4164628447, + "Signaled": "-", + "Symbol": null, + "__children": [] + }, + { + "DueTime": "0x00000001:0xaa9cb150", + "Module": "NDIS", + "Offset": 2179569720, + "Period(ms)": 0, + "Routine": 4164628447, + "Signaled": "-", + "Symbol": null, + "__children": [] + }, + { + "DueTime": "0x00000001:0xaa9e70c0", + "Module": "ntoskrnl", + "Offset": 2182248232, + "Period(ms)": 0, + "Routine": 2152618407, + "Signaled": "-", + "Symbol": "ExpTimerDpcRoutine", + "__children": [] + }, + { + "DueTime": "0x00000001:0xd56bd340", + "Module": "srv", + "Offset": 4128143248, + "Period(ms)": 0, + "Routine": 4128080773, + "Signaled": "-", + "Symbol": null, + "__children": [] + }, + { + "DueTime": "0x00000001:0xbae86a40", + "Module": "ntoskrnl", + "Offset": 2182222184, + "Period(ms)": 0, + "Routine": 2152618407, + "Signaled": "-", + "Symbol": "ExpTimerDpcRoutine", + "__children": [] + }, + { + "DueTime": "0x00000001:0xacafcdd0", + "Module": "Ntfs", + "Offset": 4164841712, + "Period(ms)": 0, + "Routine": 4164719155, + "Signaled": "-", + "Symbol": null, + "__children": [] + }, + { + "DueTime": "0x00000001:0xaa92535f", + "Module": "USBPORT", + "Offset": 2179614512, + "Period(ms)": 0, + "Routine": 4163343596, + "Signaled": "-", + "Symbol": null, + "__children": [] + }, + { + "DueTime": "0x00000001:0xad54f9d0", + "Module": "ntoskrnl", + "Offset": 2153085904, + "Period(ms)": 60000, + "Routine": 2152638914, + "Signaled": "Yes", + "Symbol": "IopIrpStackProfilerTimer", + "__children": [] + }, + { + "DueTime": "0x00000008:0x73b44670", + "Module": "ipsec", + "Offset": 4147284848, + "Period(ms)": 0, + "Routine": 4147221577, + "Signaled": "-", + "Symbol": null, + "__children": [] + }, + { + "DueTime": "0x00000001:0xaad6c270", + "Module": "NDIS", + "Offset": 2179663392, + "Period(ms)": 0, + "Routine": 4164642677, + "Signaled": "-", + "Symbol": null, + "__children": [] + }, + { + "DueTime": "0x00000001:0xaad6fab0", + "Module": "NDIS", + "Offset": 2181321392, + "Period(ms)": 1000, + "Routine": 4164630316, + "Signaled": "Yes", + "Symbol": null, + "__children": [] + }, + { + "DueTime": "0x00000008:0x73bef8c0", + "Module": "netbt", + "Offset": 2182155520, + "Period(ms)": 0, + "Routine": 4146697354, + "Signaled": "-", + "Symbol": null, + "__children": [] + } + ] +} \ No newline at end of file diff --git a/test/plugins/windows/test_data/windows.unloadedmodules.UnloadedModules.json b/test/plugins/windows/test_data/windows.unloadedmodules.UnloadedModules.json new file mode 100644 index 000000000..e287d3c14 --- /dev/null +++ b/test/plugins/windows/test_data/windows.unloadedmodules.UnloadedModules.json @@ -0,0 +1,67 @@ +{ + "WINDOWS10_GENERIC": [ + { + "EndAddress": 18446735295750344704, + "Name": "hwpolicy.sys", + "StartAddress": 18446735295750275072, + "Time": "2025-03-06T17:47:55+00:00", + "__children": [] + }, + { + "EndAddress": 18446735295728652288, + "Name": "WdBoot.sys", + "StartAddress": 18446735295728582656, + "Time": "2025-03-06T17:47:56+00:00", + "__children": [] + }, + { + "EndAddress": 18446735295786053632, + "Name": "dam.sys", + "StartAddress": 18446735295785926656, + "Time": "2025-03-06T17:48:02+00:00", + "__children": [] + }, + { + "EndAddress": 18446735295788797952, + "Name": "serial.sys", + "StartAddress": 18446735295788679168, + "Time": "2025-03-06T17:48:21+00:00", + "__children": [] + }, + { + "EndAddress": 18446735295788875776, + "Name": "serenum.sys", + "StartAddress": 18446735295788810240, + "Time": "2025-03-06T17:48:21+00:00", + "__children": [] + }, + { + "EndAddress": 18446735295761932288, + "Name": "dump_dumpfve.sys", + "StartAddress": 18446735295761809408, + "Time": "2025-03-06T17:48:27+00:00", + "__children": [] + }, + { + "EndAddress": 18446735295761629184, + "Name": "dump_vmbkmcl.sys", + "StartAddress": 18446735295761481728, + "Time": "2025-03-06T17:48:27+00:00", + "__children": [] + }, + { + "EndAddress": 18446735295761481728, + "Name": "dump_storvsc.sys", + "StartAddress": 18446735295761416192, + "Time": "2025-03-06T17:48:27+00:00", + "__children": [] + }, + { + "EndAddress": 18446735295761350656, + "Name": "dump_storport.sys", + "StartAddress": 18446735295761285120, + "Time": "2025-03-06T17:48:27+00:00", + "__children": [] + } + ] +} \ No newline at end of file diff --git a/test/plugins/windows/test_data/windows.vadinfo.VadInfo.json b/test/plugins/windows/test_data/windows.vadinfo.VadInfo.json new file mode 100644 index 000000000..d980b21a2 --- /dev/null +++ b/test/plugins/windows/test_data/windows.vadinfo.VadInfo.json @@ -0,0 +1,94 @@ +{ + "WINDOWS10_GENERIC": [ + { + "CommitCharge": 1, + "End VPN": 2147381247, + "File": null, + "File output": "Disabled", + "Offset": 18446607800399740208, + "PID": 4, + "Parent": 0, + "PrivateMemory": 1, + "Process": "System", + "Protection": "PAGE_READONLY", + "Start VPN": 2147377152, + "Tag": "VadS", + "__children": [] + }, + { + "CommitCharge": 1, + "End VPN": 2147356671, + "File": null, + "File output": "Disabled", + "Offset": 18446607800399739968, + "PID": 4, + "Parent": 18446607800399740208, + "PrivateMemory": 1, + "Process": "System", + "Protection": "PAGE_READONLY", + "Start VPN": 2147352576, + "Tag": "VadS", + "__children": [] + }, + { + "CommitCharge": 9, + "End VPN": 2004303871, + "File": "\\Windows\\SysWOW64\\ntdll.dll", + "File output": "Disabled", + "Offset": 18446607800410763840, + "PID": 4, + "Parent": 18446607800399739968, + "PrivateMemory": 0, + "Process": "System", + "Protection": "PAGE_EXECUTE_WRITECOPY", + "Start VPN": 2002583552, + "Tag": "Vad ", + "__children": [] + }, + { + "CommitCharge": 9, + "End VPN": 140703785033727, + "File": "\\Windows\\System32\\vertdll.dll", + "File output": "Disabled", + "Offset": 18446607800410768000, + "PID": 4, + "Parent": 18446607800399740208, + "PrivateMemory": 0, + "Process": "System", + "Protection": "PAGE_EXECUTE_WRITECOPY", + "Start VPN": 140703784828928, + "Tag": "Vad ", + "__children": [] + }, + { + "CommitCharge": 0, + "End VPN": 2873390796799, + "File": null, + "File output": "Disabled", + "Offset": 18446607800474375600, + "PID": 4, + "Parent": 18446607800410768000, + "PrivateMemory": 0, + "Process": "System", + "Protection": "PAGE_READWRITE", + "Start VPN": 2873390792704, + "Tag": "Vad ", + "__children": [] + }, + { + "CommitCharge": 16, + "End VPN": 140703787155455, + "File": "\\Windows\\System32\\ntdll.dll", + "File output": "Disabled", + "Offset": 18446607800410767520, + "PID": 4, + "Parent": 18446607800410768000, + "PrivateMemory": 0, + "Process": "System", + "Protection": "PAGE_EXECUTE_WRITECOPY", + "Start VPN": 140703785091072, + "Tag": "Vad ", + "__children": [] + } + ] +} \ No newline at end of file diff --git a/test/plugins/windows/test_data/windows.vadwalk.VadWalk.json b/test/plugins/windows/test_data/windows.vadwalk.VadWalk.json new file mode 100644 index 000000000..d7c884fca --- /dev/null +++ b/test/plugins/windows/test_data/windows.vadwalk.VadWalk.json @@ -0,0 +1,76 @@ +{ + "WINDOWS10_GENERIC": [ + { + "End": 2147381247, + "Left": 145201666899008, + "Offset": 145201666899248, + "PID": 4, + "Parent": 0, + "Process": "System", + "Right": 145201677927040, + "Start": 2147377152, + "Tag": "VadS", + "__children": [] + }, + { + "End": 2147356671, + "Left": 145201677922880, + "Offset": 145201666899008, + "PID": 4, + "Parent": 145201666899248, + "Process": "System", + "Right": 0, + "Start": 2147352576, + "Tag": "VadS", + "__children": [] + }, + { + "End": 2004303871, + "Left": 0, + "Offset": 145201677922880, + "PID": 4, + "Parent": 145201666899008, + "Process": "System", + "Right": 0, + "Start": 2002583552, + "Tag": "Vad ", + "__children": [] + }, + { + "End": 140703785033727, + "Left": 145201741534640, + "Offset": 145201677927040, + "PID": 4, + "Parent": 145201666899248, + "Process": "System", + "Right": 145201677926560, + "Start": 140703784828928, + "Tag": "Vad ", + "__children": [] + }, + { + "End": 2873390796799, + "Left": 0, + "Offset": 145201741534640, + "PID": 4, + "Parent": 145201677927040, + "Process": "System", + "Right": 0, + "Start": 2873390792704, + "Tag": "Vad ", + "__children": [] + }, + { + "End": 140703787155455, + "Left": 0, + "Offset": 145201677926560, + "PID": 4, + "Parent": 145201677927040, + "Process": "System", + "Right": 0, + "Start": 140703785091072, + "Tag": "Vad ", + "__children": [] + } + ] +} \ No newline at end of file diff --git a/test/plugins/windows/test_data/windows.virtmap.VirtMap.json b/test/plugins/windows/test_data/windows.virtmap.VirtMap.json new file mode 100644 index 000000000..6b80068f2 --- /dev/null +++ b/test/plugins/windows/test_data/windows.virtmap.VirtMap.json @@ -0,0 +1,82 @@ +{ + "WINDOWS10_GENERIC": [ + { + "End offset": 17592186044416, + "Region": "MiVaBootLoaded", + "Start offset": 238594023227392, + "__children": [] + }, + { + "End offset": 549755813888, + "Region": "MiVaDriverImages", + "Start offset": 272678883688448, + "__children": [] + }, + { + "End offset": 549755813888, + "Region": "MiVaHal", + "Start offset": 258385232527360, + "__children": [] + }, + { + "End offset": 3848290697216, + "Region": "MiVaNonPagedPool", + "Start offset": 266081813921792, + "__children": [] + }, + { + "End offset": 2748779069440, + "Region": "MiVaPagedPool", + "Start offset": 278135644927560, + "__children": [] + }, + { + "End offset": 17592186044416, + "Region": "MiVaPfnDatabase", + "Start offset": 166026255794176, + "__children": [] + }, + { + "End offset": 17592186044416, + "Region": "MiVaProcessSpace", + "Start offset": 191315023233024, + "__children": [] + }, + { + "End offset": 549755813888, + "Region": "MiVaSessionGlobalSpace", + "Start offset": 186367220908032, + "__children": [] + }, + { + "End offset": 17592186044416, + "Region": "MiVaSessionSpace", + "Start offset": 213305255788544, + "__children": [] + }, + { + "End offset": 1099511627776, + "Region": "MiVaSpecialPoolPaged", + "Start offset": 261683767410688, + "__children": [] + }, + { + "End offset": 1099511627776, + "Region": "MiVaSystemCache", + "Start offset": 208907209277440, + "__children": [] + }, + { + "End offset": 549755813888, + "Region": "MiVaSystemPtes", + "Start offset": 274328151130112, + "__children": [] + }, + { + "End offset": 17592186044416, + "Region": "MiVaUnused", + "Start offset": 145135534866432, + "__children": [] + } + ] +} \ No newline at end of file diff --git a/test/plugins/windows/windows.py b/test/plugins/windows/windows.py index ce05af0cd..c9cf93391 100644 --- a/test/plugins/windows/windows.py +++ b/test/plugins/windows/windows.py @@ -10,7 +10,7 @@ from test import test_volatility, WindowsSamples class TestWindowsVolshell: def test_windows_volshell(self, image, volatility, python): out = test_volatility.basic_volshell_test( - image, volatility, python, globalargs=("-w",) + image, volatility, python, volshellargs=("-w",) ) assert out.count(b" 40 @@ -60,7 +60,8 @@ class TestWindowsPslist: class TestWindowsPsscan: - def test_windows_generic_psscan(self, volatility, python, image): + def test_windows_specific_psscan(self, volatility, python): + image = WindowsSamples.WINDOWSXP_GENERIC.value.path rc, out, _err = test_volatility.runvol_plugin( "windows.psscan.PsScan", image, volatility, python ) @@ -75,40 +76,66 @@ class TestWindowsPsscan: class TestWindowsDlllist: def test_windows_generic_dlllist(self, volatility, python, image): rc, out, _err = test_volatility.runvol_plugin( - "windows.dlllist.DllList", image, volatility, python + "windows.dlllist.DllList", + image, + volatility, + python, + globalargs=("-r", "json"), ) assert rc == 0 - out = out.lower() - assert out.count(b"\n") > 10 + json_out = json.loads(out) + assert test_volatility.count_entries_flat(json_out) > 2000 + expected_rows = [ + { + "Path": "C:\\Windows\\SYSTEM32\\kernel32.dll", + "Process": "csrss.exe", + }, + { + "Path": "C:\\Windows\\system32\\USER32.dll", + "Process": "csrss.exe", + }, + ] + for expected_row in expected_rows: + assert test_volatility.match_output_row( + expected_row, json_out, case_sensitive=False + ) class TestWindowsModules: - def test_windows_generic_modules(self, volatility, python, image): + def test_windows_specific_modules(self, volatility, python): + image = WindowsSamples.WINDOWSXP_GENERIC.value.path rc, out, _err = test_volatility.runvol_plugin( - "windows.modules.Modules", image, volatility, python + "windows.modules.Modules", + image, + volatility, + python, + globalargs=("-r", "json"), ) assert rc == 0 - out = out.lower() - assert out.count(b"\n") > 10 - - -class TestWindowsHivelist: - def test_windows_generic_hivelist(self, volatility, python, image): - rc, out, _err = test_volatility.runvol_plugin( - "windows.registry.hivelist.HiveList", image, volatility, python - ) - assert rc == 0 - out = out.lower() - - not_xp = out.find(b"\\systemroot\\system32\\config\\software") - if not_xp == -1: - assert ( - out.find( - b"\\device\\harddiskvolume1\\windows\\system32\\config\\software" - ) - != -1 - ) - assert out.count(b"\n") > 10 + json_out = json.loads(out) + assert test_volatility.count_entries_flat(json_out) > 110 + expected_rows = [ + { + "Name": "ntoskrnl.exe", + "Offset": 2185216944, + "Path": "\\WINDOWS\\system32\\ntoskrnl.exe", + "Size": 2179328, + }, + { + "Name": "hal.dll", + "Offset": 2185216840, + "Path": "\\WINDOWS\\system32\\hal.dll", + "Size": 81280, + }, + { + "Name": "netbios.sys", + "Offset": 2182050616, + "Path": "\\SystemRoot\\System32\\DRIVERS\\netbios.sys", + "Size": 36864, + }, + ] + for expected_row in expected_rows: + assert test_volatility.match_output_row(expected_row, json_out) class TestWindowsDumpfiles: @@ -173,25 +200,60 @@ class TestWindowsHandles: assert out.count(b"\n") > 500 -class TestWindowsSvcscan: - def test_windows_generic_svcscan(self, volatility, python, image): +class TestWindowsSvcList: + def test_windows_generic_svclist(self, volatility, python, image): + image = WindowsSamples.WINDOWS10_GENERIC.value.path rc, out, _err = test_volatility.runvol_plugin( - "windows.svcscan.SvcScan", image, volatility, python + "windows.svclist.SvcList", + image, + volatility, + python, + globalargs=("-r", "json"), ) assert rc == 0 - assert out.find(b"Microsoft ACPI Driver") != -1 - assert out.count(b"\n") > 250 + json_out = json.loads(out) + assert len(json_out) > 250 + expected_row = { + "Binary": "\\Driver\\ACPI", + "Display": "ACPI", + "Name": "ACPI", + "Start": "SERVICE_BOOT_START", + "State": "SERVICE_RUNNING", + "Type": "SERVICE_KERNEL_DRIVER", + } + assert test_volatility.match_output_row(expected_row, json_out) + + +class TestWindowsSvcScan: + def test_windows_generic_svcscan(self, volatility, python, image): + rc, out, _err = test_volatility.runvol_plugin( + "windows.svcscan.SvcScan", + image, + volatility, + python, + globalargs=("-r", "json"), + ) + assert rc == 0 + json_out = json.loads(out) + assert len(json_out) > 250 + expected_rows = [ + {"Name": "ACPI", "Type": "SERVICE_KERNEL_DRIVER"}, + ] + for expected_row in expected_rows: + assert test_volatility.match_output_row(expected_row, json_out) class TestWindowsThrdscan: - def test_windows_generic_thrdscan(self, volatility, python, image): + def test_windows_specific_thrdscan(self, volatility, python): + image = WindowsSamples.WINDOWSXP_GENERIC.value.path rc, out, _err = test_volatility.runvol_plugin( "windows.thrdscan.ThrdScan", image, volatility, python ) assert rc == 0 - assert out.find(b"\t4\t8") != -1 - assert out.find(b"\t4\t12") != -1 - assert out.find(b"\t4\t16") != -1 + assert out.count(b"\n") > 700 + assert out.find(b"\t1812\t2768\t0x7c810856") != -1 + assert out.find(b"\t840\t2964\t0x7c810856") != -1 + assert out.find(b"\t2536\t2552\t0x7c810856") != -1 class TestWindowsPrivileges: @@ -210,20 +272,47 @@ class TestWindowsPrivileges: assert out.count(b"\n") > 20 -class TestWindowsGetsids: +class TestWindowsGetSIDs: def test_windows_generic_getsids(self, volatility, python, image): rc, out, _err = test_volatility.runvol_plugin( "windows.getsids.GetSIDs", image, volatility, python, - pluginargs=("--pid", "4"), + globalargs=("-r", "json"), ) assert rc == 0 - assert out.find(b"Local System") != -1 - assert out.find(b"Administrators") != -1 - assert out.find(b"Everyone") != -1 - assert out.find(b"Authenticated Users") != -1 + json_out = json.loads(out) + assert test_volatility.count_entries_flat(json_out) > 400 + expected_rows = [ + { + "Name": "Local System", + "Process": "csrss.exe", + "SID": "S-1-5-18", + }, + { + "Name": "Administrators", + "Process": "csrss.exe", + "SID": "S-1-5-32-544", + }, + { + "Name": "Everyone", + "Process": "csrss.exe", + "SID": "S-1-1-0", + }, + { + "Name": "Authenticated Users", + "Process": "csrss.exe", + "SID": "S-1-5-11", + }, + { + "Name": "System Mandatory Level", + "Process": "csrss.exe", + "SID": "S-1-16-16384", + }, + ] + for expected_row in expected_rows: + assert test_volatility.match_output_row(expected_row, json_out) class TestWindowsEnvars: @@ -241,7 +330,8 @@ class TestWindowsEnvars: class TestWindowsCallbacks: - def test_windows_generic_callbacks(self, volatility, python, image): + def test_windows_specific_callbacks(self, volatility, python): + image = WindowsSamples.WINDOWSXP_GENERIC.value.path rc, out, _err = test_volatility.runvol_plugin( "windows.callbacks.Callbacks", image, volatility, python ) @@ -253,20 +343,28 @@ class TestWindowsCallbacks: class TestWindowsVadwalk: - def test_windows_generic_vadwalk(self, volatility, python, image): + def test_windows_specific_vadwalk(self, volatility, python): + image = WindowsSamples.WINDOWS10_GENERIC.value.path rc, out, _err = test_volatility.runvol_plugin( - "windows.vadwalk.VadWalk", image, volatility, python + "windows.vadwalk.VadWalk", + image, + volatility, + python, + globalargs=("-r", "json"), + pluginargs=("--pid", "4"), ) assert rc == 0 - assert out.find(b"Vad") != -1 - assert out.find(b"VadS") != -1 - assert out.find(b"Vadl") != -1 - assert out.find(b"VadF") != -1 - assert out.find(b"0x0") != -1 + json_out = json.loads(out) + expected_rows = test_volatility.load_test_data( + "windows.vadwalk.VadWalk", "WINDOWS10_GENERIC" + ) + for expected_row in expected_rows: + assert test_volatility.match_output_row(expected_row, json_out) class TestWindowsDevicetree: - def test_windows_generic_devicetree(self, volatility, python, image): + def test_windows_specific_devicetree(self, volatility, python): + image = WindowsSamples.WINDOWSXP_GENERIC.value.path rc, out, _err = test_volatility.runvol_plugin( "windows.devicetree.DeviceTree", image, volatility, python ) @@ -324,3 +422,993 @@ class TestWindowsVadyarascan: ) assert rc == 0 assert out.count(b"\n") > 10 + + +class TestWindowsAmcache: + def test_windows_generic_amcache(self, volatility, python, image): + rc, out, _err = test_volatility.runvol_plugin( + "windows.amcache.Amcache", + image, + volatility, + python, + globalargs=("-r", "json"), + ) + assert rc == 0 + json_out = json.loads(out) + assert test_volatility.count_entries_flat(json_out) > 100 + # Win10+ expected package names + expected_rows = [ + { + "Path": "C:\\Windows\\SystemApps\\Microsoft.Windows.StartMenuExperienceHost_cw5n1h2txyewy", + "ProductName": "Microsoft.Windows.StartMenuExperienceHost", + }, + { + "Path": "C:\\Windows\\SystemApps\\Microsoft.Windows.FileExplorer_cw5n1h2txyewy", + "ProductName": "c5e2524a-ea46-4f67-841f-6a9465d9d515", + }, + ] + for expected_row in expected_rows: + assert test_volatility.match_output_row(expected_row, json_out) + + +class TestWindowsBigPools: + def test_windows_generic_bigpools(self, volatility, python, image): + rc, out, _err = test_volatility.runvol_plugin( + "windows.bigpools.BigPools", + image, + volatility, + python, + globalargs=("-r", "json"), + ) + assert rc == 0 + json_out = json.loads(out) + assert test_volatility.count_entries_flat(json_out) > 2000 + expected_rows = [ + { + "PoolType": "PagedPool", + }, + { + "PoolType": "PagedPoolCacheAligned", + }, + { + "PoolType": "NonPagedPoolNx", + }, + ] + for expected_row in expected_rows: + assert test_volatility.match_output_row(expected_row, json_out) + + +# FIXME: Empty on WIN10 and XP samples +# class TestWindowsCachedump: +# def test_windows_generic_cachedump(self, volatility, python, image): +# rc, out, _err = test_volatility.runvol_plugin( +# "windows.cachedump.Cachedump", +# image, +# volatility, +# python, +# globalargs=("-r", "json"), +# ) +# assert rc == 0 +# json_out = json.loads(out) + + +class TestWindowsCmdLine: + def test_windows_generic_cmdline(self, volatility, python, image): + rc, out, _err = test_volatility.runvol_plugin( + "windows.cmdline.CmdLine", + image, + volatility, + python, + ) + assert rc == 0 + assert out.count(b"\n") > 20 + out = out.lower() + assert ( + out.find(b"C:\\Windows\\system32\\svchost.exe -k DcomLaunch -p".lower()) + != -1 + ) + assert ( + out.count( + b"C:\\Windows\\system32\\svchost.exe -k LocalServiceNetworkRestricted -p".lower() + ) + > 3 + ) + + +class TestWindowsCmdScan: + def test_windows_specific_cmdscan(self, volatility, python): + image = WindowsSamples.WINDOWS10_GENERIC.value.path + rc, out, _err = test_volatility.runvol_plugin( + "windows.cmdscan.CmdScan", + image, + volatility, + python, + globalargs=("-r", "json"), + ) + assert rc == 0 + expected_row = { + "Process": "conhost.exe", + "Property": "_COMMAND_HISTORY", + } + assert test_volatility.match_output_row(expected_row, json.loads(out)) + + +class TestWindowsConsoles: + def test_windows_specific_consoles(self, volatility, python): + image = WindowsSamples.WINDOWS10_GENERIC.value.path + rc, out, _err = test_volatility.runvol_plugin( + "windows.consoles.Consoles", + image, + volatility, + python, + globalargs=("-r", "json"), + ) + assert rc == 0 + expected_row = { + "Process": "conhost.exe", + "Property": "_CONSOLE_INFORMATION", + } + assert test_volatility.match_output_row(expected_row, json.loads(out)) + + +class TestWindowsCrashinfo: + def test_windows_specific_crashinfo(self, volatility, python): + image = WindowsSamples.WINDOWS10_GENERIC.value.path + rc, out, _err = test_volatility.runvol_plugin( + "windows.crashinfo.Crashinfo", + image, + volatility, + python, + globalargs=("-r", "json"), + ) + assert rc == 0 + expected_row = { + "BitmapHeaderSize": 176128, + "BitmapPages": 511191, + "BitmapSize": 1310720, + "Comment": "PAGEPAGEPAGEPAGEPAGEPAGEPAGEPAGEPAGEPAGEPAGEPAGEPAGEPAGEPAGEPAGEPAGEPAGEPAGEPAGEPAGEPAGEPAGEPAGEPAGEPAGEPAGEPAGEPAGEPAGEPAGEPAGE", + "DirectoryTableBase": 4610162688, + "DumpType": "Bitmap Dump (0x5)", + "MachineImageType": 34404, + "MajorVersion": 15, + "MinorVersion": 19041, + "NumberProcessors": 1, + "Signature": "PAGE", + "SystemTime": "2025-03-06T17:59:20+00:00", + "SystemUpTime": "0:11:23.199374", + "__children": [], + } + assert test_volatility.match_output_row(expected_row, json.loads(out)) + + +class TestWindowsDriverIrp: + def test_windows_specific_driverirp(self, volatility, python): + image = WindowsSamples.WINDOWSXP_GENERIC.value.path + rc, out, _err = test_volatility.runvol_plugin( + "windows.driverirp.DriverIrp", + image, + volatility, + python, + ) + assert rc == 0 + assert out.count(b"\n") > 2000 + assert out.count(b"ntoskrnl") > 400 + for irp in test_volatility.load_test_data( + "windows.driverirp.DriverIrp", "GENERIC" + ): + assert out.find(irp.encode()) != -1 + + +class TestWindowsDriverScan: + def test_windows_specific_driverscan(self, volatility, python): + image = WindowsSamples.WINDOWSXP_GENERIC.value.path + rc, out, _err = test_volatility.runvol_plugin( + "windows.driverscan.DriverScan", + image, + volatility, + python, + globalargs=("-r", "json"), + ) + assert rc == 0 + json_out = json.loads(out) + assert test_volatility.count_entries_flat(json_out) > 50 + expected_rows = [ + { + "Name": "\\Driver\\ACPI_HAL", + "Service Key": "\\Driver\\ACPI_HAL", + }, + { + "Name": "\\Driver\\Tcpip", + "Service Key": "Tcpip", + }, + ] + for expected_row in expected_rows: + assert test_volatility.match_output_row(expected_row, json_out) + + +class TestWindowsGetServiceSIDs: + def test_windows_generic_getservicesids(self, volatility, python, image): + rc, out, _err = test_volatility.runvol_plugin( + "windows.getservicesids.GetServiceSIDs", + image, + volatility, + python, + ) + assert rc == 0 + assert out.count(b"S-1-5-80-") > 90 + + +class TestWindowsIAT: + def test_windows_generic_iat(self, volatility, python, image): + rc, out, _err = test_volatility.runvol_plugin( + "windows.iat.IAT", + image, + volatility, + python, + globalargs=("-r", "json"), + ) + assert rc == 0 + json_out = json.loads(out) + assert test_volatility.count_entries_flat(json_out) > 2000 + expected_rows = [ + { + "Function": "NtTerminateProcess", + "Library": "ntdll.dll", + "Name": "csrss.exe", + }, + { + "Function": "RtlSetHeapInformation", + "Library": "ntdll.dll", + "Name": "csrss.exe", + }, + ] + for expected_row in expected_rows: + assert test_volatility.match_output_row(expected_row, json_out) + + +class TestWindowsInfo: + def test_windows_specific_info(self, volatility, python): + image = WindowsSamples.WINDOWS10_GENERIC.value.path + rc, out, _err = test_volatility.runvol_plugin( + "windows.info.Info", + image, + volatility, + python, + globalargs=("-r", "json"), + ) + assert rc == 0 + json_out = json.loads(out) + expected_rows = test_volatility.load_test_data( + "windows.info.Info", "WINDOWS10_GENERIC" + ) + for expected_row in expected_rows: + assert test_volatility.match_output_row(expected_row, json_out) + + +class TestWindowsJobLinks: + def test_windows_specific_joblinks(self, volatility, python): + image = WindowsSamples.WINDOWS10_GENERIC.value.path + rc, out, _err = test_volatility.runvol_plugin( + "windows.joblinks.JobLinks", + image, + volatility, + python, + globalargs=("-r", "json"), + ) + assert rc == 0 + json_out = json.loads(out) + assert test_volatility.count_entries_flat(json_out) > 30 + expected_row = { + "Active": 1, + "JobLink": None, + "JobSess": 2, + "Name": "taskhostw.exe", + "Offset(V)": 145201782567040, + "PID": 4304, + "PPID": 1008, + "Process": "(Original Process)", + "Sess": 2, + "Term": 0, + "Total": 1, + "Wow64": False, + "__children": [ + { + "Active": 0, + "JobLink": "Yes", + "JobSess": 0, + "Name": "taskhostw.exe", + "Offset(V)": 145201782567040, + "PID": 4304, + "PPID": 1008, + "Process": "C:\\Windows\\system32\\taskhostw.exe", + "Sess": 2, + "Term": 0, + "Total": 0, + "Wow64": False, + "__children": [], + } + ], + } + + assert test_volatility.match_output_row(expected_row, json_out) + + +class TestWindowsKPCRs: + def test_windows_generic_kpcrs(self, volatility, python, image): + rc, out, _err = test_volatility.runvol_plugin( + "windows.kpcrs.KPCRs", + image, + volatility, + python, + globalargs=("-r", "json"), + ) + assert rc == 0 + assert test_volatility.count_entries_flat(json.loads(out)) > 0 + + +class TestWindowsLdrModules: + def test_windows_specific_ldrmodules(self, volatility, python): + image = WindowsSamples.WINDOWSXP_GENERIC.value.path + rc, out, _err = test_volatility.runvol_plugin( + "windows.ldrmodules.LdrModules", + image, + volatility, + python, + ) + assert rc == 0 + assert out.count(b"\n") > 800 + out = out.lower() + assert out.find(b"\\Windows\\System32\\ntdll.dll".lower()) > 10 + + +class TestWindowsLsadump: + def test_windows_specific_lsadump(self, volatility, python): + image = WindowsSamples.WINDOWSXP_GENERIC.value.path + rc, out, _err = test_volatility.runvol_plugin( + "windows.lsadump.Lsadump", + image, + volatility, + python, + globalargs=("-r", "json"), + ) + assert rc == 0 + json_out = json.loads(out) + assert test_volatility.count_entries_flat(json_out) > 5 + expected_row = { + "Hex": "01 00 00 00 2b 2b f1 09 a3 b3 4b af 02 19 5a 61 2f 09 3a 88 03 52 51 64 8a 6c d2 a8 34 07 cb 61 41 ca a4 5d f1 fb 4c e0 41 72 69 32", + "Key": "DPAPI_SYSTEM", + } + + assert test_volatility.match_output_row(expected_row, json_out) + + +class TestWindowsMBRScan: + def test_windows_specific_mbrscan(self, volatility, python): + image = WindowsSamples.WINDOWSXP_GENERIC.value.path + rc, out, _err = test_volatility.runvol_plugin( + "windows.mbrscan.MBRScan", + image, + volatility, + python, + globalargs=("-r", "json"), + ) + assert rc == 0 + json_out = json.loads(out) + assert test_volatility.count_entries_flat(json_out) > 4300 + expected_rows = [ + { + "Bootcode MD5": "dbcef88b4d770658b0050bf20b2d3061", + "Disk Signature": "82-78-77-32", + "Full MBR MD5": "8eea93bb1c63863f6e7f95b084411672", + "Potential MBR at Physical Offset": 154029739, + }, + { + "Bootcode MD5": "591213a9dfef595735e419eff6eeb39d", + "Disk Signature": "7a-74-60-53", + "Full MBR MD5": "4e00711a5014941f5ad8b3a4cde69c9c", + "Potential MBR at Physical Offset": 437808348, + }, + ] + for expected_row in expected_rows: + assert test_volatility.match_output_row(expected_row, json_out) + + +class TestWindowsMemmap: + def test_windows_specific_memmap(self, volatility, python): + image = WindowsSamples.WINDOWSXP_GENERIC.value.path + rc, out, _err = test_volatility.runvol_plugin( + "windows.memmap.Memmap", + image, + volatility, + python, + pluginargs=("--pid", "504"), + ) + assert rc == 0 + assert out.count(b"\n") > 12000 + + +class TestWindowsMFTscan: + def test_windows_specific_mftscan_ads_xp(self, volatility, python): + image = WindowsSamples.WINDOWSXP_GENERIC.value.path + rc, out, _err = test_volatility.runvol_plugin( + "windows.mftscan.ADS", + image, + volatility, + python, + globalargs=("-r", "json"), + ) + assert rc == 0 + json_out = json.loads(out) + expected_rows = [ + { + "ADS Filename": "Zone.Identifier", + "Filename": "libby_hoeler_part1.wmv", + "Hexdump": '"\n5b 5a 6f 6e 65 54 72 61 6e 73 66 65 72 5d 0d 0a [ZoneTransfer]..\n5a 6f 6e 65 49 64 3d 33 0d 0a ZoneId=3.. "', + "MFT Type": "DATA", + "Offset": 55926304, + "Record Number": 323, + "Record Type": "FILE", + }, + { + "ADS Filename": "Zone.Identifier", + "Filename": "NetZeroQuickHelpLite.exe", + "Hexdump": '"\n5b 5a 6f 6e 65 54 72 61 6e 73 66 65 72 5d 0d 0a [ZoneTransfer]..\n5a 6f 6e 65 49 64 3d 33 0d 0a ZoneId=3.. "', + "MFT Type": "DATA", + "Offset": 56102400, + "Record Number": 347, + "Record Type": "FILE", + }, + ] + for expected_row in expected_rows: + assert test_volatility.match_output_row(expected_row, json_out) + + def test_windows_specific_mftscan_ads_win10(self, volatility, python): + image = WindowsSamples.WINDOWS10_GENERIC.value.path + rc, out, _err = test_volatility.runvol_plugin( + "windows.mftscan.ADS", + image, + volatility, + python, + globalargs=("-r", "json"), + ) + assert rc == 0 + json_out = json.loads(out) + expected_rows = [ + { + "ADS Filename": "$Max", + "Filename": "$UsnJrnl", + "Hexdump": '"\n00 00 00 02 00 00 00 00 00 00 80 00 00 00 00 00 ................\nb9 dd f0 cc df 73 db 01 00 00 00 00 00 00 00 00 .....s.........."', + "MFT Type": "DATA", + "Offset": 1058018088, + "Record Number": 107240, + "Record Type": "FILE", + }, + { + "ADS Filename": "$Config", + "Filename": "$Repair", + "Hexdump": '"\n01 00 00 00 03 00 00 00 ........ "', + "MFT Type": "DATA", + "Offset": 5009678688, + "Record Number": 28, + "Record Type": "FILE", + }, + ] + for expected_row in expected_rows: + assert test_volatility.match_output_row(expected_row, json_out) + + def test_windows_specific_mftscan_mftscan(self, volatility, python): + image = WindowsSamples.WINDOWS10_GENERIC.value.path + rc, out, _err = test_volatility.runvol_plugin( + "windows.mftscan.MFTScan", + image, + volatility, + python, + ) + assert rc == 0 + assert out.count(b"\n") > 15000 + assert out.count(b"STANDARD_INFORMATION") > 5000 + assert out.count(b"FILE_NAME") > 11000 + + def test_windows_specific_mftscan_residentdata_win10(self, volatility, python): + image = WindowsSamples.WINDOWS10_GENERIC.value.path + rc, out, _err = test_volatility.runvol_plugin( + "windows.mftscan.ResidentData", + image, + volatility, + python, + globalargs=("-r", "json"), + ) + assert rc == 0 + json_out = json.loads(out) + assert test_volatility.count_entries_flat(json_out) > 850 + expected_rows = [ + { + "Filename": "index", + "Hexdump": '"\n30 5c 72 a7 1b 6d fb fc 09 00 00 00 00 00 00 00 0\\r..m..........\n00 00 00 00 00 00 00 00 ........ "', + "MFT Type": "DATA", + "Offset": 4961536280, + "Record Number": 116474, + "Record Type": "FILE", + }, + { + "Filename": "0.2.filtertrie.intermediate.txt", + "Hexdump": '"\n30 09 32 0d 0a 0.2.. "', + "MFT Type": "DATA", + "Offset": 619242944, + "Record Number": 113013, + "Record Type": "FILE", + }, + ] + for expected_row in expected_rows: + assert test_volatility.match_output_row(expected_row, json_out) + + +class TestWindowsModScan: + def test_windows_generic_modscan(self, volatility, python): + image = WindowsSamples.WINDOWSXP_GENERIC.value.path + rc, out, _err = test_volatility.runvol_plugin( + "windows.modscan.ModScan", + image, + volatility, + python, + globalargs=("-r", "json"), + ) + assert rc == 0 + json_out = json.loads(out) + assert test_volatility.count_entries_flat(json_out) > 90 + expected_rows = [ + { + "Name": "ntoskrnl.exe", + "Offset": 37733296, + "Path": "\\WINDOWS\\system32\\ntoskrnl.exe", + "Size": 2179328, + }, + { + "Name": "hal.dll", + "Offset": 37733192, + "Path": "\\WINDOWS\\system32\\hal.dll", + "Size": 81280, + }, + { + "Name": "netbios.sys", + "Offset": 34566968, + "Path": "\\SystemRoot\\System32\\DRIVERS\\netbios.sys", + "Size": 36864, + }, + ] + for expected_row in expected_rows: + assert test_volatility.match_output_row(expected_row, json_out) + + +class TestWindowsMutantScan: + def test_windows_specific_mutantscan(self, volatility, python): + image = WindowsSamples.WINDOWSXP_GENERIC.value.path + rc, out, _err = test_volatility.runvol_plugin( + "windows.mutantscan.MutantScan", + image, + volatility, + python, + ) + assert rc == 0 + assert out.count(b"\n") > 350 + + +class TestWindowsNetScan: + def test_windows_specific_netscan(self, volatility, python): + image = WindowsSamples.WINDOWS10_GENERIC.value.path + rc, out, _err = test_volatility.runvol_plugin( + "windows.netscan.NetScan", + image, + volatility, + python, + globalargs=("-r", "json"), + ) + assert rc == 0 + json_out = json.loads(out) + assert test_volatility.count_entries_flat(json_out) > 100 + expected_rows = [ + { + "Created": "2025-03-06T17:56:53+00:00", + "ForeignAddr": "13.107.246.254", + "ForeignPort": 443, + "LocalAddr": "10.0.0.4", + "LocalPort": 49929, + "Offset": 145201667934000, + "Owner": "SearchApp.exe", + "PID": 5644, + "Proto": "TCPv4", + "State": "CLOSE_WAIT", + }, + { + "Created": "2025-03-06T17:50:02+00:00", + "ForeignAddr": "168.63.129.16", + "ForeignPort": 80, + "LocalAddr": "10.0.0.4", + "LocalPort": 49689, + "Offset": 145201778694688, + "Owner": "WindowsAzureGu", + "PID": 1944, + "Proto": "TCPv4", + "State": "CLOSED", + }, + ] + for expected_row in expected_rows: + assert test_volatility.match_output_row(expected_row, json_out) + + +class TestWindowsNetStat: + def test_windows_specific_netstat(self, volatility, python): + image = WindowsSamples.WINDOWS10_GENERIC.value.path + rc, out, _err = test_volatility.runvol_plugin( + "windows.netstat.NetStat", + image, + volatility, + python, + globalargs=("-r", "json"), + ) + assert rc == 0 + json_out = json.loads(out) + assert test_volatility.count_entries_flat(json_out) > 70 + expected_rows = [ + { + "Created": "2025-03-06T17:56:53+00:00", + "ForeignAddr": "13.107.246.254", + "ForeignPort": 443, + "LocalAddr": "10.0.0.4", + "LocalPort": 49929, + "Offset": 145201667934000, + "Owner": "SearchApp.exe", + "PID": 5644, + "Proto": "TCPv4", + "State": "CLOSE_WAIT", + }, + { + "Created": "2025-03-06T17:50:02+00:00", + "ForeignAddr": "168.63.129.16", + "ForeignPort": 80, + "LocalAddr": "10.0.0.4", + "LocalPort": 49688, + "Offset": 145201778506032, + "Owner": "WindowsAzureGu", + "PID": 1944, + "Proto": "TCPv4", + "State": "ESTABLISHED", + }, + ] + for expected_row in expected_rows: + assert test_volatility.match_output_row(expected_row, json_out) + + +class TestWindowsPESymbols: + def test_windows_specific_pe_symbols_processes(self, volatility, python): + image = WindowsSamples.WINDOWSXP_GENERIC.value.path + rc, out, _err = test_volatility.runvol_plugin( + "windows.pe_symbols.PESymbols", + image, + volatility, + python, + globalargs=("-r", "json"), + pluginargs=( + "--source", + "processes", + "--module", + "ntdll.dll", + "--symbol", + "NtProtectVirtualMemory", + ), + ) + assert rc == 0 + expected_row = { + "Address": 2089868982, + "Module": "ntdll.dll", + "Symbol": "NtProtectVirtualMemory", + } + + assert test_volatility.match_output_row(expected_row, json.loads(out)) + + def test_windows_specific_pe_symbols_kernel(self, volatility, python): + image = WindowsSamples.WINDOWSXP_GENERIC.value.path + rc, out, _err = test_volatility.runvol_plugin( + "windows.pe_symbols.PESymbols", + image, + volatility, + python, + globalargs=("-r", "json"), + pluginargs=( + "--source", + "kernel", + "--module", + "ntoskrnl.exe", + "--symbol", + "ZwOpenThread", + ), + ) + assert rc == 0 + expected_row = { + "Address": 2152583356, + "Module": "ntoskrnl.exe", + "Symbol": "ZwOpenThread", + } + + assert test_volatility.match_output_row(expected_row, json.loads(out)) + + +class TestWindowsPoolScanner: + def test_windows_specific_poolscanner(self, volatility, python): + image = WindowsSamples.WINDOWSXP_GENERIC.value.path + rc, out, _err = test_volatility.runvol_plugin( + "windows.poolscanner.PoolScanner", + image, + volatility, + python, + ) + assert rc == 0 + assert out.count(b"\n") > 4800 + assert out.find(b"_FILE_OBJECT") != -1 + assert out.find(b"_ETHREAD") != -1 + assert out.find(b"_RTL_ATOM_TABLE") != -1 + assert out.find(b"_KMUTANT") != -1 + + +class TestWindowsPsTree: + def test_windows_specific_pstree(self, volatility, python): + image = WindowsSamples.WINDOWS10_GENERIC.value.path + rc, out, _err = test_volatility.runvol_plugin( + "windows.pstree.PsTree", + image, + volatility, + python, + globalargs=("-r", "json"), + ) + assert rc == 0 + json_out = json.loads(out) + assert test_volatility.count_entries_flat(json_out) > 110 + expected_row = test_volatility.load_test_data( + "windows.pstree.PsTree", "WINDOWS10_GENERIC" + ) + + assert test_volatility.match_output_row( + expected_row, json_out, children_recursive=True + ) + + +class TestWindowsRegistry: + def test_windows_specific_registry_certificates(self, volatility, python): + image = WindowsSamples.WINDOWS10_GENERIC.value.path + rc, out, _err = test_volatility.runvol_plugin( + "windows.registry.certificates.Certificates", + image, + volatility, + python, + globalargs=("-r", "json"), + ) + assert rc == 0 + json_out = json.loads(out) + assert test_volatility.count_entries_flat(json_out) > 30 + expected_row = { + "Certificate ID": "ProtectedRoots", + "Certificate path": "Software\\Microsoft\\SystemCertificates", + "Certificate section": "Root", + } + assert test_volatility.match_output_row(expected_row, json_out) + + def test_windows_generic_registry_hivelist(self, volatility, python, image): + rc, out, _err = test_volatility.runvol_plugin( + "windows.registry.hivelist.HiveList", image, volatility, python + ) + assert rc == 0 + out = out.lower() + + not_xp = out.find(b"\\systemroot\\system32\\config\\software") + if not_xp == -1: + assert ( + out.find( + b"\\device\\harddiskvolume1\\windows\\system32\\config\\software" + ) + != -1 + ) + assert out.count(b"\n") > 10 + + def test_windows_specific_registry_hivescan(self, volatility, python): + image = WindowsSamples.WINDOWS10_GENERIC.value.path + rc, out, _err = test_volatility.runvol_plugin( + "windows.registry.hivescan.HiveScan", + image, + volatility, + python, + globalargs=("-r", "json"), + ) + assert rc == 0 + json_out = json.loads(out) + expected_rows = test_volatility.load_test_data( + "windows.registry.hivescan.HiveScan", "WINDOWS10_GENERIC" + ) + for expected_row in expected_rows: + assert test_volatility.match_output_row(expected_row, json_out) + + def test_windows_specific_registry_printkey(self, volatility, python): + image = WindowsSamples.WINDOWS10_GENERIC.value.path + rc, out, _err = test_volatility.runvol_plugin( + "windows.registry.printkey.PrintKey", + image, + volatility, + python, + globalargs=("-r", "json"), + ) + assert rc == 0 + json_out = json.loads(out) + assert test_volatility.count_entries_flat(json_out) > 450 + expected_rows = test_volatility.load_test_data( + "windows.registry.printkey.PrintKey", "WINDOWS10_GENERIC" + ) + for expected_row in expected_rows: + assert test_volatility.match_output_row(expected_row, json_out) + + def test_windows_specific_registry_userassist(self, volatility, python): + image = WindowsSamples.WINDOWS10_GENERIC.value.path + rc, out, _err = test_volatility.runvol_plugin( + "windows.registry.userassist.UserAssist", + image, + volatility, + python, + globalargs=("-r", "json"), + ) + assert rc == 0 + json_out = json.loads(out) + assert test_volatility.count_entries_flat(json_out) > 35 + expected_row = test_volatility.load_test_data( + "windows.registry.userassist.UserAssist", "WINDOWS10_GENERIC" + ) + assert test_volatility.match_output_row(expected_row, json_out) + + +class TestWindowsSessions: + def test_windows_specific_sessions(self, volatility, python): + image = WindowsSamples.WINDOWS10_GENERIC.value.path + rc, out, _err = test_volatility.runvol_plugin( + "windows.sessions.Sessions", + image, + volatility, + python, + globalargs=("-r", "json"), + ) + assert rc == 0 + json_out = json.loads(out) + assert test_volatility.count_entries_flat(json_out) > 115 + expected_rows = test_volatility.load_test_data( + "windows.sessions.Sessions", "WINDOWS10_GENERIC" + ) + for expected_row in expected_rows: + assert test_volatility.match_output_row(expected_row, json_out) + + +class TestWindowsShimcacheMem: + def test_windows_specific_shimcachemem(self, volatility, python): + image = WindowsSamples.WINDOWS10_GENERIC.value.path + rc, out, _err = test_volatility.runvol_plugin( + "windows.shimcachemem.ShimcacheMem", + image, + volatility, + python, + globalargs=("-r", "json"), + ) + assert rc == 0 + json_out = json.loads(out) + expected_rows = test_volatility.load_test_data( + "windows.shimcachemem.ShimcacheMem", "WINDOWS10_GENERIC" + ) + for expected_row in expected_rows: + assert test_volatility.match_output_row(expected_row, json_out) + + +class TestWindowsSSDT: + def test_windows_specific_ssdt(self, volatility, python): + image = WindowsSamples.WINDOWS10_GENERIC.value.path + rc, out, _err = test_volatility.runvol_plugin( + "windows.ssdt.SSDT", + image, + volatility, + python, + ) + assert rc == 0 + assert out.count(b"\n") > 770 + assert out.find(b"ntoskrnl") != -1 + assert out.find(b"Nt") != -1 + assert out.find(b"xHal") != -1 + + +class TestWindowsThreads: + def test_windows_specific_threads(self, volatility, python): + image = WindowsSamples.WINDOWS10_GENERIC.value.path + rc, out, _err = test_volatility.runvol_plugin( + "windows.threads.Threads", + image, + volatility, + python, + ) + assert rc == 0 + assert out.count(b"\n") > 1730 + + +class TestWindowsTimers: + def test_windows_specific_timers(self, volatility, python): + image = WindowsSamples.WINDOWSXP_GENERIC.value.path + rc, out, _err = test_volatility.runvol_plugin( + "windows.timers.Timers", + image, + volatility, + python, + globalargs=("-r", "json"), + ) + assert rc == 0 + json_out = json.loads(out) + expected_rows = test_volatility.load_test_data( + "windows.timers.Timers", "WINDOWSXP_GENERIC" + ) + for expected_row in expected_rows: + assert test_volatility.match_output_row(expected_row, json_out) + + +class TestWindowsVadInfo: + def test_windows_specific_vadinfo(self, volatility, python): + image = WindowsSamples.WINDOWS10_GENERIC.value.path + rc, out, _err = test_volatility.runvol_plugin( + "windows.vadinfo.VadInfo", + image, + volatility, + python, + globalargs=("-r", "json"), + pluginargs=("--pid", "4"), + ) + assert rc == 0 + json_out = json.loads(out) + expected_rows = test_volatility.load_test_data( + "windows.vadinfo.VadInfo", "WINDOWS10_GENERIC" + ) + for expected_row in expected_rows: + assert test_volatility.match_output_row(expected_row, json_out) + + +class TestWindowsVerInfo: + def test_windows_specific_verinfo(self, volatility, python): + image = WindowsSamples.WINDOWSXP_GENERIC.value.path + rc, out, _err = test_volatility.runvol_plugin( + "windows.verinfo.VerInfo", + image, + volatility, + python, + globalargs=("-r", "json"), + ) + assert rc == 0 + json_out = json.loads(out) + assert test_volatility.count_entries_flat(json_out) > 125 + expected_row = { + "Base": 2152558592, + "Build": 2622, + "Major": 5, + "Minor": 1, + "Name": "ntoskrnl.exe", + "Product": 2600, + "__children": [], + } + assert test_volatility.match_output_row(expected_row, json_out) + + +class TestWindowsVirtMap: + def test_windows_specific_virtmap(self, volatility, python): + image = WindowsSamples.WINDOWS10_GENERIC.value.path + rc, out, _err = test_volatility.runvol_plugin( + "windows.virtmap.VirtMap", + image, + volatility, + python, + globalargs=("-r", "json"), + ) + assert rc == 0 + json_out = json.loads(out) + expected_rows = test_volatility.load_test_data( + "windows.virtmap.VirtMap", "WINDOWS10_GENERIC" + ) + for expected_row in expected_rows: + assert test_volatility.match_output_row(expected_row, json_out) + diff --git a/test/test_volatility.py b/test/test_volatility.py index 632a6bcde..9b7aff4be 100644 --- a/test/test_volatility.py +++ b/test/test_volatility.py @@ -11,8 +11,15 @@ import sys import tempfile import contextlib import functools +import json +import logging from typing import List, Tuple +from test import WINDOWS_TESTS_DATA_DIR + +test_logger = logging.getLogger(__name__) + + # # HELPER FUNCTIONS # @@ -55,9 +62,9 @@ def runvol_plugin( return runvol(args, volatility, python) -def runvolshell(img, volshell, python, volshellargs=None, globalargs=None): - volshellargs = volshellargs or [] - globalargs = globalargs or [] +def runvolshell( + img, volshell, python, volshellargs: Tuple = (), globalargs: Tuple = () +): args = ( globalargs + ( @@ -71,25 +78,100 @@ def runvolshell(img, volshell, python, volshellargs=None, globalargs=None): return runvol(args, volshell, python) +def load_test_data(plugin: str, test_key: str): + if plugin.startswith("windows."): + data_path = WINDOWS_TESTS_DATA_DIR / f"{plugin}.json" + # TODO: add Linux and macOS when any of these requires this API + else: + raise Exception(f"Cannot determine OS of plugin: {plugin}") + + if not data_path.exists(): + raise FileNotFoundError( + f"Test data not found for plugin {plugin} at {data_path}" + ) + + with open(data_path) as f: + # This will raise an explicit exception by itself on failures + return json.load(f)[test_key] + + +def dict_lower_strvalues(dict_to_convert: dict): + """Lower each value of type string of a dictionary + + Args: + dict_to_convert: The dictionary in which to lower the string values + Returns: + A copy of the dictionary with lowered string values + """ + + converted = {} + for key, value in dict_to_convert.items(): + if isinstance(value, str): + converted[key] = value.lower() + else: + converted[key] = value + return converted + + def match_output_row( - expected_row: dict, plugin_json_out: List[dict], exact_match: bool = False + expected_row: dict, + plugin_json_out: List[dict], + exact_match: bool = False, + case_sensitive: bool = True, + children_recursive: bool = False, ): - """Search each row of a plugin's JSON output for an expected row. Each row is a dict. + """Search each row in a plugin's JSON output for a matching row. + This method supports recursive comparisons using the "__children" key, making it useful for testing hierarchical plugins like windows.pstree. + It also maintains case sensitivity and exact matching behavior when traversing nested structures. Args: expected_row: The expected row to be found in the output plugin_json_out: The plugin's output in JSON format (typically obtained through -r json and json.loads) - exact_match: Whether to require exactly the expected row, no more no less, or to anticipate columns' addition by checking only + exact_match: Require exactly the expected row, no more no less, or anticipate columns' addition by checking only the expected row keys and values + case_sensitive: Operate case sensitive match for str values of both dictionaries or not + children_recursive: Perform a recursive match by inspecting "__children" keys of each expected_row + + Returns: + A boolean indicating whether a match was found or not """ + # Lower each string value of both dicts + if not case_sensitive: + expected_row = dict_lower_strvalues(expected_row) + plugin_json_out_tmp = [] + for row in plugin_json_out: + plugin_json_out_tmp.append(dict_lower_strvalues(row)) + plugin_json_out = plugin_json_out_tmp + if not exact_match: for row in plugin_json_out: if all( - expected_item in row.items() for expected_item in expected_row.items() + expected_item in row.items() + for expected_item in expected_row.items() + if not expected_item[0] == "__children" ): - return True + if ( + children_recursive + and "__children" in expected_row + and "__children" in row + ): + for children_expected_row in expected_row["__children"]: + if not match_output_row( + children_expected_row, + row["__children"], + case_sensitive=case_sensitive, + children_recursive=True, + ): + break + else: + # We matched all the children keys + return True + else: + # No recursion required and we already matched the row + return True else: + # No "__children" recursion here as we want to match the whole tree at once for row in plugin_json_out: if expected_row == row: return True @@ -97,12 +179,26 @@ def match_output_row( return False +def count_entries_flat(plugin_json_out: List[dict]): + """Count the number of entries as if -r json wasn't specified. Allows to get a non-hierarchical count, without running a plugin twice + (once with "-r json" and once without) while still preserving JSON features. + + Args: + plugin_json_out: The plugin's output in JSON format (typically obtained through -r json and json.loads) + """ + # Remove whitespaces between entries + # If a value contains {", it will be represented by {\" so no confusion + return json.dumps(plugin_json_out, separators=(",", ":")).count('{"') + + # # TESTS # -def basic_volshell_test(image, volatility, python, globalargs): +def basic_volshell_test( + image, volatility, python, volshellargs: Tuple = (), globalargs: Tuple = () +): # Basic VolShell test to verify requirements and ensure VolShell runs without crashing volshell_commands = [ @@ -122,7 +218,7 @@ def basic_volshell_test(image, volatility, python, globalargs): img=image, volshell=volatility, python=python, - volshellargs=("--script", filename), + volshellargs=("--script", filename) + volshellargs, globalargs=globalargs, ) finally: diff --git a/volatility3/cli/volshell/generic.py b/volatility3/cli/volshell/generic.py index 2321408fe..3a5d514fe 100644 --- a/volatility3/cli/volshell/generic.py +++ b/volatility3/cli/volshell/generic.py @@ -8,6 +8,7 @@ import random import string import struct import sys +import textwrap from typing import Any, Dict, Iterable, List, Optional, Tuple, Type, Union from urllib import parse, request @@ -23,6 +24,14 @@ try: except ImportError: has_capstone = False +try: + from IPython import terminal + from traitlets import config as traitlets_config + + has_ipython = True +except ImportError: + has_ipython = False + class Volshell(interfaces.plugins.PluginInterface): """Shell environment to directly interact with a memory image.""" @@ -51,7 +60,13 @@ class Volshell(interfaces.plugins.PluginInterface): description="File to load and execute at start", default=None, optional=True, - ) + ), + requirements.BooleanRequirement( + name="script-only", + description="Exit volshell after the script specified in --script completes", + default=False, + optional=True, + ), ] return reqs + [ requirements.TranslationLayerRequirement( @@ -69,43 +84,70 @@ class Volshell(interfaces.plugins.PluginInterface): """ # Try to enable tab completion - try: - import readline - except ImportError: - pass - else: - import rlcompleter + if not has_ipython: + try: + import readline + import rlcompleter - completer = rlcompleter.Completer(namespace=self._construct_locals_dict()) - readline.set_completer(completer.complete) - readline.parse_and_bind("tab: complete") - print("Readline imported successfully") + completer = rlcompleter.Completer( + namespace=self._construct_locals_dict() + ) + readline.set_completer(completer.complete) + readline.parse_and_bind("tab: complete") + print("Readline imported successfully") + except ImportError: + print( + "Readline or rlcompleter module could not be imported. Tab completion will not be available." + ) # TODO: provide help, consider generic functions (pslist?) and/or providing windows/linux functions mode = self.__module__.split(".")[-1] mode = mode[0].upper() + mode[1:] - banner = f""" - Call help() to see available functions + banner = textwrap.dedent( + f""" + Call help() to see available functions - Volshell mode : {mode} - Current Layer : {self.current_layer} - Current Symbol Table : {self.current_symbol_table} - Current Kernel Name : {self.current_kernel_name} -""" + Volshell mode : {mode} + Current Layer : {self.current_layer} + Current Symbol Table : {self.current_symbol_table} + Current Kernel Name : {self.current_kernel_name} + """ + ) sys.ps1 = f"({self.current_layer}) >>> " # Dict self._construct_locals_dict() will have priority on keys combined_locals = additional_locals.copy() combined_locals.update(self._construct_locals_dict()) - self.__console = code.InteractiveConsole(locals=combined_locals) + if has_ipython: + + class LayerNamePrompt(terminal.prompts.Prompts): + def in_prompt_tokens(self, cli=None): + slf = self.shell.user_ns.get("self") + layer_name = slf.current_layer if slf else "no_layer" + return [(terminal.prompts.Token.Prompt, f"[{layer_name}]> ")] + + c = traitlets_config.Config() + c.TerminalInteractiveShell.prompts_class = LayerNamePrompt + c.InteractiveShellEmbed.banner2 = banner + self.__console = terminal.embed.InteractiveShellEmbed( + config=c, user_ns=combined_locals + ) + else: + self.__console = code.InteractiveConsole(locals=combined_locals) # Since we have to do work to add the option only once for all different modes of volshell, we can't # rely on the default having been set if self.config.get("script", None) is not None: self.run_script(location=self.config["script"]) - self.__console.interact(banner=banner) + if self.config.get("script-only"): + exit() + + if has_ipython: + self.__console() + else: + self.__console.interact(banner=banner) return renderers.TreeGrid([("Terminating", str)], None) @@ -277,23 +319,25 @@ class Volshell(interfaces.plugins.PluginInterface): self._display_data(offset, remaining_data) def display_quadwords( - self, offset, count=DEFAULT_NUM_DISPLAY_BYTES, layer_name=None + self, offset, count=DEFAULT_NUM_DISPLAY_BYTES, layer_name=None, byteorder="@" ): """Displays quad-word values (8 bytes) and corresponding ASCII characters""" remaining_data = self._read_data(offset, count=count, layer_name=layer_name) - self._display_data(offset, remaining_data, format_string="Q") + self._display_data(offset, remaining_data, format_string=f"{byteorder}Q") def display_doublewords( - self, offset, count=DEFAULT_NUM_DISPLAY_BYTES, layer_name=None + self, offset, count=DEFAULT_NUM_DISPLAY_BYTES, layer_name=None, byteorder="@" ): """Displays double-word values (4 bytes) and corresponding ASCII characters""" remaining_data = self._read_data(offset, count=count, layer_name=layer_name) - self._display_data(offset, remaining_data, format_string="I") + self._display_data(offset, remaining_data, format_string=f"{byteorder}I") - def display_words(self, offset, count=DEFAULT_NUM_DISPLAY_BYTES, layer_name=None): + def display_words( + self, offset, count=DEFAULT_NUM_DISPLAY_BYTES, layer_name=None, byteorder="@" + ): """Displays word values (2 bytes) and corresponding ASCII characters""" remaining_data = self._read_data(offset, count=count, layer_name=layer_name) - self._display_data(offset, remaining_data, format_string="H") + self._display_data(offset, remaining_data, format_string=f"{byteorder}H") def regex_scan(self, pattern, count=DEFAULT_NUM_DISPLAY_BYTES, layer_name=None): """Scans for regex pattern in layer using RegExScanner.""" @@ -508,10 +552,13 @@ class Volshell(interfaces.plugins.PluginInterface): location = "file:" + request.pathname2url(location) print(f"Running code from {location}\n") accessor = resources.ResourceAccessor() - with accessor.open(url=location) as fp: - self.__console.runsource( - io.TextIOWrapper(fp, encoding="utf-8").read(), symbol="exec" - ) + with accessor.open(url=location) as handle, io.TextIOWrapper( + handle, encoding="utf-8" + ) as fp: + if has_ipython: + self.__console.ex(fp.read()) + else: + self.__console.runsource(fp.read(), symbol="exec") print("\nCode complete") def load_file(self, location: str): diff --git a/volatility3/cli/volshell/linux.py b/volatility3/cli/volshell/linux.py index b3689c3ae..27c630614 100644 --- a/volatility3/cli/volshell/linux.py +++ b/volatility3/cli/volshell/linux.py @@ -30,8 +30,8 @@ class Volshell(generic.Volshell): requirements.ModuleRequirement( name="kernel", description="Linux kernel module" ), - requirements.PluginRequirement( - name="pslist", plugin=pslist.PsList, version=(4, 0, 0) + requirements.VersionRequirement( + name="pslist", component=pslist.PsList, version=(4, 0, 0) ), requirements.IntRequirement( name="pid", description="Process ID", optional=True diff --git a/volatility3/cli/volshell/mac.py b/volatility3/cli/volshell/mac.py index 0ed35eb27..393eff20b 100644 --- a/volatility3/cli/volshell/mac.py +++ b/volatility3/cli/volshell/mac.py @@ -19,8 +19,8 @@ class Volshell(generic.Volshell): requirements.ModuleRequirement( name="kernel", description="Darwin kernel module" ), - requirements.PluginRequirement( - name="pslist", plugin=pslist.PsList, version=(3, 0, 0) + requirements.VersionRequirement( + name="pslist", component=pslist.PsList, version=(3, 0, 0) ), requirements.IntRequirement( name="pid", description="Process ID", optional=True diff --git a/volatility3/cli/volshell/windows.py b/volatility3/cli/volshell/windows.py index c5bab3b74..ce5995648 100644 --- a/volatility3/cli/volshell/windows.py +++ b/volatility3/cli/volshell/windows.py @@ -17,8 +17,8 @@ class Volshell(generic.Volshell): def get_requirements(cls): return [ requirements.ModuleRequirement(name="kernel", description="Windows kernel"), - requirements.PluginRequirement( - name="pslist", plugin=pslist.PsList, version=(3, 0, 0) + requirements.VersionRequirement( + name="pslist", component=pslist.PsList, version=(3, 0, 0) ), requirements.IntRequirement( name="pid", description="Process ID", optional=True diff --git a/volatility3/framework/constants/_version.py b/volatility3/framework/constants/_version.py index aa8e8936f..f5da4c75b 100644 --- a/volatility3/framework/constants/_version.py +++ b/volatility3/framework/constants/_version.py @@ -1,6 +1,6 @@ # We use the SemVer 2.0.0 versioning scheme VERSION_MAJOR = 2 # Number of releases of the library with a breaking change -VERSION_MINOR = 23 # Number of changes that only add to the interface +VERSION_MINOR = 25 # Number of changes that only add to the interface VERSION_PATCH = 0 # Number of changes that do not change the interface VERSION_SUFFIX = "" diff --git a/volatility3/framework/objects/__init__.py b/volatility3/framework/objects/__init__.py index 39ce6f59f..b863e103b 100644 --- a/volatility3/framework/objects/__init__.py +++ b/volatility3/framework/objects/__init__.py @@ -402,13 +402,35 @@ class Pointer(Integer): pointer should be recast. The "pointer" must always live within the space (even if the data provided is invalid). """ + mask = context.layers[object_info.native_layer_name].address_mask + new = ( + cls._get_raw_value( + context, data_format, object_info.layer_name, object_info.offset + ) + & mask + ) + return new + + @classmethod + def _get_raw_value( + cls, + context: interfaces.context.ContextInterface, + data_format: DataFormatInfo, + layer_name: str, + offset: int, + ) -> int: length, endian, signed = data_format if signed: raise ValueError("Pointers cannot have signed values") - mask = context.layers[object_info.native_layer_name].address_mask - data = context.layers.read(object_info.layer_name, object_info.offset, length) + data = context.layers.read(layer_name, offset, length) value = int.from_bytes(data, byteorder=endian, signed=signed) - return value & mask + return value + + def get_raw_value(self) -> int: + raw = self._get_raw_value( + self._context, self.vol.data_format, self.vol.layer_name, self.vol.offset + ) + return raw def dereference( self, layer_name: Optional[str] = None diff --git a/volatility3/framework/objects/utility.py b/volatility3/framework/objects/utility.py index 500c0e9a5..ef702060c 100644 --- a/volatility3/framework/objects/utility.py +++ b/volatility3/framework/objects/utility.py @@ -2,9 +2,11 @@ # which is available at https://www.volatilityfoundation.org/license/vsl-v1.0 # +import re + from typing import Optional, Union -from volatility3.framework import interfaces, objects, constants +from volatility3.framework import interfaces, objects, constants, exceptions def rol(value: int, count: int, max_bits: int = 64) -> int: @@ -33,6 +35,7 @@ def array_to_string( count: Optional[int] = None, errors: str = "replace", block_size=32, + encoding="utf-8", ) -> str: """Takes a Volatility 'Array' of characters and returns a Python string. @@ -60,6 +63,7 @@ def array_to_string( count=count, errors=errors, block_size=block_size, + encoding=encoding, ) @@ -68,6 +72,7 @@ def pointer_to_string( count: int, errors: str = "replace", block_size=32, + encoding="utf-8", ) -> str: """Takes a Volatility 'Pointer' to characters and returns a Python string. @@ -94,9 +99,101 @@ def pointer_to_string( count=count, errors=errors, block_size=block_size, + encoding=encoding, ) +def gather_contiguous_bytes_from_address( + context, data_layer, starting_address: int, count: int +) -> bytes: + """ + This method reconstructs a string from memory while also carefully examining each page + + It goes page-by-page reading the bytes. This is done by calculating page boundaries + and then only reading one page at a time. + + If a page is missing, the code initially catches the exception. + If data is non-empty (meaning at least one read succeeded), then we return what was read + If the first page fails, then we re-raise the exception + """ + + data = b"" + + if isinstance(data_layer, interfaces.layers.TranslationLayerInterface): + last_address = starting_address + + for address, length, _, _, _ in data_layer.mapping( + offset=starting_address, length=count, ignore_errors=True + ): + # we hit a swapped out page + if last_address != address: + break + + data += data_layer.read(address, length) + + last_address = address + length + + elif starting_address + count < data_layer.maximum_address: + data = data_layer.read(starting_address, count) + + # if we were able to read from the first page, we want to try and construct the string + # if the first page fails -> throw exception + if data: + return data + else: + raise exceptions.InvalidAddressException( + layer_name=data_layer, invalid_address=starting_address + ) + + +def bytes_to_decoded_string( + data: bytes, encoding: str, errors: str, return_truncated: bool = True +) -> str: + """ + Args: + data: The `bytes` buffer containing the string of a string at offset 0 + encoding: An encoding value for the encoding paramater of `bytes.decode` + errors: An errors value for the errors parameter of `bytes.decode` + return_truncated: Dictates whether truncated strings should be returned or + if a ValueError should be thrown if a truncated (broken) string was decoded + Returns: + bytes: The decoded string starting at offset of data + + This function takes a bytes buffer that contains at a string of unknown + length starting at the first byte, and returns the properly decoded string + + It starts by using Python's `bytes.decode` to attempt to decode the entire string + It then finds the termination character (\ufffd or \x00) and splices the string + Finally, it returns this spliced string after its been decoded with the + caller-specified encoding + """ + # this is the standard byte used to replace bad unicode characters + unicode_replacement_char = "\ufffd" + + # used to find the terminating byte + termination_re = re.compile(f"{unicode_replacement_char}|\x00") + + # run over the entire string, letting Python replace invalid characters + full_decoded_string = data.decode(encoding=encoding, errors="replace") + + # stop at the first terminating character or get the whole string if not found + try: + idx = termination_re.search(full_decoded_string).start() + except AttributeError: + if return_truncated: + idx = len(full_decoded_string) + else: + raise ValueError( + "return_truncated set to False and truncated string decoded." + ) + + # cut at terminating byte, if found + data = bytes(full_decoded_string[:idx], encoding=encoding) + + # return with caller-specified encoding and errors + return data.decode(encoding=encoding, errors=errors) + + def address_to_string( context: interfaces.context.ContextInterface, layer_name: str, @@ -104,6 +201,7 @@ def address_to_string( count: int, errors: str = "replace", block_size=32, + encoding="utf-8", ) -> str: """Reads a null-terminated string from a given specified memory address, processing it in blocks for efficiency. @@ -126,18 +224,10 @@ def address_to_string( raise ValueError("Count must be greater than 0") layer = context.layers[layer_name] - text = b"" - while len(text) < count: - current_block_size = min(count - len(text), block_size) - temp_text = layer.read(address + len(text), current_block_size) - idx = temp_text.find(b"\x00") - if idx != -1: - temp_text = temp_text[:idx] - text += temp_text - break - text += temp_text - return text.decode(errors=errors) + data = gather_contiguous_bytes_from_address(context, layer, address, count) + + return bytes_to_decoded_string(data=data, errors=errors, encoding=encoding) def array_of_pointers( diff --git a/volatility3/framework/plugins/linux/bash.py b/volatility3/framework/plugins/linux/bash.py index 293c47224..2a63ac329 100644 --- a/volatility3/framework/plugins/linux/bash.py +++ b/volatility3/framework/plugins/linux/bash.py @@ -32,8 +32,13 @@ class Bash(plugins.PluginInterface, timeliner.TimeLinerInterface): description="Linux kernel", architectures=["Intel32", "Intel64"], ), - requirements.PluginRequirement( - name="pslist", plugin=pslist.PsList, version=(4, 0, 0) + requirements.VersionRequirement( + name="pslist", component=pslist.PsList, version=(4, 0, 0) + ), + requirements.VersionRequirement( + name="timeliner", + component=timeliner.TimeLinerInterface, + version=(1, 0, 0), ), requirements.ListRequirement( name="pid", diff --git a/volatility3/framework/plugins/linux/boottime.py b/volatility3/framework/plugins/linux/boottime.py index c57bdd65a..0b9abb856 100644 --- a/volatility3/framework/plugins/linux/boottime.py +++ b/volatility3/framework/plugins/linux/boottime.py @@ -25,8 +25,13 @@ class Boottime(interfaces.plugins.PluginInterface, timeliner.TimeLinerInterface) description="Linux kernel", architectures=["Intel32", "Intel64"], ), - requirements.PluginRequirement( - name="pslist", plugin=pslist.PsList, version=(4, 0, 0) + requirements.VersionRequirement( + name="timeliner", + component=timeliner.TimeLinerInterface, + version=(1, 0, 0), + ), + requirements.VersionRequirement( + name="pslist", component=pslist.PsList, version=(4, 0, 0) ), ] diff --git a/volatility3/framework/plugins/linux/capabilities.py b/volatility3/framework/plugins/linux/capabilities.py index 1d0c60c11..dae6aac6a 100644 --- a/volatility3/framework/plugins/linux/capabilities.py +++ b/volatility3/framework/plugins/linux/capabilities.py @@ -60,8 +60,8 @@ class Capabilities(plugins.PluginInterface): description="Linux kernel", architectures=["Intel32", "Intel64"], ), - requirements.PluginRequirement( - name="pslist", plugin=pslist.PsList, version=(4, 0, 0) + requirements.VersionRequirement( + name="pslist", component=pslist.PsList, version=(4, 0, 0) ), requirements.ListRequirement( name="pids", diff --git a/volatility3/framework/plugins/linux/check_creds.py b/volatility3/framework/plugins/linux/check_creds.py index 96f77ce4d..e2b84d679 100644 --- a/volatility3/framework/plugins/linux/check_creds.py +++ b/volatility3/framework/plugins/linux/check_creds.py @@ -22,8 +22,8 @@ class Check_creds(interfaces.plugins.PluginInterface): description="Linux kernel", architectures=["Intel32", "Intel64"], ), - requirements.PluginRequirement( - name="pslist", plugin=pslist.PsList, version=(4, 0, 0) + requirements.VersionRequirement( + name="pslist", component=pslist.PsList, version=(4, 0, 0) ), ] diff --git a/volatility3/framework/plugins/linux/check_idt.py b/volatility3/framework/plugins/linux/check_idt.py index dbdb0e9be..e199d98d3 100644 --- a/volatility3/framework/plugins/linux/check_idt.py +++ b/volatility3/framework/plugins/linux/check_idt.py @@ -10,7 +10,6 @@ from volatility3.framework import interfaces, renderers, symbols from volatility3.framework.configuration import requirements from volatility3.framework.renderers import format_hints from volatility3.framework.symbols import linux -from volatility3.plugins.linux import lsmod vollog = logging.getLogger(__name__) @@ -34,14 +33,16 @@ class Check_idt(interfaces.plugins.PluginInterface): requirements.VersionRequirement( name="linux_utilities_modules", component=linux_utilities_modules.Modules, - version=(2, 0, 0), + version=(3, 0, 0), + ), + requirements.VersionRequirement( + name="linux_utilities_module_gatherers", + component=linux_utilities_modules.ModuleGatherers, + version=(1, 0, 0), ), requirements.VersionRequirement( name="linuxutils", component=linux.LinuxUtilities, version=(2, 0, 0) ), - requirements.PluginRequirement( - name="lsmod", plugin=lsmod.Lsmod, version=(2, 0, 0) - ), ] @staticmethod @@ -82,10 +83,10 @@ class Check_idt(interfaces.plugins.PluginInterface): vmlinux = self.context.modules[self.config["kernel"]] - modules = lsmod.Lsmod.list_modules(self.context, vmlinux.name) - - handlers = linux.LinuxUtilities.generate_kernel_handler_info( - self.context, vmlinux.name, modules + known_modules = linux_utilities_modules.Modules.run_modules_scanners( + context=self.context, + kernel_module_name=self.config["kernel"], + caller_wanted_gatherers=linux_utilities_modules.ModuleGatherers.all_gatherers_identifier, ) idt_table_size = 256 @@ -134,19 +135,24 @@ class Check_idt(interfaces.plugins.PluginInterface): module_name = renderers.NotAvailableValue() symbol_name = renderers.NotAvailableValue() else: - module_name, symbol_name = ( - linux_utilities_modules.Modules.lookup_module_address( - self.context, vmlinux.name, handlers, idt_addr + module_info, symbol_name = ( + linux_utilities_modules.Modules.module_lookup_by_address( + self.context, vmlinux.name, known_modules, idt_addr ) ) + if module_info: + module_name = module_info.name + else: + module_name = renderers.NotAvailableValue() + yield ( 0, [ format_hints.Hex(i), format_hints.Hex(idt_addr), module_name, - symbol_name, + symbol_name or renderers.NotAvailableValue(), ], ) diff --git a/volatility3/framework/plugins/linux/check_modules.py b/volatility3/framework/plugins/linux/check_modules.py index 76f75ec50..5a43bf899 100644 --- a/volatility3/framework/plugins/linux/check_modules.py +++ b/volatility3/framework/plugins/linux/check_modules.py @@ -3,15 +3,14 @@ # import logging -from typing import List, Dict +from typing import List, Dict, Generator import volatility3.framework.symbols.linux.utilities.modules as linux_utilities_modules -from volatility3.framework import interfaces, renderers, deprecation +from volatility3.framework import interfaces, deprecation from volatility3.framework.configuration import requirements -from volatility3.framework.interfaces import plugins from volatility3.framework.objects import utility -from volatility3.framework.renderers import format_hints from volatility3.framework.symbols.linux import extensions +from volatility3.framework.interfaces import plugins vollog = logging.getLogger(__name__) @@ -19,9 +18,31 @@ vollog = logging.getLogger(__name__) class Check_modules(plugins.PluginInterface): """Compares module list to sysfs info, if available""" - _version = (2, 0, 0) + _version = (3, 0, 0) _required_framework_version = (2, 0, 0) + @classmethod + def compare_kset_and_lsmod( + cls, context: str, vmlinux_name: str + ) -> Generator[extensions.module, None, None]: + kset_modules = linux_utilities_modules.Modules.get_kset_modules( + context=context, vmlinux_name=vmlinux_name + ) + + lsmod_modules = set( + str(utility.array_to_string(modules.name)) + for modules in linux_utilities_modules.Modules.list_modules( + context=context, vmlinux_module_name=vmlinux_name + ) + ) + + for mod_name in set(kset_modules.keys()).difference(lsmod_modules): + yield kset_modules[mod_name] + + run = linux_utilities_modules.ModuleDisplayPlugin.run + _generator = linux_utilities_modules.ModuleDisplayPlugin.generator + implementation = compare_kset_and_lsmod + @classmethod def get_requirements(cls) -> List[interfaces.configuration.RequirementInterface]: return [ @@ -31,9 +52,9 @@ class Check_modules(plugins.PluginInterface): architectures=["Intel32", "Intel64"], ), requirements.VersionRequirement( - name="linux_utilities_modules", - component=linux_utilities_modules.Modules, - version=(2, 0, 0), + name="linux_utilities_modules_module_display_plugin", + component=linux_utilities_modules.ModuleDisplayPlugin, + version=(1, 0, 0), ), ] @@ -41,30 +62,9 @@ class Check_modules(plugins.PluginInterface): @deprecation.deprecated_method( replacement=linux_utilities_modules.Modules.get_kset_modules, removal_date="2025-09-25", - replacement_version=(2, 0, 0), + replacement_version=(3, 0, 0), ) def get_kset_modules( cls, context: interfaces.context.ContextInterface, vmlinux_name: str ) -> Dict[str, extensions.module]: return linux_utilities_modules.Modules.get_kset_modules(context, vmlinux_name) - - def _generator(self): - kset_modules = linux_utilities_modules.Modules.get_kset_modules( - self.context, self.config["kernel"] - ) - - lsmod_modules = set( - str(utility.array_to_string(modules.name)) - for modules in linux_utilities_modules.Modules.list_modules( - self.context, self.config["kernel"] - ) - ) - - for mod_name in set(kset_modules.keys()).difference(lsmod_modules): - yield (0, (format_hints.Hex(kset_modules[mod_name]), str(mod_name))) - - def run(self): - return renderers.TreeGrid( - [("Module Address", format_hints.Hex), ("Module Name", str)], - self._generator(), - ) diff --git a/volatility3/framework/plugins/linux/check_syscall.py b/volatility3/framework/plugins/linux/check_syscall.py index 9ffd4c497..724a67810 100644 --- a/volatility3/framework/plugins/linux/check_syscall.py +++ b/volatility3/framework/plugins/linux/check_syscall.py @@ -172,8 +172,11 @@ class Check_syscall(plugins.PluginInterface): count=tblsz, ) - for i, call_addr in enumerate(table): - if not call_addr: + for i in range(len(table)): + try: + call_addr = table[i] + except exceptions.InvalidAddressException: + vollog.debug(f"Failed to get system call table entry at index {i}") continue symbols = list(vmlinux.get_symbols_by_absolute_location(call_addr)) diff --git a/volatility3/framework/plugins/linux/elfs.py b/volatility3/framework/plugins/linux/elfs.py index b9dcc3cca..8b2759907 100644 --- a/volatility3/framework/plugins/linux/elfs.py +++ b/volatility3/framework/plugins/linux/elfs.py @@ -35,8 +35,8 @@ class Elfs(plugins.PluginInterface): description="Linux kernel", architectures=["Intel32", "Intel64"], ), - requirements.PluginRequirement( - name="pslist", plugin=pslist.PsList, version=(4, 0, 0) + requirements.VersionRequirement( + name="pslist", component=pslist.PsList, version=(4, 0, 0) ), requirements.ListRequirement( name="pid", diff --git a/volatility3/framework/plugins/linux/envars.py b/volatility3/framework/plugins/linux/envars.py index 0687caa9f..f4859cb49 100644 --- a/volatility3/framework/plugins/linux/envars.py +++ b/volatility3/framework/plugins/linux/envars.py @@ -29,8 +29,8 @@ class Envars(plugins.PluginInterface): description="Linux kernel", architectures=["Intel32", "Intel64"], ), - requirements.PluginRequirement( - name="pslist", plugin=pslist.PsList, version=(4, 0, 0) + requirements.VersionRequirement( + name="pslist", component=pslist.PsList, version=(4, 0, 0) ), requirements.ListRequirement( name="pid", diff --git a/volatility3/framework/plugins/linux/hidden_modules.py b/volatility3/framework/plugins/linux/hidden_modules.py index 9891bf138..c6f5d749e 100644 --- a/volatility3/framework/plugins/linux/hidden_modules.py +++ b/volatility3/framework/plugins/linux/hidden_modules.py @@ -6,86 +6,21 @@ from typing import List, Set, Tuple, Iterable from volatility3.framework.symbols.linux.utilities import ( modules as linux_utilities_modules, ) -from volatility3.framework import renderers, interfaces, exceptions, deprecation +from volatility3.framework import interfaces, exceptions, deprecation 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 +from volatility3.framework.symbols.linux import extensions +from volatility3.framework.interfaces import plugins vollog = logging.getLogger(__name__) -class Hidden_modules(interfaces.plugins.PluginInterface): +class Hidden_modules(plugins.PluginInterface): """Carves memory to find hidden kernel modules""" _required_framework_version = (2, 10, 0) - _version = (2, 0, 0) + _version = (3, 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) - ), - requirements.VersionRequirement( - name="linux_utilities_modules", - component=linux_utilities_modules.Modules, - version=(2, 0, 0), - ), - ] - - @staticmethod - @deprecation.deprecated_method( - replacement=linux_utilities_modules.Modules.get_modules_memory_boundaries, - removal_date="2025-09-25", - replacement_version=(2, 0, 0), - ) - def get_modules_memory_boundaries( - context: interfaces.context.ContextInterface, - vmlinux_module_name: str, - ) -> Tuple[int, int]: - return linux_utilities_modules.Modules.get_modules_memory_boundaries( - context, vmlinux_module_name - ) - - @deprecation.deprecated_method( - replacement=linux_utilities_modules.Modules.get_module_address_alignment, - removal_date="2025-09-25", - replacement_version=(2, 0, 0), - ) - @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 - """ - return linux_utilities_modules.get_module_address_alignment( - context, vmlinux_module_name - ) - - @deprecation.deprecated_method( - replacement=linux_utilities_modules.Modules.get_hidden_modules, - removal_date="2025-09-25", - replacement_version=(2, 0, 0), - ) @classmethod def get_hidden_modules( cls, @@ -120,11 +55,77 @@ class Hidden_modules(interfaces.plugins.PluginInterface): vmlinux_module_name, known_module_addresses, modules_memory_boundaries ) + run = linux_utilities_modules.ModuleDisplayPlugin.run + _generator = linux_utilities_modules.ModuleDisplayPlugin.generator + implementation = linux_utilities_modules.Modules.list_modules + + @classmethod + def get_requirements(cls) -> List[interfaces.configuration.RequirementInterface]: + return [ + requirements.ModuleRequirement( + name="kernel", + description="Linux kernel", + architectures=architectures.LINUX_ARCHS, + ), + requirements.VersionRequirement( + name="linux_utilities_modules_module_display_plugin", + component=linux_utilities_modules.ModuleDisplayPlugin, + version=(1, 0, 0), + ), + ] + + @staticmethod + @deprecation.deprecated_method( + replacement=linux_utilities_modules.Modules.get_modules_memory_boundaries, + removal_date="2025-09-25", + replacement_version=(3, 0, 0), + ) + def get_modules_memory_boundaries( + context: interfaces.context.ContextInterface, + vmlinux_module_name: str, + ) -> Tuple[int, int]: + return linux_utilities_modules.Modules.get_modules_memory_boundaries( + context, vmlinux_module_name + ) + + @deprecation.deprecated_method( + replacement=linux_utilities_modules.Modules.get_module_address_alignment, + removal_date="2025-09-25", + replacement_version=(3, 0, 0), + ) + @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 + """ + return linux_utilities_modules.get_module_address_alignment( + context, vmlinux_module_name + ) + + @deprecation.deprecated_method( + replacement=linux_utilities_modules.Modules.get_hidden_modules, + removal_date="2025-09-25", + replacement_version=(3, 0, 0), + ) @staticmethod @deprecation.deprecated_method( replacement=linux_utilities_modules.Modules.validate_alignment_patterns, removal_date="2025-09-25", - replacement_version=(2, 0, 0), + replacement_version=(3, 0, 0), ) def _validate_alignment_patterns( addresses: Iterable[int], @@ -163,42 +164,35 @@ class Hidden_modules(interfaces.plugins.PluginInterface): known_module_addresses = { vmlinux_layer.canonicalize(module.vol.offset) - for module in lsmod.Lsmod.list_modules(context, vmlinux_module_name) + for module in linux_utilities_modules.Modules.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 = ( - linux_utilities_modules.Modules.get_modules_memory_boundaries( - self.context, vmlinux_module_name - ) - ) - - for module in linux_utilities_modules.Modules.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( + @classmethod + def find_hidden_modules( + cls, context, vmlinux_module_name: str + ) -> extensions.module: + if 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()) + known_module_addresses = cls.get_lsmod_module_addresses( + context, vmlinux_module_name + ) + modules_memory_boundaries = ( + linux_utilities_modules.Modules.get_modules_memory_boundaries( + context, vmlinux_module_name + ) + ) + + yield from linux_utilities_modules.Modules.get_hidden_modules( + context, + vmlinux_module_name, + known_module_addresses, + modules_memory_boundaries, + ) diff --git a/volatility3/framework/plugins/linux/iomem.py b/volatility3/framework/plugins/linux/iomem.py index 6732084db..5be6627fc 100644 --- a/volatility3/framework/plugins/linux/iomem.py +++ b/volatility3/framework/plugins/linux/iomem.py @@ -59,7 +59,7 @@ class IOMem(interfaces.plugins.PluginInterface): f"Unable to create resource object at {resource_offset:#x}. This resource, " "its sibling, and any of it's children and will be missing from the output." ) - return None + return # get name with protection against smear as following a pointer try: @@ -71,6 +71,15 @@ class IOMem(interfaces.plugins.PluginInterface): ) name = renderers.UnreadableValue() + try: + start = resource.start + end = resource.end + except exceptions.InvalidAddressException: + vollog.warning( + f"Unable to follow pointer to start and end for resource object at {resource_offset:#x}. Skipping entry." + ) + return + # mark this resource as seen in the seen set. Normally this should not be needed but will protect # against possible infinite loops. Warn the user if an infinite loop would have happened. if resource_offset in seen: @@ -79,12 +88,12 @@ class IOMem(interfaces.plugins.PluginInterface): "this should not normally occur. No further results from related resources will be " "displayed to protect against infinite loops." ) - return None + return else: seen.add(resource_offset) # yield information on this resource - yield depth, (name, resource.start, resource.end) + yield depth, (name, start, end) # process child resource if this exists if resource.child != 0: diff --git a/volatility3/framework/plugins/linux/kallsyms.py b/volatility3/framework/plugins/linux/kallsyms.py index 7dd4f06e6..c8bca03f7 100644 --- a/volatility3/framework/plugins/linux/kallsyms.py +++ b/volatility3/framework/plugins/linux/kallsyms.py @@ -73,6 +73,9 @@ class Kallsyms(plugins.PluginInterface): # resulting in incorrect values. Unfortunately, there isn't much that can be done # in such cases. # See comments on .init.scratch in arch/x86/kernel/vmlinux.lds.S for details + if not kassymbol or not kassymbol.size: + return renderers.NotAvailableValue() + return kassymbol.size if kassymbol.size >= 0 else renderers.NotAvailableValue() def _generator(self): @@ -95,6 +98,7 @@ class Kallsyms(plugins.PluginInterface): include_core = include_modules = include_ftrace = include_bpf = True symbol_generators = [] + if include_core: symbol_generators.append(kas.get_core_symbols()) if include_modules: @@ -106,17 +110,25 @@ class Kallsyms(plugins.PluginInterface): for symbols_generator in symbol_generators: for kassymbol in symbols_generator: + if not kassymbol: + continue # Symbol sizes are calculated using the address of the next non-aliased # symbol or the end of the kernel text area _end/_etext. However, some kernel # symbols are located beyond that area, which causes this method to fail for # the last symbol, resulting in a negative size. # See comments on .init.scratch in arch/x86/kernel/vmlinux.lds.S for details symbol_size = self._get_symbol_size(kassymbol) + + if kassymbol.exported is None: + exported = renderers.NotAvailableValue() + else: + exported = kassymbol.exported + fields = ( format_hints.Hex(kassymbol.address), - kassymbol.type, + kassymbol.type or renderers.NotAvailableValue(), symbol_size, - kassymbol.exported, + exported, kassymbol.subsystem, kassymbol.module_name, kassymbol.name, diff --git a/volatility3/framework/plugins/linux/keyboard_notifiers.py b/volatility3/framework/plugins/linux/keyboard_notifiers.py index 8fd2846c1..215704350 100644 --- a/volatility3/framework/plugins/linux/keyboard_notifiers.py +++ b/volatility3/framework/plugins/linux/keyboard_notifiers.py @@ -9,7 +9,6 @@ from volatility3.framework import interfaces, renderers, exceptions from volatility3.framework.configuration import requirements from volatility3.framework.renderers import format_hints from volatility3.framework.symbols import linux -from volatility3.plugins.linux import lsmod vollog = logging.getLogger(__name__) @@ -30,10 +29,12 @@ class Keyboard_notifiers(interfaces.plugins.PluginInterface): requirements.VersionRequirement( name="linux_utilities_modules", component=linux_utilities_modules.Modules, - version=(2, 0, 0), + version=(3, 0, 0), ), - requirements.PluginRequirement( - name="lsmod", plugin=lsmod.Lsmod, version=(2, 0, 0) + requirements.VersionRequirement( + name="linux_utilities_module_gatherers", + component=linux_utilities_modules.ModuleGatherers, + version=(1, 0, 0), ), requirements.VersionRequirement( name="linuxutils", component=linux.LinuxUtilities, version=(2, 0, 0) @@ -43,12 +44,6 @@ class Keyboard_notifiers(interfaces.plugins.PluginInterface): def _generator(self): vmlinux = self.context.modules[self.config["kernel"]] - modules = lsmod.Lsmod.list_modules(self.context, vmlinux.name) - - handlers = linux.LinuxUtilities.generate_kernel_handler_info( - self.context, vmlinux.name, modules - ) - try: knl_addr = vmlinux.object_from_symbol("keyboard_notifier_list") except exceptions.SymbolError: @@ -65,6 +60,12 @@ class Keyboard_notifiers(interfaces.plugins.PluginInterface): vollog.error("The head of the keyboard notifier list is paged out.") return + known_modules = linux_utilities_modules.Modules.run_modules_scanners( + context=self.context, + kernel_module_name=self.config["kernel"], + caller_wanted_gatherers=linux_utilities_modules.ModuleGatherers.all_gatherers_identifier, + ) + knl = vmlinux.object( object_type="atomic_notifier_head", offset=knl_addr.vol.offset, @@ -76,13 +77,25 @@ class Keyboard_notifiers(interfaces.plugins.PluginInterface): ): call_addr = call_back.notifier_call - module_name, symbol_name = ( - linux_utilities_modules.Modules.lookup_module_address( - self.context, vmlinux.name, handlers, call_addr + module_info, symbol_name = ( + linux_utilities_modules.Modules.module_lookup_by_address( + self.context, vmlinux.name, known_modules, call_addr ) ) - yield (0, [format_hints.Hex(call_addr), module_name, symbol_name]) + if module_info: + module_name = module_info.name + else: + module_name = renderers.NotAvailableValue() + + yield ( + 0, + [ + format_hints.Hex(call_addr), + module_name, + symbol_name or renderers.NotAvailableValue(), + ], + ) def run(self): return renderers.TreeGrid( diff --git a/volatility3/framework/plugins/linux/kthreads.py b/volatility3/framework/plugins/linux/kthreads.py index 60d24f06e..4ed0e15b9 100644 --- a/volatility3/framework/plugins/linux/kthreads.py +++ b/volatility3/framework/plugins/linux/kthreads.py @@ -5,14 +5,14 @@ import logging from typing import List import volatility3.framework.symbols.linux.utilities.modules as linux_utilities_modules -from volatility3.framework import constants, exceptions, interfaces, renderers +from volatility3.framework import exceptions, interfaces, renderers from volatility3.framework.configuration import requirements from volatility3.framework.interfaces import plugins from volatility3.framework.renderers import format_hints from volatility3.framework.symbols import linux from volatility3.framework.constants import architectures from volatility3.framework.objects import utility -from volatility3.plugins.linux import pslist, lsmod +from volatility3.plugins.linux import pslist vollog = logging.getLogger(__name__) @@ -34,36 +34,37 @@ class Kthreads(plugins.PluginInterface): requirements.VersionRequirement( name="linux_utilities_modules", component=linux_utilities_modules.Modules, - version=(2, 0, 0), + version=(3, 0, 0), + ), + requirements.VersionRequirement( + name="linux_utilities_module_gatherers", + component=linux_utilities_modules.ModuleGatherers, + version=(1, 0, 0), ), requirements.VersionRequirement( name="linuxutils", component=linux.LinuxUtilities, version=(2, 1, 0) ), - requirements.PluginRequirement( - name="pslist", plugin=pslist.PsList, version=(4, 0, 0) - ), - requirements.PluginRequirement( - name="lsmod", plugin=lsmod.Lsmod, version=(2, 0, 0) + requirements.VersionRequirement( + name="pslist", component=pslist.PsList, version=(4, 0, 0) ), ] def _generator(self): vmlinux = self.context.modules[self.config["kernel"]] - modules = lsmod.Lsmod.list_modules(self.context, vmlinux.name) - handlers = linux.LinuxUtilities.generate_kernel_handler_info( - self.context, vmlinux.name, modules - ) - - kthread_type = vmlinux.get_type( - vmlinux.symbol_table_name + constants.BANG + "kthread" - ) + kthread_type = vmlinux.get_type("kthread") if not kthread_type.has_member("threadfn"): raise exceptions.VolatilityException( "Unsupported kthread implementation. This plugin only works with kernels >= 5.8" ) + known_modules = linux_utilities_modules.Modules.run_modules_scanners( + context=self.context, + kernel_module_name=self.config["kernel"], + caller_wanted_gatherers=linux_utilities_modules.ModuleGatherers.all_gatherers_identifier, + ) + for task in pslist.PsList.list_tasks( self.context, vmlinux.name, include_threads=True ): @@ -86,9 +87,7 @@ class Kthreads(plugins.PluginInterface): if not (threadfn and threadfn.is_readable()): continue - task_name = utility.array_to_string(task.comm) - - thread_name = task_name + thread_name = utility.array_to_string(task.comm) # kernels >= 5.17 in d6986ce24fc00b0638bd29efe8fb7ba7619ed2aa full_name was added to kthread if kthread.has_member("full_name"): @@ -101,18 +100,23 @@ class Kthreads(plugins.PluginInterface): f"full_name pointer for thread at {kthread.vol.offset:#x} is paged out." ) - module_name, symbol_name = ( - linux_utilities_modules.Modules.lookup_module_address( - self.context, vmlinux.name, handlers, threadfn + module_info, symbol_name = ( + linux_utilities_modules.Modules.module_lookup_by_address( + self.context, vmlinux.name, known_modules, threadfn ) ) + if module_info: + module_name = module_info.name + else: + module_name = renderers.NotAvailableValue() + fields = [ task.pid, thread_name, format_hints.Hex(threadfn), module_name, - symbol_name, + symbol_name or renderers.NotAvailableValue(), ] yield 0, fields diff --git a/volatility3/framework/plugins/linux/library_list.py b/volatility3/framework/plugins/linux/library_list.py index e251b5689..dedd77ade 100644 --- a/volatility3/framework/plugins/linux/library_list.py +++ b/volatility3/framework/plugins/linux/library_list.py @@ -31,8 +31,8 @@ class LibraryList(interfaces.plugins.PluginInterface): description="Linux kernel", architectures=["Intel32", "Intel64"], ), - requirements.PluginRequirement( - name="pslist", plugin=pslist.PsList, version=(4, 0, 0) + requirements.VersionRequirement( + name="pslist", component=pslist.PsList, version=(4, 0, 0) ), requirements.ListRequirement( name="pids", diff --git a/volatility3/framework/plugins/linux/lsmod.py b/volatility3/framework/plugins/linux/lsmod.py index b4f881801..3029d2541 100644 --- a/volatility3/framework/plugins/linux/lsmod.py +++ b/volatility3/framework/plugins/linux/lsmod.py @@ -7,11 +7,9 @@ import logging from typing import List, Iterable import volatility3.framework.symbols.linux.utilities.modules as linux_utilities_modules -from volatility3.framework import exceptions, renderers, interfaces, deprecation +from volatility3.framework import interfaces, deprecation from volatility3.framework.configuration import requirements from volatility3.framework.interfaces import plugins -from volatility3.framework.objects import utility -from volatility3.framework.renderers import format_hints vollog = logging.getLogger(__name__) @@ -20,7 +18,11 @@ class Lsmod(plugins.PluginInterface): """Lists loaded kernel modules.""" _required_framework_version = (2, 0, 0) - _version = (2, 0, 0) + _version = (3, 0, 0) + + run = linux_utilities_modules.ModuleDisplayPlugin.run + _generator = linux_utilities_modules.ModuleDisplayPlugin.generator + implementation = linux_utilities_modules.Modules.list_modules @classmethod def get_requirements(cls) -> List[interfaces.configuration.RequirementInterface]: @@ -33,14 +35,19 @@ class Lsmod(plugins.PluginInterface): requirements.VersionRequirement( name="linux_utilities_modules", component=linux_utilities_modules.Modules, - version=(2, 0, 0), + version=(3, 0, 0), + ), + requirements.VersionRequirement( + name="linux_utilities_modules_module_display_plugin", + component=linux_utilities_modules.ModuleDisplayPlugin, + version=(1, 0, 0), ), ] @classmethod @deprecation.deprecated_method( replacement=linux_utilities_modules.Modules.list_modules, - replacement_version=(2, 0, 0), + replacement_version=(3, 0, 0), removal_date="2025-09-25", ) def list_modules( @@ -49,25 +56,3 @@ class Lsmod(plugins.PluginInterface): return linux_utilities_modules.Modules.list_modules( context, vmlinux_module_name ) - - def _generator(self): - try: - for module in linux_utilities_modules.Modules.list_modules( - self.context, self.config["kernel"] - ): - mod_size = module.get_init_size() + module.get_core_size() - - mod_name = utility.array_to_string(module.name) - - yield 0, (format_hints.Hex(module.vol.offset), mod_name, mod_size) - - except exceptions.SymbolError: - vollog.warning( - "The required symbol 'module' is not present in symbol table. Please check that kernel modules are enabled for the system under analysis." - ) - - def run(self): - return renderers.TreeGrid( - [("Offset", format_hints.Hex), ("Name", str), ("Size", int)], - self._generator(), - ) diff --git a/volatility3/framework/plugins/linux/lsof.py b/volatility3/framework/plugins/linux/lsof.py index 044e9238f..283eabca0 100644 --- a/volatility3/framework/plugins/linux/lsof.py +++ b/volatility3/framework/plugins/linux/lsof.py @@ -120,8 +120,13 @@ class Lsof(plugins.PluginInterface, timeliner.TimeLinerInterface): description="Linux kernel", architectures=["Intel32", "Intel64"], ), - requirements.PluginRequirement( - name="pslist", plugin=pslist.PsList, version=(4, 0, 0) + requirements.VersionRequirement( + name="pslist", component=pslist.PsList, version=(4, 0, 0) + ), + requirements.VersionRequirement( + name="timeliner", + component=timeliner.TimeLinerInterface, + version=(1, 0, 0), ), requirements.VersionRequirement( name="linuxutils", component=linux.LinuxUtilities, version=(2, 0, 0) diff --git a/volatility3/framework/plugins/linux/malfind.py b/volatility3/framework/plugins/linux/malfind.py index 8bbf3b89c..dad8b1f15 100644 --- a/volatility3/framework/plugins/linux/malfind.py +++ b/volatility3/framework/plugins/linux/malfind.py @@ -28,8 +28,8 @@ class Malfind(interfaces.plugins.PluginInterface): description="Linux kernel", architectures=["Intel32", "Intel64"], ), - requirements.PluginRequirement( - name="pslist", plugin=pslist.PsList, version=(4, 0, 0) + requirements.VersionRequirement( + name="pslist", component=pslist.PsList, version=(4, 0, 0) ), requirements.ListRequirement( name="pid", diff --git a/volatility3/framework/plugins/linux/modxview.py b/volatility3/framework/plugins/linux/modxview.py index f6a6f7727..ed21acfd1 100644 --- a/volatility3/framework/plugins/linux/modxview.py +++ b/volatility3/framework/plugins/linux/modxview.py @@ -34,7 +34,22 @@ spot modules presence and taints.""" requirements.VersionRequirement( name="linux_utilities_modules", component=linux_utilities_modules.Modules, - version=(2, 0, 0), + version=(3, 0, 0), + ), + requirements.VersionRequirement( + name="linux_utilities_module_gatherer_lsmod", + component=linux_utilities_modules.ModuleGathererLsmod, + version=(1, 0, 0), + ), + requirements.VersionRequirement( + name="linux_utilities_module_gatherer_sysfs", + component=linux_utilities_modules.ModuleGathererSysFs, + version=(1, 0, 0), + ), + requirements.VersionRequirement( + name="linux_utilities_module_gatherer_scanner", + component=linux_utilities_modules.ModuleGathererScanner, + version=(1, 0, 0), ), requirements.VersionRequirement( name="linux-tainting", component=tainting.Tainting, version=(1, 0, 0) @@ -50,7 +65,7 @@ spot modules presence and taints.""" @classmethod @deprecation.deprecated_method( replacement=linux_utilities_modules.Modules.flatten_run_modules_results, - replacement_version=(2, 0, 0), + replacement_version=(3, 0, 0), removal_date="2025-09-25", ) def flatten_run_modules_results( @@ -73,7 +88,7 @@ spot modules presence and taints.""" @classmethod @deprecation.deprecated_method( replacement=linux_utilities_modules.Modules.run_modules_scanners, - replacement_version=(2, 0, 0), + replacement_version=(3, 0, 0), removal_date="2025-09-25", ) def run_modules_scanners( @@ -89,35 +104,42 @@ spot modules presence and taints.""" ) def _generator(self): - kernel_name = self.config["kernel"] + kernel = self.context.modules[self.config["kernel"]] - kernel = self.context.modules[kernel_name] + wanted_gatherers = [ + linux_utilities_modules.ModuleGathererLsmod, + linux_utilities_modules.ModuleGathererSysFs, + linux_utilities_modules.ModuleGathererScanner, + ] run_results = linux_utilities_modules.Modules.run_modules_scanners( - self.context, kernel_name, flatten=False + context=self.context, + kernel_module_name=self.config["kernel"], + caller_wanted_gatherers=wanted_gatherers, + flatten=False, ) aggregated_modules = {} # We want to be explicit on the plugins results we are interested in - for plugin_name in ["lsmod", "check_modules", "hidden_modules"]: + for gatherer in wanted_gatherers: # Iterate over each recovered module - for mod_info in run_results[plugin_name]: + for mod_info in run_results[gatherer.name]: # Use offsets as unique keys, whether a module # appears in many plugin runs or not if aggregated_modules.get(mod_info.offset, None) is not None: # Append the plugin to the list of originating plugins - aggregated_modules[mod_info.offset].append(plugin_name) + aggregated_modules[mod_info.offset].append(gatherer.name) else: - aggregated_modules[mod_info.offset] = [plugin_name] + aggregated_modules[mod_info.offset] = [gatherer.name] - for module_offset, originating_plugins in aggregated_modules.items(): - # Tainting parsing capabilities applied to the module + for module_offset, gatherers in aggregated_modules.items(): module = kernel.object("module", offset=module_offset, absolute=True) + # Tainting parsing capabilities applied to the module if self.config.get("plain_taints"): taints = tainting.Tainting.get_taints_as_plain_string( self.context, - kernel_name, + self.config["kernel"], module.taints, True, ) @@ -125,7 +147,7 @@ spot modules presence and taints.""" taints = ",".join( tainting.Tainting.get_taints_parsed( self.context, - kernel_name, + self.config["kernel"], module.taints, True, ) @@ -136,9 +158,9 @@ spot modules presence and taints.""" ( module.get_name() or NotAvailableValue(), format_hints.Hex(module_offset), - "lsmod" in originating_plugins, - "check_modules" in originating_plugins, - "hidden_modules" in originating_plugins, + linux_utilities_modules.ModuleGathererLsmod.name in gatherers, + linux_utilities_modules.ModuleGathererSysFs.name in gatherers, + linux_utilities_modules.ModuleGathererScanner.name in gatherers, taints or NotAvailableValue(), ), ) @@ -149,7 +171,7 @@ spot modules presence and taints.""" ("Address", format_hints.Hex), ("In procfs", bool), ("In sysfs", bool), - ("Hidden", bool), + ("In scan", bool), ("Taints", str), ] diff --git a/volatility3/framework/plugins/linux/mountinfo.py b/volatility3/framework/plugins/linux/mountinfo.py index c56ced489..668b039db 100644 --- a/volatility3/framework/plugins/linux/mountinfo.py +++ b/volatility3/framework/plugins/linux/mountinfo.py @@ -46,8 +46,8 @@ class MountInfo(plugins.PluginInterface): description="Linux kernel", architectures=["Intel32", "Intel64"], ), - requirements.PluginRequirement( - name="pslist", plugin=pslist.PsList, version=(4, 0, 0) + requirements.VersionRequirement( + name="pslist", component=pslist.PsList, version=(4, 0, 0) ), requirements.VersionRequirement( name="linuxutils", component=linux.LinuxUtilities, version=(2, 1, 0) diff --git a/volatility3/framework/plugins/linux/netfilter.py b/volatility3/framework/plugins/linux/netfilter.py index 9c0055a54..9079adef9 100644 --- a/volatility3/framework/plugins/linux/netfilter.py +++ b/volatility3/framework/plugins/linux/netfilter.py @@ -13,12 +13,11 @@ from volatility3.framework import ( interfaces, renderers, exceptions, + deprecation, ) from volatility3.framework.renderers import format_hints from volatility3.framework.configuration import requirements -from volatility3.framework.symbols import linux from volatility3.framework.symbols.linux import network -from volatility3.plugins.linux import lsmod vollog = logging.getLogger(__name__) @@ -82,22 +81,18 @@ class AbstractNetfilter(ABC): self.ptr_size = self.vmlinux.get_type("pointer").size self.list_head_size = self.vmlinux.get_type("list_head").size - lsmod_required_version = Netfilter._required_lsmod_version - lsmod_current_version = lsmod.Lsmod.version + linuxutils_modulegatherers_required_version = ( + Netfilter._required_linuxutils_gatherers_version + ) + linuxutils_modulegatherers_current_version = ( + linux_utilities_modules.ModuleGatherers.version + ) if not requirements.VersionRequirement.matches_required( - lsmod_required_version, lsmod_current_version + linuxutils_modulegatherers_required_version, + linuxutils_modulegatherers_current_version, ): raise exceptions.PluginRequirementException( - f"linux.lsmod.Lsmod version not suitable: required {lsmod_required_version} found {lsmod_current_version}" - ) - - linuxutils_required_version = Netfilter._required_linuxutils_version - linuxutils_current_version = linux.LinuxUtilities.version - if not requirements.VersionRequirement.matches_required( - linuxutils_required_version, linuxutils_current_version - ): - raise exceptions.PluginRequirementException( - f"linux.LinuxUtilities version not suitable: required {linuxutils_required_version} found {linuxutils_current_version}" + f"linux_utilities_modules.ModuleGatherer version not suitable: required {linuxutils_modulegatherers_required_version} found {linuxutils_modulegatherers_current_version}" ) linux_net_required_version = Netfilter._required_linuxnet_version @@ -123,12 +118,13 @@ class AbstractNetfilter(ABC): f"linux_utilities_modules.Modules version not suitable: required {linux_utilities_modules_required_version} found {linux_utilities_modules_current_version}" ) - symbol_table = self._context.symbol_space[self.vmlinux.symbol_table_name] + symbol_table = context.symbol_space[self.vmlinux.symbol_table_name] network.NetSymbols.apply(symbol_table) - modules = lsmod.Lsmod.list_modules(context, kernel_module_name) - self.handlers = linux.LinuxUtilities.generate_kernel_handler_info( - context, kernel_module_name, modules + self.handlers = linux_utilities_modules.Modules.run_modules_scanners( + context=context, + kernel_module_name=kernel_module_name, + caller_wanted_gatherers=linux_utilities_modules.ModuleGatherers.all_gatherers_identifier, ) @classmethod @@ -217,10 +213,17 @@ class AbstractNetfilter(ABC): priority = int(hook_ops.priority) hook_ops_hook = hook_ops.hook - module_name = self.get_module_name_for_address(hook_ops_hook) - hooked = module_name is None + module_info, symbol_name = ( + linux_utilities_modules.Modules.module_lookup_by_address( + self._context, + self.vmlinux.name, + self.handlers, + hook_ops_hook, + ) + ) + hooked = module_info is None - yield netns, proto_name, hook_name, priority, hook_ops_hook, module_name, hooked + yield netns, proto_name, hook_name, priority, hook_ops_hook, module_info, symbol_name, hooked @classmethod @abstractmethod @@ -300,6 +303,10 @@ class AbstractNetfilter(ABC): # in other parts of the kernel source code. return ("IPV4", "ARP", "BRIDGE", "IPV6", "DECNET") + @deprecation.method_being_removed( + removal_date="2025-09-25", + message="Callers to this method should adapt `linux_utilities_modules.Modules.run_module_scanners`", + ) def get_module_name_for_address(self, addr) -> str: """Helper to obtain the module and symbol name in the format needed for the output of this plugin. @@ -724,11 +731,10 @@ class Netfilter(interfaces.plugins.PluginInterface): _required_framework_version = (2, 22, 0) - _version = (1, 1, 1) + _version = (2, 0, 0) - _required_linux_utilities_modules_version = (2, 0, 0) - _required_linuxutils_version = (2, 1, 0) - _required_lsmod_version = (2, 0, 0) + _required_linux_utilities_modules_version = (3, 0, 0) + _required_linuxutils_gatherers_version = (1, 0, 0) _required_linuxnet_version = (1, 0, 0) @classmethod @@ -740,17 +746,9 @@ class Netfilter(interfaces.plugins.PluginInterface): architectures=["Intel32", "Intel64"], ), requirements.VersionRequirement( - name="linux_utilities_modules", - component=linux_utilities_modules.Modules, - version=cls._required_linux_utilities_modules_version, - ), - requirements.PluginRequirement( - name="lsmod", plugin=lsmod.Lsmod, version=cls._required_lsmod_version - ), - requirements.VersionRequirement( - name="linuxutils", - component=linux.LinuxUtilities, - version=cls._required_linuxutils_version, + name="linux_utilities_module_gatherers", + component=linux_utilities_modules.ModuleGatherers, + version=cls._required_linuxutils_gatherers_version, ), requirements.VersionRequirement( name="linuxnet", @@ -766,16 +764,24 @@ class Netfilter(interfaces.plugins.PluginInterface): hook_name, priority, hook_func, - module_name, + module_info, + symbol_name, hooked, ) = fields + + if module_info: + module_name = module_info.name + else: + module_name = renderers.NotAvailableValue() + return ( netns, proto_name, hook_name, priority, format_hints.Hex(hook_func), - module_name or renderers.NotAvailableValue(), + module_name, + symbol_name or renderers.NotAvailableValue(), str(hooked), ) @@ -794,6 +800,7 @@ class Netfilter(interfaces.plugins.PluginInterface): ("Priority", int), ("Handler", format_hints.Hex), ("Module", str), + ("Symbol", str), ("Is Hooked", str), ] return renderers.TreeGrid(headers, self._generator()) diff --git a/volatility3/framework/plugins/linux/pagecache.py b/volatility3/framework/plugins/linux/pagecache.py index 3d2db7fd7..0bb3b9263 100644 --- a/volatility3/framework/plugins/linux/pagecache.py +++ b/volatility3/framework/plugins/linux/pagecache.py @@ -126,8 +126,13 @@ class Files(plugins.PluginInterface, timeliner.TimeLinerInterface): description="Linux kernel", architectures=architectures.LINUX_ARCHS, ), - requirements.PluginRequirement( - name="mountinfo", plugin=mountinfo.MountInfo, version=(1, 2, 0) + requirements.VersionRequirement( + name="mountinfo", component=mountinfo.MountInfo, version=(1, 2, 0) + ), + requirements.VersionRequirement( + name="timeliner", + component=timeliner.TimeLinerInterface, + version=(1, 0, 0), ), requirements.ListRequirement( name="type", @@ -431,8 +436,8 @@ class InodePages(plugins.PluginInterface): description="Linux kernel", architectures=architectures.LINUX_ARCHS, ), - requirements.PluginRequirement( - name="files", plugin=Files, version=(1, 0, 0) + requirements.VersionRequirement( + name="files", component=Files, version=(1, 0, 0) ), requirements.StringRequirement( name="find", @@ -650,11 +655,11 @@ class RecoverFs(plugins.PluginInterface): description="Linux kernel", architectures=architectures.LINUX_ARCHS, ), - requirements.PluginRequirement( - name="files", plugin=Files, version=(1, 1, 0) + requirements.VersionRequirement( + name="files", component=Files, version=(1, 1, 0) ), - requirements.PluginRequirement( - name="inodepages", plugin=InodePages, version=(3, 0, 0) + requirements.VersionRequirement( + name="inodepages", component=InodePages, version=(3, 0, 0) ), requirements.BooleanRequirement( name="tmpfs_only", diff --git a/volatility3/framework/plugins/linux/pidhashtable.py b/volatility3/framework/plugins/linux/pidhashtable.py index 060b3928e..b4b1643e1 100644 --- a/volatility3/framework/plugins/linux/pidhashtable.py +++ b/volatility3/framework/plugins/linux/pidhashtable.py @@ -29,8 +29,8 @@ class PIDHashTable(plugins.PluginInterface): description="Linux kernel", architectures=["Intel32", "Intel64"], ), - requirements.PluginRequirement( - name="pslist", plugin=pslist.PsList, version=(4, 0, 0) + requirements.VersionRequirement( + name="pslist", component=pslist.PsList, version=(4, 0, 0) ), requirements.VersionRequirement( name="linuxutils", component=linux.LinuxUtilities, version=(2, 1, 0) diff --git a/volatility3/framework/plugins/linux/proc.py b/volatility3/framework/plugins/linux/proc.py index 5acba6594..e9a126374 100644 --- a/volatility3/framework/plugins/linux/proc.py +++ b/volatility3/framework/plugins/linux/proc.py @@ -34,8 +34,8 @@ class Maps(plugins.PluginInterface): description="Linux kernel", architectures=["Intel32", "Intel64"], ), - requirements.PluginRequirement( - name="pslist", plugin=pslist.PsList, version=(4, 0, 0) + requirements.VersionRequirement( + name="pslist", component=pslist.PsList, version=(4, 0, 0) ), requirements.ListRequirement( name="pid", diff --git a/volatility3/framework/plugins/linux/psaux.py b/volatility3/framework/plugins/linux/psaux.py index 1a118dba6..e6653251c 100644 --- a/volatility3/framework/plugins/linux/psaux.py +++ b/volatility3/framework/plugins/linux/psaux.py @@ -26,8 +26,8 @@ class PsAux(plugins.PluginInterface): description="Linux kernel", architectures=["Intel32", "Intel64"], ), - requirements.PluginRequirement( - name="pslist", plugin=pslist.PsList, version=(4, 0, 0) + requirements.VersionRequirement( + name="pslist", component=pslist.PsList, version=(4, 0, 0) ), requirements.ListRequirement( name="pid", diff --git a/volatility3/framework/plugins/linux/pscallstack.py b/volatility3/framework/plugins/linux/pscallstack.py index 8931ca581..c22e00161 100644 --- a/volatility3/framework/plugins/linux/pscallstack.py +++ b/volatility3/framework/plugins/linux/pscallstack.py @@ -45,8 +45,8 @@ class PsCallStack(plugins.PluginInterface): requirements.VersionRequirement( name="Kallsyms", component=kallsyms.Kallsyms, version=(1, 0, 0) ), - requirements.PluginRequirement( - name="pslist", plugin=pslist.PsList, version=(4, 0, 0) + requirements.VersionRequirement( + name="pslist", component=pslist.PsList, version=(4, 0, 0) ), requirements.ListRequirement( name="pid", @@ -118,9 +118,15 @@ class PsCallStack(plugins.PluginInterface): current_sp = rsp_start idx = 0 while current_sp < task_top_of_stack: - stack_value_bytes = task_layer.read(current_sp, pointer_size) + try: + stack_value_bytes = task_layer.read(current_sp, pointer_size) + except exceptions.InvalidAddressException: + break stack_value = int.from_bytes(stack_value_bytes, byteorder=byte_order) - + if not stack_value: + idx += 1 + current_sp += pointer_size + continue kassymbol = kas.lookup_address(stack_value) sp_address = current_sp & vmlinux_layer.address_mask stack_value &= vmlinux_layer.address_mask diff --git a/volatility3/framework/plugins/linux/pslist.py b/volatility3/framework/plugins/linux/pslist.py index 4c42fc992..2f0cc00b7 100644 --- a/volatility3/framework/plugins/linux/pslist.py +++ b/volatility3/framework/plugins/linux/pslist.py @@ -44,8 +44,8 @@ class PsList(interfaces.plugins.PluginInterface, timeliner.TimeLinerInterface): description="Linux kernel", architectures=["Intel32", "Intel64"], ), - requirements.PluginRequirement( - name="elfs", plugin=elfs.Elfs, version=(2, 0, 0) + requirements.VersionRequirement( + name="elfs", component=elfs.Elfs, version=(2, 0, 0) ), requirements.ListRequirement( name="pid", @@ -53,6 +53,11 @@ class PsList(interfaces.plugins.PluginInterface, timeliner.TimeLinerInterface): element_type=int, optional=True, ), + requirements.VersionRequirement( + name="timeliner", + component=timeliner.TimeLinerInterface, + version=(1, 0, 0), + ), requirements.BooleanRequirement( name="threads", description="Include user threads", diff --git a/volatility3/framework/plugins/linux/psscan.py b/volatility3/framework/plugins/linux/psscan.py index 6c4c5eb35..0813cebed 100644 --- a/volatility3/framework/plugins/linux/psscan.py +++ b/volatility3/framework/plugins/linux/psscan.py @@ -38,8 +38,8 @@ class PsScan(interfaces.plugins.PluginInterface): description="Linux kernel", architectures=["Intel32", "Intel64"], ), - requirements.PluginRequirement( - name="pslist", plugin=pslist.PsList, version=(4, 0, 0) + requirements.VersionRequirement( + name="pslist", component=pslist.PsList, version=(4, 0, 0) ), ] diff --git a/volatility3/framework/plugins/linux/pstree.py b/volatility3/framework/plugins/linux/pstree.py index c5290774b..e7bbdb8d5 100644 --- a/volatility3/framework/plugins/linux/pstree.py +++ b/volatility3/framework/plugins/linux/pstree.py @@ -27,8 +27,8 @@ class PsTree(interfaces.plugins.PluginInterface): description="Linux kernel", architectures=["Intel32", "Intel64"], ), - requirements.PluginRequirement( - name="pslist", plugin=pslist.PsList, version=(4, 0, 0) + requirements.VersionRequirement( + name="pslist", component=pslist.PsList, version=(4, 0, 0) ), requirements.ListRequirement( name="pid", diff --git a/volatility3/framework/plugins/linux/ptrace.py b/volatility3/framework/plugins/linux/ptrace.py index 6493f22b9..356d5e72c 100644 --- a/volatility3/framework/plugins/linux/ptrace.py +++ b/volatility3/framework/plugins/linux/ptrace.py @@ -29,8 +29,8 @@ class Ptrace(plugins.PluginInterface): description="Linux kernel", architectures=architectures.LINUX_ARCHS, ), - requirements.PluginRequirement( - name="pslist", plugin=pslist.PsList, version=(4, 0, 0) + requirements.VersionRequirement( + name="pslist", component=pslist.PsList, version=(4, 0, 0) ), ] diff --git a/volatility3/framework/plugins/linux/sockstat.py b/volatility3/framework/plugins/linux/sockstat.py index adbb5d6ea..da5d8cb8c 100644 --- a/volatility3/framework/plugins/linux/sockstat.py +++ b/volatility3/framework/plugins/linux/sockstat.py @@ -463,11 +463,11 @@ class Sockstat(plugins.PluginInterface): requirements.VersionRequirement( name="SockHandlers", component=SockHandlers, version=(4, 0, 0) ), - requirements.PluginRequirement( - name="lsof", plugin=lsof.Lsof, version=(2, 0, 0) + requirements.VersionRequirement( + name="lsof", component=lsof.Lsof, version=(2, 0, 0) ), - requirements.PluginRequirement( - name="pslist", plugin=pslist.PsList, version=(4, 0, 0) + requirements.VersionRequirement( + name="pslist", component=pslist.PsList, version=(4, 0, 0) ), requirements.VersionRequirement( name="linuxutils", component=linux.LinuxUtilities, version=(2, 0, 0) diff --git a/volatility3/framework/plugins/linux/tracing/ftrace.py b/volatility3/framework/plugins/linux/tracing/ftrace.py index 17766cc74..c5e4f9ef8 100644 --- a/volatility3/framework/plugins/linux/tracing/ftrace.py +++ b/volatility3/framework/plugins/linux/tracing/ftrace.py @@ -5,7 +5,7 @@ # Public researches: https://i.blackhat.com/USA21/Wednesday-Handouts/us-21-Fixing-A-Memory-Forensics-Blind-Spot-Linux-Kernel-Tracing-wp.pdf import logging -from typing import Dict, List, Generator +from typing import List, Generator from enum import Enum from dataclasses import dataclass @@ -65,7 +65,7 @@ class CheckFtrace(interfaces.plugins.PluginInterface): Investigate the ftrace infrastructure to uncover kernel attached callbacks, which can be leveraged to hook kernel functions and modify their behaviour.""" - _version = (3, 0, 0) + _version = (4, 0, 0) _required_framework_version = (2, 19, 0) @classmethod @@ -79,7 +79,12 @@ class CheckFtrace(interfaces.plugins.PluginInterface): requirements.VersionRequirement( name="linux_utilities_modules", component=linux_utilities_modules.Modules, - version=(2, 0, 0), + version=(3, 0, 0), + ), + requirements.VersionRequirement( + name="linux_utilities_module_gatherers", + component=linux_utilities_modules.ModuleGatherers, + version=(1, 0, 0), ), requirements.BooleanRequirement( name="show_ftrace_flags", @@ -127,9 +132,8 @@ class CheckFtrace(interfaces.plugins.PluginInterface): cls, context: interfaces.context.ContextInterface, kernel_module_name: str, - known_modules: Dict[str, List[linux_utilities_modules.Modules.ModuleInfo]], + known_modules: List[linux_utilities_modules.ModuleInfo], ftrace_ops: interfaces.objects.ObjectInterface, - run_hidden_modules: bool = True, ) -> Generator[ParsedFtraceOps, None, None]: """Parse an ftrace_ops struct to highlight ftrace kernel hooking. Iterates over embedded ftrace_func_entry entries, which point to hooked memory areas. @@ -137,8 +141,6 @@ class CheckFtrace(interfaces.plugins.PluginInterface): Args: known_modules: A dict of known modules, used to locate callbacks origin. Typically obtained through run_modules_scanners(). ftrace_ops: The ftrace_ops struct to parse - run_hidden_modules: Whether to run the hidden_modules plugin or not. Note: it won't be run, even if specified, \ - if the "hidden_modules" key is present in known_modules. Yields: An iterable of ParsedFtraceOps dataclasses, containing a selection of useful fields (callback, hook, module) related to an ftrace_ops struct @@ -223,7 +225,9 @@ class CheckFtrace(interfaces.plugins.PluginInterface): return known_modules = linux_utilities_modules.Modules.run_modules_scanners( - self.context, kernel_name, run_hidden_modules=True + context=self.context, + kernel_module_name=self.config["kernel"], + caller_wanted_gatherers=linux_utilities_modules.ModuleGatherers.all_gatherers_identifier, ) for ftrace_ops in self.iterate_ftrace_ops_list(self.context, kernel_name): diff --git a/volatility3/framework/plugins/linux/tracing/perf_events.py b/volatility3/framework/plugins/linux/tracing/perf_events.py new file mode 100644 index 000000000..3e0a40579 --- /dev/null +++ b/volatility3/framework/plugins/linux/tracing/perf_events.py @@ -0,0 +1,145 @@ +# This file is Copyright 2025 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, Tuple, Generator, Optional + +from volatility3.framework import renderers, interfaces, constants, exceptions +from volatility3.framework.renderers import format_hints +from volatility3.framework.configuration import requirements +from volatility3.framework.interfaces import plugins +from volatility3.framework.objects import utility +from volatility3.plugins.linux import pslist + +vollog = logging.getLogger(__name__) + + +class PerfEvents(plugins.PluginInterface): + """Lists performance events for each process.""" + + _required_framework_version = (2, 0, 0) + _version = (1, 0, 0) + + @classmethod + def get_requirements(cls) -> List[interfaces.configuration.RequirementInterface]: + return [ + requirements.ModuleRequirement( + name="kernel", + description="Linux kernel", + architectures=["Intel32", "Intel64"], + ), + requirements.PluginRequirement( + name="pslist", plugin=pslist.PsList, version=(4, 0, 0) + ), + ] + + @classmethod + def list_perf_events(cls, context, vmlinux_module_name: str) -> Generator[ + Tuple[ + interfaces.objects.ObjectInterface, + interfaces.objects.ObjectInterface, + Optional[str], + Optional[str], + Optional[str], + Optional[int], + ], + None, + None, + ]: + """ + Walks the `perf_event_list` of each `task_struct` and reports valid event structures found + This plugin is one of several to detect eBPF based malware + + Args: + context: + vmlinux_module_name: + + Returns: + A tuple of the task struct, performance event object, event name, program name, full name, and program address + """ + vmlinux = context.modules[vmlinux_module_name] + + if not vmlinux.has_type("perf_event") or not vmlinux.get_type( + "perf_event" + ).has_member("owner_entry"): + vollog.warning( + "This kernel does not have performance events enabled (CONFIG_PERF_EVENTS). Cannot proceed." + ) + return + + for task in pslist.PsList.list_tasks( + context, vmlinux_module_name, include_threads=True + ): + + # walk the list of perf_event entries for this process + for event in task.perf_event_list.to_list( + vmlinux.symbol_table_name + constants.BANG + "perf_event", "owner_entry" + ): + # if the names are smeared then bail + try: + event_name = utility.pointer_to_string(event.pmu.name, count=64) + try: + full_name = utility.array_to_string( + event.prog.aux.ksym.name, count=512 + ) + except AttributeError: + full_name = None + + program_name = utility.array_to_string(event.prog.aux.name) + except exceptions.InvalidAddressException: + continue + + # if the kernel has the prog member then ensure it is not 0 + if hasattr(event, "prog"): + program_address = event.prog + if program_address == 0: + continue + + else: + program_address = None + + yield task, event_name, program_name, full_name, program_address + + def _generator(self): + for ( + task, + event_name, + program_name, + full_name, + program_address, + ) in self.list_perf_events(self.context, self.config["kernel"]): + task_name = utility.array_to_string(task.comm) + + # We at least need one useful string... + if event_name is None and program_name is None and full_name is None: + continue + + if program_address is not None: + program_address = format_hints.Hex(program_address) + else: + program_address = renderers.NotAvailableValue() + + yield ( + 0, + ( + task.pid, + task_name, + event_name or renderers.NotAvailableValue(), + program_name or renderers.NotAvailableValue(), + full_name or renderers.NotAvailableValue(), + program_address, + ), + ) + + def run(self) -> renderers.TreeGrid: + return renderers.TreeGrid( + [ + ("PID", int), + ("Process", str), + ("Event", str), + ("Short Program Name", str), + ("Full Name", str), + ("Address", format_hints.Hex), + ], + self._generator(), + ) diff --git a/volatility3/framework/plugins/linux/tracing/tracepoints.py b/volatility3/framework/plugins/linux/tracing/tracepoints.py index fe6d11af9..9d4a4a2e3 100644 --- a/volatility3/framework/plugins/linux/tracing/tracepoints.py +++ b/volatility3/framework/plugins/linux/tracing/tracepoints.py @@ -5,7 +5,7 @@ # Public researches: https://i.blackhat.com/USA21/Wednesday-Handouts/us-21-Fixing-A-Memory-Forensics-Blind-Spot-Linux-Kernel-Tracing-wp.pdf import logging -from typing import Dict, Iterable, List, Optional +from typing import Iterable, List, Optional from dataclasses import dataclass import volatility3.framework.symbols.linux.utilities.modules as linux_utilities_modules @@ -38,7 +38,7 @@ class CheckTracepoints(interfaces.plugins.PluginInterface): Investigate the tracepoints subsystem to uncover kernel attached probes, which can be leveraged to hook kernel functions and modify their behaviour.""" - _version = (1, 0, 0) + _version = (2, 0, 0) _required_framework_version = (2, 19, 0) @classmethod @@ -52,7 +52,12 @@ class CheckTracepoints(interfaces.plugins.PluginInterface): requirements.VersionRequirement( name="linux_utilities_modules", component=linux_utilities_modules.Modules, - version=(2, 0, 0), + version=(3, 0, 0), + ), + requirements.VersionRequirement( + name="linux_utilities_module_gatherers", + component=linux_utilities_modules.ModuleGatherers, + version=(1, 0, 0), ), ] @@ -96,7 +101,7 @@ class CheckTracepoints(interfaces.plugins.PluginInterface): cls, context: interfaces.context.ContextInterface, kernel_module_name: str, - known_modules: Dict[str, List[linux_utilities_modules.Modules.ModuleInfo]], + known_modules: List[linux_utilities_modules.ModuleInfo], tracepoint: interfaces.objects.ObjectInterface, run_hidden_modules: bool = True, ) -> Optional[Iterable[ParsedTracepointFunc]]: @@ -229,7 +234,9 @@ class CheckTracepoints(interfaces.plugins.PluginInterface): return known_modules = linux_utilities_modules.Modules.run_modules_scanners( - self.context, kernel_name, run_hidden_modules=False + context=self.context, + kernel_module_name=self.config["kernel"], + caller_wanted_gatherers=linux_utilities_modules.ModuleGatherers.all_gatherers_identifier, ) tracepoints = self.iterate_tracepoints_array(self.context, kernel_name) diff --git a/volatility3/framework/plugins/linux/tty_check.py b/volatility3/framework/plugins/linux/tty_check.py index 281d46eda..7d30b84ee 100644 --- a/volatility3/framework/plugins/linux/tty_check.py +++ b/volatility3/framework/plugins/linux/tty_check.py @@ -12,7 +12,6 @@ from volatility3.framework.interfaces import plugins from volatility3.framework.objects import utility from volatility3.framework.renderers import format_hints from volatility3.framework.symbols import linux -from volatility3.plugins.linux import lsmod vollog = logging.getLogger(__name__) @@ -33,10 +32,12 @@ class tty_check(plugins.PluginInterface): requirements.VersionRequirement( name="linux_utilities_modules", component=linux_utilities_modules.Modules, - version=(2, 0, 0), + version=(3, 0, 0), ), - requirements.PluginRequirement( - name="lsmod", plugin=lsmod.Lsmod, version=(2, 0, 0) + requirements.VersionRequirement( + name="linux_utilities_module_gatherers", + component=linux_utilities_modules.ModuleGatherers, + version=(1, 0, 0), ), requirements.VersionRequirement( name="linuxutils", component=linux.LinuxUtilities, version=(2, 0, 0) @@ -46,12 +47,6 @@ class tty_check(plugins.PluginInterface): def _generator(self): vmlinux = self.context.modules[self.config["kernel"]] - modules = lsmod.Lsmod.list_modules(self.context, vmlinux.name) - - handlers = linux.LinuxUtilities.generate_kernel_handler_info( - self.context, vmlinux.name, modules - ) - try: tty_drivers = vmlinux.object_from_symbol("tty_drivers").cast("list_head") except exceptions.SymbolError: @@ -64,6 +59,12 @@ class tty_check(plugins.PluginInterface): "This means you are either analyzing an unsupported kernel version or that your symbol table is corrupt." ) + known_modules = linux_utilities_modules.Modules.run_modules_scanners( + context=self.context, + kernel_module_name=self.config["kernel"], + caller_wanted_gatherers=linux_utilities_modules.ModuleGatherers.all_gatherers_identifier, + ) + for tty in tty_drivers.to_list( vmlinux.symbol_table_name + constants.BANG + "tty_driver", "tty_drivers" ): @@ -87,13 +88,23 @@ class tty_check(plugins.PluginInterface): except exceptions.InvalidAddressException: continue - module_name, symbol_name = ( - linux_utilities_modules.Modules.lookup_module_address( - self.context, vmlinux.name, handlers, recv_buf + module_info, symbol_name = ( + linux_utilities_modules.Modules.module_lookup_by_address( + self.context, vmlinux.name, known_modules, recv_buf ) ) - yield (0, (name, format_hints.Hex(recv_buf), module_name, symbol_name)) + if module_info: + module_name = module_info.name + else: + module_name = renderers.NotAvailableValue() + + yield 0, ( + name, + format_hints.Hex(recv_buf), + module_name, + symbol_name or renderers.NotAvailableValue(), + ) def run(self): return renderers.TreeGrid( diff --git a/volatility3/framework/plugins/linux/vmaregexscan.py b/volatility3/framework/plugins/linux/vmaregexscan.py index 8fb96da1e..4c8ef5b8f 100644 --- a/volatility3/framework/plugins/linux/vmaregexscan.py +++ b/volatility3/framework/plugins/linux/vmaregexscan.py @@ -34,8 +34,8 @@ class VmaRegExScan(plugins.PluginInterface): description="Linux kernel", architectures=["Intel32", "Intel64"], ), - requirements.PluginRequirement( - name="pslist", plugin=pslist.PsList, version=(4, 0, 0) + requirements.VersionRequirement( + name="pslist", component=pslist.PsList, version=(4, 0, 0) ), requirements.ListRequirement( name="pid", diff --git a/volatility3/framework/plugins/linux/vmayarascan.py b/volatility3/framework/plugins/linux/vmayarascan.py index e9e56dd0f..2f15a1e7e 100644 --- a/volatility3/framework/plugins/linux/vmayarascan.py +++ b/volatility3/framework/plugins/linux/vmayarascan.py @@ -30,11 +30,11 @@ class VmaYaraScan(interfaces.plugins.PluginInterface): description="Process IDs to include (all other processes are excluded)", optional=True, ), - requirements.PluginRequirement( - name="pslist", plugin=pslist.PsList, version=(4, 0, 0) + requirements.VersionRequirement( + name="pslist", component=pslist.PsList, version=(4, 0, 0) ), - requirements.PluginRequirement( - name="yarascan", plugin=yarascan.YaraScan, version=(2, 0, 0) + requirements.VersionRequirement( + name="yarascan", component=yarascan.YaraScan, version=(2, 0, 0) ), requirements.VersionRequirement( name="yarascanner", component=yarascan.YaraScanner, version=(2, 0, 0) diff --git a/volatility3/framework/plugins/mac/bash.py b/volatility3/framework/plugins/mac/bash.py index 5be5e74d6..4cbade1cf 100644 --- a/volatility3/framework/plugins/mac/bash.py +++ b/volatility3/framework/plugins/mac/bash.py @@ -30,8 +30,13 @@ class Bash(plugins.PluginInterface, timeliner.TimeLinerInterface): description="Kernel module for the OS", architectures=["Intel32", "Intel64"], ), - requirements.PluginRequirement( - name="pslist", plugin=pslist.PsList, version=(3, 0, 0) + requirements.VersionRequirement( + name="pslist", component=pslist.PsList, version=(3, 0, 0) + ), + requirements.VersionRequirement( + name="timeliner", + component=timeliner.TimeLinerInterface, + version=(1, 0, 0), ), requirements.ListRequirement( name="pid", diff --git a/volatility3/framework/plugins/mac/check_syscall.py b/volatility3/framework/plugins/mac/check_syscall.py index 5c22e6463..ed86b1a41 100644 --- a/volatility3/framework/plugins/mac/check_syscall.py +++ b/volatility3/framework/plugins/mac/check_syscall.py @@ -31,8 +31,8 @@ class Check_syscall(plugins.PluginInterface): requirements.VersionRequirement( name="macutils", component=mac.MacUtilities, version=(1, 0, 0) ), - requirements.PluginRequirement( - name="lsmod", plugin=lsmod.Lsmod, version=(2, 0, 0) + requirements.VersionRequirement( + name="lsmod", component=lsmod.Lsmod, version=(2, 0, 0) ), ] diff --git a/volatility3/framework/plugins/mac/check_sysctl.py b/volatility3/framework/plugins/mac/check_sysctl.py index ed3e34aea..d9c9a4dbd 100644 --- a/volatility3/framework/plugins/mac/check_sysctl.py +++ b/volatility3/framework/plugins/mac/check_sysctl.py @@ -33,8 +33,8 @@ class Check_sysctl(plugins.PluginInterface): requirements.VersionRequirement( name="macutils", component=mac.MacUtilities, version=(1, 0, 0) ), - requirements.PluginRequirement( - name="lsmod", plugin=lsmod.Lsmod, version=(2, 0, 0) + requirements.VersionRequirement( + name="lsmod", component=lsmod.Lsmod, version=(2, 0, 0) ), ] diff --git a/volatility3/framework/plugins/mac/check_trap_table.py b/volatility3/framework/plugins/mac/check_trap_table.py index 60f237208..6e0f4b8a9 100644 --- a/volatility3/framework/plugins/mac/check_trap_table.py +++ b/volatility3/framework/plugins/mac/check_trap_table.py @@ -29,8 +29,8 @@ class Check_trap_table(plugins.PluginInterface): description="Kernel module for the OS", architectures=["Intel32", "Intel64"], ), - requirements.PluginRequirement( - name="lsmod", plugin=lsmod.Lsmod, version=(2, 0, 0) + requirements.VersionRequirement( + name="lsmod", component=lsmod.Lsmod, version=(2, 0, 0) ), requirements.VersionRequirement( name="macutils", component=mac.MacUtilities, version=(1, 0, 0) diff --git a/volatility3/framework/plugins/mac/kauth_listeners.py b/volatility3/framework/plugins/mac/kauth_listeners.py index ed43bfb42..ca236c04a 100644 --- a/volatility3/framework/plugins/mac/kauth_listeners.py +++ b/volatility3/framework/plugins/mac/kauth_listeners.py @@ -26,11 +26,13 @@ class Kauth_listeners(interfaces.plugins.PluginInterface): requirements.VersionRequirement( name="macutils", component=mac.MacUtilities, version=(1, 1, 0) ), - requirements.PluginRequirement( - name="lsmod", plugin=lsmod.Lsmod, version=(2, 0, 0) + requirements.VersionRequirement( + name="lsmod", component=lsmod.Lsmod, version=(2, 0, 0) ), - requirements.PluginRequirement( - name="kauth_scopes", plugin=kauth_scopes.Kauth_scopes, version=(2, 0, 0) + requirements.VersionRequirement( + name="kauth_scopes", + component=kauth_scopes.Kauth_scopes, + version=(2, 0, 0), ), ] diff --git a/volatility3/framework/plugins/mac/kauth_scopes.py b/volatility3/framework/plugins/mac/kauth_scopes.py index c2c473eac..6420d9955 100644 --- a/volatility3/framework/plugins/mac/kauth_scopes.py +++ b/volatility3/framework/plugins/mac/kauth_scopes.py @@ -31,8 +31,8 @@ class Kauth_scopes(interfaces.plugins.PluginInterface): requirements.VersionRequirement( name="macutils", component=mac.MacUtilities, version=(1, 1, 0) ), - requirements.PluginRequirement( - name="lsmod", plugin=lsmod.Lsmod, version=(2, 0, 0) + requirements.VersionRequirement( + name="lsmod", component=lsmod.Lsmod, version=(2, 0, 0) ), ] diff --git a/volatility3/framework/plugins/mac/kevents.py b/volatility3/framework/plugins/mac/kevents.py index 41fde31ca..e36de8c84 100644 --- a/volatility3/framework/plugins/mac/kevents.py +++ b/volatility3/framework/plugins/mac/kevents.py @@ -71,8 +71,8 @@ class Kevents(interfaces.plugins.PluginInterface): description="Kernel module for the OS", architectures=["Intel32", "Intel64"], ), - requirements.PluginRequirement( - name="pslist", plugin=pslist.PsList, version=(3, 0, 0) + requirements.VersionRequirement( + name="pslist", component=pslist.PsList, version=(3, 0, 0) ), requirements.VersionRequirement( name="macutils", component=mac.MacUtilities, version=(1, 2, 0) diff --git a/volatility3/framework/plugins/mac/list_files.py b/volatility3/framework/plugins/mac/list_files.py index c18b0b7a2..bf3dcfce6 100644 --- a/volatility3/framework/plugins/mac/list_files.py +++ b/volatility3/framework/plugins/mac/list_files.py @@ -28,8 +28,8 @@ class List_Files(plugins.PluginInterface): description="Kernel module for the OS", architectures=["Intel32", "Intel64"], ), - requirements.PluginRequirement( - name="mount", plugin=mount.Mount, version=(2, 0, 0) + requirements.VersionRequirement( + name="mount", component=mount.Mount, version=(2, 0, 0) ), ] diff --git a/volatility3/framework/plugins/mac/lsof.py b/volatility3/framework/plugins/mac/lsof.py index 6832b837f..3191aeff6 100644 --- a/volatility3/framework/plugins/mac/lsof.py +++ b/volatility3/framework/plugins/mac/lsof.py @@ -29,8 +29,8 @@ class Lsof(plugins.PluginInterface): requirements.VersionRequirement( name="macutils", component=mac.MacUtilities, version=(1, 0, 0) ), - requirements.PluginRequirement( - name="pslist", plugin=pslist.PsList, version=(3, 0, 0) + requirements.VersionRequirement( + name="pslist", component=pslist.PsList, version=(3, 0, 0) ), requirements.ListRequirement( name="pid", diff --git a/volatility3/framework/plugins/mac/malfind.py b/volatility3/framework/plugins/mac/malfind.py index 3094ada85..7d28c2d2a 100644 --- a/volatility3/framework/plugins/mac/malfind.py +++ b/volatility3/framework/plugins/mac/malfind.py @@ -23,8 +23,8 @@ class Malfind(interfaces.plugins.PluginInterface): description="Kernel module for the OS", architectures=["Intel32", "Intel64"], ), - requirements.PluginRequirement( - name="pslist", plugin=pslist.PsList, version=(3, 0, 0) + requirements.VersionRequirement( + name="pslist", component=pslist.PsList, version=(3, 0, 0) ), requirements.ListRequirement( name="pid", diff --git a/volatility3/framework/plugins/mac/netstat.py b/volatility3/framework/plugins/mac/netstat.py index 76bba25f6..2eb7132f2 100644 --- a/volatility3/framework/plugins/mac/netstat.py +++ b/volatility3/framework/plugins/mac/netstat.py @@ -29,8 +29,8 @@ class Netstat(plugins.PluginInterface): description="Kernel module for the OS", architectures=["Intel32", "Intel64"], ), - requirements.PluginRequirement( - name="pslist", plugin=pslist.PsList, version=(3, 0, 0) + requirements.VersionRequirement( + name="pslist", component=pslist.PsList, version=(3, 0, 0) ), requirements.VersionRequirement( name="macutils", component=mac.MacUtilities, version=(1, 0, 0) diff --git a/volatility3/framework/plugins/mac/proc_maps.py b/volatility3/framework/plugins/mac/proc_maps.py index bd905615d..87f3559ea 100644 --- a/volatility3/framework/plugins/mac/proc_maps.py +++ b/volatility3/framework/plugins/mac/proc_maps.py @@ -28,8 +28,8 @@ class Maps(interfaces.plugins.PluginInterface): description="Kernel module for the OS", architectures=["Intel32", "Intel64"], ), - requirements.PluginRequirement( - name="pslist", plugin=pslist.PsList, version=(3, 0, 0) + requirements.VersionRequirement( + name="pslist", component=pslist.PsList, version=(3, 0, 0) ), requirements.ListRequirement( name="pid", diff --git a/volatility3/framework/plugins/mac/psaux.py b/volatility3/framework/plugins/mac/psaux.py index 28c238263..ba9b7b5f6 100644 --- a/volatility3/framework/plugins/mac/psaux.py +++ b/volatility3/framework/plugins/mac/psaux.py @@ -24,8 +24,8 @@ class Psaux(plugins.PluginInterface): description="Kernel module for the OS", architectures=["Intel32", "Intel64"], ), - requirements.PluginRequirement( - name="pslist", plugin=pslist.PsList, version=(3, 0, 0) + requirements.VersionRequirement( + name="pslist", component=pslist.PsList, version=(3, 0, 0) ), requirements.ListRequirement( name="pid", diff --git a/volatility3/framework/plugins/mac/pstree.py b/volatility3/framework/plugins/mac/pstree.py index ad5bb309b..260029b11 100644 --- a/volatility3/framework/plugins/mac/pstree.py +++ b/volatility3/framework/plugins/mac/pstree.py @@ -28,8 +28,8 @@ class PsTree(plugins.PluginInterface): description="Kernel module for the OS", architectures=["Intel32", "Intel64"], ), - requirements.PluginRequirement( - name="pslist", plugin=pslist.PsList, version=(3, 0, 0) + requirements.VersionRequirement( + name="pslist", component=pslist.PsList, version=(3, 0, 0) ), ] diff --git a/volatility3/framework/plugins/mac/socket_filters.py b/volatility3/framework/plugins/mac/socket_filters.py index 49e77163e..2675ccdd0 100644 --- a/volatility3/framework/plugins/mac/socket_filters.py +++ b/volatility3/framework/plugins/mac/socket_filters.py @@ -32,8 +32,8 @@ class Socket_filters(plugins.PluginInterface): requirements.VersionRequirement( name="macutils", component=mac.MacUtilities, version=(1, 0, 0) ), - requirements.PluginRequirement( - name="lsmod", plugin=lsmod.Lsmod, version=(2, 0, 0) + requirements.VersionRequirement( + name="lsmod", component=lsmod.Lsmod, version=(2, 0, 0) ), ] diff --git a/volatility3/framework/plugins/mac/trustedbsd.py b/volatility3/framework/plugins/mac/trustedbsd.py index a03e2a903..3d76a018b 100644 --- a/volatility3/framework/plugins/mac/trustedbsd.py +++ b/volatility3/framework/plugins/mac/trustedbsd.py @@ -33,8 +33,8 @@ class Trustedbsd(plugins.PluginInterface): requirements.VersionRequirement( name="macutils", component=mac.MacUtilities, version=(1, 3, 0) ), - requirements.PluginRequirement( - name="lsmod", plugin=lsmod.Lsmod, version=(2, 0, 0) + requirements.VersionRequirement( + name="lsmod", component=lsmod.Lsmod, version=(2, 0, 0) ), ] diff --git a/volatility3/framework/plugins/timeliner.py b/volatility3/framework/plugins/timeliner.py index 6000704eb..f65868705 100644 --- a/volatility3/framework/plugins/timeliner.py +++ b/volatility3/framework/plugins/timeliner.py @@ -25,10 +25,14 @@ class TimeLinerType(enum.IntEnum): CHANGED = 4 -class TimeLinerInterface(metaclass=abc.ABCMeta): +class TimeLinerInterface( + interfaces.configuration.VersionableInterface, metaclass=abc.ABCMeta +): """Interface defining methods that timeliner will use to generate a body file.""" + _version = (1, 0, 0) + @abc.abstractmethod def generate_timeline( self, diff --git a/volatility3/framework/plugins/windows/callbacks.py b/volatility3/framework/plugins/windows/callbacks.py index bb326fd41..bcdd37869 100644 --- a/volatility3/framework/plugins/windows/callbacks.py +++ b/volatility3/framework/plugins/windows/callbacks.py @@ -38,17 +38,17 @@ class Callbacks(interfaces.plugins.PluginInterface): description="Windows kernel", architectures=["Intel32", "Intel64"], ), - requirements.PluginRequirement( - name="ssdt", plugin=ssdt.SSDT, version=(2, 0, 0) + requirements.VersionRequirement( + name="ssdt", component=ssdt.SSDT, version=(2, 0, 0) ), - requirements.PluginRequirement( - name="poolscanner", plugin=poolscanner.PoolScanner, version=(3, 0, 0) + requirements.VersionRequirement( + name="poolscanner", component=poolscanner.PoolScanner, version=(3, 0, 0) ), - requirements.PluginRequirement( - name="driverirp", plugin=driverirp.DriverIrp, version=(1, 0, 0) + requirements.VersionRequirement( + name="driverirp", component=driverirp.DriverIrp, version=(1, 0, 0) ), - requirements.PluginRequirement( - name="handles", plugin=handles.Handles, version=(3, 0, 0) + requirements.VersionRequirement( + name="handles", component=handles.Handles, version=(3, 0, 0) ), ] diff --git a/volatility3/framework/plugins/windows/cmdline.py b/volatility3/framework/plugins/windows/cmdline.py index c095cff9e..733b06605 100644 --- a/volatility3/framework/plugins/windows/cmdline.py +++ b/volatility3/framework/plugins/windows/cmdline.py @@ -27,8 +27,8 @@ class CmdLine(interfaces.plugins.PluginInterface): description="Windows kernel", architectures=["Intel32", "Intel64"], ), - requirements.PluginRequirement( - name="pslist", plugin=pslist.PsList, version=(3, 0, 0) + requirements.VersionRequirement( + name="pslist", component=pslist.PsList, version=(3, 0, 0) ), requirements.ListRequirement( name="pid", diff --git a/volatility3/framework/plugins/windows/cmdscan.py b/volatility3/framework/plugins/windows/cmdscan.py index b7eab79fb..8c477b57d 100644 --- a/volatility3/framework/plugins/windows/cmdscan.py +++ b/volatility3/framework/plugins/windows/cmdscan.py @@ -38,8 +38,8 @@ class CmdScan(interfaces.plugins.PluginInterface): requirements.VersionRequirement( name="pslist", component=pslist.PsList, version=(3, 0, 0) ), - requirements.PluginRequirement( - name="consoles", plugin=consoles.Consoles, version=(3, 0, 0) + requirements.VersionRequirement( + name="consoles", component=consoles.Consoles, version=(3, 0, 0) ), requirements.BooleanRequirement( name="no_registry", diff --git a/volatility3/framework/plugins/windows/consoles.py b/volatility3/framework/plugins/windows/consoles.py index a63b044d9..6cfc6d588 100644 --- a/volatility3/framework/plugins/windows/consoles.py +++ b/volatility3/framework/plugins/windows/consoles.py @@ -48,8 +48,11 @@ class Consoles(interfaces.plugins.PluginInterface): requirements.VersionRequirement( name="verinfo", component=verinfo.VerInfo, version=(1, 0, 0) ), - requirements.PluginRequirement( - name="hivelist", plugin=hivelist.HiveList, version=(2, 0, 0) + requirements.VersionRequirement( + name="info", component=info.Info, version=(2, 0, 0) + ), + requirements.VersionRequirement( + name="hivelist", component=hivelist.HiveList, version=(2, 0, 0) ), requirements.BooleanRequirement( name="no_registry", diff --git a/volatility3/framework/plugins/windows/deskscan.py b/volatility3/framework/plugins/windows/deskscan.py index 6a8ff9e65..35430be5d 100644 --- a/volatility3/framework/plugins/windows/deskscan.py +++ b/volatility3/framework/plugins/windows/deskscan.py @@ -31,12 +31,12 @@ class DeskScan(desktops.Desktops): description="Windows kernel", architectures=["Intel32", "Intel64"], ), - requirements.PluginRequirement( - name="desktops", plugin=desktops.Desktops, version=(1, 0, 0) + requirements.VersionRequirement( + name="desktops", component=desktops.Desktops, version=(1, 0, 0) ), - requirements.PluginRequirement( + requirements.VersionRequirement( name="windowstations", - plugin=windowstations.WindowStations, + component=windowstations.WindowStations, version=(1, 0, 0), ), ] diff --git a/volatility3/framework/plugins/windows/desktops.py b/volatility3/framework/plugins/windows/desktops.py index 1085ff36d..c6557085e 100644 --- a/volatility3/framework/plugins/windows/desktops.py +++ b/volatility3/framework/plugins/windows/desktops.py @@ -31,9 +31,9 @@ class Desktops(interfaces.plugins.PluginInterface): description="Windows kernel", architectures=["Intel32", "Intel64"], ), - requirements.PluginRequirement( + requirements.VersionRequirement( name="windowstations", - plugin=windowstations.WindowStations, + component=windowstations.WindowStations, version=(1, 0, 0), ), ] diff --git a/volatility3/framework/plugins/windows/devicetree.py b/volatility3/framework/plugins/windows/devicetree.py index 012a8750d..17ec1c451 100644 --- a/volatility3/framework/plugins/windows/devicetree.py +++ b/volatility3/framework/plugins/windows/devicetree.py @@ -89,8 +89,8 @@ class DeviceTree(interfaces.plugins.PluginInterface): description="Windows kernel", architectures=["Intel32", "Intel64"], ), - requirements.PluginRequirement( - name="driverscan", plugin=driverscan.DriverScan, version=(2, 0, 0) + requirements.VersionRequirement( + name="driverscan", component=driverscan.DriverScan, version=(2, 0, 0) ), ] diff --git a/volatility3/framework/plugins/windows/direct_system_calls.py b/volatility3/framework/plugins/windows/direct_system_calls.py index 9d5b81507..60dbf728c 100644 --- a/volatility3/framework/plugins/windows/direct_system_calls.py +++ b/volatility3/framework/plugins/windows/direct_system_calls.py @@ -91,14 +91,14 @@ class DirectSystemCalls(interfaces.plugins.PluginInterface): description="Windows kernel", architectures=["Intel32", "Intel64"], ), - requirements.PluginRequirement( - name="pslist", plugin=pslist.PsList, version=(3, 0, 0) + requirements.VersionRequirement( + name="pslist", component=pslist.PsList, version=(3, 0, 0) ), requirements.VersionRequirement( name="yarascanner", component=yarascan.YaraScanner, version=(2, 1, 0) ), - requirements.PluginRequirement( - name="yarascan", plugin=yarascan.YaraScan, version=(2, 0, 0) + requirements.VersionRequirement( + name="yarascan", component=yarascan.YaraScan, version=(2, 0, 0) ), ] diff --git a/volatility3/framework/plugins/windows/dlllist.py b/volatility3/framework/plugins/windows/dlllist.py index b1c6f2f05..b851cf7fd 100644 --- a/volatility3/framework/plugins/windows/dlllist.py +++ b/volatility3/framework/plugins/windows/dlllist.py @@ -36,6 +36,11 @@ class DllList(interfaces.plugins.PluginInterface, timeliner.TimeLinerInterface): requirements.VersionRequirement( name="pslist", component=pslist.PsList, version=(3, 0, 0) ), + requirements.VersionRequirement( + name="timeliner", + component=timeliner.TimeLinerInterface, + version=(1, 0, 0), + ), requirements.VersionRequirement( name="psscan", component=psscan.PsScan, version=(2, 0, 0) ), diff --git a/volatility3/framework/plugins/windows/driverirp.py b/volatility3/framework/plugins/windows/driverirp.py index 20d8ac170..d5452fa9c 100644 --- a/volatility3/framework/plugins/windows/driverirp.py +++ b/volatility3/framework/plugins/windows/driverirp.py @@ -58,14 +58,14 @@ class DriverIrp(interfaces.plugins.PluginInterface): description="Windows kernel", architectures=["Intel32", "Intel64"], ), - requirements.PluginRequirement( - name="ssdt", plugin=ssdt.SSDT, version=(2, 0, 0) + requirements.VersionRequirement( + name="ssdt", component=ssdt.SSDT, version=(2, 0, 0) ), - requirements.PluginRequirement( - name="driverscan", plugin=driverscan.DriverScan, version=(2, 0, 0) + requirements.VersionRequirement( + name="driverscan", component=driverscan.DriverScan, version=(2, 0, 0) ), - requirements.PluginRequirement( - name="modules", plugin=modules.Modules, version=(3, 0, 0) + requirements.VersionRequirement( + name="modules", component=modules.Modules, version=(3, 0, 0) ), ] diff --git a/volatility3/framework/plugins/windows/drivermodule.py b/volatility3/framework/plugins/windows/drivermodule.py index 97e9e5b3c..c31fe2500 100644 --- a/volatility3/framework/plugins/windows/drivermodule.py +++ b/volatility3/framework/plugins/windows/drivermodule.py @@ -25,14 +25,14 @@ class DriverModule(interfaces.plugins.PluginInterface): description="Windows kernel", architectures=["Intel32", "Intel64"], ), - requirements.PluginRequirement( - name="ssdt", plugin=ssdt.SSDT, version=(2, 0, 0) + requirements.VersionRequirement( + name="ssdt", component=ssdt.SSDT, version=(2, 0, 0) ), - requirements.PluginRequirement( - name="driverscan", plugin=driverscan.DriverScan, version=(2, 0, 0) + requirements.VersionRequirement( + name="driverscan", component=driverscan.DriverScan, version=(2, 0, 0) ), - requirements.PluginRequirement( - name="modules", plugin=modules.Modules, version=(3, 0, 0) + requirements.VersionRequirement( + name="modules", component=modules.Modules, version=(3, 0, 0) ), ] diff --git a/volatility3/framework/plugins/windows/driverscan.py b/volatility3/framework/plugins/windows/driverscan.py index 57edfe0b6..57d365d00 100644 --- a/volatility3/framework/plugins/windows/driverscan.py +++ b/volatility3/framework/plugins/windows/driverscan.py @@ -24,8 +24,11 @@ class DriverScan(interfaces.plugins.PluginInterface): description="Windows kernel", architectures=["Intel32", "Intel64"], ), - requirements.PluginRequirement( - name="poolscanner", plugin=poolscanner.PoolScanner, version=(3, 0, 0) + requirements.VersionRequirement( + name="poolscanner", component=poolscanner.PoolScanner, version=(3, 0, 0) + ), + requirements.VersionRequirement( + name="modules", component=modules.Modules, version=(3, 0, 0) ), ] diff --git a/volatility3/framework/plugins/windows/envars.py b/volatility3/framework/plugins/windows/envars.py index 6360ca10b..6c07e797f 100644 --- a/volatility3/framework/plugins/windows/envars.py +++ b/volatility3/framework/plugins/windows/envars.py @@ -39,11 +39,11 @@ class Envars(interfaces.plugins.PluginInterface): description="Suppress common and non-persistent variables", optional=True, ), - requirements.PluginRequirement( - name="pslist", plugin=pslist.PsList, version=(3, 0, 0) + requirements.VersionRequirement( + name="pslist", component=pslist.PsList, version=(3, 0, 0) ), - requirements.PluginRequirement( - name="hivelist", plugin=hivelist.HiveList, version=(2, 0, 0) + requirements.VersionRequirement( + name="hivelist", component=hivelist.HiveList, version=(2, 0, 0) ), ] diff --git a/volatility3/framework/plugins/windows/filescan.py b/volatility3/framework/plugins/windows/filescan.py index e0c823756..f417e3e5e 100644 --- a/volatility3/framework/plugins/windows/filescan.py +++ b/volatility3/framework/plugins/windows/filescan.py @@ -24,8 +24,8 @@ class FileScan(interfaces.plugins.PluginInterface): description="Windows kernel", architectures=["Intel32", "Intel64"], ), - requirements.PluginRequirement( - name="poolscanner", plugin=poolscanner.PoolScanner, version=(3, 0, 0) + requirements.VersionRequirement( + name="poolscanner", component=poolscanner.PoolScanner, version=(3, 0, 0) ), ] diff --git a/volatility3/framework/plugins/windows/getservicesids.py b/volatility3/framework/plugins/windows/getservicesids.py index 19a73fba8..c04472eab 100644 --- a/volatility3/framework/plugins/windows/getservicesids.py +++ b/volatility3/framework/plugins/windows/getservicesids.py @@ -68,8 +68,8 @@ class GetServiceSIDs(interfaces.plugins.PluginInterface): description="Windows kernel", architectures=["Intel32", "Intel64"], ), - requirements.PluginRequirement( - name="hivelist", plugin=hivelist.HiveList, version=(2, 0, 0) + requirements.VersionRequirement( + name="hivelist", component=hivelist.HiveList, version=(2, 0, 0) ), ] diff --git a/volatility3/framework/plugins/windows/getsids.py b/volatility3/framework/plugins/windows/getsids.py index 786dc3394..27894646e 100644 --- a/volatility3/framework/plugins/windows/getsids.py +++ b/volatility3/framework/plugins/windows/getsids.py @@ -83,11 +83,11 @@ class GetSIDs(interfaces.plugins.PluginInterface): element_type=int, optional=True, ), - requirements.PluginRequirement( - name="pslist", plugin=pslist.PsList, version=(3, 0, 0) + requirements.VersionRequirement( + name="pslist", component=pslist.PsList, version=(3, 0, 0) ), - requirements.PluginRequirement( - name="hivelist", plugin=hivelist.HiveList, version=(2, 0, 0) + requirements.VersionRequirement( + name="hivelist", component=hivelist.HiveList, version=(2, 0, 0) ), ] diff --git a/volatility3/framework/plugins/windows/handles.py b/volatility3/framework/plugins/windows/handles.py index 9627b5caa..2f257772f 100644 --- a/volatility3/framework/plugins/windows/handles.py +++ b/volatility3/framework/plugins/windows/handles.py @@ -35,8 +35,8 @@ class Handles(interfaces.plugins.PluginInterface): description="Windows kernel", architectures=["Intel32", "Intel64"], ), - requirements.PluginRequirement( - name="pslist", plugin=pslist.PsList, version=(3, 0, 0) + requirements.VersionRequirement( + name="pslist", component=pslist.PsList, version=(3, 0, 0) ), requirements.VersionRequirement( name="psscan", component=psscan.PsScan, version=(2, 0, 0) diff --git a/volatility3/framework/plugins/windows/indirect_system_calls.py b/volatility3/framework/plugins/windows/indirect_system_calls.py index 9f3fc4359..26216d2c3 100644 --- a/volatility3/framework/plugins/windows/indirect_system_calls.py +++ b/volatility3/framework/plugins/windows/indirect_system_calls.py @@ -46,12 +46,12 @@ class IndirectSystemCalls(direct_system_calls.DirectSystemCalls): requirements.VersionRequirement( name="yarascanner", component=yarascan.YaraScanner, version=(2, 1, 0) ), - requirements.PluginRequirement( - name="yarascan", plugin=yarascan.YaraScan, version=(2, 0, 0) + requirements.VersionRequirement( + name="yarascan", component=yarascan.YaraScan, version=(2, 0, 0) ), - requirements.PluginRequirement( + requirements.VersionRequirement( name="direct_system_calls", - plugin=direct_system_calls.DirectSystemCalls, + component=direct_system_calls.DirectSystemCalls, version=(2, 0, 0), ), ] diff --git a/volatility3/framework/plugins/windows/memmap.py b/volatility3/framework/plugins/windows/memmap.py index 5a7bd1b9a..af4564259 100644 --- a/volatility3/framework/plugins/windows/memmap.py +++ b/volatility3/framework/plugins/windows/memmap.py @@ -27,8 +27,8 @@ class Memmap(interfaces.plugins.PluginInterface): description="Windows kernel", architectures=["Intel32", "Intel64"], ), - requirements.PluginRequirement( - name="pslist", plugin=pslist.PsList, version=(3, 0, 0) + requirements.VersionRequirement( + name="pslist", component=pslist.PsList, version=(3, 0, 0) ), requirements.IntRequirement( name="pid", diff --git a/volatility3/framework/plugins/windows/mftscan.py b/volatility3/framework/plugins/windows/mftscan.py index 2c5827a25..74cadc833 100644 --- a/volatility3/framework/plugins/windows/mftscan.py +++ b/volatility3/framework/plugins/windows/mftscan.py @@ -32,9 +32,17 @@ class MFTScan(interfaces.plugins.PluginInterface, timeliner.TimeLinerInterface): description="Memory layer for the kernel", architectures=["Intel32", "Intel64"], ), + requirements.VersionRequirement( + name="timeliner", + component=timeliner.TimeLinerInterface, + version=(1, 0, 0), + ), requirements.VersionRequirement( name="yarascanner", component=yarascan.YaraScanner, version=(2, 1, 0) ), + requirements.VersionRequirement( + name="yarascan", component=yarascan.YaraScan, version=(2, 0, 0) + ), ] @classmethod @@ -333,8 +341,8 @@ class ADS(interfaces.plugins.PluginInterface): @classmethod def get_requirements(cls): return [ - requirements.PluginRequirement( - name="MFTScan", plugin=MFTScan, version=(2, 0, 0) + requirements.VersionRequirement( + name="MFTScan", component=MFTScan, version=(2, 0, 0) ), requirements.TranslationLayerRequirement( name="primary", @@ -403,8 +411,8 @@ class ResidentData(interfaces.plugins.PluginInterface): @classmethod def get_requirements(cls): return [ - requirements.PluginRequirement( - name="MFTScan", plugin=MFTScan, version=(2, 0, 0) + requirements.VersionRequirement( + name="MFTScan", component=MFTScan, version=(2, 0, 0) ), requirements.TranslationLayerRequirement( name="primary", diff --git a/volatility3/framework/plugins/windows/mutantscan.py b/volatility3/framework/plugins/windows/mutantscan.py index 38685677a..ba2824bfc 100644 --- a/volatility3/framework/plugins/windows/mutantscan.py +++ b/volatility3/framework/plugins/windows/mutantscan.py @@ -24,8 +24,8 @@ class MutantScan(interfaces.plugins.PluginInterface): description="Windows kernel", architectures=["Intel32", "Intel64"], ), - requirements.PluginRequirement( - name="poolscanner", plugin=poolscanner.PoolScanner, version=(3, 0, 0) + requirements.VersionRequirement( + name="poolscanner", component=poolscanner.PoolScanner, version=(3, 0, 0) ), ] diff --git a/volatility3/framework/plugins/windows/netscan.py b/volatility3/framework/plugins/windows/netscan.py index 1ab748864..fa422e103 100644 --- a/volatility3/framework/plugins/windows/netscan.py +++ b/volatility3/framework/plugins/windows/netscan.py @@ -39,6 +39,11 @@ class NetScan(interfaces.plugins.PluginInterface, timeliner.TimeLinerInterface): requirements.VersionRequirement( name="info", component=info.Info, version=(2, 0, 0) ), + requirements.VersionRequirement( + name="timeliner", + component=timeliner.TimeLinerInterface, + version=(1, 0, 0), + ), requirements.VersionRequirement( name="verinfo", component=verinfo.VerInfo, version=(1, 0, 0) ), diff --git a/volatility3/framework/plugins/windows/netstat.py b/volatility3/framework/plugins/windows/netstat.py index 655ef710a..cf7f5272a 100644 --- a/volatility3/framework/plugins/windows/netstat.py +++ b/volatility3/framework/plugins/windows/netstat.py @@ -39,6 +39,11 @@ class NetStat(interfaces.plugins.PluginInterface, timeliner.TimeLinerInterface): requirements.VersionRequirement( name="modules", component=modules.Modules, version=(3, 0, 0) ), + requirements.VersionRequirement( + name="timeliner", + component=timeliner.TimeLinerInterface, + version=(1, 0, 0), + ), requirements.VersionRequirement( name="pdbutil", component=pdbutil.PDBUtility, version=(1, 0, 0) ), diff --git a/volatility3/framework/plugins/windows/orphan_kernel_threads.py b/volatility3/framework/plugins/windows/orphan_kernel_threads.py index 151fe88c9..b4dec0fc5 100644 --- a/volatility3/framework/plugins/windows/orphan_kernel_threads.py +++ b/volatility3/framework/plugins/windows/orphan_kernel_threads.py @@ -33,14 +33,14 @@ class Threads(thrdscan.ThrdScan): description="Windows kernel", architectures=["Intel32", "Intel64"], ), - requirements.PluginRequirement( - name="thrdscan", plugin=thrdscan.ThrdScan, version=(1, 1, 0) + requirements.VersionRequirement( + name="thrdscan", component=thrdscan.ThrdScan, version=(2, 0, 0) ), - requirements.PluginRequirement( - name="ssdt", plugin=ssdt.SSDT, version=(2, 0, 0) + requirements.VersionRequirement( + name="ssdt", component=ssdt.SSDT, version=(2, 0, 0) ), - requirements.PluginRequirement( - name="modules", plugin=modules.Modules, version=(3, 0, 0) + requirements.VersionRequirement( + name="modules", component=modules.Modules, version=(3, 0, 0) ), ] diff --git a/volatility3/framework/plugins/windows/pe_symbols.py b/volatility3/framework/plugins/windows/pe_symbols.py index b26e8d113..3a08a1002 100644 --- a/volatility3/framework/plugins/windows/pe_symbols.py +++ b/volatility3/framework/plugins/windows/pe_symbols.py @@ -229,7 +229,6 @@ class ExportSymbolFinder(PESymbolFinder): Returns: address: the address of the symbol, if found """ - for export in self._symbol_module: sym_name = self._get_name(export) if sym_name and sym_name == name: @@ -413,8 +412,10 @@ class PESymbols(interfaces.plugins.PluginInterface): ) for mod_name, unresolved_symbols in missing_symbols.items(): - for symbol in unresolved_symbols: - vollog.debug(f"Unable to resolve symbol {symbol} in module {mod_name}") + for symbol_key, symbols in unresolved_symbols.items(): + vollog.debug( + f"Unable to resolve symbols {symbols} of type {symbol_key} in module {mod_name}" + ) return found_symbols @@ -632,7 +633,7 @@ class PESymbols(interfaces.plugins.PluginInterface): def _get_symbol_value( wanted_symbols: filter_module_info, symbol_resolver: PESymbolFinder, - ) -> Generator[Tuple[str, int, str, int], None, None]: + ) -> Generator[Tuple[str, str, int], None, None]: """ Enumerates the symbols specified as wanted by the calling plugin @@ -641,7 +642,7 @@ class PESymbols(interfaces.plugins.PluginInterface): symbol_resolver: method in a layer to resolve the symbols Returns: - Tuple[str, int, str, int]: the index and value of the found symbol in the wanted list, and the name and address of resolved symbol + Tuple[str, str, int]: the symbol identifier (key) of the found symbol in the wanted list, and the name and address of resolved symbol """ if ( wanted_names_identifier not in wanted_symbols @@ -661,15 +662,25 @@ class PESymbols(interfaces.plugins.PluginInterface): # address or name if symbol_key in wanted_symbols: # walk each wanted address or name - for value_index, wanted_value in enumerate(wanted_symbols[symbol_key]): - symbol_value = symbol_getter(wanted_value) + # build dict in this function for debugging and tracking + all_wanted = [] + for wanted_value in wanted_symbols[symbol_key]: + all_wanted.append(wanted_value) + + for value_index, wanted_value in enumerate(all_wanted): + symbol_value = symbol_getter(wanted_value) if symbol_value: # yield out deleteion key, deletion index, symbol name, symbol address if symbol_key == wanted_names_identifier: - yield symbol_key, value_index, wanted_value, symbol_value # type: ignore + yield symbol_key, wanted_value, symbol_value else: - yield symbol_key, value_index, symbol_value, wanted_value # type: ignore + yield symbol_key, symbol_value, wanted_value + + for value in all_wanted: + vollog.debug( + f"Unable to resolve value {value} using getter {symbol_getter}" + ) @classmethod def _validate_wanted_modules( @@ -742,7 +753,7 @@ class PESymbols(interfaces.plugins.PluginInterface): PESymbols._find_symbols_through_exports, ] - found: found_symbols_module = [] + found_symbols: found_symbols_module = [] # the symbols wanted from this module by the caller wanted = wanted_modules[mod_name] @@ -760,12 +771,17 @@ class PESymbols(interfaces.plugins.PluginInterface): vollog.debug(f"Have resolver for method {method}") for ( symbol_key, - value_index, symbol_name, symbol_address, ) in PESymbols._get_symbol_value(remaining, symbol_resolver): - found.append((symbol_name, symbol_address)) - del remaining[symbol_key][value_index] + found_symbols.append((symbol_name, symbol_address)) + + if symbol_key == wanted_names_identifier: + to_remove = symbol_name + else: + to_remove = symbol_address + + remaining[symbol_key].remove(to_remove) # everything was resolved, stop this resolver # remove this key from the remaining symbols to resolve @@ -781,7 +797,7 @@ class PESymbols(interfaces.plugins.PluginInterface): if done_processing: break - return found, remaining + return found_symbols, remaining @classmethod def find_symbols( @@ -970,7 +986,8 @@ class PESymbols(interfaces.plugins.PluginInterface): Generator[Tuple[interfaces.objects.ObjectInterface, str, ranges_type]]: Yields tuple of process objects, layers, and VADs mapping files """ procs = pslist.PsList.list_processes( - context=context, kernel_module_name=kernel_module_name + context=context, + kernel_module_name=kernel_module_name, ) for proc in procs: diff --git a/volatility3/framework/plugins/windows/poolscanner.py b/volatility3/framework/plugins/windows/poolscanner.py index 1f29a3aed..975ed2326 100644 --- a/volatility3/framework/plugins/windows/poolscanner.py +++ b/volatility3/framework/plugins/windows/poolscanner.py @@ -139,8 +139,8 @@ class PoolScanner(plugins.PluginInterface): description="Windows kernel", architectures=["Intel32", "Intel64"], ), - requirements.PluginRequirement( - name="handles", plugin=handles.Handles, version=(3, 0, 0) + requirements.VersionRequirement( + name="handles", component=handles.Handles, version=(3, 0, 0) ), ] diff --git a/volatility3/framework/plugins/windows/privileges.py b/volatility3/framework/plugins/windows/privileges.py index e41915442..6bc59bab6 100644 --- a/volatility3/framework/plugins/windows/privileges.py +++ b/volatility3/framework/plugins/windows/privileges.py @@ -60,8 +60,8 @@ class Privs(interfaces.plugins.PluginInterface): element_type=int, optional=True, ), - requirements.PluginRequirement( - name="pslist", plugin=pslist.PsList, version=(3, 0, 0) + requirements.VersionRequirement( + name="pslist", component=pslist.PsList, version=(3, 0, 0) ), ] diff --git a/volatility3/framework/plugins/windows/processghosting.py b/volatility3/framework/plugins/windows/processghosting.py index 5bc6bc5a3..7e7f6d3cc 100644 --- a/volatility3/framework/plugins/windows/processghosting.py +++ b/volatility3/framework/plugins/windows/processghosting.py @@ -2,21 +2,23 @@ # which is available at https://www.volatilityfoundation.org/license/vsl-v1.0 # import logging -import contextlib + +from typing import Optional, Tuple, Generator, Dict from volatility3.framework import interfaces, exceptions from volatility3.framework import renderers from volatility3.framework.configuration import requirements from volatility3.framework.objects import utility from volatility3.framework.renderers import format_hints -from volatility3.plugins.windows import pslist +from volatility3.plugins.windows import pslist, vadinfo vollog = logging.getLogger(__name__) class ProcessGhosting(interfaces.plugins.PluginInterface): - """Lists processes whose DeletePending bit is set or whose FILE_OBJECT is set to 0""" + """Lists processes whose DeletePending bit is set or whose FILE_OBJECT is set to 0 or Vads that are DeleteOnClose""" + _version = (1, 0, 0) _required_framework_version = (2, 4, 0) @classmethod @@ -31,54 +33,168 @@ class ProcessGhosting(interfaces.plugins.PluginInterface): requirements.VersionRequirement( name="pslist", component=pslist.PsList, version=(3, 0, 0) ), + requirements.VersionRequirement( + name="vadinfo", component=vadinfo.VadInfo, version=(2, 0, 1) + ), ] + @classmethod + def _process_checks( + cls, + proc: interfaces.objects.ObjectInterface, + mapped_files: Dict[int, Tuple[str, interfaces.objects.ObjectInterface]], + ) -> Generator[ + Tuple[int, Optional[int], Optional[int], int, Optional[str]], None, None + ]: + """ + Checks the EPROCESS for signs of ghosting + """ + if not proc.has_member("ImageFilePointer"): + return + + delete_pending = None + + # if it is 0 then its a side effect of process ghosting + if proc.ImageFilePointer.vol.offset != 0: + try: + file_object = proc.ImageFilePointer + delete_pending = file_object.DeletePending + file_object = file_object.dereference().vol.offset + except exceptions.InvalidAddressException: + file_object = 0 + + # ImageFilePointer equal to 0 means process ghosting or similar techniques were used + else: + file_object = 0 + + # delete_pending besides 0 or 1 = smear + if isinstance(delete_pending, int) and delete_pending not in [0, 1]: + vollog.debug( + f"Invalid delete_pending value {delete_pending} found for process {proc.UniqueProcessId}" + ) + delete_pending = None + + if file_object == 0 or delete_pending == 1: + yield file_object, delete_pending, None, proc.SectionBaseAddress + + @classmethod + def _vad_checks( + cls, control_area: interfaces.objects.ObjectInterface, vad_path: str + ) -> Generator[Tuple[int, Optional[int], Optional[int]], None, None]: + """ + Checks the control area for delete on close or delete pending being set + """ + try: + file_object = control_area.FilePointer.dereference().cast("_FILE_OBJECT") + except exceptions.InvalidAddressException: + return + + try: + delete_on_close = control_area.u.Flags.DeleteOnClose + except exceptions.InvalidAddressException: + delete_on_close = None + + if delete_on_close and vad_path.lower().endswith((".exe", ".dll")): + yield file_object.vol.offset, None, delete_on_close + + try: + delete_pending = file_object.DeletePending + except exceptions.InvalidAddressException: + delete_pending = None + + if delete_pending == 1: + yield file_object.vol.offset, delete_pending, None + + @classmethod + def check_for_ghosting( + cls, + proc: interfaces.objects.ObjectInterface, + mapped_files: Dict[int, Tuple[str, interfaces.objects.ObjectInterface]], + ) -> Generator[ + Tuple[int, Optional[int], Optional[int], int, Optional[str]], None, None + ]: + """ + Returns process or vad info for ghosting files + + Args: + proc: + mapped_files: A dictionary mapping vad base addreses to the path and vad instance for the process + + Return: + A Generator of tuples of the file object address, the delete pending state, delete on close state, base address of the VAD, and the path + """ + # check the direct file object of the process + yield from cls._process_checks(proc, mapped_files) + + # walk each vad, check if it is pending delete or has its delete on close bit set + for vad_base, (path, vad) in mapped_files.items(): + # these checks have no meaning for private memory areas + if vad.get_private_memory() == 1: + continue + + try: + if vad.has_member("ControlArea"): + control_area = vad.ControlArea + elif vad.has_member("Subsection"): + control_area = vad.Subsection.ControlArea + # We got here from a short vad, likely smear + else: + continue + except exceptions.InvalidAddressException: + vollog.debug( + f"Unable to get control area for vad at base {vad_base:#x} for process with pid {proc.UniqueProcessId}" + ) + continue + + for file_object_address, delete_pending, delete_on_close in cls._vad_checks( + control_area, path + ): + yield format_hints.Hex( + file_object_address + ), delete_pending, delete_on_close, vad_base + def _generator(self, procs): kernel = self.context.modules[self.config["kernel"]] - if not kernel.get_type("_EPROCESS").has_member("ImageFilePointer"): + has_imagefilepointer = kernel.get_type("_EPROCESS").has_member( + "ImageFilePointer" + ) + if not has_imagefilepointer: vollog.warning( - "This plugin only supports Windows 10 builds when the ImageFilePointer member of _EPROCESS is present" + "ImageFilePointer checks are only supported on Windows 10+ builds when the ImageFilePointer member of _EPROCESS is present" ) - return for proc in procs: - delete_pending = renderers.UnreadableValue() process_name = utility.array_to_string(proc.ImageFileName) + pid = proc.UniqueProcessId - # if it is 0 then its a side effect of process ghosting - if proc.ImageFilePointer.vol.offset != 0: - try: - file_object = proc.ImageFilePointer - delete_pending = file_object.DeletePending - except exceptions.InvalidAddressException: - file_object = 0 + # base address -> (file path, VAD instance) + mapped_files: Dict[int, Tuple[str, interfaces.objects.ObjectInterface]] = {} + for vad in vadinfo.VadInfo.list_vads(proc): + path = vad.get_file_name() + if isinstance(path, str): + mapped_files[vad.get_start()] = (path, vad) - # ImageFilePointer equal to 0 means process ghosting or similar techniques were used - else: - file_object = 0 + for ( + file_object_address, + delete_pending, + delete_on_close, + base_address, + ) in self.check_for_ghosting(proc, mapped_files): + vad_info = mapped_files.get(base_address) + if vad_info: + path = vad_info[0] + else: + path = renderers.NotAvailableValue() - if isinstance(delete_pending, int) and delete_pending not in [0, 1]: - vollog.debug( - f"Invalid delete_pending value {delete_pending} found for {process_name} {proc.UniqueProcessId}" - ) - - # delete_pending besides 0 or 1 = smear - if file_object == 0 or delete_pending == 1: - path = renderers.UnreadableValue() - if file_object: - with contextlib.suppress(exceptions.InvalidAddressException): - path = file_object.FileName.String - - yield ( - 0, - ( - proc.UniqueProcessId, - process_name, - format_hints.Hex(file_object), - delete_pending, - path, - ), + yield 0, ( + pid, + process_name, + format_hints.Hex(base_address), + format_hints.Hex(file_object_address), + delete_pending or renderers.NotApplicableValue(), + delete_on_close or renderers.NotApplicableValue(), + path, ) def run(self): @@ -88,8 +204,10 @@ class ProcessGhosting(interfaces.plugins.PluginInterface): [ ("PID", int), ("Process", str), + ("Base", format_hints.Hex), ("FILE_OBJECT", format_hints.Hex), - ("DeletePending", str), + ("DeletePending", int), + ("DeleteOnClose", int), ("Path", str), ], self._generator( diff --git a/volatility3/framework/plugins/windows/pslist.py b/volatility3/framework/plugins/windows/pslist.py index 1cb2c6356..b92fdf66c 100644 --- a/volatility3/framework/plugins/windows/pslist.py +++ b/volatility3/framework/plugins/windows/pslist.py @@ -41,6 +41,11 @@ class PsList(interfaces.plugins.PluginInterface, timeliner.TimeLinerInterface): default=cls.PHYSICAL_DEFAULT, optional=True, ), + requirements.VersionRequirement( + name="timeliner", + component=timeliner.TimeLinerInterface, + version=(1, 0, 0), + ), requirements.ListRequirement( name="pid", element_type=int, diff --git a/volatility3/framework/plugins/windows/psscan.py b/volatility3/framework/plugins/windows/psscan.py index 99fa9640b..ae37c20a1 100644 --- a/volatility3/framework/plugins/windows/psscan.py +++ b/volatility3/framework/plugins/windows/psscan.py @@ -33,8 +33,13 @@ class PsScan(interfaces.plugins.PluginInterface, timeliner.TimeLinerInterface): description="Windows kernel", architectures=["Intel32", "Intel64"], ), - requirements.PluginRequirement( - name="pslist", plugin=pslist.PsList, version=(3, 0, 0) + requirements.VersionRequirement( + name="pslist", component=pslist.PsList, version=(3, 0, 0) + ), + requirements.VersionRequirement( + name="timeliner", + component=timeliner.TimeLinerInterface, + version=(1, 0, 0), ), requirements.VersionRequirement( name="info", component=info.Info, version=(2, 0, 0) diff --git a/volatility3/framework/plugins/windows/psxview.py b/volatility3/framework/plugins/windows/psxview.py index 7329588cc..7c3444f70 100644 --- a/volatility3/framework/plugins/windows/psxview.py +++ b/volatility3/framework/plugins/windows/psxview.py @@ -55,7 +55,7 @@ We recommend using -r pretty if you are looking at this plugin's output in a ter name="psscan", component=psscan.PsScan, version=(2, 0, 0) ), requirements.VersionRequirement( - name="thrdscan", component=thrdscan.ThrdScan, version=(1, 0, 0) + name="thrdscan", component=thrdscan.ThrdScan, version=(2, 0, 0) ), requirements.VersionRequirement( name="handles", component=handles.Handles, version=(3, 0, 0) diff --git a/volatility3/framework/plugins/windows/registry/amcache.py b/volatility3/framework/plugins/windows/registry/amcache.py index 5920cd266..a6ad1848e 100644 --- a/volatility3/framework/plugins/windows/registry/amcache.py +++ b/volatility3/framework/plugins/windows/registry/amcache.py @@ -231,8 +231,13 @@ class Amcache(interfaces.plugins.PluginInterface, timeliner.TimeLinerInterface): description="Windows kernel", architectures=["Intel32", "Intel64"], ), - requirements.PluginRequirement( - name="hivelist", plugin=hivelist.HiveList, version=(2, 0, 0) + requirements.VersionRequirement( + name="hivelist", component=hivelist.HiveList, version=(2, 0, 0) + ), + requirements.VersionRequirement( + name="timeliner", + component=timeliner.TimeLinerInterface, + version=(1, 0, 0), ), ] @@ -432,7 +437,6 @@ class Amcache(interfaces.plugins.PluginInterface, timeliner.TimeLinerInterface): wanted_values = [key.value for key in val_enum] for file_key in inv_app_file_key.get_subkeys(): - vollog.debug( f"Parsing Win10 InventoryApplicationFile key {file_key.get_name()}" ) @@ -600,7 +604,10 @@ class Amcache(interfaces.plugins.PluginInterface, timeliner.TimeLinerInterface): try: files = sorted( list( - self.parse_inventory_app_file_key(amcache.get_key("Root\\InventoryApplicationFile")), # type: ignore + self.parse_inventory_app_file_key( + amcache.get_key("Root\\InventoryApplicationFile") + ), + # type: ignore ), key=_entry_sort_key, ) diff --git a/volatility3/framework/plugins/windows/registry/cachedump.py b/volatility3/framework/plugins/windows/registry/cachedump.py index c46ea237e..49c5495c2 100644 --- a/volatility3/framework/plugins/windows/registry/cachedump.py +++ b/volatility3/framework/plugins/windows/registry/cachedump.py @@ -31,14 +31,14 @@ class Cachedump(interfaces.plugins.PluginInterface): description="Windows kernel", architectures=["Intel32", "Intel64"], ), - requirements.PluginRequirement( - name="hivelist", plugin=hivelist.HiveList, version=(2, 0, 0) + requirements.VersionRequirement( + name="hivelist", component=hivelist.HiveList, version=(2, 0, 0) ), - requirements.PluginRequirement( - name="lsadump", plugin=lsadump.Lsadump, version=(1, 0, 0) + requirements.VersionRequirement( + name="lsadump", component=lsadump.Lsadump, version=(1, 0, 0) ), - requirements.PluginRequirement( - name="hashdump", plugin=hashdump.Hashdump, version=(1, 1, 0) + requirements.VersionRequirement( + name="hashdump", component=hashdump.Hashdump, version=(1, 1, 0) ), ] diff --git a/volatility3/framework/plugins/windows/registry/getcellroutine.py b/volatility3/framework/plugins/windows/registry/getcellroutine.py index 5be4254ba..5f3b1dcaa 100644 --- a/volatility3/framework/plugins/windows/registry/getcellroutine.py +++ b/volatility3/framework/plugins/windows/registry/getcellroutine.py @@ -27,11 +27,11 @@ class GetCellRoutine(interfaces.plugins.PluginInterface): description="Windows kernel", architectures=["Intel32", "Intel64"], ), - requirements.PluginRequirement( - name="hivelist", plugin=hivelist.HiveList, version=(2, 0, 0) + requirements.VersionRequirement( + name="hivelist", component=hivelist.HiveList, version=(2, 0, 0) ), - requirements.PluginRequirement( - name="ssdt", plugin=ssdt.SSDT, version=(2, 0, 0) + requirements.VersionRequirement( + name="ssdt", component=ssdt.SSDT, version=(2, 0, 0) ), ] diff --git a/volatility3/framework/plugins/windows/registry/hashdump.py b/volatility3/framework/plugins/windows/registry/hashdump.py index 68d5f834a..630aa1cfd 100644 --- a/volatility3/framework/plugins/windows/registry/hashdump.py +++ b/volatility3/framework/plugins/windows/registry/hashdump.py @@ -33,8 +33,8 @@ class Hashdump(interfaces.plugins.PluginInterface): description="Windows kernel", architectures=["Intel32", "Intel64"], ), - requirements.PluginRequirement( - name="hivelist", plugin=hivelist.HiveList, version=(2, 0, 0) + requirements.VersionRequirement( + name="hivelist", component=hivelist.HiveList, version=(2, 0, 0) ), ] diff --git a/volatility3/framework/plugins/windows/registry/hivelist.py b/volatility3/framework/plugins/windows/registry/hivelist.py index fefd24b67..ec2fbc4c7 100644 --- a/volatility3/framework/plugins/windows/registry/hivelist.py +++ b/volatility3/framework/plugins/windows/registry/hivelist.py @@ -60,8 +60,8 @@ class HiveList(interfaces.plugins.PluginInterface): optional=True, default=None, ), - requirements.PluginRequirement( - name="hivescan", plugin=hivescan.HiveScan, version=(2, 0, 0) + requirements.VersionRequirement( + name="hivescan", component=hivescan.HiveScan, version=(2, 0, 0) ), requirements.BooleanRequirement( name="dump", diff --git a/volatility3/framework/plugins/windows/registry/hivescan.py b/volatility3/framework/plugins/windows/registry/hivescan.py index 10843f8ab..2ebc52f53 100644 --- a/volatility3/framework/plugins/windows/registry/hivescan.py +++ b/volatility3/framework/plugins/windows/registry/hivescan.py @@ -25,11 +25,11 @@ class HiveScan(interfaces.plugins.PluginInterface): description="Windows kernel", architectures=["Intel32", "Intel64"], ), - requirements.PluginRequirement( - name="poolscanner", plugin=poolscanner.PoolScanner, version=(3, 0, 0) + requirements.VersionRequirement( + name="poolscanner", component=poolscanner.PoolScanner, version=(3, 0, 0) ), - requirements.PluginRequirement( - name="bigpools", plugin=bigpools.BigPools, version=(2, 0, 0) + requirements.VersionRequirement( + name="bigpools", component=bigpools.BigPools, version=(2, 0, 0) ), ] diff --git a/volatility3/framework/plugins/windows/registry/printkey.py b/volatility3/framework/plugins/windows/registry/printkey.py index c8b8f9cfb..6ca56b1bb 100644 --- a/volatility3/framework/plugins/windows/registry/printkey.py +++ b/volatility3/framework/plugins/windows/registry/printkey.py @@ -35,8 +35,8 @@ class PrintKey(interfaces.plugins.PluginInterface): description="Windows kernel", architectures=["Intel32", "Intel64"], ), - requirements.PluginRequirement( - name="hivelist", plugin=hivelist.HiveList, version=(2, 0, 0) + requirements.VersionRequirement( + name="hivelist", component=hivelist.HiveList, version=(2, 0, 0) ), requirements.IntRequirement( name="offset", description="Hive Offset", default=None, optional=True diff --git a/volatility3/framework/plugins/windows/registry/scheduled_tasks.py b/volatility3/framework/plugins/windows/registry/scheduled_tasks.py index 3aa82a69e..09ebed7b9 100644 --- a/volatility3/framework/plugins/windows/registry/scheduled_tasks.py +++ b/volatility3/framework/plugins/windows/registry/scheduled_tasks.py @@ -1123,8 +1123,13 @@ class ScheduledTasks(interfaces.plugins.PluginInterface, timeliner.TimeLinerInte description="Windows kernel", architectures=["Intel33", "Intel64"], ), - requirements.PluginRequirement( - name="hivelist", plugin=hivelist.HiveList, version=(2, 0, 0) + requirements.VersionRequirement( + name="hivelist", component=hivelist.HiveList, version=(2, 0, 0) + ), + requirements.VersionRequirement( + name="timeliner", + component=timeliner.TimeLinerInterface, + version=(1, 0, 0), ), ] diff --git a/volatility3/framework/plugins/windows/registry/userassist.py b/volatility3/framework/plugins/windows/registry/userassist.py index ef51b91bf..809d0b2b3 100644 --- a/volatility3/framework/plugins/windows/registry/userassist.py +++ b/volatility3/framework/plugins/windows/registry/userassist.py @@ -56,8 +56,13 @@ class UserAssist(interfaces.plugins.PluginInterface, timeliner.TimeLinerInterfac requirements.IntRequirement( name="offset", description="Hive Offset", default=None, optional=True ), - requirements.PluginRequirement( - name="hivelist", plugin=hivelist.HiveList, version=(2, 0, 0) + requirements.VersionRequirement( + name="hivelist", component=hivelist.HiveList, version=(2, 0, 0) + ), + requirements.VersionRequirement( + name="timeliner", + component=timeliner.TimeLinerInterface, + version=(1, 0, 0), ), ] diff --git a/volatility3/framework/plugins/windows/sessions.py b/volatility3/framework/plugins/windows/sessions.py index 73a537cd4..29d0b2104 100644 --- a/volatility3/framework/plugins/windows/sessions.py +++ b/volatility3/framework/plugins/windows/sessions.py @@ -27,8 +27,13 @@ class Sessions(interfaces.plugins.PluginInterface, timeliner.TimeLinerInterface) description="Windows kernel", architectures=["Intel32", "Intel64"], ), - requirements.PluginRequirement( - name="pslist", plugin=pslist.PsList, version=(3, 0, 0) + requirements.VersionRequirement( + name="pslist", component=pslist.PsList, version=(3, 0, 0) + ), + requirements.VersionRequirement( + name="timeliner", + component=timeliner.TimeLinerInterface, + version=(1, 0, 0), ), requirements.ListRequirement( name="pid", diff --git a/volatility3/framework/plugins/windows/shimcachemem.py b/volatility3/framework/plugins/windows/shimcachemem.py index f26bf3d6b..7883dfba3 100644 --- a/volatility3/framework/plugins/windows/shimcachemem.py +++ b/volatility3/framework/plugins/windows/shimcachemem.py @@ -64,8 +64,13 @@ class ShimcacheMem(interfaces.plugins.PluginInterface, timeliner.TimeLinerInterf description="Windows kernel", architectures=["Intel32", "Intel64"], ), - requirements.PluginRequirement( - name="pslist", plugin=pslist.PsList, version=(3, 0, 0) + requirements.VersionRequirement( + name="pslist", component=pslist.PsList, version=(3, 0, 0) + ), + requirements.VersionRequirement( + name="timeliner", + component=timeliner.TimeLinerInterface, + version=(1, 0, 0), ), requirements.VersionRequirement( name="vadinfo", component=vadinfo.VadInfo, version=(2, 0, 0) diff --git a/volatility3/framework/plugins/windows/ssdt.py b/volatility3/framework/plugins/windows/ssdt.py index ed7d1310d..b4fa39950 100644 --- a/volatility3/framework/plugins/windows/ssdt.py +++ b/volatility3/framework/plugins/windows/ssdt.py @@ -31,8 +31,8 @@ class SSDT(plugins.PluginInterface): description="Windows kernel", architectures=["Intel32", "Intel64"], ), - requirements.PluginRequirement( - name="modules", plugin=modules.Modules, version=(3, 0, 0) + requirements.VersionRequirement( + name="modules", component=modules.Modules, version=(3, 0, 0) ), ] diff --git a/volatility3/framework/plugins/windows/strings.py b/volatility3/framework/plugins/windows/strings.py index 46784e48a..9ea4ffed0 100644 --- a/volatility3/framework/plugins/windows/strings.py +++ b/volatility3/framework/plugins/windows/strings.py @@ -33,8 +33,8 @@ class Strings(interfaces.plugins.PluginInterface): description="Windows kernel", architectures=["Intel32", "Intel64"], ), - requirements.PluginRequirement( - name="pslist", plugin=pslist.PsList, version=(3, 0, 0) + requirements.VersionRequirement( + name="pslist", component=pslist.PsList, version=(3, 0, 0) ), requirements.ListRequirement( name="pid", diff --git a/volatility3/framework/plugins/windows/suspicious_threads.py b/volatility3/framework/plugins/windows/suspicious_threads.py index c98b06792..eabc637c8 100644 --- a/volatility3/framework/plugins/windows/suspicious_threads.py +++ b/volatility3/framework/plugins/windows/suspicious_threads.py @@ -35,7 +35,7 @@ class SuspiciousThreads(interfaces.plugins.PluginInterface): optional=True, ), requirements.VersionRequirement( - name="thrdscan", component=thrdscan.ThrdScan, version=(1, 1, 0) + name="thrdscan", component=thrdscan.ThrdScan, version=(2, 0, 0) ), requirements.VersionRequirement( name="pslist", component=pslist.PsList, version=(3, 0, 0) @@ -181,11 +181,11 @@ class SuspiciousThreads(interfaces.plugins.PluginInterface): if not info: continue - _, _, tid, start_address, _, _ = info + _, _, tid, start_address, _, win32_start_address, _, _, _ = info addresses = [ (start_address, "Start"), - (thread.Win32StartAddress, "Win32Start"), + (win32_start_address, "Win32Start"), ] for address, context in addresses: diff --git a/volatility3/framework/plugins/windows/svclist.py b/volatility3/framework/plugins/windows/svclist.py index 00d4aa647..24ac2278f 100644 --- a/volatility3/framework/plugins/windows/svclist.py +++ b/volatility3/framework/plugins/windows/svclist.py @@ -31,8 +31,11 @@ class SvcList(svcscan.SvcScan): def get_requirements(cls) -> List[interfaces.configuration.RequirementInterface]: # Since we're calling the plugin, make sure we have the plugin's requirements return [ - requirements.PluginRequirement( - name="svcscan", plugin=svcscan.SvcScan, version=(4, 0, 0) + requirements.VersionRequirement( + name="svcscan", component=svcscan.SvcScan, version=(4, 0, 0) + ), + requirements.VersionRequirement( + name="pslist", component=pslist.PsList, version=(3, 0, 0) ), requirements.ModuleRequirement( name="kernel", diff --git a/volatility3/framework/plugins/windows/svcscan.py b/volatility3/framework/plugins/windows/svcscan.py index 80400ec5a..94ce02897 100644 --- a/volatility3/framework/plugins/windows/svcscan.py +++ b/volatility3/framework/plugins/windows/svcscan.py @@ -50,11 +50,11 @@ class SvcScan(interfaces.plugins.PluginInterface): description="Windows kernel", architectures=["Intel32", "Intel64"], ), - requirements.PluginRequirement( - name="pslist", plugin=pslist.PsList, version=(3, 0, 0) + requirements.VersionRequirement( + name="pslist", component=pslist.PsList, version=(3, 0, 0) ), - requirements.PluginRequirement( - name="hivelist", plugin=hivelist.HiveList, version=(2, 0, 0) + requirements.VersionRequirement( + name="hivelist", component=hivelist.HiveList, version=(2, 0, 0) ), ] diff --git a/volatility3/framework/plugins/windows/symlinkscan.py b/volatility3/framework/plugins/windows/symlinkscan.py index 358ea130e..cdcb5d3d3 100644 --- a/volatility3/framework/plugins/windows/symlinkscan.py +++ b/volatility3/framework/plugins/windows/symlinkscan.py @@ -27,6 +27,11 @@ class SymlinkScan(interfaces.plugins.PluginInterface, timeliner.TimeLinerInterfa description="Windows kernel", architectures=["Intel32", "Intel64"], ), + requirements.VersionRequirement( + name="timeliner", + component=timeliner.TimeLinerInterface, + version=(1, 0, 0), + ), requirements.VersionRequirement( name="poolscanner", component=poolscanner.PoolScanner, version=(3, 0, 0) ), diff --git a/volatility3/framework/plugins/windows/thrdscan.py b/volatility3/framework/plugins/windows/thrdscan.py index 369db1fd8..0ac3d0c33 100644 --- a/volatility3/framework/plugins/windows/thrdscan.py +++ b/volatility3/framework/plugins/windows/thrdscan.py @@ -3,12 +3,12 @@ ## import logging import datetime -from typing import Callable, Iterable +from typing import Callable, Iterable, Tuple, Optional, Dict from volatility3.framework import renderers, interfaces, exceptions from volatility3.framework.configuration import requirements from volatility3.framework.renderers import format_hints -from volatility3.plugins.windows import poolscanner +from volatility3.plugins.windows import poolscanner, pe_symbols from volatility3.plugins import timeliner vollog = logging.getLogger(__name__) @@ -19,7 +19,7 @@ class ThrdScan(interfaces.plugins.PluginInterface, timeliner.TimeLinerInterface) # version 2.6.0 adds support for scanning for 'Ethread' structures by pool tags _required_framework_version = (2, 6, 0) - _version = (1, 1, 0) + _version = (2, 0, 0) def __init__(self, *args, **kwargs): super().__init__(*args, **kwargs) @@ -33,8 +33,16 @@ class ThrdScan(interfaces.plugins.PluginInterface, timeliner.TimeLinerInterface) description="Windows kernel", architectures=["Intel32", "Intel64"], ), - requirements.PluginRequirement( - name="poolscanner", plugin=poolscanner.PoolScanner, version=(3, 0, 0) + requirements.VersionRequirement( + name="poolscanner", component=poolscanner.PoolScanner, version=(3, 0, 0) + ), + requirements.VersionRequirement( + name="pe_symbols", component=pe_symbols.PESymbols, version=(3, 0, 0) + ), + requirements.VersionRequirement( + name="timeliner", + component=timeliner.TimeLinerInterface, + version=(1, 0, 0), ), ] @@ -67,27 +75,74 @@ class ThrdScan(interfaces.plugins.PluginInterface, timeliner.TimeLinerInterface) yield mem_object @classmethod - def gather_thread_info(cls, ethread): + def gather_thread_info( + cls, + ethread: interfaces.objects.ObjectInterface, + vads_cache: Dict[int, pe_symbols.ranges_type] = None, + ) -> Tuple[ + int, + int, + int, + int, + Optional[str], + int, + Optional[str], + Optional[datetime.datetime], + Optional[datetime.datetime], + ]: try: thread_offset = ethread.vol.offset owner_proc_pid = ethread.Cid.UniqueProcess thread_tid = ethread.Cid.UniqueThread thread_start_addr = ethread.StartAddress + thread_win32start_addr = ethread.Win32StartAddress thread_create_time = ( ethread.get_create_time() ) # datetime.datetime object / volatility3.framework.renderers.UnparsableValue object thread_exit_time = ( ethread.get_exit_time() ) # datetime.datetime object / volatility3.framework.renderers.UnparsableValue object + + owner_proc = None + if vads_cache is not None: + owner_proc = ethread.owning_process() except exceptions.InvalidAddressException: vollog.debug(f"Thread invalid address {ethread.vol.offset:#x}") return None + # don't look for VADs in kernel threads, just let them get reported with empty paths + if ( + owner_proc_pid != 4 + and owner_proc.InheritedFromUniqueProcessId != 4 + and vads_cache is not None + ): + vads = pe_symbols.PESymbols.get_vads_for_process_cache( + vads_cache, owner_proc + ) + if not vads or len(vads) < 5: + vollog.debug( + f"Not enough vads for process at {owner_proc.vol.offset:#x}. Skipping thread at {ethread.vol.offset:#x}" + ) + return None + + start_path = pe_symbols.PESymbols.filepath_for_address( + vads, thread_start_addr + ) + win32start_path = pe_symbols.PESymbols.filepath_for_address( + vads, thread_win32start_addr + ) + else: + start_path = None + win32start_path = None + return ( format_hints.Hex(thread_offset), owner_proc_pid, thread_tid, format_hints.Hex(thread_start_addr), + start_path, + format_hints.Hex(thread_win32start_addr), + win32start_path, thread_create_time, thread_exit_time, ) @@ -95,11 +150,34 @@ class ThrdScan(interfaces.plugins.PluginInterface, timeliner.TimeLinerInterface) def _generator(self, filter_func: Callable): kernel_name = self.config["kernel"] + vads_cache: Dict[int, pe_symbols.ranges_type] = {} + for ethread in self.implementation(self.context, kernel_name): - info = self.gather_thread_info(ethread) + info = self.gather_thread_info(ethread, vads_cache) if info: - yield (0, info) + ( + offset, + pid, + tid, + start_addr, + start_path, + win32start_addr, + win32start_path, + create_time, + exit_time, + ) = info + yield 0, ( + offset, + pid, + tid, + start_addr, + start_path or renderers.NotAvailableValue(), + win32start_addr, + win32start_path or renderers.NotAvailableValue(), + create_time, + exit_time, + ) def generate_timeline(self): filt_func = self.filter_func(self.config) @@ -145,6 +223,9 @@ class ThrdScan(interfaces.plugins.PluginInterface, timeliner.TimeLinerInterface) ("PID", int), ("TID", int), ("StartAddress", format_hints.Hex), + ("StartPath", str), + ("Win32StartAddress", format_hints.Hex), + ("Win32StartPath", str), ("CreateTime", datetime.datetime), ("ExitTime", datetime.datetime), ], diff --git a/volatility3/framework/plugins/windows/threads.py b/volatility3/framework/plugins/windows/threads.py index 77062e8c6..d040fa990 100644 --- a/volatility3/framework/plugins/windows/threads.py +++ b/volatility3/framework/plugins/windows/threads.py @@ -31,8 +31,11 @@ class Threads(thrdscan.ThrdScan): description="Windows kernel", architectures=["Intel32", "Intel64"], ), - requirements.PluginRequirement( - name="thrdscan", plugin=thrdscan.ThrdScan, version=(1, 1, 0) + requirements.VersionRequirement( + name="thrdscan", component=thrdscan.ThrdScan, version=(2, 0, 0) + ), + requirements.VersionRequirement( + name="pslist", component=pslist.PsList, version=(3, 0, 0) ), ] diff --git a/volatility3/framework/plugins/windows/timers.py b/volatility3/framework/plugins/windows/timers.py index 1f100bf1c..f530a4c7b 100644 --- a/volatility3/framework/plugins/windows/timers.py +++ b/volatility3/framework/plugins/windows/timers.py @@ -11,6 +11,7 @@ from volatility3.framework import ( interfaces, constants, symbols, + exceptions, ) from volatility3.framework.configuration import requirements from volatility3.framework.renderers import format_hints @@ -131,6 +132,7 @@ class Timers(interfaces.plugins.PluginInterface): ): if not timer.valid_type(): continue + try: dpc = timer.get_dpc() if dpc == 0: @@ -138,7 +140,10 @@ class Timers(interfaces.plugins.PluginInterface): if dpc.DeferredRoutine == 0: continue deferred_routine = dpc.DeferredRoutine - except Exception: + except exceptions.InvalidAddressException as exc: + vollog.debug( + f"Failed to get _KTIMER.Dpc due to {exc.__class__.__name__}" + ) continue module_symbols = list( diff --git a/volatility3/framework/plugins/windows/unhooked_system_calls.py b/volatility3/framework/plugins/windows/unhooked_system_calls.py index 132cf4e4f..3ff0aa158 100644 --- a/volatility3/framework/plugins/windows/unhooked_system_calls.py +++ b/volatility3/framework/plugins/windows/unhooked_system_calls.py @@ -97,8 +97,8 @@ class unhooked_system_calls(interfaces.plugins.PluginInterface): requirements.VersionRequirement( name="pslist", component=pslist.PsList, version=(3, 0, 0) ), - requirements.PluginRequirement( - name="pe_symbols", plugin=pe_symbols.PESymbols, version=(3, 0, 0) + requirements.VersionRequirement( + name="pe_symbols", component=pe_symbols.PESymbols, version=(3, 0, 0) ), ] diff --git a/volatility3/framework/plugins/windows/unloadedmodules.py b/volatility3/framework/plugins/windows/unloadedmodules.py index cadacf4ff..692f2c4a4 100644 --- a/volatility3/framework/plugins/windows/unloadedmodules.py +++ b/volatility3/framework/plugins/windows/unloadedmodules.py @@ -33,6 +33,14 @@ class UnloadedModules(interfaces.plugins.PluginInterface, timeliner.TimeLinerInt description="Windows kernel", architectures=["Intel32", "Intel64"], ), + requirements.VersionRequirement( + name="timeliner", + component=timeliner.TimeLinerInterface, + version=(1, 0, 0), + ), + requirements.VersionRequirement( + name="modules", component=modules.Modules, version=(3, 0, 0) + ), ] @classmethod diff --git a/volatility3/framework/plugins/windows/vadinfo.py b/volatility3/framework/plugins/windows/vadinfo.py index 2b1d3f4bc..22d42505f 100644 --- a/volatility3/framework/plugins/windows/vadinfo.py +++ b/volatility3/framework/plugins/windows/vadinfo.py @@ -63,8 +63,8 @@ class VadInfo(interfaces.plugins.PluginInterface): element_type=int, optional=True, ), - requirements.PluginRequirement( - name="pslist", plugin=pslist.PsList, version=(3, 0, 0) + requirements.VersionRequirement( + name="pslist", component=pslist.PsList, version=(3, 0, 0) ), requirements.BooleanRequirement( name="dump", diff --git a/volatility3/framework/plugins/windows/vadregexscan.py b/volatility3/framework/plugins/windows/vadregexscan.py index 5d2356f54..9b666cbcb 100644 --- a/volatility3/framework/plugins/windows/vadregexscan.py +++ b/volatility3/framework/plugins/windows/vadregexscan.py @@ -32,8 +32,8 @@ class VadRegExScan(plugins.PluginInterface): description="Windows kernel", architectures=["Intel32", "Intel64"], ), - requirements.PluginRequirement( - name="pslist", plugin=pslist.PsList, version=(3, 0, 0) + requirements.VersionRequirement( + name="pslist", component=pslist.PsList, version=(3, 0, 0) ), requirements.ListRequirement( name="pid", diff --git a/volatility3/framework/plugins/windows/vadwalk.py b/volatility3/framework/plugins/windows/vadwalk.py index cc8105e0c..38b5d197e 100644 --- a/volatility3/framework/plugins/windows/vadwalk.py +++ b/volatility3/framework/plugins/windows/vadwalk.py @@ -28,11 +28,11 @@ class VadWalk(interfaces.plugins.PluginInterface): description="Windows kernel", architectures=["Intel32", "Intel64"], ), - requirements.PluginRequirement( - name="pslist", plugin=pslist.PsList, version=(3, 0, 0) + requirements.VersionRequirement( + name="pslist", component=pslist.PsList, version=(3, 0, 0) ), - requirements.PluginRequirement( - name="vadinfo", plugin=vadinfo.VadInfo, version=(2, 0, 0) + requirements.VersionRequirement( + name="vadinfo", component=vadinfo.VadInfo, version=(2, 0, 0) ), requirements.ListRequirement( name="pid", diff --git a/volatility3/framework/plugins/windows/vadyarascan.py b/volatility3/framework/plugins/windows/vadyarascan.py index a19206e22..b86869969 100644 --- a/volatility3/framework/plugins/windows/vadyarascan.py +++ b/volatility3/framework/plugins/windows/vadyarascan.py @@ -29,14 +29,14 @@ class VadYaraScan(interfaces.plugins.PluginInterface): description="Windows kernel", architectures=["Intel32", "Intel64"], ), - requirements.PluginRequirement( - name="pslist", plugin=pslist.PsList, version=(3, 0, 0) + requirements.VersionRequirement( + name="pslist", component=pslist.PsList, version=(3, 0, 0) ), requirements.VersionRequirement( name="yarascanner", component=yarascan.YaraScanner, version=(2, 0, 0) ), - requirements.PluginRequirement( - name="yarascan", plugin=yarascan.YaraScan, version=(2, 0, 0) + requirements.VersionRequirement( + name="yarascan", component=yarascan.YaraScan, version=(2, 0, 0) ), requirements.ListRequirement( name="pid", diff --git a/volatility3/framework/plugins/windows/verinfo.py b/volatility3/framework/plugins/windows/verinfo.py index 49bf0b212..b5eba7ec6 100644 --- a/volatility3/framework/plugins/windows/verinfo.py +++ b/volatility3/framework/plugins/windows/verinfo.py @@ -42,11 +42,11 @@ class VerInfo(interfaces.plugins.PluginInterface): description="Windows kernel", architectures=["Intel32", "Intel64"], ), - requirements.PluginRequirement( - name="pslist", plugin=pslist.PsList, version=(3, 0, 0) + requirements.VersionRequirement( + name="pslist", component=pslist.PsList, version=(3, 0, 0) ), - requirements.PluginRequirement( - name="modules", plugin=modules.Modules, version=(3, 0, 0) + requirements.VersionRequirement( + name="modules", component=modules.Modules, version=(3, 0, 0) ), requirements.BooleanRequirement( name="extensive", diff --git a/volatility3/framework/plugins/windows/windows.py b/volatility3/framework/plugins/windows/windows.py new file mode 100644 index 000000000..9d4317df9 --- /dev/null +++ b/volatility3/framework/plugins/windows/windows.py @@ -0,0 +1,139 @@ +# This file is Copyright 2025 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, Iterable + +from volatility3.framework import interfaces, renderers, exceptions +from volatility3.framework.objects import utility +from volatility3.framework.configuration import requirements +from volatility3.framework.renderers import format_hints +from volatility3.plugins.windows import windowstations + +vollog = logging.getLogger(__name__) + + +class Windows(interfaces.plugins.PluginInterface): + """Enumerates the Windows of Desktop instances""" + + _required_framework_version = (2, 0, 0) + _version = (1, 0, 0) + + @classmethod + def get_requirements(cls) -> List[interfaces.configuration.RequirementInterface]: + # Since we're calling the plugin, make sure we have the plugin's requirements + return [ + requirements.ModuleRequirement( + name="kernel", + description="Windows kernel", + architectures=["Intel32", "Intel64"], + ), + requirements.VersionRequirement( + name="windowstations", + component=windowstations.WindowStations, + version=(1, 0, 0), + ), + ] + + @classmethod + def list_windows( + cls, + context: interfaces.context.ContextInterface, + config_path: str, + kernel_module_name: str, + ) -> Iterable[interfaces.objects.ObjectInterface]: + """ + Enumerates the desktops of each window station + For each found, enumerates its windows within the desktop + """ + kernel = context.modules[kernel_module_name] + + for ( + winsta, + station_name, + session_id, + ) in windowstations.WindowStations.scan_window_stations( + context, config_path, kernel_module_name + ): + # for each window station, walk its list of desktops + for desktop, desktop_name in winsta.desktops(kernel.symbol_table_name): + try: + top_window = desktop.pDeskInfo.spwnd + except exceptions.InvalidAddressException: + vollog.debug( + f"Desktop with name {desktop_name} in window station {station_name} has a broken window pointer." + ) + continue + + for window, window_name in desktop.windows(top_window): + yield station_name, desktop_name, window, window_name + + def _generator(self): + kernel_name = self.config["kernel"] + + # call the implementation for finding windows and gather attributes + for station_name, desktop_name, window, window_name in self.list_windows( + self.context, self.config_path, kernel_name + ): + # We need a valid process and session id for the window to display it + process = window.get_process() + process_name = None + if process: + try: + process_name = utility.array_to_string(process.ImageFileName) + process_pid = process.UniqueProcessId + except exceptions.InvalidAddressException: + vollog.debug( + f"Unable to read name and pid of the process for window {window.vol.offset:#x}" + ) + + if process_name is None: + vollog.debug( + f"Invalid process reference for the process hosting window {window.vol.offset:#x}" + ) + continue + + sess_id = window.get_session_id() + if sess_id is None: + vollog.debug( + f"Unable to read session id of the process for window {window.vol.offset:#x} in process {process_name}" + ) + continue + + # procedures can be empty, but if set, should be a valid pointer + window_proc = window.get_window_procedure() + if window_proc is None: + window_proc = renderers.NotAvailableValue() + elif window_proc == 0 or window_proc > 0x1000: + window_proc = format_hints.Hex(window_proc) + else: + vollog.debug( + f"Invalid window procedure {window_proc} for the window {window.vol.offset:#x}" + ) + continue + + yield 0, ( + format_hints.Hex(window.vol.offset), + station_name, + sess_id, + desktop_name, + window_name or renderers.NotAvailableValue(), + window_proc, + process_name, + process_pid, + ) + + def run(self): + return renderers.TreeGrid( + [ + ("Offset", format_hints.Hex), + ("Station", str), + ("Session", int), + ("Desktop", str), + ("Window", str), + ("Procedure", format_hints.Hex), + ("Process", str), + ("PID", int), + ], + self._generator(), + ) diff --git a/volatility3/framework/plugins/windows/windowstations.py b/volatility3/framework/plugins/windows/windowstations.py index cd02938cd..1f95b0531 100644 --- a/volatility3/framework/plugins/windows/windowstations.py +++ b/volatility3/framework/plugins/windows/windowstations.py @@ -10,8 +10,8 @@ from volatility3.framework.configuration import requirements from volatility3.framework.renderers import format_hints from volatility3.framework.symbols import intermed from volatility3.framework.symbols.windows import versions -from volatility3.framework.symbols.windows.extensions import gui from volatility3.plugins.windows import poolscanner, modules +from volatility3.framework.symbols.windows.extensions import gui vollog = logging.getLogger(__name__) @@ -52,6 +52,9 @@ class WindowStations(interfaces.plugins.PluginInterface): requirements.VersionRequirement( name="modules", component=modules.Modules, version=(3, 0, 0) ), + requirements.VersionRequirement( + name="GUIExtensions", component=gui.GUIExtensions, version=(1, 0, 0) + ), ] @staticmethod @@ -96,7 +99,7 @@ class WindowStations(interfaces.plugins.PluginInterface): config_path=config_path, sub_path=os.path.join("windows", "gui"), filename=symbol_filename, - class_types=gui.class_types, + class_types=gui.GUIExtensions.class_types, table_mapping=table_mapping, ) diff --git a/volatility3/framework/symbols/linux/extensions/__init__.py b/volatility3/framework/symbols/linux/extensions/__init__.py index 39605cf52..b9ce84ed1 100644 --- a/volatility3/framework/symbols/linux/extensions/__init__.py +++ b/volatility3/framework/symbols/linux/extensions/__init__.py @@ -99,13 +99,13 @@ class module(generic.GenericIntelProcess): return self.mem[module_mem_index] - def _get_mem_size(self, mod_mem_type_name): + def _get_mem_size(self, mod_mem_type_name) -> int: return self._get_mem_type(mod_mem_type_name).size - def _get_mem_base(self, mod_mem_type_name): + def _get_mem_base(self, mod_mem_type_name) -> int: return self._get_mem_type(mod_mem_type_name).base - def get_module_base(self): + def get_module_base(self) -> int: if self.has_member("mem"): # kernels 6.4+ return self._get_mem_base("MOD_TEXT") elif self.has_member("core_layout"): @@ -115,7 +115,7 @@ class module(generic.GenericIntelProcess): raise AttributeError("Unable to get module base") - def get_init_size(self): + def get_init_size(self) -> int: if self.has_member("mem"): # kernels 6.4+ return ( self._get_mem_size("MOD_INIT_TEXT") @@ -129,7 +129,7 @@ class module(generic.GenericIntelProcess): raise AttributeError("Unable to determine .init section size of module") - def get_core_size(self): + def get_core_size(self) -> int: if self.has_member("mem"): # kernels 6.4+ return ( self._get_mem_size("MOD_TEXT") @@ -144,7 +144,7 @@ class module(generic.GenericIntelProcess): raise AttributeError("Unable to determine core size of module") - def get_core_text_size(self): + def get_core_text_size(self) -> int: if self.has_member("mem"): # kernels 6.4+ return self._get_mem_size("MOD_TEXT") elif self.has_member("core_layout"): @@ -154,7 +154,7 @@ class module(generic.GenericIntelProcess): raise AttributeError("Unable to determine core text size of module") - def get_module_core(self): + def get_module_core(self) -> objects.Pointer: if self.has_member("mem"): # kernels 6.4+ return self._get_mem_base("MOD_TEXT") elif self.has_member("core_layout"): @@ -163,7 +163,7 @@ class module(generic.GenericIntelProcess): return self.module_core raise AttributeError("Unable to get module core") - def get_module_init(self): + def get_module_init(self) -> objects.Pointer: if self.has_member("mem"): # kernels 6.4+ return self._get_mem_base("MOD_INIT_TEXT") elif self.has_member("init_layout"): @@ -172,9 +172,12 @@ class module(generic.GenericIntelProcess): return self.module_init raise AttributeError("Unable to get module init") - def get_name(self): + def get_name(self) -> Optional[str]: """Get the name of the module as a string""" - return utility.array_to_string(self.name) + try: + return utility.array_to_string(self.name) + except exceptions.InvalidAddressException: + return None def _get_sect_count(self, grp: interfaces.objects.ObjectInterface) -> int: """Try to determine the number of valid sections""" @@ -256,14 +259,22 @@ class module(generic.GenericIntelProcess): elf_sym_obj.cached_strtab = self.section_strtab yield elf_sym_obj - def get_symbols_names_and_addresses(self) -> Iterable[Tuple[str, int]]: + def get_symbols_names_and_addresses( + self, max_symbols: int = 4096 + ) -> Iterable[Tuple[str, int]]: """Get names and addresses for each symbol of the module Yields: A tuple for each symbol containing the symbol name and its corresponding value """ layer = self._context.layers[self.vol.layer_name] - for elf_sym_obj in self.get_symbols(): + for iteration_counter, elf_sym_obj in enumerate(self.get_symbols()): + if iteration_counter > max_symbols: + vollog.debug( + f"Hit maximum symbols ({max_symbols}) for ELF at {self.vol.offset:#x} in layer {self.vol.layer_name}" + ) + return + sym_name = elf_sym_obj.get_name() if not sym_name: continue @@ -328,46 +339,70 @@ class module(generic.GenericIntelProcess): return None @property - def section_symtab(self): - if self.has_member("kallsyms"): - return self.kallsyms.symtab - elif self.has_member("symtab"): - return self.symtab + def section_symtab(self) -> Optional[interfaces.objects.ObjectInterface]: + try: + if self.has_member("kallsyms"): + return self.kallsyms.symtab + elif self.has_member("symtab"): + return self.symtab + except exceptions.InvalidAddressException: + vollog.debug( + f"Page fault encountered when accessing symtab of ELF at {self.vol.offset:#x} in {self.vol.layer_name}" + ) + return None raise AttributeError("Unable to get symtab") @property - def num_symtab(self): - if self.has_member("kallsyms"): - return int(self.kallsyms.num_symtab) - elif self.has_member("num_symtab"): - return int(self.member("num_symtab")) + def num_symtab(self) -> Optional[int]: + try: + if self.has_member("kallsyms"): + return int(self.kallsyms.num_symtab) + elif self.has_member("num_symtab"): + return int(self.member("num_symtab")) + except exceptions.InvalidAddressException: + vollog.debug( + f"Page fault encountered when accessing num_symtab of ELF at {self.vol.offset:#x} in {self.vol.layer_name}" + ) + return None raise AttributeError("Unable to determine number of symbols") @property - def section_strtab(self): - # Newer kernels - if self.has_member("kallsyms"): - return self.kallsyms.strtab - # Older kernels - elif self.has_member("strtab"): - return self.strtab + def section_strtab(self) -> Optional[interfaces.objects.ObjectInterface]: + try: + # Newer kernels + if self.has_member("kallsyms"): + return self.kallsyms.strtab + # Older kernels + elif self.has_member("strtab"): + return self.strtab + except exceptions.InvalidAddressException: + vollog.debug( + f"Page fault encountered when accessing strtab of ELF at {self.vol.offset:#x} in {self.vol.layer_name}" + ) + return None raise AttributeError("Unable to get strtab") @property - def section_typetab(self): - if self.has_member("kallsyms") and self.kallsyms.has_member("typetab"): - # kernels >= 4.5 8244062ef1e54502ef55f54cced659913f244c3e: kallsyms was added - # kernels >= 5.2 1c7651f43777cdd59c1aaa82c87324d3e7438c7b: types have its own array - return self.kallsyms.typetab + def section_typetab(self) -> Optional[interfaces.objects.ObjectInterface]: + try: + if self.has_member("kallsyms") and self.kallsyms.has_member("typetab"): + # kernels >= 4.5 8244062ef1e54502ef55f54cced659913f244c3e: kallsyms was added + # kernels >= 5.2 1c7651f43777cdd59c1aaa82c87324d3e7438c7b: types have its own array + return self.kallsyms.typetab + except exceptions.InvalidAddressException: + vollog.debug( + f"Page fault encountered when accessing typetab of ELF at {self.vol.offset:#x} in {self.vol.layer_name}" + ) + return None raise AttributeError("Unable to get typetab section, it needs a kernel >= 5.2") def get_symbol_type( self, symbol: interfaces.objects.ObjectInterface, symbol_index: int - ) -> str: + ) -> Optional[str]: """Determines the type of a given ELF symbol. Args: @@ -377,14 +412,20 @@ class module(generic.GenericIntelProcess): Returns: A single-character string representing the symbol type """ - if self.has_member("kallsyms") and self.kallsyms.has_member("typetab"): - # kernels >= 5.2 1c7651f43777cdd59c1aaa82c87324d3e7438c7b types have its own array - layer = self._context.layers[self.vol.layer_name] - sym_type = layer.read(self.section_typetab + symbol_index, 1) - sym_type = sym_type.decode("utf-8", errors="ignore") - else: - # kernels < 5.2 the type was stored in the st_info - sym_type = chr(symbol.st_info) + try: + if self.has_member("kallsyms") and self.kallsyms.has_member("typetab"): + # kernels >= 5.2 1c7651f43777cdd59c1aaa82c87324d3e7438c7b types have its own array + layer = self._context.layers[self.vol.layer_name] + sym_type = layer.read(self.section_typetab + symbol_index, 1) + sym_type = sym_type.decode("utf-8", errors="ignore") + else: + # kernels < 5.2 the type was stored in the st_info + sym_type = chr(symbol.st_info) + except exceptions.InvalidAddressException: + vollog.debug( + f"Page fault encountered when accessing symbol type of index {symbol_index} of ELF at {self.vol.offset:#x} in {self.vol.layer_name}" + ) + return None return sym_type @@ -1986,7 +2027,10 @@ class bpf_prog(objects.StructType): # 'prog_aux' was added in kernels 3.18 return None - return self.aux.get_name() + try: + return self.aux.get_name() + except exceptions.InvalidAddressException: + return None def bpf_jit_binary_hdr_address(self) -> int: """Return the jitted BPF program start address @@ -2048,11 +2092,13 @@ class bpf_prog_aux(objects.StructType): # 'name' was added in kernels 4.15 return None - if not self.name: + try: + if not self.name: + return None + return utility.array_to_string(self.name) + except exceptions.InvalidAddressException: return None - return utility.array_to_string(self.name) - class cred(objects.StructType): # struct cred was added in kernels 2.6.29 @@ -2988,7 +3034,9 @@ class latch_tree_root(objects.StructType): rb_node = rb_node_ptr.dereference() lt_node = self._get_lt_node_from_rb_node(rb_node, idx) c = comp_function(key, lt_node) - if c < 0: + if c is None: + return None + elif c < 0: rb_node_ptr = rb_node.rb_left elif c > 0: rb_node_ptr = rb_node.rb_right @@ -3005,7 +3053,7 @@ class kernel_symbol(objects.StructType): long_mask = (1 << layer.bits_per_register) - 1 return (self.vol.offset + off) & long_mask - def get_name(self) -> str: + def _do_get_name(self) -> str: if self.has_member("name_offset"): # kernel >= 4.19 and CONFIG_HAVE_ARCH_PREL32_RELOCATIONS=y # See 7290d58095712a89f845e1bca05334796dd49ed2 @@ -3025,7 +3073,13 @@ class kernel_symbol(objects.StructType): return name_bytes.decode("utf-8", errors="ignore") - def get_value(self) -> int: + def get_name(self) -> Optional[str]: + try: + return self._do_get_name() + except exceptions.InvalidAddressException: + return None + + def _do_get_value(self) -> int: if self.has_member("value_offset"): # kernel >= 4.19 and CONFIG_HAVE_ARCH_PREL32_RELOCATIONS=y # See 7290d58095712a89f845e1bca05334796dd49ed2 @@ -3036,7 +3090,13 @@ class kernel_symbol(objects.StructType): raise AttributeError("Unsupported kernel_symbol type implementation") - def get_namespace(self) -> str: + def get_value(self) -> Optional[int]: + try: + return self._do_get_value() + except exceptions.InvalidAddressException: + return None + + def _do_get_namespace(self) -> str: if self.has_member("namespace_offset"): # kernel >= 4.19 and CONFIG_HAVE_ARCH_PREL32_RELOCATIONS=y # See 7290d58095712a89f845e1bca05334796dd49ed2 @@ -3055,3 +3115,9 @@ class kernel_symbol(objects.StructType): namespace_bytes = namespace_bytes[:idx] return namespace_bytes.decode("utf-8", errors="ignore") + + def get_namespace(self) -> Optional[str]: + try: + return self._do_get_namespace() + except exceptions.InvalidAddressException: + return None diff --git a/volatility3/framework/symbols/linux/extensions/elf.py b/volatility3/framework/symbols/linux/extensions/elf.py index 7105a05ea..564439c64 100644 --- a/volatility3/framework/symbols/linux/extensions/elf.py +++ b/volatility3/framework/symbols/linux/extensions/elf.py @@ -329,7 +329,10 @@ class elf_sym(objects.StructType): def get_name(self) -> Optional[str]: """Returns the symbol name""" - addr = self._cached_strtab + self.st_name + try: + addr = self._cached_strtab + self.st_name + except exceptions.InvalidAddressException: + return None layer = self._context.layers[self.vol.layer_name] name_bytes = layer.read(addr, self._MAX_NAME_LENGTH, pad=True) diff --git a/volatility3/framework/symbols/linux/kallsyms.py b/volatility3/framework/symbols/linux/kallsyms.py index 298725a7a..35aba3ca5 100644 --- a/volatility3/framework/symbols/linux/kallsyms.py +++ b/volatility3/framework/symbols/linux/kallsyms.py @@ -6,12 +6,12 @@ import functools import logging from typing import Iterator, List, Optional, Tuple +import volatility3.framework.symbols.linux.utilities.modules as linux_utilities_modules from volatility3.framework import constants, exceptions, interfaces from volatility3.framework.configuration import requirements from volatility3.framework.constants import linux as linux_constants from volatility3.framework.objects import utility from volatility3.framework.symbols import linux -from volatility3.plugins.linux import lsmod vollog = logging.getLogger(__name__) @@ -186,7 +186,10 @@ class KASSymbol(KASSymbolBasic): # If lowercase, the symbol is usually local; if uppercase, the symbol is # global (external). There are however a few lowercase symbols that are shown # for special global symbols ("u", "v" and "w"). - self.exported = bool(self.type.isupper() or self.type in ("u", "v", "w")) + if self.type: + self.exported = bool(self.type.isupper() or self.type in ("u", "v", "w")) + else: + self.exported = None @functools.cached_property def type_description(self) -> Optional[str]: @@ -200,10 +203,12 @@ class KASSymbol(KASSymbolBasic): if symbol_type_description: return symbol_type_description - # Otherwise, use the lowercase version - symbol_type_description = linux_constants.NM_TYPES_DESC.get( - self.type.lower(), None - ) + if self.type: + # Otherwise, use the lowercase version + symbol_type_description = linux_constants.NM_TYPES_DESC.get( + self.type.lower(), None + ) + return symbol_type_description @@ -304,28 +309,35 @@ class Kallsyms(interfaces.configuration.VersionableInterface): @classmethod def _assert_versions(cls) -> None: """Verify versions of shared dependencies""" - lsmod_version_required = (2, 0, 0) + linux_utilities_modules_version_required = (3, 0, 0) if not requirements.VersionRequirement.matches_required( - lsmod_version_required, lsmod.Lsmod.version + linux_utilities_modules_version_required, + linux_utilities_modules.Modules.version, ): raise exceptions.VolatilityException( - "Lsmod version not suitable: " - f"required {lsmod_version_required} found {lsmod.Lsmod.version}", + "linux_utilities_modules.Modules version not suitable: " + f"required {linux_utilities_modules_version_required} found {linux_utilities_modules.Modules.version}", ) return None - def _read_bytes(self, address: int, size: int) -> bytes: + def _read_bytes(self, address: int, size: int) -> Optional[bytes]: layer = self._context.layers[self._layer_name] - return layer.read(address, size).decode() + try: + return layer.read(address, size).decode() + except exceptions.InvalidAddressException: + return None - def _read_int(self, address: int, size: int, signed: bool = False) -> int: + def _read_int(self, address: int, size: int, signed: bool = False) -> Optional[int]: layer = self._context.layers[self._layer_name] - return int.from_bytes( - layer.read(address, size), - byteorder=self._endian, - signed=signed, - ) + try: + return int.from_bytes( + layer.read(address, size), + byteorder=self._endian, + signed=signed, + ) + except exceptions.InvalidAddressException: + return None def _bootstrap(self) -> None: layer = self._context.layers[self._layer_name] @@ -402,7 +414,20 @@ class Kallsyms(interfaces.configuration.VersionableInterface): """ current_offset = 0 for sym_idx in range(self._kallsyms_num_syms): - kassymbol, compressed_length = self._get_symbol(current_offset, sym_idx) + try: + kassymbol, compressed_length = self._get_symbol(current_offset, sym_idx) + except exceptions.InvalidAddressException: + vollog.debug( + f"Unable to reconstruct core symbol at offset {current_offset:#x} and index {sym_idx}" + ) + continue + + if compressed_length is None: + vollog.debug( + f"Unable to reconstruct compressed_length at offset {current_offset:#x} and index {sym_idx}" + ) + break + if kassymbol: yield kassymbol @@ -485,7 +510,7 @@ class Kallsyms(interfaces.configuration.VersionableInterface): ) return kassymbolbasic, compressed_length - def _get_symbol_address_by_index(self, index: int) -> int: + def _get_symbol_address_by_index(self, index: int) -> Optional[int]: """Return symbol address based on the symbol index in the kallsyms arrays. Based on kallsyms_sym_address() @@ -502,6 +527,8 @@ class Kallsyms(interfaces.configuration.VersionableInterface): signed_int_size = 4 sym_offset_ptr = self._kallsyms_offsets_address + (index * signed_int_size) sym_addr = self._read_int(sym_offset_ptr, signed_int_size, signed=True) + if sym_addr is None: + return None if sym_addr < 0: # Negative offsets are relative to kallsyms_relative_base - 1 @@ -517,35 +544,56 @@ class Kallsyms(interfaces.configuration.VersionableInterface): self._long_size, signed=False, ) + if kallsyms_address is None: + return None + return kallsyms_address & layer.address_mask else: raise exceptions.VolatilityException("Unsupported kernel") @functools.lru_cache - def _get_symbol_pos(self, address: int) -> Tuple[int, int]: + def _get_symbol_pos(self, address: int) -> Optional[Tuple[int, int]]: """Returns the symbol position in the kallsyms arrays and its size.""" low = 0 high = self._kallsyms_num_syms while high - low > 1: mid = low + (high - low) // 2 - if self._get_symbol_address_by_index(mid) <= address: + symbol_index = self._get_symbol_address_by_index(mid) + if symbol_index is None: + return None, None + elif symbol_index <= address: low = mid else: high = mid + # prevent accidental bleed through + symbol_index = None + # Search for the first aliased symbol. *Aliased symbols* are symbols with the same address. - while low and self._get_symbol_address_by_index( - low - 1 - ) == self._get_symbol_address_by_index(low): - low -= 1 + while low: + symbol_index = self._get_symbol_address_by_index(low - 1) + if symbol_index is None: + return None, None + + if symbol_index == self._get_symbol_address_by_index(low): + low -= 1 + else: + break symbol_start = self._get_symbol_address_by_index(low) + if symbol_start is None: + return None, None + symbol_end = 0 # Search for next non-aliased symbol. for idx in range(low + 1, self._kallsyms_num_syms): - if self._get_symbol_address_by_index(idx) > symbol_start: + symbol_index = self._get_symbol_address_by_index(idx) + if symbol_index is None: + return None, None + + if symbol_index > symbol_start: symbol_end = self._get_symbol_address_by_index(idx) break @@ -664,6 +712,8 @@ class Kallsyms(interfaces.configuration.VersionableInterface): return None pos, sym_size = self._get_symbol_pos(address) + if pos is None: + return None offset = self._get_symbol_offset(pos) sym_address = self._get_symbol_address_by_index(pos) kassymbolbasic, _compressed_length = self._expand_symbol(offset) @@ -722,7 +772,13 @@ class Kallsyms(interfaces.configuration.VersionableInterface): self._kas_config.stop_ksymtab, ) - return kernel_symbol is not None and kernel_symbol.get_value() == address + if kernel_symbol is not None: + if hasattr(kernel_symbol, "get_value"): + return kernel_symbol.get_value() == address + else: + return kernel_symbol.vol.offset == address + + return None def _elfsym_to_kassymbol( self, @@ -855,7 +911,9 @@ class Kallsyms(interfaces.configuration.VersionableInterface): self, ) -> List[Tuple[interfaces.objects.ObjectInterface, int, int]]: modules_region = [] - for module in lsmod.Lsmod.list_modules(self._context, self._module_name): + for module in linux_utilities_modules.Modules.list_modules( + self._context, self._module_name + ): minimum_address, maximum_address = module.get_module_address_boundaries() module_region = module, minimum_address, maximum_address modules_region.append(module_region) @@ -923,21 +981,35 @@ class Kallsyms(interfaces.configuration.VersionableInterface): return self._search_module_by_address(address) @functools.lru_cache - def _get_type_cache(self, name: str): + def _get_type_cache(self, name: str) -> Optional[interfaces.objects.Template]: vmlinux = self._context.modules[self._module_name] - return vmlinux.get_type(name) + try: + return vmlinux.get_type(name) + except exceptions.SymbolError: + return None def _mod_tree_comp( self, address: int, latch_tree_node: interfaces.objects.ObjectInterface - ) -> int: + ) -> Optional[int]: vmlinux = self._context.modules[self._module_name] - module_memory_mtn_offset = self._get_type_cache( - "module_memory" - ).relative_child_offset("mtn") - mod_tree_node_mod_offset = self._get_type_cache( - "mod_tree_node" - ).relative_child_offset("mod") + module_memory_mtn = self._get_type_cache("module_memory") + if not module_memory_mtn: + vollog.debug( + "`module_memory` symbol not present in the symbol table. Cannot proceed." + ) + return None + + module_memory_mtn_offset = module_memory_mtn.relative_child_offset("mtn") + + mod_tree_node_mod = self._get_type_cache("mod_tree_node") + if not mod_tree_node_mod: + vollog.debug( + "`mod_tree_node` symbol not present in the symbol table. Cannot proceed." + ) + return None + + mod_tree_node_mod_offset = mod_tree_node_mod.relative_child_offset("mod") module_memory_offset = ( latch_tree_node.vol.offset @@ -1033,7 +1105,9 @@ class Kallsyms(interfaces.configuration.VersionableInterface): name: str, other: str, ) -> int: - if name == other: + if name is None or other is None: + return None + elif name == other: return 0 elif name < other: return -1 @@ -1087,7 +1161,9 @@ class Kallsyms(interfaces.configuration.VersionableInterface): KASSymbol objects """ layer = self._context.layers[self._layer_name] - for module in lsmod.Lsmod.list_modules(self._context, self._module_name): + for module in linux_utilities_modules.Modules.list_modules( + self._context, self._module_name + ): module_name = utility.array_to_string(module.name) for elf_sym_idx, elf_sym_obj in enumerate(module.get_symbols()): sym_name = elf_sym_obj.get_name() @@ -1252,23 +1328,33 @@ class Kallsyms(interfaces.configuration.VersionableInterface): # Even when bpf_jit_kallsyms is disabled (/proc/sys/net/core/bpf_jit_kallsyms = 0), # this function will still be able to gather the symbols. - bpf_kallsyms_list = vmlinux.object_from_symbol("bpf_kallsyms") + try: + bpf_kallsyms_list = vmlinux.object_from_symbol("bpf_kallsyms") + except exceptions.SymbolError: + vollog.debug( + "`bpf_kallsyms` symbol not present in the symbol table. Cannot proceed." + ) + return None + for elem in bpf_kallsyms_list.to_list(list_type_symname, list_head_member): - # See kernel's bpf_get_kallsym() - if list_type == "bpf_ksym": - # kernels >= 5.8 - bpf_ksym = elem - sym_name = utility.array_to_string(bpf_ksym.name) - sym_addr = bpf_ksym.start - sym_size = bpf_ksym.end - bpf_ksym.start - else: - # list_type == "bpf_prog_aux" 3.18 <= kernels < 5.8 - bpf_prog_aux = elem - bpf_prog = bpf_prog_aux.prog - sym_name = bpf_prog.get_name() - sym_addr = bpf_prog.bpf_func - sym_start, sym_end = bpf_prog.get_address_region() - sym_size = sym_end - sym_start + try: + # See kernel's bpf_get_kallsym() + if list_type == "bpf_ksym": + # kernels >= 5.8 + bpf_ksym = elem + sym_name = utility.array_to_string(bpf_ksym.name) + sym_addr = bpf_ksym.start + sym_size = bpf_ksym.end - bpf_ksym.start + else: + # list_type == "bpf_prog_aux" 3.18 <= kernels < 5.8 + bpf_prog_aux = elem + bpf_prog = bpf_prog_aux.prog + sym_name = bpf_prog.get_name() + sym_addr = bpf_prog.bpf_func + sym_start, sym_end = bpf_prog.get_address_region() + sym_size = sym_end - sym_start + except exceptions.InvalidAddressException: + continue # The following are also hardcoded in the Linux kernel # see kernel's get_ksymbol_bpf(), bpf_get_kallsym() and BPF_SYM_ELF_TYPE @@ -1322,7 +1408,7 @@ class Kallsyms(interfaces.configuration.VersionableInterface): sym_size = symbol_end - symbol_start elif vmlinux.has_type("latch_tree_root") and vmlinux.get_type( "bpf_prog_aux" - ).child_template("ksym_tnode"): + ).has_member("ksym_tnode"): # For 4.11 <= kernels < 5.7 # latch_tree_root was added in kernels 4.2 ade3f510f93a5613b672febe88eff8ea7f1c63b7 # BPF kallsyms support was added in kernels 4.11 74451e66d516c55e309e8d89a4a1e7596e46aacd diff --git a/volatility3/framework/symbols/linux/utilities/modules.py b/volatility3/framework/symbols/linux/utilities/modules.py index 0eeb2b33c..f987c352e 100644 --- a/volatility3/framework/symbols/linux/utilities/modules.py +++ b/volatility3/framework/symbols/linux/utilities/modules.py @@ -1,6 +1,18 @@ import logging import warnings -from typing import Iterable, Iterator, List, Optional, Tuple, NamedTuple, Dict, Set +from typing import ( + Iterable, + Iterator, + List, + Optional, + Tuple, + NamedTuple, + Dict, + Set, + Generator, + Union, +) +from abc import ABCMeta, abstractmethod from volatility3 import framework from volatility3.framework import ( @@ -9,38 +21,67 @@ from volatility3.framework import ( deprecation, exceptions, objects, + renderers, ) +from volatility3.framework.constants import architectures +from volatility3.framework.renderers import format_hints +from volatility3.framework.configuration import requirements from volatility3.framework.objects import utility from volatility3.framework.symbols.linux import extensions +from volatility3.framework.symbols.linux.utilities import tainting vollog = logging.getLogger(__name__) +class ModuleInfo(NamedTuple): + """ + Used to track the name and boundary of a kernel module + """ + + offset: int + name: str + start: int + end: int + + +class ModuleGathererInterface( + interfaces.configuration.VersionableInterface, metaclass=ABCMeta +): + _version = (1, 0, 0) + _required_framework_version = (2, 0, 0) + + framework.require_interface_version(*_required_framework_version) + + gatherer_return_type = Generator[Union[ModuleInfo, "extensions.module"], None, None] + + # Must be set to a unique, descriptive name of the gathering technique or data structure source + name = None + + @classmethod + @abstractmethod + def gather_modules( + cls, context: interfaces.context.ContextInterface, kernel_module_name: str + ) -> gatherer_return_type: + """ + This method must return a generator (yield) of each `gatherer_return_type` found from its source + """ + + class Modules(interfaces.configuration.VersionableInterface): """Kernel modules related utilities.""" - _version = (2, 0, 0) + _version = (3, 0, 1) _required_framework_version = (2, 0, 0) framework.require_interface_version(*_required_framework_version) - class ModuleInfo(NamedTuple): - """ - Used to track the name and boundary of a kernel module - """ - - offset: int - name: str - start: int - end: int - - @staticmethod + @classmethod def module_lookup_by_address( + cls, context: interfaces.context.ContextInterface, kernel_module_name: str, modules: Iterable[ModuleInfo], target_address: int, - run_hidden_modules: bool = True, ) -> Optional[Tuple[ModuleInfo, Optional[str]]]: """ Determine if a target address lies in a module memory space. @@ -98,7 +139,7 @@ class Modules(interfaces.configuration.VersionableInterface): module = kernel.object("module", offset=module.offset, absolute=True) symbol_name = module.get_symbol_by_address(target_address) - if symbol_name: + if symbol_name and symbol_name.find(constants.BANG) != -1: symbol_name = symbol_name.split(constants.BANG)[1] return match, symbol_name @@ -189,98 +230,80 @@ class Modules(interfaces.configuration.VersionableInterface): end = start + module.get_core_size() - return Modules.ModuleInfo(module.vol.offset, mod_name, start, end) - - @staticmethod - def get_kernel_module_info( - context: interfaces.context.ContextInterface, - kernel_module_name: str, - ) -> ModuleInfo: - """ - Returns a ModuleInfo instance that encodes the kernel - This is required to map function pointers to the kerenl executable - """ - kernel = context.modules[kernel_module_name] - - address_mask = context.layers[kernel.layer_name].address_mask - - start_addr = kernel.object_from_symbol("_text") - start_addr = start_addr.vol.offset & address_mask - - end_addr = kernel.object_from_symbol("_etext") - end_addr = end_addr.vol.offset & address_mask - - return Modules.ModuleInfo( - start_addr, constants.linux.KERNEL_NAME, start_addr, end_addr - ) + return ModuleInfo(module.vol.offset, mod_name, start, end) @classmethod def run_modules_scanners( cls, context: interfaces.context.ContextInterface, - kernel_name: str, - run_hidden_modules: bool = True, + kernel_module_name: str, + caller_wanted_gatherers: List[ModuleGathererInterface], flatten: bool = True, ) -> Dict[str, List[ModuleInfo]]: """Run module scanning plugins and aggregate the results. It is designed to not operate any inter-plugin results triage. - Args: - run_hidden_modules: specify if the hidden_modules plugin should be run - Returns: - Dictionary mapping each plugin to its corresponding result - """ + Rules for `caller_wanted_gatherers`: + If `ModuleGatherers.all_gathers_identifier` is specified then every source will be populated - kernel = context.modules[kernel_name] + If empty or an invalid gatherer is specified then a ValueError is thrown + + All gatherer names must be unique + Args: + called_wanted_sources: The list of sources to gather modules. + flatten: Whether to de-duplicate modules across gatherers + Returns: + Dictionary mapping each gatherer to its corresponding result + """ + if not caller_wanted_gatherers: + raise ValueError( + "`caller_wanted_gatherers` must have at least one gatherer." + ) + + if not isinstance(caller_wanted_gatherers, Iterable): + raise ValueError("`caller_wanted_gatherers` must be iterable") + + seen_names = set() + + for gatherer in caller_wanted_gatherers: + if not issubclass(gatherer, ModuleGathererInterface): + raise ValueError( + f"Invalid gatherer sent through `caller_wanted_gatherers`: {gatherer}" + ) + + if not gatherer.name: + raise ValueError( + f"{gatherer} does not have a valid name attribute, which is required. It must be a non-zero length string." + ) + + if gatherer.name in seen_names: + raise ValueError( + f"{gatherer} has a name {gatherer.name} which has already been processed. Names must be unique." + ) + + seen_names.add(gatherer.name) + + kernel = context.modules[kernel_module_name] address_mask = context.layers[kernel.layer_name].address_mask - run_results = {} + run_results: Dict[ModuleGathererInterface, List[ModuleInfo]] = {} - # the kernel module boundaries - run_results["kernel"] = [cls.get_kernel_module_info(context, kernel_name)] + # Walk each source gathering modules + for gatherer in caller_wanted_gatherers: + run_results[gatherer.name] = [] - # lsmod - run_results["lsmod"] = [] + # process each module coming from back the current source + for module in gatherer.gather_modules(context, kernel_module_name): - for module in cls.list_modules(context, kernel_name): - modinfo = cls.get_module_info_for_module(address_mask, module) - if modinfo: - run_results["lsmod"].append(modinfo) + # the kernel sends back a ModuleInfo directly + if isinstance(module, ModuleInfo): + modinfo = module + else: + modinfo = cls.get_module_info_for_module(address_mask, module) - # check_modules - run_results["check_modules"] = [] - - sysfs_modules: dict = cls.get_kset_modules(context, kernel_name) - - for m_offset in sysfs_modules.values(): - module = kernel.object(object_type="module", offset=m_offset, absolute=True) - modinfo = cls.get_module_info_for_module(address_mask, module) - if modinfo: - run_results["check_modules"].append(modinfo) - - # hidden_modules - if run_hidden_modules: - known_modules_addresses = set( - context.layers[kernel.layer_name].canonicalize(modinfo.start) - for modinfo in run_results["kernel"] - + run_results["lsmod"] - + run_results["check_modules"] - ) - modules_memory_boundaries = cls.get_modules_memory_boundaries( - context, kernel_name - ) - run_results["hidden_modules"] = [] - - for module in cls.get_hidden_modules( - context, - kernel_name, - known_modules_addresses, - modules_memory_boundaries, - ): - modinfo = cls.get_module_info_for_module(address_mask, module) if modinfo: - run_results["hidden_modules"].append(modinfo) + run_results[gatherer.name].append(modinfo) if flatten: return cls.flatten_run_modules_results(run_results) @@ -338,7 +361,7 @@ class Modules(interfaces.configuration.VersionableInterface): Returns: List of ModuleInfo objects """ - uniq_modules: List[Modules.ModuleInfo] = [] + uniq_modules: List[ModuleInfo] = [] seen_addresses: int = set() @@ -534,3 +557,439 @@ class Modules(interfaces.configuration.VersionableInterface): True if all the addresses meet the alignment """ return all(addr % address_alignment == 0 for addr in addresses) + + @classmethod + def _get_param_handlers( + cls, context: interfaces.context.ContextInterface, vmlinux_name: str + ) -> Tuple[Dict[int, str], Dict[str, Optional[int]]]: + """ + This function builds the dictionaries needed to map kernel parameters to their types + We need these values and information to properly decode each parameter to its input representation + """ + kernel = context.modules[vmlinux_name] + + # All the integer type parameters + pairs = { + "param_get_invbool": "int", + "param_get_bool": "int", + "param_get_int": "int", + "param_get_ulong": "long unsigned int", + "param_get_ullong": "long long unsigned int", + "param_get_long": "long int", + "param_get_uint": "unsigned int", + "param_get_ushort": "short unsigned int", + "param_get_short": "short int", + "param_get_byte": "char", + } + + int_handlers: Dict[int, str] = {} + + for sym_name, val_type in pairs.items(): + try: + sym_address = kernel.get_absolute_symbol_address(sym_name) + except exceptions.SymbolError: + continue + + int_handlers[sym_address] = val_type + + # Strings, arrays, booleans + getters = { + "param_get_string": None, + "param_array_get": None, + "param_get_charp": None, + "param_get_bool": None, + "param_get_invbool": None, + } + + for sym_name in getters: + try: + sym_address = kernel.get_absolute_symbol_address(sym_name) + except exceptions.SymbolError: + continue + + getters[sym_name] = sym_address + + return int_handlers, getters + + @classmethod + def _get_param_val( + cls, + context: interfaces.context.ContextInterface, + vmlinux_name: str, + int_handlers, + getters, + module, + param, + ) -> Optional[Union[str, int]]: + """ + Properly determines the type of a parameter and decodes based on the type. + The type is determined by examining its `get` function, which will be a pointer to + predefined operations handler for particular parameter types. + """ + + # Attempt to retrieve the `get` pointer. Bail if smeared + try: + if hasattr(param, "get"): + param_func = param.get + else: + param_func = param.ops.get + + except exceptions.InvalidAddressException: + return None + + if not param_func: + return None + + kernel = context.modules[vmlinux_name] + + # For arrays, recusively get the value of each member as the type can be different + if param_func == getters["param_array_get"]: + array = param.arr + + if array.num: + max_index = array.num.dereference() + else: + max_index = array.member("max") + + if max_index > 32: + vollog.debug( + f"Skipping array parameter with invalid index for module {module.vol.offset:#x}" + ) + return None + + element_vals = [] + for i in range(max_index): + kp = kernel.object( + object_type="kernel_param", + offset=array.elem + (array.elemsize * i), + absolute=True, + ) + + element_vals.append( + cls._get_param_val( + context, vmlinux_name, int_handlers, getters, module, kp + ) + ) + + # nothing was gathered + if not element_vals: + return None + + return ",".join([str(ele) for ele in element_vals]) + + # strings types + elif param_func in [getters["param_get_string"], getters["param_get_charp"]]: + try: + if param_func == getters["param_get_string"]: + count = param.member("str").maxlen + else: + count = 256 + + return utility.pointer_to_string(param.member("str"), count=count) + except exceptions.InvalidAddressException: + vollog.debug( + f"Skipping string parameter with invalid address for module {module.vol.offset:#x}" + ) + return None + + # The integer handles, which also encompass boolean handlers + elif param_func in int_handlers: + try: + int_value = kernel.object( + object_type=int_handlers[param_func], offset=param.arg + ) + except exceptions.InvalidAddressException: + vollog.debug( + f"Skipping {int_handlers[param_func]} parameter with invalid address for module {module.vol.offset:#x}" + ) + return None + + if param_func == getters["param_get_bool"]: + if int_value == 0: + return "N" + else: + return "Y" + elif param_func == getters["param_get_invbool"]: + if int_value == 0: + return "Y" + else: + return "N" + else: + return int_value + + else: + handler_symbol = kernel.get_symbols_by_absolute_location(param_func) + + msg = f"Unknown kernel parameter handling function ({handler_symbol}) at address {param_func:#x} for module at {module.vol.offset:#x}" + + # If a new kernel has a handler symbol we don't support then we want to always see that information + # If the handler doesn't map to a kernel symbol then its smeared/invalid + if handler_symbol: + vollog.warning(msg) + else: + vollog.debug(msg) + + return None + + @classmethod + def get_load_parameters( + cls, + context: interfaces.context.ContextInterface, + vmlinux_name: str, + module: extensions.module, + ) -> Generator[Tuple[str, Optional[Union[str, int]]], None, None]: + """ + Recovers the load parameters of the given kernel module + Returns a tuple (key,value) for each parameter + """ + if not hasattr(module, "kp"): + vollog.debug( + "kp member missing for struct module. Cannot recover parameters." + ) + return None + + if module.num_kp > 128: + vollog.debug( + f"Smeared number of parameters ({module.num_kp}) found for module at offset {module.vol.offset:#x}" + ) + return None + + kernel = context.modules[vmlinux_name] + + int_handlers, getters = cls._get_param_handlers(context, vmlinux_name) + + # Build the array of parameters + param_array = kernel.object( + object_type="array", + offset=module.kp.dereference().vol.offset, + subtype=kernel.get_type("kernel_param"), + count=module.num_kp, + absolute=True, + ) + + for i in range(len(param_array)): + try: + param = param_array[i] + name = utility.pointer_to_string(param.name, count=32) + except exceptions.InvalidAddressException: + vollog.debug( + f"Smeared load parameter module at offset {module.vol.offset:#x}" + ) + continue + + value = cls._get_param_val( + context, vmlinux_name, int_handlers, getters, module, param + ) + + yield name, value + + +class ModuleGathererLsmod(ModuleGathererInterface): + """ + Gathers modules from the main kernel list + """ + + _version = (1, 0, 0) + + name = "Lsmod" + + @classmethod + def gather_modules( + cls, context: interfaces.context.ContextInterface, kernel_module_name: str + ) -> ModuleGathererInterface.gatherer_return_type: + yield from Modules.list_modules(context, kernel_module_name) + + +class ModuleGathererSysFs(ModuleGathererInterface): + """ + Gathers modules from the sysfs /sys/modules objects + """ + + _version = (1, 0, 0) + + name = "SysFs" + + @classmethod + def gather_modules( + cls, context: interfaces.context.ContextInterface, kernel_module_name: str + ) -> ModuleGathererInterface.gatherer_return_type: + kernel = context.modules[kernel_module_name] + + sysfs_modules: dict = Modules.get_kset_modules(context, kernel_module_name) + + for m_offset in sysfs_modules.values(): + yield kernel.object(object_type="module", offset=m_offset, absolute=True) + + +class ModuleGathererScanner(ModuleGathererInterface): + """ + Gathers modules by scanning memory + """ + + _version = (1, 0, 0) + + name = "Scanner" + + @classmethod + def gather_modules( + cls, context: interfaces.context.ContextInterface, kernel_module_name: str + ) -> ModuleGathererInterface.gatherer_return_type: + modules_memory_boundaries = Modules.get_modules_memory_boundaries( + context, kernel_module_name + ) + + # Send in an empty list to not filter on any modules + yield from Modules.get_hidden_modules( + context=context, + vmlinux_module_name=kernel_module_name, + known_module_addresses=[], + modules_memory_boundaries=modules_memory_boundaries, + ) + + +class ModuleGathererKernel(ModuleGathererInterface): + """ + Creates a ModuleInfo instance for the kernel so that plugins + can determine when function pointers reference the kernel + """ + + _version = (1, 0, 0) + + name = "kernel" + + @classmethod + def gather_modules( + cls, context: interfaces.context.ContextInterface, kernel_module_name: str + ) -> ModuleGathererInterface.gatherer_return_type: + """ + Returns a ModuleInfo instance that encodes the kernel + This is required to map function pointers to the kerenl executable + """ + kernel = context.modules[kernel_module_name] + + address_mask = context.layers[kernel.layer_name].address_mask + + start_addr = kernel.object_from_symbol("_text") + start_addr = start_addr.vol.offset & address_mask + + end_addr = kernel.object_from_symbol("_etext") + end_addr = end_addr.vol.offset & address_mask + + yield ModuleInfo(start_addr, constants.linux.KERNEL_NAME, start_addr, end_addr) + + +class ModuleGatherers( + interfaces.configuration.VersionableInterface, + interfaces.configuration.ConfigurableInterface, +): + _version = (1, 0, 0) + _required_framework_version = (2, 0, 0) + + framework.require_interface_version(*_required_framework_version) + + # Valid sources of cores kernel module gatherers to send to `run_module_scanners` + # With few exceptions, rootkit checking plugins want all sources + # This provides a stable identifier as new sources are added over time + all_gatherers_identifier = [ + ModuleGathererLsmod, + ModuleGathererSysFs, + ModuleGathererScanner, + ModuleGathererKernel, + ] + + @classmethod + def get_requirements(cls): + reqs = [] + + # for now, all versions are 1, this will be broken out if/when that changes + for gatherer in ModuleGatherers.all_gatherers_identifier: + reqs.append( + requirements.VersionRequirement( + name=gatherer.name.replace(" ", ""), + component=gatherer, + version=(1, 0, 0), + ) + ) + + return reqs + + +class ModuleDisplayPlugin(interfaces.configuration.VersionableInterface): + """ + Plugins that enumerate kernel modules (lsmod, check_modules, etc.) + must inherit from this class to have unified output columns across plugins. + The constructor of the plugin must call super() with the `implementation` set + """ + + _version = (1, 0, 0) + _required_framework_version = (2, 0, 0) + + framework.require_interface_version(*_required_framework_version) + + @classmethod + def get_requirements(cls) -> List[interfaces.configuration.RequirementInterface]: + return [ + requirements.ModuleRequirement( + name="kernel", + description="Linux kernel", + architectures=architectures.LINUX_ARCHS, + ), + requirements.VersionRequirement( + name="linux_utilities_modules", + component=Modules, + version=(3, 0, 1), + ), + requirements.VersionRequirement( + name="linux-tainting", component=tainting.Tainting, version=(1, 0, 0) + ), + ] + + def generator(self): + """ + Uses the implementation set in the constructor call to produce consistent output fields + across module gathering plugins + """ + for module in self.implementation(self.context, self.config["kernel"]): + try: + name = utility.array_to_string(module.name) + except exceptions.InvalidAddressException: + vollog.debug( + f"Unable to recover name for module {module.vol.offset:#x} from implementation {self.implementation}" + ) + continue + + code_size = format_hints.Hex( + module.get_init_size() + module.get_core_size() + ) + + taints = ",".join( + tainting.Tainting.get_taints_parsed( + self.context, self.config["kernel"], module.taints, True + ) + ) + + parameters_iter = Modules.get_load_parameters( + self.context, self.config["kernel"], module + ) + + parameters = ", ".join([f"{key}={value}" for key, value in parameters_iter]) + + yield 0, ( + format_hints.Hex(module.vol.offset), + name, + format_hints.Hex(code_size), + taints, + parameters, + ) + + def run(self): + return renderers.TreeGrid( + [ + ("Offset", format_hints.Hex), + ("Module Name", str), + ("Code Size", format_hints.Hex), + ("Taints", str), + ("Load Arguments", str), + ], + self._generator(), + ) diff --git a/volatility3/framework/symbols/windows/extensions/__init__.py b/volatility3/framework/symbols/windows/extensions/__init__.py index 933178c91..75608cfc6 100755 --- a/volatility3/framework/symbols/windows/extensions/__init__.py +++ b/volatility3/framework/symbols/windows/extensions/__init__.py @@ -22,9 +22,8 @@ from volatility3.framework.interfaces.objects import ObjectInterface from volatility3.framework.layers import intel from volatility3.framework.objects import utility from volatility3.framework.renderers import conversion -from volatility3.framework.symbols import generic +from volatility3.framework.symbols import generic, windows from volatility3.framework.symbols.windows.extensions import pool -from volatility3.framework.symbols import windows vollog = logging.getLogger(__name__) @@ -1221,19 +1220,6 @@ class KTIMER(objects.StructType): return "Yes" return "-" - def get_raw_dpc(self): - """Returns the encoded DPC since it may not look like a pointer after encoding""" - symbol_table_name = self.get_symbol_table_name() - pointer_type = self._context.symbol_space.get_type( - symbol_table_name + constants.BANG + "pointer" - ) - - return self._context.object( - object_type=pointer_type, - layer_name=self.vol.layer_name, - offset=self.Dpc.vol.offset, - ) - def valid_type(self): return self.Header.Type in self.VALID_TYPES @@ -1268,7 +1254,7 @@ class KTIMER(objects.StructType): ) low_byte = (wait_never) & 0xFF - entry = utility.rol(self.get_raw_dpc() ^ wait_never, low_byte) + entry = utility.rol(self.Dpc.get_raw_value() ^ wait_never, low_byte) swap_xor = self._context.layers[self.vol.native_layer_name].canonicalize( self.vol.offset ) diff --git a/volatility3/framework/symbols/windows/extensions/gui.py b/volatility3/framework/symbols/windows/extensions/gui.py index 92693dba5..d1835631f 100644 --- a/volatility3/framework/symbols/windows/extensions/gui.py +++ b/volatility3/framework/symbols/windows/extensions/gui.py @@ -2,126 +2,332 @@ # which is available at https://www.volatilityfoundation.org/license/vsl-v1.0 # -from typing import Optional, Tuple, Iterator +import logging +from typing import Optional, Tuple, Iterator, Generator +from volatility3 import framework from volatility3.framework import exceptions, constants, interfaces from volatility3.framework import objects from volatility3.framework.objects import utility +from volatility3.framework.symbols.windows import extensions from volatility3.framework.symbols.windows.extensions import pool +vollog = logging.getLogger(__name__) -class tagWINDOWSTATION(objects.StructType, pool.ExecutiveObject): - def is_valid(self) -> bool: - sid = self.get_session_id() - return sid is not None and 0 <= sid < 256 - def get_session_id(self) -> Optional[int]: - try: - return self.dwSessionId - except exceptions.InvalidAddressException: - return None +class GUIExtensions(interfaces.configuration.VersionableInterface): + _version = (1, 0, 0) + _required_framework_version = (2, 0, 0) - def traverse(self, max_stations: int = 15): - """ - Traverses the window stations referenced in the list of stations - """ - seen = set() + framework.require_interface_version(*_required_framework_version) - # include the first window station - yield self + class tagWINDOWSTATION(objects.StructType, pool.ExecutiveObject): + def is_valid(self) -> bool: + sid = self.get_session_id() + return sid is not None and 0 <= sid < 256 - while len(seen) < max_stations: + def get_session_id(self) -> Optional[int]: try: - winsta = self.rpwinstaNext.dereference() + return self.dwSessionId except exceptions.InvalidAddressException: - break + return None - if winsta.vol.offset in seen: - break + def traverse(self, max_stations: int = 15): + """ + Traverses the window stations referenced in the list of stations + """ + seen = set() - yield winsta + # include the first window station + yield self - seen.add(winsta.vol.offset) + while len(seen) < max_stations: + try: + winsta = self.rpwinstaNext.dereference() + except exceptions.InvalidAddressException: + break + + if winsta.vol.offset in seen: + break + + yield winsta + + seen.add(winsta.vol.offset) + + def get_info(self, kernel_symbol_table_name) -> Optional[Tuple[str, int]]: + try: + name = self.get_name(kernel_symbol_table_name) + session_id = self.get_session_id() + except exceptions.InvalidAddressException: + return None, None + + # attempt to avoid smear + if session_id is not None and session_id < 256 and name and len(name) > 1: + return name, session_id - def get_info(self, kernel_symbol_table_name) -> Optional[Tuple[str, int]]: - try: - name = self.get_name(kernel_symbol_table_name) - session_id = self.get_session_id() - except exceptions.InvalidAddressException: return None, None - # attempt to avoid smear - if session_id is not None and session_id < 256 and name and len(name) > 1: - return name, session_id + def desktops(self, symbol_table_name, max_desktops: int = 12): + seen = set() - return None, None + while len(seen) < max_desktops: + try: + desktop = self.rpdeskList.dereference() + name = desktop.get_name(symbol_table_name) + except exceptions.InvalidAddressException: + break - def desktops(self, symbol_table_name, max_desktops: int = 12): - seen = set() + if desktop.vol.offset in seen: + break - while len(seen) < max_desktops: + yield desktop, name + + seen.add(desktop.vol.offset) + + class tagDESKTOP(objects.StructType, pool.ExecutiveObject): + def is_valid(self) -> bool: + """ + Enforce a valid session ID and Window station + We aren't interested in terminated desktops as there are so many pointers + going from station -> desktop -> windows, that we would just be processing junk. + Even if the pointers were still in tact by some miracle, its not that helpful to + have a floating desktop appear in the output as you can't do much with it. + """ + sid = self.get_session_id() + + valid_sid = sid is not None and 0 <= sid < 256 + + if valid_sid: + return self.get_window_station() is not None + + return False + + def get_window_station(self) -> Optional["GUIExtensions.tagWINDOWSTATION"]: + """ + Attempts to return the window station for this desktop + """ try: - desktop = self.rpdeskList.dereference() - name = desktop.get_name(symbol_table_name) + return self.rpwinstaParent.dereference() except exceptions.InvalidAddressException: - break + return None - if desktop.vol.offset in seen: - break + def get_session_id(self) -> Optional[int]: + """ + Attempts to return the session ID for this desktop + """ + winsta = self.get_window_station() + if winsta: + return winsta.get_session_id() - yield desktop, name - - seen.add(desktop.vol.offset) - - -class tagDESKTOP(objects.StructType, pool.ExecutiveObject): - def is_valid(self) -> bool: - """ - Enforce a valid sid + owning window station - """ - sid = self.get_session_id() - - valid_sid = sid is not None and 0 <= sid < 256 - - if valid_sid: - return self.get_window_station() is not None - - return False - - def get_window_station(self) -> Optional["tagWINDOWSTATION"]: - try: - return self.rpwinstaParent.dereference() - except exceptions.InvalidAddressException: return None - def get_session_id(self) -> Optional[int]: - winsta = self.get_window_station() - if winsta: - return winsta.get_session_id() + def get_threads( + self, + ) -> Iterator[Tuple[interfaces.objects.ObjectInterface, str, int]]: + """ + Returns the threads of each desktop along with owning process information + """ + symbol_table_name = self.vol.type_name.split(constants.BANG)[0] - return None + for thread in self.PtiList.to_list( + symbol_table_name + constants.BANG + "tagTHREADINFO", "PtiLink" + ): + try: + process_name = utility.array_to_string( + thread.ppi.Process.ImageFileName + ) + process_pid = thread.ppi.Process.UniqueProcessId + except exceptions.InvalidAddressException: + continue - def get_threads( - self, - ) -> Iterator[Tuple[interfaces.objects.ObjectInterface, str, int]]: - """ - Returns the threads of each desktop along with owning process information - """ - symbol_table_name = self.vol.type_name.split(constants.BANG)[0] + yield thread, process_name, process_pid + + def _do_get_windows( + self, window, max_windows + ) -> Generator[Tuple[interfaces.objects.ObjectInterface, str], None, None]: + """ + Recusively walks and yields the adjacent and child windows + """ + seen_windows = set() + seen_children = set() + + if not window.vol.offset: + return + + yield window, window.get_name() + + seen_windows.add(window) + + # Walk adjacent windows + while len(seen_windows) < max_windows: + try: + window = window.spwndNext.dereference() + except exceptions.InvalidAddressException: + break + + if not window.vol.offset: + break + + if window.vol.offset in seen_windows: + break + + yield window, window.get_name() + + seen_windows.add(window) + + # Walk children windows and recursively yield them + for window in seen_windows: + child = window + + while len(seen_windows) + len(seen_children) < max_windows: + try: + child = child.spwndChild + except exceptions.InvalidAddressException: + break + + if not child.vol.offset: + break + + if child in seen_children: + break + seen_children.add(child) + + yield from self._do_get_windows(child, max_windows) + + def windows( + self, window, max_windows=10000 + ) -> Generator[Tuple[interfaces.objects.ObjectInterface, str], None, None]: + """ + Enumerates all windows adjacent to and children of `window` + + Args: + window: The window to enumerate windows from + + Returns: + A generator of tuples containing the window and its name + """ + seen_windows = set() + + for window, window_name in self._do_get_windows(window, max_windows): + if window.vol.offset in seen_windows: + continue + + seen_windows.add(window.vol.offset) + + yield window, window_name + + if len(seen_windows) == max_windows: + break + + class tagWND(objects.StructType, pool.ExecutiveObject): + + def is_valid(self) -> bool: + """ + Enforce a valid sid + """ + sid = self.get_session_id() + + return sid is not None and 0 <= sid < 256 + + def get_name(self) -> Optional[str]: + """ + directName appeared in later Windows 10 versions and is pointer + strName is a unicode string directly in the structure + """ + if self.has_member("directName"): + try: + return utility.pointer_to_string( + self.directName, count=256, encoding="utf16" + ) + except exceptions.InvalidAddressException: + vollog.debug( + f"directname for window at {self.vol.offset:#x} in layer {self.vol.layer_name} is invalid" + ) - for thread in self.PtiList.to_list( - symbol_table_name + constants.BANG + "tagTHREADINFO", "PtiLink" - ): try: - process_name = utility.array_to_string(thread.ppi.Process.ImageFileName) - process_pid = thread.ppi.Process.UniqueProcessId + return self.strName.get_string() except exceptions.InvalidAddressException: - continue + vollog.debug( + f"strName for window at {self.vol.offset:#x} in layer {self.vol.layer_name} is invalid" + ) - yield thread, process_name, process_pid + return None + def get_session_id(self) -> Optional[int]: + """ + Uses its tagDESKTOP pointer to find its session + """ + desktop = self.get_desktop() + if desktop: + return desktop.get_session_id() -class_types = { - "tagWINDOWSTATION": tagWINDOWSTATION, - "tagDESKTOP": tagDESKTOP, -} + return None + + def get_desktop(self) -> Optional["GUIExtensions.tagDESKTOP"]: + """ + Attempts to return the host desktop (tagDESKTOP) for this window + """ + try: + return self.head.rpdesk.dereference() + except exceptions.InvalidAddressException: + vollog.debug( + f"Reading the desktop pointer for window {self.vol.offset:#x} caused a page fault" + ) + return None + + def get_process(self) -> Optional["extensions.EPROCESS"]: + """ + Attempts to return the host process (_EPROCESS) for this window + """ + try: + return self.head.pti.ppi.Process.dereference() + except exceptions.InvalidAddressException: + vollog.debug( + f"Reading the process pointer for window {self.vol.offset:#x} caused a page fault" + ) + return None + + def get_window_procedure(self): + """ + Attempts to return the window procedure for this windows + """ + try: + # >= 17134 + if hasattr(self, "subPointer"): + return self.subPointer.lpfnWndProc + else: + return self.lpfnWndProc + except exceptions.InvalidAddressException: + vollog.debug( + f"Invalid window procedure for window {self.vol.offset:#x}" + ) + return None + + # This is copy/paste from UNICODE_STRING in `symbols/windows/extensions/__init__.py` + # The versioning of modules would get very ugly if we let different modules share implementations + # across different data structures + class LARGE_UNICODE_STRING(objects.StructType): + """A class for Windows unicode string structures.""" + + def get_string(self) -> interfaces.objects.ObjectInterface: + # We explicitly do *not* catch errors here, we allow an exception to be thrown + # (otherwise there's no way to determine anything went wrong) + # It's up to the user of this method to catch exceptions + + # We manually construct an object rather than casting a dereferenced pointer in case + # the buffer length is 0 and the pointer is a NULL pointer + return self._context.object( + self.vol.type_name.split(constants.BANG)[0] + constants.BANG + "string", + layer_name=self.Buffer.vol.native_layer_name, + offset=self.Buffer, + max_length=self.Length, + errors="replace", + encoding="utf16", + ) + + class_types = { + "tagWINDOWSTATION": tagWINDOWSTATION, + "tagDESKTOP": tagDESKTOP, + "tagWND": tagWND, + "_LARGE_UNICODE_STRING": LARGE_UNICODE_STRING, + } diff --git a/volatility3/framework/symbols/windows/gui/gui-win10-10586-x64.json b/volatility3/framework/symbols/windows/gui/gui-win10-10586-x64.json index a542d4778..0308f4c7d 100644 --- a/volatility3/framework/symbols/windows/gui/gui-win10-10586-x64.json +++ b/volatility3/framework/symbols/windows/gui/gui-win10-10586-x64.json @@ -18036,6 +18036,12 @@ "signed": false, "size": 1 }, + "char": { + "kind": "char", + "endian": "little", + "signed": false, + "size": 1 + }, "float": { "kind": "float", "endian": "little", diff --git a/volatility3/framework/symbols/windows/gui/gui-win10-15063-x64.json b/volatility3/framework/symbols/windows/gui/gui-win10-15063-x64.json index 7c4af02e1..a69cbb7c3 100644 --- a/volatility3/framework/symbols/windows/gui/gui-win10-15063-x64.json +++ b/volatility3/framework/symbols/windows/gui/gui-win10-15063-x64.json @@ -18036,6 +18036,12 @@ "signed": false, "size": 1 }, + "char": { + "kind": "char", + "endian": "little", + "signed": false, + "size": 1 + }, "float": { "kind": "float", "endian": "little", diff --git a/volatility3/framework/symbols/windows/gui/gui-win10-16299-x64.json b/volatility3/framework/symbols/windows/gui/gui-win10-16299-x64.json index 1ff5fdcd9..54e8aeec3 100644 --- a/volatility3/framework/symbols/windows/gui/gui-win10-16299-x64.json +++ b/volatility3/framework/symbols/windows/gui/gui-win10-16299-x64.json @@ -18036,6 +18036,12 @@ "signed": false, "size": 1 }, + "char": { + "kind": "char", + "endian": "little", + "signed": false, + "size": 1 + }, "float": { "kind": "float", "endian": "little", diff --git a/volatility3/framework/symbols/windows/gui/gui-win10-17134-x64.json b/volatility3/framework/symbols/windows/gui/gui-win10-17134-x64.json index f74c8dd5b..48b97a1c2 100644 --- a/volatility3/framework/symbols/windows/gui/gui-win10-17134-x64.json +++ b/volatility3/framework/symbols/windows/gui/gui-win10-17134-x64.json @@ -12464,8 +12464,8 @@ "directName": { "type": { "subtype": { - "kind": "struct", - "name": "nt_symbols!String" + "kind": "base", + "name": "char" }, "kind": "pointer" }, @@ -18079,6 +18079,12 @@ "signed": false, "size": 1 }, + "char": { + "kind": "char", + "endian": "little", + "signed": false, + "size": 1 + }, "float": { "kind": "float", "endian": "little", diff --git a/volatility3/framework/symbols/windows/gui/gui-win10-17735-x64.json b/volatility3/framework/symbols/windows/gui/gui-win10-17735-x64.json index 88e419100..affaf8731 100644 --- a/volatility3/framework/symbols/windows/gui/gui-win10-17735-x64.json +++ b/volatility3/framework/symbols/windows/gui/gui-win10-17735-x64.json @@ -12464,8 +12464,8 @@ "directName": { "type": { "subtype": { - "kind": "struct", - "name": "nt_symbols!String" + "kind": "base", + "name": "char" }, "kind": "pointer" }, @@ -18079,6 +18079,12 @@ "signed": false, "size": 1 }, + "char": { + "kind": "char", + "endian": "little", + "signed": false, + "size": 1 + }, "float": { "kind": "float", "endian": "little", diff --git a/volatility3/framework/symbols/windows/gui/gui-win10-17763-x64.json b/volatility3/framework/symbols/windows/gui/gui-win10-17763-x64.json index ed183f39b..33db6c28d 100644 --- a/volatility3/framework/symbols/windows/gui/gui-win10-17763-x64.json +++ b/volatility3/framework/symbols/windows/gui/gui-win10-17763-x64.json @@ -12464,8 +12464,8 @@ "directName": { "type": { "subtype": { - "kind": "struct", - "name": "nt_symbols!String" + "kind": "base", + "name": "char" }, "kind": "pointer" }, @@ -18079,6 +18079,12 @@ "signed": false, "size": 1 }, + "char": { + "kind": "char", + "endian": "little", + "signed": false, + "size": 1 + }, "float": { "kind": "float", "endian": "little", diff --git a/volatility3/framework/symbols/windows/gui/gui-win10-18362-x64.json b/volatility3/framework/symbols/windows/gui/gui-win10-18362-x64.json index f568eedbe..bb1c74f7f 100644 --- a/volatility3/framework/symbols/windows/gui/gui-win10-18362-x64.json +++ b/volatility3/framework/symbols/windows/gui/gui-win10-18362-x64.json @@ -12464,8 +12464,8 @@ "directName": { "type": { "subtype": { - "kind": "struct", - "name": "nt_symbols!String" + "kind": "base", + "name": "char" }, "kind": "pointer" }, @@ -18079,6 +18079,12 @@ "signed": false, "size": 1 }, + "char": { + "kind": "char", + "endian": "little", + "signed": false, + "size": 1 + }, "float": { "kind": "float", "endian": "little", diff --git a/volatility3/framework/symbols/windows/gui/gui-win10-19041-x64.json b/volatility3/framework/symbols/windows/gui/gui-win10-19041-x64.json index be4341cfd..74868f1a7 100644 --- a/volatility3/framework/symbols/windows/gui/gui-win10-19041-x64.json +++ b/volatility3/framework/symbols/windows/gui/gui-win10-19041-x64.json @@ -12464,8 +12464,8 @@ "directName": { "type": { "subtype": { - "kind": "struct", - "name": "nt_symbols!String" + "kind": "base", + "name": "char" }, "kind": "pointer" }, @@ -18079,6 +18079,12 @@ "signed": false, "size": 1 }, + "char": { + "kind": "char", + "endian": "little", + "signed": false, + "size": 1 + }, "float": { "kind": "float", "endian": "little", diff --git a/volatility3/framework/symbols/windows/gui/gui-win10-19577-x64.json b/volatility3/framework/symbols/windows/gui/gui-win10-19577-x64.json index 68692dcf4..e718710cf 100644 --- a/volatility3/framework/symbols/windows/gui/gui-win10-19577-x64.json +++ b/volatility3/framework/symbols/windows/gui/gui-win10-19577-x64.json @@ -12464,8 +12464,8 @@ "directName": { "type": { "subtype": { - "kind": "struct", - "name": "nt_symbols!String" + "kind": "base", + "name": "char" }, "kind": "pointer" }, @@ -18079,6 +18079,12 @@ "signed": false, "size": 1 }, + "char": { + "kind": "char", + "endian": "little", + "signed": false, + "size": 1 + }, "float": { "kind": "float", "endian": "little", diff --git a/volatility3/framework/symbols/windows/gui/gui-win7sp0-x64.json b/volatility3/framework/symbols/windows/gui/gui-win7sp0-x64.json index ec81241b2..9b413baaa 100644 --- a/volatility3/framework/symbols/windows/gui/gui-win7sp0-x64.json +++ b/volatility3/framework/symbols/windows/gui/gui-win7sp0-x64.json @@ -18619,6 +18619,12 @@ "signed": false, "size": 1 }, + "char": { + "kind": "char", + "endian": "little", + "signed": false, + "size": 1 + }, "float": { "kind": "float", "endian": "little", diff --git a/volatility3/framework/symbols/windows/gui/gui-win7sp1-x64.json b/volatility3/framework/symbols/windows/gui/gui-win7sp1-x64.json index ae844e535..b856506a7 100644 --- a/volatility3/framework/symbols/windows/gui/gui-win7sp1-x64.json +++ b/volatility3/framework/symbols/windows/gui/gui-win7sp1-x64.json @@ -17985,6 +17985,12 @@ "signed": false, "size": 1 }, + "char": { + "kind": "char", + "endian": "little", + "signed": false, + "size": 1 + }, "float": { "kind": "float", "endian": "little", diff --git a/volatility3/framework/symbols/windows/gui/gui-win8-x64.json b/volatility3/framework/symbols/windows/gui/gui-win8-x64.json index e7581413f..8662bf62b 100644 --- a/volatility3/framework/symbols/windows/gui/gui-win8-x64.json +++ b/volatility3/framework/symbols/windows/gui/gui-win8-x64.json @@ -17992,6 +17992,12 @@ "signed": false, "size": 1 }, + "char": { + "kind": "char", + "endian": "little", + "signed": false, + "size": 1 + }, "float": { "kind": "float", "endian": "little", diff --git a/volatility3/plugins/windows/registry/certificates.py b/volatility3/plugins/windows/registry/certificates.py index fd33d75a7..caf244f95 100644 --- a/volatility3/plugins/windows/registry/certificates.py +++ b/volatility3/plugins/windows/registry/certificates.py @@ -24,11 +24,11 @@ class Certificates(interfaces.plugins.PluginInterface): description="Windows kernel", architectures=["Intel32", "Intel64"], ), - requirements.PluginRequirement( - name="hivelist", plugin=hivelist.HiveList, version=(2, 0, 0) + requirements.VersionRequirement( + name="hivelist", component=hivelist.HiveList, version=(2, 0, 0) ), - requirements.PluginRequirement( - name="printkey", plugin=printkey.PrintKey, version=(1, 0, 0) + requirements.VersionRequirement( + name="printkey", component=printkey.PrintKey, version=(1, 0, 0) ), requirements.BooleanRequirement( name="dump",