mirror of
https://github.com/volatilityfoundation/volatility3.git
synced 2026-09-06 17:57:38 +02:00
Merge branch 'develop' into volshell_display_types_pointer_upgrade_2025
This commit is contained in:
@@ -0,0 +1,24 @@
|
||||
name: Volatility3 Code Analysis
|
||||
on: [push, pull_request]
|
||||
jobs:
|
||||
|
||||
build:
|
||||
runs-on: ubuntu-22.04
|
||||
strategy:
|
||||
matrix:
|
||||
python-version: ["3.8"]
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- name: Set up Python ${{ matrix.python-version }}
|
||||
uses: actions/setup-python@v5
|
||||
with:
|
||||
python-version: ${{ matrix.python-version }}
|
||||
|
||||
- name: Install dependencies
|
||||
run: |
|
||||
python -m pip install --upgrade pip
|
||||
pip install .[test]
|
||||
|
||||
- name: Testing...
|
||||
run: |
|
||||
python ./test/volatility3_code_analysis.py
|
||||
+94
-2
@@ -4,6 +4,100 @@ API Changes
|
||||
When an addition to the existing API is made, the minor version is bumped.
|
||||
When an API feature or function is removed or changed, the major version is bumped.
|
||||
|
||||
2.25.0
|
||||
======
|
||||
Pointer class now supports `get_raw_value()`.
|
||||
`KTIMER` no longer supports `get_raw_dpc()`.
|
||||
|
||||
2.24.0
|
||||
======
|
||||
Support `encoding` parameter for `objects.utility.array_to_string`
|
||||
|
||||
2.23.0
|
||||
======
|
||||
Add support for windows GUI classes and OS distinguishers.
|
||||
Add a symbol_table_name for `ExecutiveObject.get_object_header()`/
|
||||
|
||||
2.22.0
|
||||
======
|
||||
Linux net constants added.
|
||||
Network objects moved to separate versionable module.
|
||||
|
||||
2.21.0
|
||||
======
|
||||
`uuid` method added to `linux.extensions`.
|
||||
|
||||
2.20.0
|
||||
======
|
||||
NM_TYPES_DESC constants added to linux.
|
||||
`latch_tree_root` and `kernel_symbol` added to linux extensions.
|
||||
Linux `module` class additions:
|
||||
* `get_module_address_boundaries`
|
||||
* `section_typetab`
|
||||
Linux `task_struct` class additions:
|
||||
* `get_address_space_layer`
|
||||
* `state`
|
||||
Linux `bpf_prog` class additions:
|
||||
* `bpf_jit_binary_hdr_address`
|
||||
|
||||
2.19.0
|
||||
======
|
||||
Introduction of `Modules` versionable linux extension module.
|
||||
Deprecation of some `LinuxUtilities` functions relating to modules.
|
||||
|
||||
2.18.0
|
||||
======
|
||||
Addition of `scatterlist` linux extension.
|
||||
|
||||
2.17.0
|
||||
======
|
||||
The addition of a `types` member to `SymbolInterface`
|
||||
|
||||
2.16.0
|
||||
======
|
||||
Addition of TAINT_FLAG constants, `TaintFlag` dataclass
|
||||
Addition of linux `tainting` versionable module
|
||||
|
||||
2.15.0
|
||||
======
|
||||
Addition of `convert_fourcc_code` to `LinuxUtilities` class
|
||||
|
||||
2.14.0
|
||||
======
|
||||
No significant changes (part of the 2.16.0 PR which took time in development)
|
||||
|
||||
2.13.0
|
||||
======
|
||||
Linux `task` object extension addition of `getppid`
|
||||
|
||||
2.12.0
|
||||
======
|
||||
Changes to the Intel layer to support `PROT_NONE` pages.
|
||||
|
||||
2.11.0
|
||||
======
|
||||
Addition of `get_type` method to windows `CM_KEY_NODE` registry structure
|
||||
|
||||
2.10.0
|
||||
======
|
||||
No significant API changes (CLI changes to the JSONL text renderer)
|
||||
|
||||
2.9.0
|
||||
=====
|
||||
No significant API changes (change to call `linux.LinuxUtilities.get_module_from_volobj_type` to get the kernel)
|
||||
|
||||
2.8.0
|
||||
=====
|
||||
Addition of the `BinOrAbsent`, `HexOrAbsent`, `HexBytesOrAbsent` and `MultiTypeDataOrAbsent` data type renderers
|
||||
|
||||
2.7.0
|
||||
=====
|
||||
Addition of `is_valid`, `get_create_time` and `get_exit_time` to ETHREAD structure
|
||||
|
||||
2.6.0
|
||||
=====
|
||||
No significant changes (again, the version got bump twice in the PR straight to 2.7.0)
|
||||
|
||||
2.5.0
|
||||
=====
|
||||
Add in support for specifying a type override for object_from_symbol
|
||||
@@ -50,5 +144,3 @@ an absolute offset. This can be done with `Module.get_absolute_symbol_address`
|
||||
* Added context.modules
|
||||
* Added ModuleRequirement
|
||||
* Added get\_symbols\_by\_absolute\_location
|
||||
|
||||
|
||||
|
||||
@@ -9,7 +9,7 @@
|
||||
"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": "",
|
||||
"Raw Data": "N/A",
|
||||
"Time Focused": null,
|
||||
"Type": "Key",
|
||||
"__children": [
|
||||
@@ -23,7 +23,7 @@
|
||||
"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 ........ \"",
|
||||
"Raw Data": "00 00 00 00 07 00 00 00 00 00 00 00 07 00 00 00 00 00 80 bf 00 00 80 bf 00 00 80 bf 00 00 80 bf 00 00 80 bf 00 00 80 bf 00 00 80 bf 00 00 80 bf 00 00 80 bf 00 00 80 bf ff ff ff ff 90 86 6b 31 fd 8d db 01 00 00 00 00",
|
||||
"Time Focused": "0:00:00.507000",
|
||||
"Type": "Value",
|
||||
"__children": []
|
||||
@@ -38,7 +38,7 @@
|
||||
"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 ........ \"",
|
||||
"Raw Data": "00 00 00 00 01 00 00 00 00 00 00 00 01 00 00 00 00 00 80 bf 00 00 80 bf 00 00 80 bf 00 00 80 bf 00 00 80 bf 00 00 80 bf 00 00 80 bf 00 00 80 bf 00 00 80 bf 00 00 80 bf ff ff ff ff f0 82 cf ca 95 8e db 01 00 00 00 00",
|
||||
"Time Focused": "0:00:00.501000",
|
||||
"Type": "Value",
|
||||
"__children": []
|
||||
@@ -53,7 +53,7 @@
|
||||
"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 ........ \"",
|
||||
"Raw Data": "00 00 00 00 04 00 00 00 00 00 00 00 04 00 00 00 00 00 80 bf 00 00 80 bf 00 00 80 bf 00 00 80 bf 00 00 80 bf 00 00 80 bf 00 00 80 bf 00 00 80 bf 00 00 80 bf 00 00 80 bf ff ff ff ff 10 67 cf 4d be 8e db 01 00 00 00 00",
|
||||
"Time Focused": "0:00:00.504000",
|
||||
"Type": "Value",
|
||||
"__children": []
|
||||
@@ -68,7 +68,7 @@
|
||||
"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 ........ \"",
|
||||
"Raw Data": "00 00 00 00 01 00 00 00 00 00 00 00 01 00 00 00 00 00 80 bf 00 00 80 bf 00 00 80 bf 00 00 80 bf 00 00 80 bf 00 00 80 bf 00 00 80 bf 00 00 80 bf 00 00 80 bf 00 00 80 bf ff ff ff ff d0 99 66 6c c0 8e db 01 00 00 00 00",
|
||||
"Time Focused": "0:00:00.501000",
|
||||
"Type": "Value",
|
||||
"__children": []
|
||||
@@ -83,7 +83,7 @@
|
||||
"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 ........ \"",
|
||||
"Raw Data": "00 00 00 00 01 00 00 00 00 00 00 00 01 00 00 00 00 00 80 bf 00 00 80 bf 00 00 80 bf 00 00 80 bf 00 00 80 bf 00 00 80 bf 00 00 80 bf 00 00 80 bf 00 00 80 bf 00 00 80 bf ff ff ff ff 00 62 ba 89 c0 8e db 01 00 00 00 00",
|
||||
"Time Focused": "0:00:00.501000",
|
||||
"Type": "Value",
|
||||
"__children": []
|
||||
@@ -98,7 +98,7 @@
|
||||
"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 ........ \"",
|
||||
"Raw Data": "00 00 00 00 02 00 00 00 00 00 00 00 02 00 00 00 00 00 80 bf 00 00 80 bf 00 00 80 bf 00 00 80 bf 00 00 80 bf 00 00 80 bf 00 00 80 bf 00 00 80 bf 00 00 80 bf 00 00 80 bf ff ff ff ff b0 24 49 23 c1 8e db 01 00 00 00 00",
|
||||
"Time Focused": "0:00:00.502000",
|
||||
"Type": "Value",
|
||||
"__children": []
|
||||
@@ -113,11 +113,11 @@
|
||||
"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 ........ \"",
|
||||
"Raw Data": "00 00 00 00 01 00 00 00 00 00 00 00 01 00 00 00 00 00 80 bf 00 00 80 bf 00 00 80 bf 00 00 80 bf 00 00 80 bf 00 00 80 bf 00 00 80 bf 00 00 80 bf 00 00 80 bf 00 00 80 bf ff ff ff ff 60 3d 89 2e c1 8e db 01 00 00 00 00",
|
||||
"Time Focused": "0:00:00.501000",
|
||||
"Type": "Value",
|
||||
"__children": []
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,10 +1,10 @@
|
||||
import json
|
||||
import hashlib
|
||||
import shutil
|
||||
import contextlib
|
||||
import tempfile
|
||||
import hashlib
|
||||
import json
|
||||
import os
|
||||
from test import test_volatility, WindowsSamples
|
||||
import shutil
|
||||
import tempfile
|
||||
from test import WindowsSamples, test_volatility
|
||||
|
||||
|
||||
class TestWindowsVolshell:
|
||||
@@ -843,20 +843,22 @@ class TestWindowsMFTscan:
|
||||
{
|
||||
"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.. "',
|
||||
"Hexdump": "5b 5a 6f 6e 65 54 72 61 6e 73 66 65 72 5d 0d 0a 5a 6f 6e 65 49 64 3d 33 0d 0a",
|
||||
"MFT Type": "DATA",
|
||||
"Offset": 55926304,
|
||||
"Record Number": 323,
|
||||
"Record Type": "FILE",
|
||||
"__children": [],
|
||||
},
|
||||
{
|
||||
"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.. "',
|
||||
"Hexdump": "5b 5a 6f 6e 65 54 72 61 6e 73 66 65 72 5d 0d 0a 5a 6f 6e 65 49 64 3d 33 0d 0a",
|
||||
"MFT Type": "DATA",
|
||||
"Offset": 56102400,
|
||||
"Record Number": 347,
|
||||
"Record Type": "FILE",
|
||||
"__children": [],
|
||||
},
|
||||
]
|
||||
for expected_row in expected_rows:
|
||||
@@ -877,20 +879,22 @@ class TestWindowsMFTscan:
|
||||
{
|
||||
"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.........."',
|
||||
"Hexdump": "00 00 00 02 00 00 00 00 00 00 80 00 00 00 00 00 b9 dd f0 cc df 73 db 01 00 00 00 00 00 00 00 00",
|
||||
"MFT Type": "DATA",
|
||||
"Offset": 1058018088,
|
||||
"Offset": 26235616,
|
||||
"Record Number": 107240,
|
||||
"Record Type": "FILE",
|
||||
"__children": [],
|
||||
},
|
||||
{
|
||||
"ADS Filename": "$Config",
|
||||
"Filename": "$Repair",
|
||||
"Hexdump": '"\n01 00 00 00 03 00 00 00 ........ "',
|
||||
"ADS Filename": "$SRAT",
|
||||
"Filename": "$Bitmap",
|
||||
"Hexdump": "a4 5f fd 60 38 00 01 03 10 00 0c 00 04 00 00 00 01 00 00 00 01 00 00 00 8d 4e 16 00 02 00 00 00 a0 00 00 00 00 00 06 00 03 00 00 00 01 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 4a 7b 01 00 00 00 00 00",
|
||||
"MFT Type": "DATA",
|
||||
"Offset": 5009678688,
|
||||
"Record Number": 28,
|
||||
"Offset": 1052277088,
|
||||
"Record Number": 6,
|
||||
"Record Type": "FILE",
|
||||
"__children": [],
|
||||
},
|
||||
]
|
||||
for expected_row in expected_rows:
|
||||
@@ -924,7 +928,7 @@ class TestWindowsMFTscan:
|
||||
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 ........ "',
|
||||
"Hexdump": "30 5c 72 a7 1b 6d fb fc 09 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00",
|
||||
"MFT Type": "DATA",
|
||||
"Offset": 4961536280,
|
||||
"Record Number": 116474,
|
||||
@@ -932,7 +936,7 @@ class TestWindowsMFTscan:
|
||||
},
|
||||
{
|
||||
"Filename": "0.2.filtertrie.intermediate.txt",
|
||||
"Hexdump": '"\n30 09 32 0d 0a 0.2.. "',
|
||||
"Hexdump": "30 09 32 0d 0a",
|
||||
"MFT Type": "DATA",
|
||||
"Offset": 619242944,
|
||||
"Record Number": 113013,
|
||||
@@ -1411,4 +1415,3 @@ class TestWindowsVirtMap:
|
||||
)
|
||||
for expected_row in expected_rows:
|
||||
assert test_volatility.match_output_row(expected_row, json_out)
|
||||
|
||||
|
||||
@@ -0,0 +1,440 @@
|
||||
"""
|
||||
This script performs syntax analysis on the volatility3 source tree through a combination of AST analysis and import-time introspection of classes.
|
||||
|
||||
The current checks it implements are:
|
||||
1. Ensure that classes derived from `ConfigurableInterface` properly
|
||||
declare all `VersionableInterface` classes that they make use of in their
|
||||
`get_requirements()` classmethod.
|
||||
|
||||
:WARNING: a notable exception to this are classes defined within factory
|
||||
functions. Because these classes are not created until the factory function
|
||||
is called, they therefore do no exist at import time and cannot be checked
|
||||
by this script. It is important to keep in mind during code review that
|
||||
this is a best-effort check and does not make guarantees about the
|
||||
completeness of declared requirements.
|
||||
"""
|
||||
|
||||
import abc
|
||||
import argparse
|
||||
import ast
|
||||
import importlib
|
||||
import inspect
|
||||
import logging
|
||||
import pkgutil
|
||||
import sys
|
||||
import traceback
|
||||
import types
|
||||
from typing import Any, Iterator, List, Optional, Tuple, Type, Union
|
||||
|
||||
from volatility3.framework import configuration, interfaces
|
||||
from volatility3.framework.deprecation import PluginRenameClass
|
||||
|
||||
logging.basicConfig(format="%(levelname)s: %(message)s")
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
|
||||
class NodeVisitor:
|
||||
def visit(self, node):
|
||||
"""Visit a node."""
|
||||
method = "visit_" + node.__class__.__name__
|
||||
visitor = getattr(self, method, self.generic_visit)
|
||||
self.enter(node)
|
||||
result = visitor(node)
|
||||
self.leave(node)
|
||||
return result
|
||||
|
||||
def enter(self, node):
|
||||
"""Called when entering a node."""
|
||||
method = "enter_" + node.__class__.__name__
|
||||
visitor = getattr(self, method, self.generic_enter)
|
||||
return visitor(node)
|
||||
|
||||
def leave(self, node):
|
||||
"""Called when leaving a node."""
|
||||
method = "leave_" + node.__class__.__name__
|
||||
visitor = getattr(self, method, self.generic_leave)
|
||||
return visitor(node)
|
||||
|
||||
def generic_visit(self, node):
|
||||
"""Called if no explicit visitor function exists for a node."""
|
||||
for _, value in ast.iter_fields(node):
|
||||
if isinstance(value, list):
|
||||
for item in value:
|
||||
if isinstance(item, ast.AST):
|
||||
self.visit(item)
|
||||
elif isinstance(value, ast.AST):
|
||||
self.visit(value)
|
||||
|
||||
def generic_enter(self, node):
|
||||
"""Default enter behavior."""
|
||||
|
||||
def generic_leave(self, node):
|
||||
"""Default leave behavior."""
|
||||
|
||||
|
||||
class CodeViolation(metaclass=abc.ABCMeta):
|
||||
def __init__(self, module: types.ModuleType, node: ast.AST) -> None:
|
||||
self.module = module
|
||||
self.node = node
|
||||
|
||||
def __str__(self):
|
||||
return f"Issue in module {self.module.__name__}: line {self.node.lineno}, col {self.node.col_offset}"
|
||||
|
||||
|
||||
class UnrequiredVersionableUsage(CodeViolation):
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
module: types.ModuleType,
|
||||
node: ast.AST,
|
||||
consuming_class: str,
|
||||
versionable_item_class: str,
|
||||
) -> None:
|
||||
super().__init__(module, node)
|
||||
self.consuming_class = consuming_class
|
||||
self.versionable_item_class = versionable_item_class
|
||||
|
||||
def __str__(self) -> str:
|
||||
return (
|
||||
super().__str__()
|
||||
+ ": "
|
||||
+ (
|
||||
f"Found usage of {self.versionable_item_class} "
|
||||
f"in class {self.consuming_class} that is not declared "
|
||||
f"in {self.consuming_class}'s `get_requirements()` classmethod"
|
||||
)
|
||||
)
|
||||
|
||||
|
||||
class DirectVolatilityImportUsage(CodeViolation):
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
module: types.ModuleType,
|
||||
node: ast.AST,
|
||||
importing_module: str,
|
||||
imported_item: object,
|
||||
imported_name: str,
|
||||
) -> None:
|
||||
self.imported_item = imported_item
|
||||
self.imported_name = imported_name
|
||||
self.importing_module = importing_module
|
||||
super().__init__(module, node)
|
||||
|
||||
def __str__(self) -> str:
|
||||
components = self.importing_module.split(".")
|
||||
return (
|
||||
super().__str__()
|
||||
+ ": "
|
||||
+ (
|
||||
f"Direct import of {self.imported_name} "
|
||||
f"({type(self.imported_item)}) "
|
||||
f"from module {self.importing_module} - "
|
||||
"change to "
|
||||
f"'from {'.'.join(components[:-1])} import {components[-1]} and using {components[-1]}.{self.imported_name}"
|
||||
)
|
||||
)
|
||||
|
||||
|
||||
def is_versionable(var):
|
||||
try:
|
||||
return (
|
||||
issubclass(var, interfaces.configuration.VersionableInterface)
|
||||
and var is not interfaces.configuration.VersionableInterface
|
||||
and not inspect.isabstract(var)
|
||||
and not (hasattr(var, "hidden") and getattr(var, "hidden") is True)
|
||||
)
|
||||
except TypeError:
|
||||
return False
|
||||
|
||||
|
||||
def is_configurable(var):
|
||||
try:
|
||||
return issubclass(var, interfaces.configuration.ConfigurableInterface)
|
||||
except TypeError:
|
||||
return False
|
||||
|
||||
|
||||
class ModuleVisitor(NodeVisitor):
|
||||
def __init__(self, module: types.ModuleType) -> None:
|
||||
self._module = module
|
||||
self._scopes = []
|
||||
self._violations = []
|
||||
|
||||
@property
|
||||
def violations(self):
|
||||
return self._violations
|
||||
|
||||
def _check_vol3_import_from(self, node: ast.ImportFrom):
|
||||
"""
|
||||
Ensure that the only thing imported from a volatility3 module (apart
|
||||
from the root volatility3 module) are functions and modules. This
|
||||
prevents re-exporting of classes and variables from modules that use
|
||||
them.
|
||||
"""
|
||||
if (
|
||||
node.module
|
||||
and node.module.startswith("volatility3.") # Give a pass to volatility3 module
|
||||
and node.module != "volatility3.framework.constants._version" # make an exception for this
|
||||
):
|
||||
for name in node.names:
|
||||
try:
|
||||
item = vars(self._module)[
|
||||
name.asname if name.asname is not None else name.name
|
||||
]
|
||||
except KeyError:
|
||||
logger.debug(
|
||||
"Couldn't find imported name %s in module %s",
|
||||
name.asname or name.name,
|
||||
self._module.__name__,
|
||||
)
|
||||
continue
|
||||
|
||||
if not (isinstance(item, types.ModuleType) or inspect.isfunction(item)):
|
||||
self._violations.append(
|
||||
DirectVolatilityImportUsage(
|
||||
self._module,
|
||||
node,
|
||||
node.module,
|
||||
item,
|
||||
name.asname or name.name,
|
||||
)
|
||||
)
|
||||
|
||||
def enter_ImportFrom(self, node: ast.ImportFrom):
|
||||
self._check_vol3_import_from(node)
|
||||
|
||||
|
||||
def enter_ClassDef(self, node: ast.ClassDef) -> Any:
|
||||
logger.debug("Entering class %s", node.name)
|
||||
clazz = None
|
||||
try:
|
||||
clazz = vars(self._module)[str(node.name)]
|
||||
except KeyError:
|
||||
logger.debug(
|
||||
"Failed to get %s from module scope: (%s)",
|
||||
node.name,
|
||||
self._module.__name__,
|
||||
)
|
||||
if self._scopes:
|
||||
try:
|
||||
logger.debug(
|
||||
"Attempting to get class %s from scope of %s",
|
||||
node.name,
|
||||
self._scopes[-1].__name__,
|
||||
)
|
||||
clazz = getattr(self._scopes[-1], node.name)
|
||||
except AttributeError:
|
||||
logger.debug(
|
||||
"Class not found in scope of %s", self._scopes[-1].__name__
|
||||
)
|
||||
if clazz:
|
||||
self._scopes.append(clazz)
|
||||
|
||||
if clazz and is_configurable(clazz):
|
||||
logger.info("Checking configurable class %s", clazz.__name__)
|
||||
visitor = ConfigurableClassVisitor(self._module, clazz)
|
||||
visitor.visit(node)
|
||||
self._violations += visitor.violations
|
||||
|
||||
self.generic_visit(node)
|
||||
|
||||
def leave_ClassDef(self, node: ast.ClassDef):
|
||||
logger.debug("Leaving class %s", node.name)
|
||||
try:
|
||||
scoped_class = next(
|
||||
scope for scope in self._scopes if scope.__name__ == node.name
|
||||
)
|
||||
self._scopes.remove(scoped_class)
|
||||
except StopIteration:
|
||||
logger.debug("%s not found in scope list", node.name)
|
||||
|
||||
|
||||
class ConfigurableClassVisitor(NodeVisitor):
|
||||
def __init__(
|
||||
self,
|
||||
module: types.ModuleType,
|
||||
clazz: Optional[Type[interfaces.configuration.ConfigurableInterface]],
|
||||
) -> None:
|
||||
self._module = module
|
||||
self._current_object = None
|
||||
self._clazz = clazz
|
||||
self._seen = set()
|
||||
self._violations: List[CodeViolation] = []
|
||||
|
||||
@property
|
||||
def versioned_classes(self):
|
||||
return (
|
||||
[
|
||||
req._component
|
||||
for req in self._clazz.get_requirements()
|
||||
if isinstance(req, configuration.requirements.VersionRequirement)
|
||||
]
|
||||
if self._clazz is not None
|
||||
else []
|
||||
)
|
||||
|
||||
def check_item(self, item: Type, node: Union[ast.Name, ast.Attribute]):
|
||||
if (
|
||||
is_versionable(item)
|
||||
and self._clazz is not None
|
||||
and item not in self.versioned_classes
|
||||
and item is not self._clazz
|
||||
and not issubclass(self._clazz, PluginRenameClass)
|
||||
):
|
||||
logger.info(
|
||||
"Found versionable item %s, checking against %s",
|
||||
str(item),
|
||||
str(self.versioned_classes),
|
||||
)
|
||||
result = UnrequiredVersionableUsage(
|
||||
self._module, node, self._clazz.__name__, item.__name__
|
||||
)
|
||||
self._violations.append(result)
|
||||
|
||||
@property
|
||||
def violations(self):
|
||||
return self._violations
|
||||
|
||||
def visit_Name(self, node: ast.Name):
|
||||
try:
|
||||
logger.debug(
|
||||
"Checking module %s for name %s", self._module.__name__, node.id
|
||||
)
|
||||
item = vars(self._module)[str(node.id)]
|
||||
logger.debug("Found %s in %s namespace", node.id, self._module.__name__)
|
||||
except KeyError:
|
||||
return
|
||||
|
||||
self.check_item(item, node)
|
||||
|
||||
def visit_Attribute(
|
||||
self, node: ast.Attribute
|
||||
) -> Optional[UnrequiredVersionableUsage]:
|
||||
if self._clazz is None:
|
||||
self.generic_visit(node)
|
||||
return
|
||||
|
||||
if (node.lineno, node.col_offset) in self._seen:
|
||||
return
|
||||
|
||||
self._seen.add((node.lineno, node.col_offset))
|
||||
|
||||
stack = []
|
||||
root = node
|
||||
while True:
|
||||
stack.append(node.attr)
|
||||
if isinstance(node.value, ast.Attribute):
|
||||
node = node.value
|
||||
elif isinstance(node.value, ast.Name):
|
||||
stack.append(node.value.id)
|
||||
break
|
||||
else:
|
||||
break
|
||||
|
||||
current = None
|
||||
logger.debug("Checking %s", ".".join(stack[::-1]))
|
||||
for item in stack[::-1]:
|
||||
try:
|
||||
current = (
|
||||
vars(self._module)[item]
|
||||
if current is None
|
||||
else getattr(current, item)
|
||||
)
|
||||
except (KeyError, AttributeError) as exc:
|
||||
logger.debug(
|
||||
"Failed to get attribute %s (%s)%s",
|
||||
item,
|
||||
exc.__class__.__name__,
|
||||
(" on" + str(current)) if current is not None else "",
|
||||
)
|
||||
break
|
||||
|
||||
self.check_item(current, root)
|
||||
|
||||
|
||||
def report_missing_requirements() -> Iterator[Tuple[str, UnrequiredVersionableUsage]]:
|
||||
vol3 = importlib.import_module("volatility3")
|
||||
|
||||
for _, module_name, _ in pkgutil.walk_packages(
|
||||
vol3.__path__, vol3.__name__ + ".", onerror=lambda _: None
|
||||
):
|
||||
modname = module_name.replace(
|
||||
"volatility3.framework.plugins", "volatility3.plugins"
|
||||
)
|
||||
try:
|
||||
# import the module that we want to check
|
||||
plugin_module = importlib.import_module(modname)
|
||||
|
||||
except ImportError as exc:
|
||||
logger.warning("Failed to import %s: %s", modname, str(exc))
|
||||
continue
|
||||
except Exception as exc:
|
||||
logger.warning(
|
||||
"An unexpected exception occurred while importing %s: %s",
|
||||
modname,
|
||||
str(exc),
|
||||
)
|
||||
traceback.print_exc()
|
||||
continue
|
||||
|
||||
logger.info("Checking module %s", plugin_module.__name__)
|
||||
if plugin_module.__file__ is None:
|
||||
logger.warning("Plugin module %s has no source file", modname)
|
||||
continue
|
||||
|
||||
try:
|
||||
with open(plugin_module.__file__, "rb") as f:
|
||||
source = f.read()
|
||||
except OSError:
|
||||
logger.warning(
|
||||
"Failed to read file contents for %s", plugin_module.__file__
|
||||
)
|
||||
continue
|
||||
|
||||
try:
|
||||
module_ast_root = ast.parse(source)
|
||||
except (SyntaxError, ValueError) as exc:
|
||||
logger.warning(
|
||||
"Failed to parse source for %s: %s", plugin_module.__file__, str(exc)
|
||||
)
|
||||
raise
|
||||
|
||||
mod_visitor = ModuleVisitor(plugin_module)
|
||||
mod_visitor.visit(module_ast_root)
|
||||
|
||||
if mod_visitor.violations:
|
||||
yield from (
|
||||
(plugin_module.__name__, res) for res in iter(mod_visitor.violations)
|
||||
)
|
||||
|
||||
|
||||
def perform_review():
|
||||
found = 0
|
||||
for mod, usage in report_missing_requirements():
|
||||
found += 1
|
||||
print(str(usage))
|
||||
|
||||
if found:
|
||||
print(f"Found {found} issues")
|
||||
sys.exit(1)
|
||||
|
||||
print("All configurable classes passed validation!")
|
||||
|
||||
|
||||
def parse_args() -> argparse.Namespace:
|
||||
parser = argparse.ArgumentParser()
|
||||
parser.add_argument("-v", "--verbose", action="count", dest="verbosity", default=0)
|
||||
return parser.parse_args()
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
args = parse_args()
|
||||
if args.verbosity == 0:
|
||||
logger.setLevel(logging.WARNING)
|
||||
elif args.verbosity == 1:
|
||||
logger.setLevel(logging.INFO)
|
||||
elif args.verbosity > 1:
|
||||
logger.setLevel(logging.DEBUG)
|
||||
|
||||
perform_review()
|
||||
@@ -9,7 +9,7 @@ import random
|
||||
import string
|
||||
import sys
|
||||
from functools import wraps
|
||||
from typing import Any, Callable, Dict, List, Tuple
|
||||
from typing import Any, Callable, Dict, List, Optional, Set, Tuple, TypeVar, Union
|
||||
from volatility3.cli import text_filter
|
||||
|
||||
from volatility3.framework import exceptions, interfaces, renderers
|
||||
@@ -80,7 +80,12 @@ def multitypedata_as_text(value: format_hints.MultiTypeData) -> str:
|
||||
return hex_bytes_as_text(value)
|
||||
|
||||
|
||||
def optional(func: Callable) -> Callable:
|
||||
T = TypeVar("T")
|
||||
|
||||
|
||||
def optional(
|
||||
func: Callable[[Union[interfaces.renderers.BaseAbsentValue, T]], str],
|
||||
) -> Callable[[T], str]:
|
||||
@wraps(func)
|
||||
def wrapped(x: Any) -> str:
|
||||
if isinstance(x, interfaces.renderers.BaseAbsentValue):
|
||||
@@ -110,7 +115,7 @@ def quoted_optional(func: Callable) -> Callable:
|
||||
return wrapped
|
||||
|
||||
|
||||
def display_disassembly(disasm: interfaces.renderers.Disassembly) -> str:
|
||||
def display_disassembly(disasm: renderers.Disassembly) -> str:
|
||||
"""Renders a disassembly renderer type into string format.
|
||||
|
||||
Args:
|
||||
@@ -137,13 +142,116 @@ def display_disassembly(disasm: interfaces.renderers.Disassembly) -> str:
|
||||
return QuickTextRenderer._type_renderers[bytes](disasm.data)
|
||||
|
||||
|
||||
class CLITypeRenderer(interfaces.renderers.TypeRendererInterface):
|
||||
def __init__(self, func):
|
||||
super().__init__(func=optional(func))
|
||||
|
||||
|
||||
class LayerDataRenderer(CLITypeRenderer):
|
||||
"""Renders a LayerData object into data/bytes"""
|
||||
|
||||
def __init__(self):
|
||||
self.context_byte_len = 0
|
||||
self.width = 16
|
||||
self.display_offset = False
|
||||
self.display_hex = True
|
||||
self.display_ascii = True
|
||||
|
||||
def render(
|
||||
data: Union[renderers.LayerData, interfaces.renderers.BaseAbsentValue],
|
||||
) -> str:
|
||||
if isinstance(data, interfaces.renderers.BaseAbsentValue):
|
||||
# FIXME: Do something cleverer here
|
||||
return ""
|
||||
|
||||
specific_data, error_bytes = self.render_bytes(data)
|
||||
|
||||
printables = ""
|
||||
output = "\n"
|
||||
for count, byte in enumerate(specific_data):
|
||||
if count not in error_bytes:
|
||||
output += f"{byte:02x} "
|
||||
char = chr(byte)
|
||||
printables += char if 0x20 <= byte <= 0x7E else "."
|
||||
else:
|
||||
output += "__ "
|
||||
printables += "."
|
||||
if count % self.width == self.width - 1:
|
||||
output += printables
|
||||
if count < len(specific_data) - 1:
|
||||
output += "\n"
|
||||
printables = ""
|
||||
|
||||
# Handle leftovers when the length is not mutiple of width
|
||||
if printables:
|
||||
padding = self.width - len(printables)
|
||||
output += " " * padding
|
||||
output += printables
|
||||
output += " " * padding
|
||||
|
||||
return output
|
||||
|
||||
render_func = render
|
||||
return super().__init__(render_func)
|
||||
|
||||
def render_bytes(self, data: renderers.LayerData) -> Tuple[bytes, Set[int]]:
|
||||
"""Renders a valid LayerData into bytes (with context bytes)"""
|
||||
context_byte_len = self.context_byte_len if not data.no_surrounding else 0
|
||||
|
||||
layer = data.context.layers[data.layer_name]
|
||||
# Map of the holes
|
||||
error_bytes = set()
|
||||
start_offset = data.offset - context_byte_len
|
||||
end_offset = data.offset + data.length + context_byte_len
|
||||
if isinstance(layer, interfaces.layers.TranslationLayerInterface):
|
||||
error_bytes = set()
|
||||
mapping = iter(layer.mapping(start_offset, end_offset, True))
|
||||
current_map = next(mapping)
|
||||
for i in range(start_offset, end_offset):
|
||||
# Run through the bytes, check if they're present
|
||||
offset, sublength, _, _, _ = current_map
|
||||
if i < offset:
|
||||
error_bytes.add(i - start_offset)
|
||||
if i > offset + sublength:
|
||||
try:
|
||||
current_map = next(mapping)
|
||||
except StopIteration:
|
||||
pass
|
||||
offset, sublength, _, _, _ = current_map
|
||||
if i > offset + sublength:
|
||||
error_bytes.add(i - start_offset)
|
||||
|
||||
# Padded data
|
||||
specific_data = data.context.layers[data.layer_name].read(
|
||||
start_offset,
|
||||
end_offset - start_offset,
|
||||
True,
|
||||
)
|
||||
|
||||
return specific_data, error_bytes
|
||||
|
||||
|
||||
class CLIRenderer(interfaces.renderers.Renderer):
|
||||
"""Class to add specific requirements for CLI renderers."""
|
||||
|
||||
_type_renderers = {
|
||||
format_hints.Bin: CLITypeRenderer(lambda x: f"0b{x:b}"),
|
||||
format_hints.Hex: CLITypeRenderer(lambda x: f"0x{x:x}"),
|
||||
format_hints.HexBytes: CLITypeRenderer(hex_bytes_as_text),
|
||||
format_hints.MultiTypeData: CLITypeRenderer(multitypedata_as_text),
|
||||
renderers.Disassembly: CLITypeRenderer(display_disassembly),
|
||||
bytes: CLITypeRenderer(lambda x: " ".join(f"{b:02x}" for b in x)),
|
||||
renderers.LayerData: LayerDataRenderer(),
|
||||
datetime.datetime: CLITypeRenderer(
|
||||
lambda x: x.strftime("%Y-%m-%d %H:%M:%S.%f %Z")
|
||||
),
|
||||
"default": CLITypeRenderer(lambda x: f"{x}"),
|
||||
}
|
||||
|
||||
name = "unnamed"
|
||||
structured_output = False
|
||||
filter: text_filter.CLIFilter = None
|
||||
column_hide_list: list = None
|
||||
filter: Optional[text_filter.CLIFilter] = None
|
||||
column_hide_list: Optional[list] = None
|
||||
|
||||
def ignored_columns(
|
||||
self,
|
||||
@@ -170,21 +278,11 @@ class CLIRenderer(interfaces.renderers.Renderer):
|
||||
|
||||
|
||||
class QuickTextRenderer(CLIRenderer):
|
||||
_type_renderers = {
|
||||
format_hints.Bin: optional(lambda x: f"0b{x:b}"),
|
||||
format_hints.Hex: optional(lambda x: f"0x{x:x}"),
|
||||
format_hints.HexBytes: optional(hex_bytes_as_text),
|
||||
format_hints.MultiTypeData: quoted_optional(multitypedata_as_text),
|
||||
interfaces.renderers.Disassembly: optional(display_disassembly),
|
||||
bytes: optional(lambda x: " ".join(f"{b:02x}" for b in x)),
|
||||
datetime.datetime: optional(lambda x: x.strftime("%Y-%m-%d %H:%M:%S.%f %Z")),
|
||||
"default": optional(lambda x: f"{x}"),
|
||||
}
|
||||
|
||||
name = "quick"
|
||||
|
||||
def get_render_options(self):
|
||||
pass
|
||||
return []
|
||||
|
||||
def render(self, grid: interfaces.renderers.TreeGrid) -> None:
|
||||
"""Renders each column immediately to stdout.
|
||||
@@ -242,7 +340,7 @@ class NoneRenderer(CLIRenderer):
|
||||
name = "none"
|
||||
|
||||
def get_render_options(self):
|
||||
pass
|
||||
return []
|
||||
|
||||
def render(self, grid: interfaces.renderers.TreeGrid) -> None:
|
||||
if not grid.populated:
|
||||
@@ -250,22 +348,12 @@ class NoneRenderer(CLIRenderer):
|
||||
|
||||
|
||||
class CSVRenderer(CLIRenderer):
|
||||
_type_renderers = {
|
||||
format_hints.Bin: optional(lambda x: f"0b{x:b}"),
|
||||
format_hints.Hex: optional(lambda x: f"0x{x:x}"),
|
||||
format_hints.HexBytes: optional(hex_bytes_as_text),
|
||||
format_hints.MultiTypeData: optional(multitypedata_as_text),
|
||||
interfaces.renderers.Disassembly: optional(display_disassembly),
|
||||
bytes: optional(lambda x: " ".join(f"{b:02x}" for b in x)),
|
||||
datetime.datetime: optional(lambda x: x.strftime("%Y-%m-%d %H:%M:%S.%f %Z")),
|
||||
"default": optional(lambda x: f"{x}"),
|
||||
}
|
||||
|
||||
name = "csv"
|
||||
structured_output = True
|
||||
|
||||
def get_render_options(self):
|
||||
pass
|
||||
return []
|
||||
|
||||
def render(self, grid: interfaces.renderers.TreeGrid) -> None:
|
||||
"""Renders each row immediately to stdout.
|
||||
@@ -316,12 +404,10 @@ class CSVRenderer(CLIRenderer):
|
||||
|
||||
|
||||
class PrettyTextRenderer(CLIRenderer):
|
||||
_type_renderers = QuickTextRenderer._type_renderers
|
||||
|
||||
name = "pretty"
|
||||
|
||||
def get_render_options(self):
|
||||
pass
|
||||
return []
|
||||
|
||||
def render(self, grid: interfaces.renderers.TreeGrid) -> None:
|
||||
"""Renders each column immediately to stdout.
|
||||
@@ -380,7 +466,9 @@ class PrettyTextRenderer(CLIRenderer):
|
||||
accumulator.append((node.path_depth, line))
|
||||
return accumulator
|
||||
|
||||
final_output: List[Tuple[int, Dict[interfaces.renderers.Column, bytes]]] = []
|
||||
final_output: List[Tuple[int, Dict[interfaces.renderers.Column, list[str]]]] = (
|
||||
[]
|
||||
)
|
||||
if not grid.populated:
|
||||
grid.populate(visitor, final_output)
|
||||
else:
|
||||
@@ -447,9 +535,18 @@ class PrettyTextRenderer(CLIRenderer):
|
||||
|
||||
class JsonRenderer(CLIRenderer):
|
||||
_type_renderers = {
|
||||
format_hints.HexBytes: quoted_optional(hex_bytes_as_text),
|
||||
interfaces.renderers.Disassembly: quoted_optional(display_disassembly),
|
||||
format_hints.HexBytes: lambda x: (
|
||||
x.hex(" ")
|
||||
if not isinstance(x, interfaces.renderers.BaseAbsentValue)
|
||||
else "N/A"
|
||||
),
|
||||
renderers.Disassembly: quoted_optional(display_disassembly),
|
||||
format_hints.MultiTypeData: quoted_optional(multitypedata_as_text),
|
||||
renderers.LayerData: lambda x: (
|
||||
LayerDataRenderer().render_bytes(x)[0].hex(" ")
|
||||
if not isinstance(x, interfaces.renderers.BaseAbsentValue)
|
||||
else "N/A"
|
||||
),
|
||||
bytes: optional(lambda x: " ".join(f"{b:02x}" for b in x)),
|
||||
datetime.datetime: lambda x: (
|
||||
x.isoformat()
|
||||
@@ -463,7 +560,7 @@ class JsonRenderer(CLIRenderer):
|
||||
structured_output = True
|
||||
|
||||
def get_render_options(self) -> List[interfaces.renderers.RenderOption]:
|
||||
pass
|
||||
return []
|
||||
|
||||
def output_result(self, outfd, result):
|
||||
"""Outputs the JSON data to a file in a particular format"""
|
||||
|
||||
@@ -40,6 +40,8 @@ class Volshell(interfaces.plugins.PluginInterface):
|
||||
|
||||
_required_framework_version = (2, 0, 0)
|
||||
|
||||
_version = (1, 0, 0)
|
||||
|
||||
DEFAULT_NUM_DISPLAY_BYTES = 128
|
||||
|
||||
def __init__(self, *args, **kwargs):
|
||||
@@ -54,9 +56,18 @@ class Volshell(interfaces.plugins.PluginInterface):
|
||||
|
||||
@classmethod
|
||||
def get_requirements(cls) -> List[interfaces.configuration.RequirementInterface]:
|
||||
reqs: List[interfaces.configuration.RequirementInterface] = []
|
||||
reqs: List[interfaces.configuration.RequirementInterface] = [
|
||||
requirements.VersionRequirement(
|
||||
name="regex_scanner",
|
||||
component=scanners.RegExScanner,
|
||||
version=(1, 0, 0),
|
||||
),
|
||||
]
|
||||
if cls == Volshell:
|
||||
reqs = [
|
||||
reqs += [
|
||||
requirements.TranslationLayerRequirement(
|
||||
name="primary", description="Memory layer for the kernel"
|
||||
),
|
||||
requirements.URIRequirement(
|
||||
name="script",
|
||||
description="File to load and execute at start",
|
||||
@@ -70,11 +81,8 @@ class Volshell(interfaces.plugins.PluginInterface):
|
||||
optional=True,
|
||||
),
|
||||
]
|
||||
return reqs + [
|
||||
requirements.TranslationLayerRequirement(
|
||||
name="primary", description="Memory layer for the kernel"
|
||||
),
|
||||
]
|
||||
|
||||
return reqs
|
||||
|
||||
def run(
|
||||
self, additional_locals: Dict[str, Any] = {}
|
||||
@@ -502,6 +510,7 @@ class Volshell(interfaces.plugins.PluginInterface):
|
||||
relative_offset, member_type = volobject.vol.members[member]
|
||||
len_offset = len(hex(relative_offset))
|
||||
len_member = len(member)
|
||||
|
||||
member_type_name = self._get_type_name_with_pointer(
|
||||
member_type
|
||||
) # special case for pointers to show what they point to
|
||||
@@ -509,6 +518,7 @@ class Volshell(interfaces.plugins.PluginInterface):
|
||||
if len(member_type_name) > MAX_TYPENAME_DISPLAY_LENGTH:
|
||||
len_typename = MAX_TYPENAME_DISPLAY_LENGTH
|
||||
member_type_name = f"{member_type_name[:len_typename - 3]}..."
|
||||
|
||||
if isinstance(volobject, interfaces.objects.ObjectInterface):
|
||||
# We're an instance, so also display the data
|
||||
try:
|
||||
@@ -593,6 +603,7 @@ class Volshell(interfaces.plugins.PluginInterface):
|
||||
return f"{display_type_string} @ {hex(volobject.vol.offset)} -> {self._display_value(volobject)}"
|
||||
else:
|
||||
return f"{display_type_string}: {self._display_value(volobject)}"
|
||||
|
||||
else:
|
||||
return display_type_string
|
||||
|
||||
|
||||
@@ -36,7 +36,12 @@ class Volshell(generic.Volshell):
|
||||
requirements.IntRequirement(
|
||||
name="pid", description="Process ID", optional=True
|
||||
),
|
||||
]
|
||||
requirements.VersionRequirement(
|
||||
name="generic_volshell",
|
||||
component=generic.Volshell,
|
||||
version=(1, 0, 0),
|
||||
),
|
||||
] + super().get_requirements()
|
||||
|
||||
def change_task(self, pid=None):
|
||||
"""Change the current process and layer, based on a process ID"""
|
||||
|
||||
@@ -25,7 +25,12 @@ class Volshell(generic.Volshell):
|
||||
requirements.IntRequirement(
|
||||
name="pid", description="Process ID", optional=True
|
||||
),
|
||||
]
|
||||
requirements.VersionRequirement(
|
||||
name="generic_volshell",
|
||||
component=generic.Volshell,
|
||||
version=(1, 0, 0),
|
||||
),
|
||||
] + super().get_requirements()
|
||||
|
||||
def change_task(self, pid=None):
|
||||
"""Change the current process and layer, based on a process ID"""
|
||||
|
||||
@@ -23,7 +23,12 @@ class Volshell(generic.Volshell):
|
||||
requirements.IntRequirement(
|
||||
name="pid", description="Process ID", optional=True
|
||||
),
|
||||
]
|
||||
requirements.VersionRequirement(
|
||||
name="generic_volshell",
|
||||
component=generic.Volshell,
|
||||
version=(1, 0, 0),
|
||||
),
|
||||
] + super().get_requirements()
|
||||
|
||||
def change_process(self, pid=None):
|
||||
"""Change the current process and layer, based on a process ID"""
|
||||
|
||||
@@ -17,7 +17,7 @@ from volatility3.framework import constants, exceptions, interfaces, layers
|
||||
from volatility3.framework.configuration import requirements
|
||||
from volatility3.framework.layers import intel, scanners
|
||||
from volatility3.framework.symbols import native
|
||||
from volatility3.framework.symbols.windows.pdbutil import PDBUtility
|
||||
from volatility3.framework.symbols.windows import pdbutil
|
||||
|
||||
if __name__ == "__main__":
|
||||
import sys
|
||||
@@ -50,6 +50,21 @@ class KernelPDBScanner(interfaces.automagic.AutomagicInterface):
|
||||
max_pdb_size = 0x400000
|
||||
exclusion_list = ["linux", "mac"]
|
||||
|
||||
@classmethod
|
||||
def get_requirements(cls) -> List[interfaces.configuration.RequirementInterface]:
|
||||
return [
|
||||
requirements.VersionRequirement(
|
||||
name="pdb_utility",
|
||||
component=pdbutil.PDBUtility,
|
||||
version=(1, 0, 1),
|
||||
),
|
||||
requirements.VersionRequirement(
|
||||
name="bytes_scanner",
|
||||
component=scanners.BytesScanner,
|
||||
version=(1, 0, 0),
|
||||
),
|
||||
]
|
||||
|
||||
def find_virtual_layers_from_req(
|
||||
self,
|
||||
context: interfaces.context.ContextInterface,
|
||||
@@ -120,7 +135,7 @@ class KernelPDBScanner(interfaces.automagic.AutomagicInterface):
|
||||
):
|
||||
raise TypeError("PDB name or GUID not a string value")
|
||||
|
||||
PDBUtility.load_windows_symbol_table(
|
||||
pdbutil.PDBUtility.load_windows_symbol_table(
|
||||
context=context,
|
||||
guid=kernel["GUID"],
|
||||
age=kernel["age"],
|
||||
@@ -259,7 +274,7 @@ class KernelPDBScanner(interfaces.automagic.AutomagicInterface):
|
||||
bytes(name + ".pdb", "utf-8")
|
||||
for name in constants.windows.KERNEL_MODULE_NAMES
|
||||
]
|
||||
kernels = PDBUtility.pdbname_scan(
|
||||
kernels = pdbutil.PDBUtility.pdbname_scan(
|
||||
ctx=context,
|
||||
layer_name=layer_to_scan,
|
||||
start=start_scan_address,
|
||||
@@ -362,7 +377,7 @@ class KernelPDBScanner(interfaces.automagic.AutomagicInterface):
|
||||
with contextlib.suppress(exceptions.InvalidAddressException):
|
||||
if vlayer.read(address, 0x2) == b"MZ":
|
||||
res = list(
|
||||
PDBUtility.pdbname_scan(
|
||||
pdbutil.PDBUtility.pdbname_scan(
|
||||
ctx=context,
|
||||
layer_name=vlayer.name,
|
||||
page_size=vlayer.page_size,
|
||||
|
||||
@@ -40,7 +40,12 @@ class SymbolFinder(interfaces.automagic.AutomagicInterface):
|
||||
name="SQLiteCache",
|
||||
component=symbol_cache.SqliteCache,
|
||||
version=(1, 0, 0),
|
||||
)
|
||||
),
|
||||
requirements.VersionRequirement(
|
||||
name="multi_string_scanner",
|
||||
component=scanners.MultiStringScanner,
|
||||
version=(1, 0, 0),
|
||||
),
|
||||
]
|
||||
|
||||
@property
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
# We use the SemVer 2.0.0 versioning scheme
|
||||
VERSION_MAJOR = 2 # Number of releases of the library with a breaking change
|
||||
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_MINOR = 26 # Number of changes that only add to the interface
|
||||
VERSION_PATCH = 1 # Number of changes that do not change the interface
|
||||
VERSION_SUFFIX = ""
|
||||
|
||||
PACKAGE_VERSION = (
|
||||
|
||||
@@ -130,7 +130,7 @@ class Context(interfaces.context.ContextInterface):
|
||||
object_info=interfaces.objects.ObjectInformation(
|
||||
layer_name=layer_name,
|
||||
offset=offset,
|
||||
native_layer_name=native_layer_name,
|
||||
native_layer_name=native_layer_name or layer_name,
|
||||
size=object_template.size,
|
||||
),
|
||||
)
|
||||
|
||||
@@ -11,7 +11,6 @@ size of the invalid page.
|
||||
from typing import Callable, Dict, Optional, Tuple
|
||||
|
||||
from volatility3.framework import interfaces
|
||||
from volatility3.framework.interfaces.configuration import VersionableInterface
|
||||
|
||||
|
||||
class VolatilityException(Exception):
|
||||
@@ -143,7 +142,7 @@ class VersionMismatchException(VolatilityException):
|
||||
def __init__(
|
||||
self,
|
||||
source_component: Callable,
|
||||
target_component: VersionableInterface,
|
||||
target_component: interfaces.configuration.VersionableInterface,
|
||||
target_version: Tuple[int, int, int],
|
||||
failure_reason: str = None,
|
||||
*args,
|
||||
@@ -151,7 +150,7 @@ class VersionMismatchException(VolatilityException):
|
||||
"""
|
||||
Args:
|
||||
source_component: The component that required the target component
|
||||
target_component: The component that is required. Must inherit from VersionableInterface
|
||||
target_component: The component that is required. Must inherit from interfaces.configuration.VersionableInterface
|
||||
target_version: The version of the target component that was required, and ultimately was not satisfied
|
||||
failure_reason: A detailed failure reason to enhance debugging and bug tracking
|
||||
"""
|
||||
|
||||
@@ -8,7 +8,7 @@ import collections
|
||||
import collections.abc
|
||||
import contextlib
|
||||
import logging
|
||||
from typing import Any, Dict, List, Mapping, Optional
|
||||
from typing import Any, Dict, List, Mapping, NamedTuple, Optional
|
||||
|
||||
from volatility3.framework import constants, interfaces
|
||||
|
||||
@@ -52,7 +52,7 @@ class ReadOnlyMapping(collections.abc.Mapping):
|
||||
return dict(self) == dict(other)
|
||||
|
||||
|
||||
class ObjectInformation(ReadOnlyMapping):
|
||||
class ObjectInformation(NamedTuple):
|
||||
"""Contains common information useful/pertinent only to an individual
|
||||
object (like an instance)
|
||||
|
||||
@@ -63,35 +63,20 @@ class ObjectInformation(ReadOnlyMapping):
|
||||
in a single place. These values are based on the :class:`ReadOnlyMapping` class, to prevent their modification.
|
||||
"""
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
layer_name: str,
|
||||
offset: int,
|
||||
member_name: Optional[str] = None,
|
||||
parent: Optional["ObjectInterface"] = None,
|
||||
native_layer_name: Optional[str] = None,
|
||||
size: Optional[int] = None,
|
||||
):
|
||||
"""Constructs a container for basic information about an object.
|
||||
layer_name: str
|
||||
offset: int
|
||||
native_layer_name: str
|
||||
member_name: Optional[str] = None
|
||||
parent: Optional["ObjectInterface"] = None
|
||||
size: Optional[int] = None
|
||||
|
||||
Args:
|
||||
layer_name: Layer from which the data for the object will be read
|
||||
offset: Offset within the layer at which the data for the object will be read
|
||||
member_name: If the object was accessed as a member of a parent object, this was the name used to access it
|
||||
parent: If the object was accessed as a member of a parent object, this is the parent object
|
||||
native_layer_name: If this object references other objects (such as a pointer), what layer those objects live in
|
||||
size: The size that the whole structure consumes in bytes
|
||||
"""
|
||||
super().__init__(
|
||||
{
|
||||
"layer_name": layer_name,
|
||||
"offset": offset,
|
||||
"member_name": member_name,
|
||||
"parent": parent,
|
||||
"native_layer_name": native_layer_name or layer_name,
|
||||
"size": size,
|
||||
}
|
||||
)
|
||||
def __getitem__(self, key):
|
||||
if key in self._fields:
|
||||
return getattr(self, key)
|
||||
raise KeyError(f"NamedTuple does not have a key {key}")
|
||||
|
||||
def __contains__(self, key):
|
||||
return key in self._fields
|
||||
|
||||
|
||||
class ObjectInterface(metaclass=abc.ABCMeta):
|
||||
@@ -183,7 +168,7 @@ class ObjectInterface(metaclass=abc.ABCMeta):
|
||||
offset=self.vol.offset,
|
||||
member_name=self.vol.member_name,
|
||||
parent=self.vol.parent,
|
||||
native_layer_name=self.vol.native_layer_name,
|
||||
native_layer_name=self.vol.native_layer_name or self.vol.layer_name,
|
||||
size=object_template.size,
|
||||
)
|
||||
return object_template(context=self._context, object_info=object_info)
|
||||
|
||||
@@ -10,7 +10,8 @@ suitable output.
|
||||
"""
|
||||
|
||||
import datetime
|
||||
from abc import abstractmethod, ABCMeta
|
||||
import warnings
|
||||
from abc import ABCMeta, abstractmethod
|
||||
from collections import abc
|
||||
from typing import (
|
||||
Any,
|
||||
@@ -20,11 +21,28 @@ from typing import (
|
||||
List,
|
||||
NamedTuple,
|
||||
Optional,
|
||||
TypeVar,
|
||||
Type,
|
||||
Tuple,
|
||||
Type,
|
||||
TypeVar,
|
||||
Union,
|
||||
)
|
||||
from typing import Dict
|
||||
|
||||
from volatility3.framework import interfaces
|
||||
|
||||
|
||||
class BasicType:
|
||||
def __str__(self) -> str:
|
||||
"""Fallback method for rendering basic types"""
|
||||
return str(self)
|
||||
|
||||
|
||||
class BaseAbsentValue:
|
||||
"""Class that represents values which are not present for some reason."""
|
||||
|
||||
def __str__(self) -> str:
|
||||
"""Fallback method for rendering basic types"""
|
||||
return "-"
|
||||
|
||||
|
||||
class Column(NamedTuple):
|
||||
@@ -34,11 +52,37 @@ class Column(NamedTuple):
|
||||
|
||||
RenderOption = Any
|
||||
|
||||
T = TypeVar("T")
|
||||
|
||||
|
||||
class TypeRendererInterface:
|
||||
type = T
|
||||
|
||||
def __init__(
|
||||
self, func: Optional[Callable] = None, options: Optional[Dict[str, Any]] = None
|
||||
):
|
||||
self._options = options or {}
|
||||
setattr(self, "render", func)
|
||||
|
||||
@property
|
||||
def options(self):
|
||||
return self._options
|
||||
|
||||
def render(self, data: Union[T, BaseAbsentValue]) -> Any:
|
||||
"""Renders a specific datatype"""
|
||||
return ""
|
||||
|
||||
def __call__(self, data: Union[T, BaseAbsentValue]) -> Any:
|
||||
"""Shortcut for render"""
|
||||
return self.render(data)
|
||||
|
||||
|
||||
class Renderer(metaclass=ABCMeta):
|
||||
"""Class that defines the interface that all output renderers must
|
||||
support."""
|
||||
|
||||
_type_renderers: Dict[Union[Type, str], Callable]
|
||||
|
||||
def __init__(self, options: Optional[List[RenderOption]] = None) -> None:
|
||||
"""Accepts an options object to configure the renderers."""
|
||||
# FIXME: Once the config option objects are in place, put the _type_check in place
|
||||
@@ -102,11 +146,7 @@ class TreeNode(abc.Sequence, metaclass=ABCMeta):
|
||||
"""
|
||||
|
||||
|
||||
class BaseAbsentValue:
|
||||
"""Class that represents values which are not present for some reason."""
|
||||
|
||||
|
||||
class Disassembly:
|
||||
class Disassembly(BasicType):
|
||||
"""A class to indicate that the bytes provided should be disassembled
|
||||
(based on the architecture)"""
|
||||
|
||||
@@ -115,6 +155,10 @@ class Disassembly:
|
||||
def __init__(
|
||||
self, data: bytes, offset: int = 0, architecture: str = "intel64"
|
||||
) -> None:
|
||||
warnings.warn(
|
||||
"interfaces.renderers.Disassembly is now renderers.Disassembly",
|
||||
FutureWarning,
|
||||
)
|
||||
self.data = data
|
||||
self.architecture = None
|
||||
if architecture in self.possible_architectures:
|
||||
@@ -123,6 +167,10 @@ class Disassembly:
|
||||
raise TypeError("Offset must be an integer type")
|
||||
self.offset = offset
|
||||
|
||||
def __str__(self) -> str:
|
||||
"""Fallback method of rendering"""
|
||||
return str(self.data)
|
||||
|
||||
|
||||
# We don't class these off a shared base, because the BaseTypes must only
|
||||
# contain the types that the validator will accept (which would not include the base)
|
||||
@@ -135,7 +183,7 @@ BaseTypes = Union[
|
||||
Type[bytes],
|
||||
Type[datetime.datetime],
|
||||
Type[BaseAbsentValue],
|
||||
Type[Disassembly],
|
||||
Type[BasicType],
|
||||
]
|
||||
ColumnsType = List[Tuple[str, BaseTypes]]
|
||||
VisitorSignature = Callable[[TreeNode, _Type], _Type]
|
||||
@@ -154,16 +202,15 @@ class TreeGrid(metaclass=ABCMeta):
|
||||
and to create cycles.
|
||||
"""
|
||||
|
||||
base_types: ClassVar[Tuple] = (
|
||||
int,
|
||||
str,
|
||||
float,
|
||||
bytes,
|
||||
datetime.datetime,
|
||||
Disassembly,
|
||||
)
|
||||
# TODO: Figure out why this isn't just BaseTypes (which includes AbsentValues'
|
||||
base_types: ClassVar[Tuple] = (int, str, float, bytes, datetime.datetime, BasicType)
|
||||
|
||||
def __init__(self, columns: ColumnsType, generator: Generator) -> None:
|
||||
def __init__(
|
||||
self,
|
||||
columns: ColumnsType,
|
||||
generator: Generator,
|
||||
context: Optional["interfaces.context.ContextInterface"] = None,
|
||||
) -> None:
|
||||
"""Constructs a TreeGrid object using a specific set of columns.
|
||||
|
||||
The TreeGrid itself is a root element, that can have children but no values.
|
||||
@@ -174,6 +221,15 @@ class TreeGrid(metaclass=ABCMeta):
|
||||
columns: A list of column tuples made up of (name, type).
|
||||
generator: An iterable containing row for a tree grid, each row contains a indent level followed by the values for each column in order.
|
||||
"""
|
||||
self._context = context
|
||||
|
||||
@property
|
||||
def context(self) -> Optional["interfaces.context.ContextInterface"]:
|
||||
"""Returns the context value for the tree grid (to retrieve data items)
|
||||
|
||||
This is a property to ensure the renderers don't try changing the context for any reason
|
||||
"""
|
||||
return self._context
|
||||
|
||||
@staticmethod
|
||||
@abstractmethod
|
||||
|
||||
@@ -10,7 +10,6 @@ from typing import Any, Dict, Iterable, List, Mapping, Optional, Tuple, Type
|
||||
from volatility3.framework import constants, exceptions, interfaces
|
||||
from volatility3.framework.configuration import requirements
|
||||
from volatility3.framework.interfaces import configuration, objects
|
||||
from volatility3.framework.interfaces.configuration import RequirementInterface
|
||||
|
||||
|
||||
class SymbolInterface:
|
||||
@@ -347,7 +346,7 @@ class SymbolTableInterface(
|
||||
return config
|
||||
|
||||
@classmethod
|
||||
def get_requirements(cls) -> List[RequirementInterface]:
|
||||
def get_requirements(cls) -> List[configuration.RequirementInterface]:
|
||||
return super().get_requirements() + [
|
||||
requirements.IntRequirement(
|
||||
name="symbol_mask",
|
||||
|
||||
@@ -6,7 +6,7 @@ import struct
|
||||
from typing import Optional
|
||||
|
||||
from volatility3.framework import exceptions, interfaces, constants
|
||||
from volatility3.framework.constants.linux import ELF_CLASS
|
||||
from volatility3.framework.constants import linux as linux_constants
|
||||
from volatility3.framework.layers import segmented
|
||||
from volatility3.framework.symbols import intermed
|
||||
|
||||
@@ -23,7 +23,7 @@ class Elf64Layer(segmented.SegmentedLayer):
|
||||
|
||||
_header_struct = struct.Struct("<IBBB")
|
||||
MAGIC = 0x464C457F # "\x7fELF"
|
||||
ELF_CLASS = ELF_CLASS.ELFCLASS64
|
||||
ELF_CLASS = linux_constants.ELF_CLASS.ELFCLASS64
|
||||
|
||||
def __init__(
|
||||
self, context: interfaces.context.ContextInterface, config_path: str, name: str
|
||||
|
||||
@@ -9,6 +9,7 @@ import struct
|
||||
from typing import Any, Dict, List, Optional, Set, Tuple
|
||||
|
||||
from volatility3.framework import constants, exceptions, interfaces
|
||||
from volatility3.framework.configuration import requirements
|
||||
from volatility3.framework.layers import scanners, segmented
|
||||
from volatility3.framework.symbols import intermed
|
||||
|
||||
@@ -99,6 +100,16 @@ class QemuSuspendLayer(segmented.NonLinearlySegmentedLayer):
|
||||
context=context, config_path=config_path, name=name, metadata=metadata
|
||||
)
|
||||
|
||||
@classmethod
|
||||
def get_requirements(cls) -> List[interfaces.configuration.RequirementInterface]:
|
||||
return [
|
||||
requirements.VersionRequirement(
|
||||
name="regex_scanner",
|
||||
component=scanners.RegExScanner,
|
||||
version=(1, 0, 0),
|
||||
),
|
||||
]
|
||||
|
||||
@classmethod
|
||||
def _check_header(
|
||||
cls, base_layer: interfaces.layers.DataLayerInterface, name: str = ""
|
||||
|
||||
@@ -7,11 +7,6 @@ from typing import Any, Callable, Dict, Iterable, List, Optional, Tuple, Union
|
||||
|
||||
from volatility3.framework import constants, exceptions, interfaces, objects
|
||||
from volatility3.framework.configuration import requirements
|
||||
from volatility3.framework.configuration.requirements import (
|
||||
IntRequirement,
|
||||
TranslationLayerRequirement,
|
||||
)
|
||||
from volatility3.framework.exceptions import InvalidAddressException
|
||||
from volatility3.framework.layers import linear
|
||||
from volatility3.framework.symbols import intermed
|
||||
from volatility3.framework.symbols.windows import extensions
|
||||
@@ -154,7 +149,7 @@ class RegistryHive(linear.LinearlyMappedLayer):
|
||||
@property
|
||||
def root_cell_offset(self) -> int:
|
||||
"""Returns the offset for the root cell in this hive."""
|
||||
with contextlib.suppress(InvalidAddressException):
|
||||
with contextlib.suppress(exceptions.InvalidAddressException):
|
||||
if (
|
||||
self._base_block.Signature.cast(
|
||||
"string", max_length=4, encoding="latin-1"
|
||||
@@ -271,7 +266,7 @@ class RegistryHive(linear.LinearlyMappedLayer):
|
||||
@classmethod
|
||||
def get_requirements(cls) -> List[interfaces.configuration.RequirementInterface]:
|
||||
return [
|
||||
IntRequirement(
|
||||
requirements.IntRequirement(
|
||||
name="hive_offset",
|
||||
description="Offset within the base layer at which the hive lives",
|
||||
default=0,
|
||||
@@ -280,7 +275,7 @@ class RegistryHive(linear.LinearlyMappedLayer):
|
||||
requirements.SymbolTableRequirement(
|
||||
name="nt_symbols", description="Windows kernel symbols"
|
||||
),
|
||||
TranslationLayerRequirement(
|
||||
requirements.TranslationLayerRequirement(
|
||||
name="base_layer",
|
||||
description="Layer in which the registry hive lives",
|
||||
optional=False,
|
||||
|
||||
@@ -11,6 +11,8 @@ from volatility3.framework.layers.scanners import multiregexp as multiregexp
|
||||
class BytesScanner(layers.ScannerInterface):
|
||||
thread_safe = True
|
||||
|
||||
_version = (1, 0, 0)
|
||||
|
||||
_required_framework_version = (2, 0, 0)
|
||||
|
||||
def __init__(self, needle: bytes) -> None:
|
||||
@@ -38,6 +40,8 @@ class RegExScanner(layers.ScannerInterface):
|
||||
|
||||
thread_safe = True
|
||||
|
||||
_version = (1, 0, 0)
|
||||
|
||||
_required_framework_version = (2, 0, 0)
|
||||
|
||||
def __init__(self, pattern: bytes, flags: int = re.DOTALL) -> None:
|
||||
@@ -57,6 +61,7 @@ class RegExScanner(layers.ScannerInterface):
|
||||
class MultiStringScanner(layers.ScannerInterface):
|
||||
thread_safe = True
|
||||
|
||||
_version = (1, 0, 0)
|
||||
_required_framework_version = (2, 0, 0)
|
||||
|
||||
def __init__(self, patterns: List[bytes]) -> None:
|
||||
|
||||
@@ -5,7 +5,7 @@ from typing import Optional
|
||||
from volatility3.framework import constants, interfaces, exceptions
|
||||
from volatility3.framework.layers import elf
|
||||
from volatility3.framework.symbols import intermed
|
||||
from volatility3.framework.constants.linux import ELF_CLASS
|
||||
from volatility3.framework.constants import linux as linux_constants
|
||||
|
||||
vollog = logging.getLogger(__name__)
|
||||
|
||||
@@ -15,7 +15,7 @@ class XenCoreDumpLayer(elf.Elf64Layer):
|
||||
|
||||
_header_struct = struct.Struct("<IBBB")
|
||||
MAGIC = 0x464C457F # "\x7fELF"
|
||||
ELF_CLASS = ELF_CLASS.ELFCLASS64
|
||||
ELF_CLASS = linux_constants.ELF_CLASS.ELFCLASS64
|
||||
|
||||
def __init__(
|
||||
self, context: interfaces.context.ContextInterface, config_path: str, name: str
|
||||
|
||||
@@ -458,6 +458,7 @@ class Pointer(Integer):
|
||||
offset=offset,
|
||||
parent=self,
|
||||
size=self.vol.subtype.size,
|
||||
native_layer_name=layer_name,
|
||||
),
|
||||
)
|
||||
return self._cache[layer_name]
|
||||
@@ -811,7 +812,7 @@ class Array(interfaces.objects.ObjectInterface, collections.abc.Sequence):
|
||||
layer_name=self.vol.layer_name,
|
||||
offset=mask & (self.vol.offset + (self.vol.subtype.size * index)),
|
||||
parent=self,
|
||||
native_layer_name=self.vol.native_layer_name,
|
||||
native_layer_name=self.vol.native_layer_name or self.vol.layer_name,
|
||||
size=self.vol.subtype.size,
|
||||
)
|
||||
result += [self.vol.subtype(context=self._context, object_info=object_info)]
|
||||
@@ -978,7 +979,7 @@ class AggregateType(interfaces.objects.ObjectInterface):
|
||||
offset=mask & (self.vol.offset + relative_offset),
|
||||
member_name=attr,
|
||||
parent=self,
|
||||
native_layer_name=self.vol.native_layer_name,
|
||||
native_layer_name=self.vol.native_layer_name or self.vol.layer_name,
|
||||
size=template.size,
|
||||
)
|
||||
member = template(context=self._context, object_info=object_info)
|
||||
|
||||
@@ -22,7 +22,12 @@ class Banners(interfaces.plugins.PluginInterface):
|
||||
return [
|
||||
requirements.TranslationLayerRequirement(
|
||||
name="primary", description="Memory layer to scan"
|
||||
)
|
||||
),
|
||||
requirements.VersionRequirement(
|
||||
name="regex_scanner",
|
||||
component=scanners.RegExScanner,
|
||||
version=(1, 0, 0),
|
||||
),
|
||||
]
|
||||
|
||||
def _generator(self):
|
||||
|
||||
@@ -13,7 +13,7 @@ from volatility3.framework.configuration import requirements
|
||||
from volatility3.framework.interfaces import plugins
|
||||
from volatility3.framework.layers import scanners
|
||||
from volatility3.framework.objects import utility
|
||||
from volatility3.framework.symbols.linux.bash import BashIntermedSymbols
|
||||
from volatility3.framework.symbols.linux import bash
|
||||
from volatility3.plugins import timeliner
|
||||
from volatility3.plugins.linux import pslist
|
||||
|
||||
@@ -40,6 +40,16 @@ class Bash(plugins.PluginInterface, timeliner.TimeLinerInterface):
|
||||
component=timeliner.TimeLinerInterface,
|
||||
version=(1, 0, 0),
|
||||
),
|
||||
requirements.VersionRequirement(
|
||||
name="multi_string_scanner",
|
||||
component=scanners.MultiStringScanner,
|
||||
version=(1, 0, 0),
|
||||
),
|
||||
requirements.VersionRequirement(
|
||||
name="bytes_scanner",
|
||||
component=scanners.BytesScanner,
|
||||
version=(1, 0, 0),
|
||||
),
|
||||
requirements.ListRequirement(
|
||||
name="pid",
|
||||
element_type=int,
|
||||
@@ -60,7 +70,7 @@ class Bash(plugins.PluginInterface, timeliner.TimeLinerInterface):
|
||||
pack_format = "Q"
|
||||
bash_json_file = "bash64"
|
||||
|
||||
bash_table_name = BashIntermedSymbols.create(
|
||||
bash_table_name = bash.BashIntermedSymbols.create(
|
||||
self.context, self.config_path, "linux", bash_json_file
|
||||
)
|
||||
|
||||
|
||||
@@ -4,7 +4,7 @@
|
||||
"""A module containing a plugin that verifies the operation function
|
||||
pointers of network protocols."""
|
||||
import logging
|
||||
from typing import List
|
||||
from typing import List, Tuple, Generator
|
||||
|
||||
from volatility3.framework import exceptions, interfaces
|
||||
from volatility3.framework import renderers
|
||||
@@ -18,6 +18,7 @@ vollog = logging.getLogger(__name__)
|
||||
class Check_afinfo(plugins.PluginInterface):
|
||||
"""Verifies the operation function pointers of network protocols."""
|
||||
|
||||
_version = (1, 0, 0)
|
||||
_required_framework_version = (2, 0, 0)
|
||||
|
||||
@classmethod
|
||||
@@ -30,61 +31,80 @@ class Check_afinfo(plugins.PluginInterface):
|
||||
),
|
||||
]
|
||||
|
||||
# returns whether the symbol is found within the kernel (system.map) or not
|
||||
def _is_known_address(self, handler_addr):
|
||||
symbols = list(self.context.symbol_space.get_symbols_by_location(handler_addr))
|
||||
@classmethod
|
||||
def _check_members(
|
||||
cls,
|
||||
context: interfaces.context.ContextInterface,
|
||||
vmlinux_name: str,
|
||||
var_ops: interfaces.objects.ObjectInterface,
|
||||
var_name: str,
|
||||
members: List[str],
|
||||
) -> Generator[Tuple[str, str, int], None, None]:
|
||||
"""
|
||||
Yields any members that are not pointing inside the kernel
|
||||
"""
|
||||
|
||||
return len(symbols) > 0
|
||||
vmlinux = context.modules[vmlinux_name]
|
||||
|
||||
def _check_members(self, var_ops, var_name, members):
|
||||
for check in members:
|
||||
# redhat-specific garbage
|
||||
if check.startswith("__UNIQUE_ID_rh_kabi_hide"):
|
||||
continue
|
||||
|
||||
if check == "write":
|
||||
addr = var_ops.member(attr="write")
|
||||
else:
|
||||
addr = getattr(var_ops, check)
|
||||
# These structures have members like `write` and `next`, which are built in Python functions
|
||||
addr = var_ops.member(attr=check)
|
||||
|
||||
if addr and addr != 0 and not self._is_known_address(addr):
|
||||
yield check, addr
|
||||
# Unimplemented handlers are set to 0
|
||||
if not addr:
|
||||
continue
|
||||
|
||||
def _check_afinfo(self, var_name, var, op_members, seq_members):
|
||||
# check if object has a least one of the members used for analysis by this function
|
||||
required_members = ["seq_fops", "seq_ops", "seq_show"]
|
||||
has_required_member = any(var.has_member(member) for member in required_members)
|
||||
if not has_required_member:
|
||||
vollog.debug(
|
||||
f"{var_name} object at {hex(var.vol.offset)} had none of the required members: {', '.join([member for member in required_members])}"
|
||||
)
|
||||
raise exceptions.PluginRequirementException
|
||||
if len(vmlinux.get_symbols_by_absolute_location(addr)) == 0:
|
||||
yield var_name, check, addr
|
||||
|
||||
@classmethod
|
||||
def _check_pre_4_18_ops(
|
||||
cls,
|
||||
context: interfaces.context.ContextInterface,
|
||||
vmlinux_name: str,
|
||||
var_name: str,
|
||||
var: interfaces.objects.ObjectInterface,
|
||||
op_members: List[str],
|
||||
seq_members: List[str],
|
||||
):
|
||||
"""
|
||||
Finds the correct way to reference `op_members`
|
||||
"""
|
||||
vmlinux = context.modules[vmlinux_name]
|
||||
|
||||
if var.has_member("seq_fops"):
|
||||
for hooked_member, hook_address in self._check_members(
|
||||
var.seq_fops, var_name, op_members
|
||||
):
|
||||
yield var_name, hooked_member, hook_address
|
||||
|
||||
yield from cls._check_members(
|
||||
context, vmlinux_name, var.seq_fops, var_name, op_members
|
||||
)
|
||||
# newer kernels
|
||||
if var.has_member("seq_ops"):
|
||||
for hooked_member, hook_address in self._check_members(
|
||||
var.seq_ops, var_name, seq_members
|
||||
):
|
||||
yield var_name, hooked_member, hook_address
|
||||
yield from cls._check_members(
|
||||
context, vmlinux_name, var.seq_ops, var_name, seq_members
|
||||
)
|
||||
|
||||
# this is the most commonly hooked member by rootkits, so a force a check on it
|
||||
elif var.has_member("seq_show"):
|
||||
if len(vmlinux.get_symbols_by_location(var.seq_show)) == 0:
|
||||
yield var_name, "show", var.seq_show
|
||||
else:
|
||||
if var.has_member("seq_show"):
|
||||
if not self._is_known_address(var.seq_show):
|
||||
yield var_name, "show", var.seq_show
|
||||
|
||||
def _generator(self):
|
||||
vmlinux = self.context.modules[self.config["kernel"]]
|
||||
|
||||
op_members = vmlinux.get_type("file_operations").members
|
||||
seq_members = vmlinux.get_type("seq_operations").members
|
||||
raise exceptions.VolatilityException(
|
||||
"_check_afinfo_pre_4_18: Unable to find sequence operations members for checking."
|
||||
)
|
||||
|
||||
@classmethod
|
||||
def _check_afinfo_pre_4_18(
|
||||
cls,
|
||||
context: interfaces.context.ContextInterface,
|
||||
vmlinux_name: str,
|
||||
seq_members: str,
|
||||
) -> Generator[Tuple[str, str, int], None, None]:
|
||||
"""
|
||||
Checks the operations structures for network protocols of < 4.18 systems
|
||||
"""
|
||||
tcp = ("tcp_seq_afinfo", ["tcp6_seq_afinfo", "tcp4_seq_afinfo"])
|
||||
udp = (
|
||||
"udp_seq_afinfo",
|
||||
@@ -97,39 +117,93 @@ class Check_afinfo(plugins.PluginInterface):
|
||||
)
|
||||
protocols = [tcp, udp]
|
||||
|
||||
# used to track the calls to _check_afinfo and the
|
||||
# number of errors produced due to missing members
|
||||
symbols_checked = set()
|
||||
symbols_with_errors = set()
|
||||
vmlinux = context.modules[vmlinux_name]
|
||||
|
||||
op_members = vmlinux.get_type("file_operations").members
|
||||
|
||||
# loop through all symbols
|
||||
for struct_type, global_vars in protocols:
|
||||
for global_var_name in global_vars:
|
||||
# this will lookup fail for the IPv6 protocols on kernels without IPv6 support
|
||||
try:
|
||||
global_var = vmlinux.get_symbol(global_var_name)
|
||||
global_var = vmlinux.object_from_symbol(global_var_name)
|
||||
except exceptions.SymbolError:
|
||||
continue
|
||||
|
||||
global_var = vmlinux.object(
|
||||
object_type=struct_type, offset=global_var.address
|
||||
yield from cls._check_pre_4_18_ops(
|
||||
context,
|
||||
vmlinux_name,
|
||||
global_var_name,
|
||||
global_var,
|
||||
op_members,
|
||||
seq_members,
|
||||
)
|
||||
|
||||
symbols_checked.add(global_var_name)
|
||||
try:
|
||||
for name, member, address in self._check_afinfo(
|
||||
global_var_name, global_var, op_members, seq_members
|
||||
):
|
||||
yield 0, (name, member, format_hints.Hex(address))
|
||||
except exceptions.PluginRequirementException:
|
||||
symbols_with_errors.add(global_var_name)
|
||||
@classmethod
|
||||
def _check_afinfo_post_4_18(
|
||||
cls,
|
||||
context: interfaces.context.ContextInterface,
|
||||
vmlinux_name: str,
|
||||
seq_members: str,
|
||||
) -> Generator[Tuple[str, str, int], None, None]:
|
||||
"""
|
||||
Checks the operations structures for network protocols of >= 4.18 systems
|
||||
"""
|
||||
vmlinux = context.modules[vmlinux_name]
|
||||
|
||||
# if every call to _check_afinfo failed show a warning
|
||||
if symbols_checked == symbols_with_errors:
|
||||
vollog.warning(
|
||||
"This plugin was not able to check for hooks. This means you are either analyzing an unsupported kernel version or that your symbol table is corrupt."
|
||||
ops_structs = [
|
||||
"raw_seq_ops",
|
||||
"udp_seq_ops",
|
||||
"arp_seq_ops",
|
||||
"unix_seq_ops",
|
||||
"udp6_seq_ops",
|
||||
"raw6_seq_ops",
|
||||
"tcp_seq_ops",
|
||||
"tcp4_seq_ops",
|
||||
"tcp6_seq_ops",
|
||||
"packet_seq_ops",
|
||||
]
|
||||
|
||||
for protocol_ops_var in ops_structs:
|
||||
# These will fail if the particular kernel doesn't have support for a protocol like IPv6
|
||||
try:
|
||||
protocol_ops = vmlinux.object_from_symbol(protocol_ops_var)
|
||||
except exceptions.SymbolError:
|
||||
continue
|
||||
|
||||
yield from cls._check_members(
|
||||
context, vmlinux_name, protocol_ops, protocol_ops_var, seq_members
|
||||
)
|
||||
|
||||
@classmethod
|
||||
def check_afinfo(
|
||||
cls, context: interfaces.context.ContextInterface, vmlinux_name
|
||||
) -> Generator[Tuple[str, str, int], None, None]:
|
||||
"""
|
||||
Walks the network protocol operations structures for common network protocols.
|
||||
Reports any initialized operations members that do not point inside the kernel.
|
||||
"""
|
||||
vmlinux = context.modules[vmlinux_name]
|
||||
|
||||
type_check = vmlinux.get_type("tcp_seq_afinfo")
|
||||
if type_check.has_member("seq_fops"):
|
||||
checker = cls._check_afinfo_pre_4_18
|
||||
else:
|
||||
checker = cls._check_afinfo_post_4_18
|
||||
|
||||
seq_members = vmlinux.get_type("seq_operations").members
|
||||
|
||||
yield from checker(context, vmlinux_name, seq_members)
|
||||
|
||||
def _generator(self):
|
||||
"""
|
||||
A simple wrapper around `check_afino`
|
||||
"""
|
||||
for name, member, address in self.check_afinfo(
|
||||
self.context, self.config["kernel"]
|
||||
):
|
||||
yield 0, (name, member, format_hints.Hex(address))
|
||||
|
||||
def run(self):
|
||||
return renderers.TreeGrid(
|
||||
[
|
||||
|
||||
@@ -46,6 +46,11 @@ class Check_modules(plugins.PluginInterface):
|
||||
@classmethod
|
||||
def get_requirements(cls) -> List[interfaces.configuration.RequirementInterface]:
|
||||
return [
|
||||
requirements.VersionRequirement(
|
||||
name="modules",
|
||||
component=linux_utilities_modules.Modules,
|
||||
version=(3, 0, 1),
|
||||
),
|
||||
requirements.VersionRequirement(
|
||||
name="linux_utilities_modules_module_display_plugin",
|
||||
component=linux_utilities_modules.ModuleDisplayPlugin,
|
||||
|
||||
@@ -14,7 +14,7 @@ from volatility3.framework.objects import utility
|
||||
from volatility3.framework.renderers import format_hints
|
||||
from volatility3.framework.symbols import intermed
|
||||
from volatility3.framework.symbols.linux.extensions import elf
|
||||
from volatility3.framework.constants.linux import ELF_MAX_EXTRACTION_SIZE
|
||||
from volatility3.framework.constants import linux as linux_constants
|
||||
from volatility3.plugins.linux import pslist
|
||||
|
||||
|
||||
@@ -116,7 +116,7 @@ class Elfs(plugins.PluginInterface):
|
||||
real_size = end - start
|
||||
|
||||
# Check if ELF has a legitimate size
|
||||
if real_size < 0 or real_size > ELF_MAX_EXTRACTION_SIZE:
|
||||
if real_size < 0 or real_size > linux_constants.ELF_MAX_EXTRACTION_SIZE:
|
||||
raise ValueError(f"The claimed size of the ELF is invalid: {real_size}")
|
||||
|
||||
sections[start] = real_size
|
||||
|
||||
@@ -93,6 +93,11 @@ class Hidden_modules(plugins.PluginInterface):
|
||||
component=linux_utilities_modules.ModuleDisplayPlugin,
|
||||
version=(1, 0, 0),
|
||||
),
|
||||
requirements.VersionRequirement(
|
||||
name="linux_utilities_modules",
|
||||
component=linux_utilities_modules.Modules,
|
||||
version=(3, 0, 1),
|
||||
),
|
||||
] + linux_utilities_modules.ModuleDisplayPlugin.get_requirements()
|
||||
|
||||
@staticmethod
|
||||
|
||||
@@ -18,7 +18,7 @@ class Malfind(interfaces.plugins.PluginInterface):
|
||||
"""Lists process memory ranges that potentially contain injected code."""
|
||||
|
||||
_required_framework_version = (2, 0, 0)
|
||||
_version = (1, 0, 2)
|
||||
_version = (1, 0, 3)
|
||||
|
||||
@classmethod
|
||||
def get_requirements(cls) -> List[interfaces.configuration.RequirementInterface]:
|
||||
@@ -76,9 +76,7 @@ class Malfind(interfaces.plugins.PluginInterface):
|
||||
else:
|
||||
architecture = "intel64"
|
||||
|
||||
disasm = interfaces.renderers.Disassembly(
|
||||
data, vma.vm_start, architecture
|
||||
)
|
||||
disasm = renderers.Disassembly(data, vma.vm_start, architecture)
|
||||
|
||||
yield (
|
||||
0,
|
||||
@@ -106,7 +104,7 @@ class Malfind(interfaces.plugins.PluginInterface):
|
||||
("Path", str),
|
||||
("Protection", str),
|
||||
("Hexdump", format_hints.HexBytes),
|
||||
("Disasm", interfaces.renderers.Disassembly),
|
||||
("Disasm", renderers.Disassembly),
|
||||
],
|
||||
self._generator(
|
||||
pslist.PsList.list_tasks(
|
||||
|
||||
@@ -36,6 +36,11 @@ class ModuleExtract(interfaces.plugins.PluginInterface):
|
||||
description="Base virtual address to reconstruct an ELF file",
|
||||
optional=False,
|
||||
),
|
||||
requirements.VersionRequirement(
|
||||
name="linux_utilities_module_extract",
|
||||
version=(1, 0, 0),
|
||||
component=linux_utilities_module_extract.ModuleExtract,
|
||||
),
|
||||
]
|
||||
|
||||
def _generator(self):
|
||||
|
||||
@@ -6,9 +6,9 @@ from typing import List, Dict, Iterator
|
||||
|
||||
import volatility3.framework.symbols.linux.utilities.modules as linux_utilities_modules
|
||||
|
||||
from volatility3.framework import interfaces, deprecation
|
||||
from volatility3.framework import interfaces, deprecation, renderers
|
||||
from volatility3.framework.configuration import requirements
|
||||
from volatility3.framework.renderers import format_hints, TreeGrid, NotAvailableValue
|
||||
from volatility3.framework.renderers import format_hints
|
||||
from volatility3.framework.symbols.linux import extensions
|
||||
from volatility3.framework.constants import architectures
|
||||
from volatility3.framework.symbols.linux.utilities import tainting
|
||||
@@ -156,12 +156,12 @@ spot modules presence and taints."""
|
||||
yield (
|
||||
0,
|
||||
(
|
||||
module.get_name() or NotAvailableValue(),
|
||||
module.get_name() or renderers.NotAvailableValue(),
|
||||
format_hints.Hex(module_offset),
|
||||
linux_utilities_modules.ModuleGathererLsmod.name in gatherers,
|
||||
linux_utilities_modules.ModuleGathererSysFs.name in gatherers,
|
||||
linux_utilities_modules.ModuleGathererScanner.name in gatherers,
|
||||
taints or NotAvailableValue(),
|
||||
taints or renderers.NotAvailableValue(),
|
||||
),
|
||||
)
|
||||
|
||||
@@ -175,7 +175,7 @@ spot modules presence and taints."""
|
||||
("Taints", str),
|
||||
]
|
||||
|
||||
return TreeGrid(
|
||||
return renderers.TreeGrid(
|
||||
columns,
|
||||
self._generator(),
|
||||
)
|
||||
|
||||
@@ -41,6 +41,11 @@ class PsScan(interfaces.plugins.PluginInterface):
|
||||
requirements.VersionRequirement(
|
||||
name="pslist", component=pslist.PsList, version=(4, 0, 0)
|
||||
),
|
||||
requirements.VersionRequirement(
|
||||
name="multi_string_scanner",
|
||||
component=scanners.MultiStringScanner,
|
||||
version=(1, 0, 0),
|
||||
),
|
||||
]
|
||||
|
||||
def _generator(self):
|
||||
|
||||
@@ -5,8 +5,8 @@
|
||||
import logging
|
||||
from typing import Callable, Tuple, List, Dict
|
||||
|
||||
from volatility3.framework import interfaces, exceptions, constants, objects
|
||||
from volatility3.framework.renderers import TreeGrid, NotAvailableValue, format_hints
|
||||
from volatility3.framework import interfaces, exceptions, constants, objects, renderers
|
||||
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
|
||||
@@ -44,7 +44,7 @@ class SockHandlers(interfaces.configuration.VersionableInterface):
|
||||
try:
|
||||
netns_id = task.nsproxy.net_ns.get_inode()
|
||||
except AttributeError:
|
||||
netns_id = NotAvailableValue()
|
||||
netns_id = renderers.NotAvailableValue()
|
||||
|
||||
self._netdevices = self._build_network_devices_map(netns_id)
|
||||
|
||||
@@ -79,7 +79,7 @@ class SockHandlers(interfaces.configuration.VersionableInterface):
|
||||
)
|
||||
for net_dev in net.dev_base_head.to_list(net_device_symname, "dev_list"):
|
||||
if (
|
||||
isinstance(netns_id, NotAvailableValue)
|
||||
isinstance(netns_id, renderers.NotAvailableValue)
|
||||
or net.get_inode() != netns_id
|
||||
):
|
||||
continue
|
||||
@@ -263,7 +263,7 @@ class SockHandlers(interfaces.configuration.VersionableInterface):
|
||||
# Kernel >= 3.7.10
|
||||
src_port = netlink_sock.get_portid()
|
||||
except AttributeError:
|
||||
src_port = NotAvailableValue()
|
||||
src_port = renderers.NotAvailableValue()
|
||||
|
||||
dst_addr = f"group:0x{netlink_sock.dst_group:08x}"
|
||||
module = netlink_sock.module
|
||||
@@ -273,7 +273,7 @@ class SockHandlers(interfaces.configuration.VersionableInterface):
|
||||
try:
|
||||
dst_port = netlink_sock.get_dst_portid()
|
||||
except AttributeError:
|
||||
dst_port = NotAvailableValue()
|
||||
dst_port = renderers.NotAvailableValue()
|
||||
|
||||
state = netlink_sock.get_state()
|
||||
|
||||
@@ -571,7 +571,7 @@ class Sockstat(plugins.PluginInterface):
|
||||
try:
|
||||
netns_id = net.get_inode()
|
||||
except AttributeError:
|
||||
netns_id = NotAvailableValue()
|
||||
netns_id = renderers.NotAvailableValue()
|
||||
|
||||
yield task, netns_id, fd_num, family, sock_type, protocol, sock_fields
|
||||
|
||||
@@ -586,10 +586,11 @@ class Sockstat(plugins.PluginInterface):
|
||||
`sock_stat` and `protocol` formatted.
|
||||
"""
|
||||
sock_stat = [
|
||||
NotAvailableValue() if field is None else str(field) for field in sock_stat
|
||||
renderers.NotAvailableValue() if field is None else str(field)
|
||||
for field in sock_stat
|
||||
]
|
||||
if protocol is None:
|
||||
protocol = NotAvailableValue()
|
||||
protocol = renderers.NotAvailableValue()
|
||||
|
||||
return tuple(sock_stat), protocol
|
||||
|
||||
@@ -641,7 +642,7 @@ class Sockstat(plugins.PluginInterface):
|
||||
socket_filter_str = (
|
||||
",".join(f"{k}={v}" for k, v in extended.items())
|
||||
if extended
|
||||
else NotAvailableValue()
|
||||
else renderers.NotAvailableValue()
|
||||
)
|
||||
|
||||
task_comm = utility.array_to_string(task.comm)
|
||||
@@ -685,6 +686,6 @@ class Sockstat(plugins.PluginInterface):
|
||||
("Filter", str),
|
||||
]
|
||||
|
||||
return TreeGrid(
|
||||
return renderers.TreeGrid(
|
||||
tree_grid_args, self._generator(pids, netns_id, kernel_module_name)
|
||||
)
|
||||
|
||||
@@ -10,9 +10,9 @@ from enum import Enum
|
||||
from dataclasses import dataclass
|
||||
|
||||
import volatility3.framework.symbols.linux.utilities.modules as linux_utilities_modules
|
||||
from volatility3.framework import constants, exceptions, interfaces
|
||||
from volatility3.framework import constants, exceptions, interfaces, renderers
|
||||
from volatility3.framework.configuration import requirements
|
||||
from volatility3.framework.renderers import format_hints, TreeGrid, NotAvailableValue
|
||||
from volatility3.framework.renderers import format_hints
|
||||
from volatility3.framework.constants import architectures
|
||||
|
||||
vollog = logging.getLogger(__name__)
|
||||
@@ -239,14 +239,14 @@ class CheckFtrace(interfaces.plugins.PluginInterface):
|
||||
):
|
||||
formatted_results = (
|
||||
format_hints.Hex(ftrace_ops_parsed.ftrace_ops_offset),
|
||||
ftrace_ops_parsed.callback_symbol or NotAvailableValue(),
|
||||
ftrace_ops_parsed.callback_symbol or renderers.NotAvailableValue(),
|
||||
format_hints.Hex(ftrace_ops_parsed.callback_address),
|
||||
ftrace_ops_parsed.hooked_symbols or NotAvailableValue(),
|
||||
ftrace_ops_parsed.module_name or NotAvailableValue(),
|
||||
ftrace_ops_parsed.hooked_symbols or renderers.NotAvailableValue(),
|
||||
ftrace_ops_parsed.module_name or renderers.NotAvailableValue(),
|
||||
(
|
||||
format_hints.Hex(ftrace_ops_parsed.module_address)
|
||||
if ftrace_ops_parsed.module_address is not None
|
||||
else NotAvailableValue()
|
||||
else renderers.NotAvailableValue()
|
||||
),
|
||||
)
|
||||
if self.config["show_ftrace_flags"]:
|
||||
@@ -266,7 +266,7 @@ class CheckFtrace(interfaces.plugins.PluginInterface):
|
||||
if self.config.get("show_ftrace_flags"):
|
||||
columns.append(("Flags", str))
|
||||
|
||||
return TreeGrid(
|
||||
return renderers.TreeGrid(
|
||||
columns,
|
||||
self._generator(),
|
||||
)
|
||||
|
||||
@@ -5,15 +5,15 @@
|
||||
# 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 Iterable, List, Optional
|
||||
from dataclasses import dataclass
|
||||
from typing import Iterable, List, Optional
|
||||
|
||||
import volatility3.framework.symbols.linux.utilities.modules as linux_utilities_modules
|
||||
from volatility3.framework import constants, exceptions, interfaces
|
||||
from volatility3.framework import constants, exceptions, interfaces, renderers
|
||||
from volatility3.framework.configuration import requirements
|
||||
from volatility3.framework.renderers import format_hints, NotAvailableValue, TreeGrid
|
||||
from volatility3.framework.objects import utility
|
||||
from volatility3.framework.constants import architectures
|
||||
from volatility3.framework.objects import utility
|
||||
from volatility3.framework.renderers import format_hints
|
||||
|
||||
vollog = logging.getLogger(__name__)
|
||||
|
||||
@@ -250,14 +250,14 @@ class CheckTracepoints(interfaces.plugins.PluginInterface):
|
||||
formatted_results = (
|
||||
tracepoint_parsed.tracepoint_name,
|
||||
format_hints.Hex(tracepoint_parsed.tracepoint_address),
|
||||
tracepoint_parsed.probe_name or NotAvailableValue(),
|
||||
tracepoint_parsed.probe_name or renderers.NotAvailableValue(),
|
||||
format_hints.Hex(tracepoint_parsed.probe_address),
|
||||
tracepoint_parsed.probe_priority or NotAvailableValue(),
|
||||
tracepoint_parsed.module_name or NotAvailableValue(),
|
||||
tracepoint_parsed.probe_priority or renderers.NotAvailableValue(),
|
||||
tracepoint_parsed.module_name or renderers.NotAvailableValue(),
|
||||
(
|
||||
format_hints.Hex(tracepoint_parsed.module_address)
|
||||
if tracepoint_parsed.module_address is not None
|
||||
else NotAvailableValue()
|
||||
else renderers.NotAvailableValue()
|
||||
),
|
||||
)
|
||||
yield (
|
||||
@@ -276,7 +276,7 @@ class CheckTracepoints(interfaces.plugins.PluginInterface):
|
||||
("Module address", format_hints.Hex),
|
||||
]
|
||||
|
||||
return TreeGrid(
|
||||
return renderers.TreeGrid(
|
||||
columns,
|
||||
self._generator(),
|
||||
)
|
||||
|
||||
@@ -46,6 +46,11 @@ class VmaRegExScan(plugins.PluginInterface):
|
||||
requirements.StringRequirement(
|
||||
name="pattern", description="RegEx pattern", optional=False
|
||||
),
|
||||
requirements.VersionRequirement(
|
||||
name="regex_scanner",
|
||||
component=scanners.RegExScanner,
|
||||
version=(1, 0, 0),
|
||||
),
|
||||
requirements.IntRequirement(
|
||||
name="maxsize",
|
||||
description="Maximum size in bytes for displayed context",
|
||||
|
||||
@@ -17,8 +17,8 @@ vollog = logging.getLogger(__name__)
|
||||
class VmaYaraScan(interfaces.plugins.PluginInterface):
|
||||
"""Scans all virtual memory areas for tasks using yara."""
|
||||
|
||||
_required_framework_version = (2, 4, 0)
|
||||
_version = (1, 0, 3)
|
||||
_required_framework_version = (2, 22, 0)
|
||||
_version = (1, 0, 4)
|
||||
|
||||
@classmethod
|
||||
def get_requirements(cls) -> List[interfaces.configuration.RequirementInterface]:
|
||||
@@ -97,12 +97,18 @@ class VmaYaraScan(interfaces.plugins.PluginInterface):
|
||||
for offset, rule_name, name, value in scanner(
|
||||
proc_layer.read(start, size, pad=True), start
|
||||
):
|
||||
layer_data = renderers.LayerData(
|
||||
context=self.context,
|
||||
offset=offset,
|
||||
layer_name=proc_layer.name,
|
||||
length=len(value),
|
||||
)
|
||||
yield 0, (
|
||||
format_hints.Hex(offset),
|
||||
task.tgid,
|
||||
rule_name,
|
||||
name,
|
||||
value,
|
||||
layer_data,
|
||||
)
|
||||
|
||||
@classmethod
|
||||
@@ -130,7 +136,7 @@ class VmaYaraScan(interfaces.plugins.PluginInterface):
|
||||
("PID", int),
|
||||
("Rule", str),
|
||||
("Component", str),
|
||||
("Value", bytes),
|
||||
("Value", renderers.LayerData),
|
||||
],
|
||||
self._generator(),
|
||||
)
|
||||
|
||||
@@ -12,7 +12,7 @@ from volatility3.framework.configuration import requirements
|
||||
from volatility3.framework.interfaces import plugins
|
||||
from volatility3.framework.layers import scanners
|
||||
from volatility3.framework.objects import utility
|
||||
from volatility3.framework.symbols.linux.bash import BashIntermedSymbols
|
||||
from volatility3.framework.symbols.linux import bash
|
||||
from volatility3.plugins import timeliner
|
||||
from volatility3.plugins.mac import pslist
|
||||
|
||||
@@ -38,6 +38,16 @@ class Bash(plugins.PluginInterface, timeliner.TimeLinerInterface):
|
||||
component=timeliner.TimeLinerInterface,
|
||||
version=(1, 0, 0),
|
||||
),
|
||||
requirements.VersionRequirement(
|
||||
name="multi_string_scanner",
|
||||
component=scanners.MultiStringScanner,
|
||||
version=(1, 0, 0),
|
||||
),
|
||||
requirements.VersionRequirement(
|
||||
name="bytes_scanner",
|
||||
component=scanners.BytesScanner,
|
||||
version=(1, 0, 0),
|
||||
),
|
||||
requirements.ListRequirement(
|
||||
name="pid",
|
||||
description="Filter on specific process IDs",
|
||||
@@ -58,7 +68,7 @@ class Bash(plugins.PluginInterface, timeliner.TimeLinerInterface):
|
||||
pack_format = "Q"
|
||||
bash_json_file = "bash64"
|
||||
|
||||
bash_table_name = BashIntermedSymbols.create(
|
||||
bash_table_name = bash.BashIntermedSymbols.create(
|
||||
self.context, self.config_path, "linux", bash_json_file
|
||||
)
|
||||
|
||||
|
||||
@@ -31,6 +31,11 @@ class List_Files(plugins.PluginInterface):
|
||||
requirements.VersionRequirement(
|
||||
name="mount", component=mount.Mount, version=(2, 0, 0)
|
||||
),
|
||||
requirements.VersionRequirement(
|
||||
name="mac_utilities",
|
||||
component=mac.MacUtilities,
|
||||
version=(1, 3, 0),
|
||||
),
|
||||
]
|
||||
|
||||
@classmethod
|
||||
|
||||
@@ -13,7 +13,7 @@ from volatility3.plugins.mac import pslist
|
||||
class Malfind(interfaces.plugins.PluginInterface):
|
||||
"""Lists process memory ranges that potentially contain injected code."""
|
||||
|
||||
_required_framework_version = (2, 0, 0)
|
||||
_required_framework_version = (2, 0, 1)
|
||||
|
||||
@classmethod
|
||||
def get_requirements(cls):
|
||||
@@ -68,9 +68,7 @@ class Malfind(interfaces.plugins.PluginInterface):
|
||||
else:
|
||||
architecture = "intel64"
|
||||
|
||||
disasm = interfaces.renderers.Disassembly(
|
||||
data, vma.links.start, architecture
|
||||
)
|
||||
disasm = renderers.Disassembly(data, vma.links.start, architecture)
|
||||
|
||||
yield (
|
||||
0,
|
||||
@@ -99,7 +97,7 @@ class Malfind(interfaces.plugins.PluginInterface):
|
||||
("End", format_hints.Hex),
|
||||
("Protection", str),
|
||||
("Hexdump", format_hints.HexBytes),
|
||||
("Disasm", interfaces.renderers.Disassembly),
|
||||
("Disasm", renderers.Disassembly),
|
||||
],
|
||||
self._generator(
|
||||
list_tasks(self.context, self.config["kernel"], filter_func=filter_func)
|
||||
|
||||
@@ -39,6 +39,11 @@ class RegExScan(plugins.PluginInterface):
|
||||
default=cls.MAXSIZE_DEFAULT,
|
||||
optional=True,
|
||||
),
|
||||
requirements.VersionRequirement(
|
||||
name="regex_scanner",
|
||||
component=scanners.RegExScanner,
|
||||
version=(1, 0, 0),
|
||||
),
|
||||
]
|
||||
|
||||
def _generator(self, regex_pattern):
|
||||
|
||||
@@ -26,6 +26,8 @@ class VMCSTest(enum.IntFlag):
|
||||
|
||||
|
||||
class PageStartScanner(interfaces.layers.ScannerInterface):
|
||||
_version = (1, 0, 0)
|
||||
|
||||
def __init__(self, signatures: List[bytes], page_size: int = 0x1000):
|
||||
super().__init__()
|
||||
if not len(signatures):
|
||||
@@ -69,6 +71,11 @@ class Vmscan(plugins.PluginInterface):
|
||||
requirements.TranslationLayerRequirement(
|
||||
name="primary", description="Physical base memory layer"
|
||||
),
|
||||
requirements.VersionRequirement(
|
||||
name="page_start_scanner",
|
||||
component=PageStartScanner,
|
||||
version=(1, 0, 0),
|
||||
),
|
||||
requirements.IntRequirement(
|
||||
name="log-threshold",
|
||||
description="Number of criteria failed to log to debug output",
|
||||
|
||||
@@ -41,6 +41,11 @@ class CmdScan(interfaces.plugins.PluginInterface):
|
||||
requirements.VersionRequirement(
|
||||
name="consoles", component=consoles.Consoles, version=(3, 0, 0)
|
||||
),
|
||||
requirements.VersionRequirement(
|
||||
name="bytes_scanner",
|
||||
component=scanners.BytesScanner,
|
||||
version=(1, 0, 0),
|
||||
),
|
||||
requirements.BooleanRequirement(
|
||||
name="no_registry",
|
||||
description="Don't search the registry for possible values of CommandHistorySize",
|
||||
|
||||
@@ -54,6 +54,11 @@ class Consoles(interfaces.plugins.PluginInterface):
|
||||
requirements.VersionRequirement(
|
||||
name="hivelist", component=hivelist.HiveList, version=(2, 0, 0)
|
||||
),
|
||||
requirements.VersionRequirement(
|
||||
name="bytes_scanner",
|
||||
component=scanners.BytesScanner,
|
||||
version=(1, 0, 0),
|
||||
),
|
||||
requirements.BooleanRequirement(
|
||||
name="no_registry",
|
||||
description="Don't search the registry for possible values of CommandHistorySize and HistoryBufferMax",
|
||||
|
||||
@@ -7,9 +7,14 @@ import ntpath
|
||||
import re
|
||||
from typing import List, Tuple, Type, Optional, Generator
|
||||
|
||||
from volatility3.framework import interfaces, renderers, exceptions, constants
|
||||
from volatility3.framework import (
|
||||
interfaces,
|
||||
exceptions,
|
||||
constants,
|
||||
renderers,
|
||||
)
|
||||
from volatility3.framework.configuration import requirements
|
||||
from volatility3.framework.renderers import format_hints, UnreadableValue
|
||||
from volatility3.framework.renderers import format_hints
|
||||
from volatility3.plugins.windows import handles
|
||||
from volatility3.plugins.windows import pslist
|
||||
|
||||
@@ -258,7 +263,7 @@ class DumpFiles(interfaces.plugins.PluginInterface):
|
||||
|
||||
if file_re:
|
||||
name = file_obj.file_name_with_device()
|
||||
if isinstance(name, UnreadableValue):
|
||||
if isinstance(name, renderers.UnreadableValue):
|
||||
continue
|
||||
if not file_re.search(name):
|
||||
continue
|
||||
@@ -298,7 +303,7 @@ class DumpFiles(interfaces.plugins.PluginInterface):
|
||||
|
||||
if file_re:
|
||||
name = file_obj.file_name_with_device()
|
||||
if isinstance(name, UnreadableValue):
|
||||
if isinstance(name, renderers.UnreadableValue):
|
||||
continue
|
||||
if not file_re.search(name):
|
||||
continue
|
||||
|
||||
@@ -3,12 +3,11 @@
|
||||
#
|
||||
|
||||
import time
|
||||
from typing import List, Tuple, Iterable
|
||||
from typing import Iterable, List, Tuple
|
||||
|
||||
from volatility3.framework import constants, interfaces, layers, symbols
|
||||
from volatility3.framework import constants, interfaces, layers, renderers, symbols
|
||||
from volatility3.framework.configuration import requirements
|
||||
from volatility3.framework.interfaces import plugins
|
||||
from volatility3.framework.renderers import TreeGrid
|
||||
from volatility3.framework.symbols import intermed
|
||||
from volatility3.framework.symbols.windows.extensions import kdbg, pe
|
||||
|
||||
@@ -294,4 +293,6 @@ class Info(plugins.PluginInterface):
|
||||
)
|
||||
|
||||
def run(self):
|
||||
return TreeGrid([("Variable", str), ("Value", str)], self._generator())
|
||||
return renderers.TreeGrid(
|
||||
[("Variable", str), ("Value", str)], self._generator()
|
||||
)
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
# which is available at https://www.volatilityfoundation.org/license/vsl-v1.0
|
||||
#
|
||||
import logging
|
||||
from typing import Iterable, Tuple
|
||||
from typing import Iterable, Generator, Tuple
|
||||
|
||||
from volatility3.framework import interfaces, symbols, exceptions
|
||||
from volatility3.framework import renderers
|
||||
@@ -17,7 +17,8 @@ vollog = logging.getLogger(__name__)
|
||||
class Malfind(interfaces.plugins.PluginInterface):
|
||||
"""Lists process memory ranges that potentially contain injected code."""
|
||||
|
||||
_required_framework_version = (2, 4, 0)
|
||||
_required_framework_version = (2, 22, 0)
|
||||
_version = (1, 1, 0)
|
||||
|
||||
@classmethod
|
||||
def get_requirements(cls):
|
||||
@@ -88,6 +89,25 @@ class Malfind(interfaces.plugins.PluginInterface):
|
||||
symbol_table: str,
|
||||
proc: interfaces.objects.ObjectInterface,
|
||||
) -> Iterable[Tuple[interfaces.objects.ObjectInterface, bytes]]:
|
||||
for vad, data_object in cls.list_injection_sites(
|
||||
context, kernel_layer_name, symbol_table, proc
|
||||
):
|
||||
yield vad, data_object.context.layers[data_object.layer_name].read(
|
||||
data_object.offset, data_object.length
|
||||
)
|
||||
|
||||
@classmethod
|
||||
def list_injection_sites(
|
||||
cls,
|
||||
context: interfaces.context.ContextInterface,
|
||||
kernel_layer_name: str,
|
||||
symbol_table: str,
|
||||
proc: interfaces.objects.ObjectInterface,
|
||||
) -> Generator[
|
||||
Tuple[interfaces.objects.ObjectInterface, renderers.LayerData],
|
||||
None,
|
||||
None,
|
||||
]:
|
||||
"""Generate memory regions for a process that may contain injected
|
||||
code.
|
||||
|
||||
@@ -156,8 +176,16 @@ class Malfind(interfaces.plugins.PluginInterface):
|
||||
vollog.warning(
|
||||
f"[proc_id {proc_id}] Found suspicious DIRTY + {protection_string} page at {hex(dirty_page)}",
|
||||
)
|
||||
data = proc_layer.read(vad.get_start(), 64, pad=True)
|
||||
yield vad, data
|
||||
start = vad.get_start()
|
||||
length = 64
|
||||
data = renderers.LayerData(
|
||||
context=context,
|
||||
layer_name=proc_layer_name,
|
||||
offset=start,
|
||||
length=length,
|
||||
no_surrounding=True,
|
||||
)
|
||||
yield (vad, data)
|
||||
|
||||
def _generator(self, procs):
|
||||
# determine if we're on a 32 or 64 bit kernel
|
||||
@@ -166,7 +194,7 @@ class Malfind(interfaces.plugins.PluginInterface):
|
||||
# set refined criteria to know when to add to "Notes" column
|
||||
refined_criteria = {
|
||||
b"MZ": "MZ header",
|
||||
b"\x55\x8B": "PE header",
|
||||
b"\x55\x8b": "PE header",
|
||||
b"\x55\x48": "Function prologue",
|
||||
b"\x55\x89": "Function prologue",
|
||||
}
|
||||
@@ -179,11 +207,14 @@ class Malfind(interfaces.plugins.PluginInterface):
|
||||
# by default, "Notes" column will be set to N/A
|
||||
process_name = utility.array_to_string(proc.ImageFileName)
|
||||
|
||||
for vad, data in self.list_injections(
|
||||
for vad, data_object in self.list_injection_sites(
|
||||
self.context, kernel.layer_name, kernel.symbol_table_name, proc
|
||||
):
|
||||
notes = renderers.NotApplicableValue()
|
||||
# Check for unique headers and update "Notes" column if criteria is met
|
||||
data = data_object.context.layers[data_object.layer_name].read(
|
||||
data_object.offset, data_object.length, True
|
||||
)
|
||||
if data[0:2] in refined_criteria:
|
||||
notes = refined_criteria[data[0:2]]
|
||||
|
||||
@@ -193,9 +224,7 @@ class Malfind(interfaces.plugins.PluginInterface):
|
||||
else:
|
||||
architecture = "intel64"
|
||||
|
||||
disasm = interfaces.renderers.Disassembly(
|
||||
data, vad.get_start(), architecture
|
||||
)
|
||||
disasm = renderers.Disassembly(data, vad.get_start(), architecture)
|
||||
|
||||
file_output = "Disabled"
|
||||
if self.config["dump"]:
|
||||
@@ -231,7 +260,7 @@ class Malfind(interfaces.plugins.PluginInterface):
|
||||
vad.get_private_memory(),
|
||||
file_output,
|
||||
notes,
|
||||
format_hints.HexBytes(data),
|
||||
data_object,
|
||||
disasm,
|
||||
),
|
||||
)
|
||||
@@ -251,8 +280,8 @@ class Malfind(interfaces.plugins.PluginInterface):
|
||||
("PrivateMemory", int),
|
||||
("File output", str),
|
||||
("Notes", str),
|
||||
("Hexdump", format_hints.HexBytes),
|
||||
("Disasm", interfaces.renderers.Disassembly),
|
||||
("Hexdump", renderers.LayerData),
|
||||
("Disasm", renderers.Disassembly),
|
||||
],
|
||||
self._generator(
|
||||
pslist.PsList.list_processes(
|
||||
|
||||
@@ -20,8 +20,8 @@ vollog = logging.getLogger(__name__)
|
||||
class MBRScan(interfaces.plugins.PluginInterface):
|
||||
"""Scans for and parses potential Master Boot Records (MBRs)"""
|
||||
|
||||
_required_framework_version = (2, 0, 1)
|
||||
_version = (1, 0, 0)
|
||||
_required_framework_version = (2, 22, 0)
|
||||
_version = (1, 0, 1)
|
||||
|
||||
@classmethod
|
||||
def get_requirements(cls) -> List[interfaces.configuration.RequirementInterface]:
|
||||
@@ -37,6 +37,11 @@ class MBRScan(interfaces.plugins.PluginInterface):
|
||||
default=False,
|
||||
optional=True,
|
||||
),
|
||||
requirements.VersionRequirement(
|
||||
name="multi_string_scanner",
|
||||
component=scanners.MultiStringScanner,
|
||||
version=(1, 0, 0),
|
||||
),
|
||||
]
|
||||
|
||||
@classmethod
|
||||
@@ -74,7 +79,7 @@ class MBRScan(interfaces.plugins.PluginInterface):
|
||||
partition_table_object = symbol_table + constants.BANG + "PARTITION_TABLE"
|
||||
|
||||
# Define Signature and Data Length
|
||||
mbr_signature = b"\x55\xAA"
|
||||
mbr_signature = b"\x55\xaa"
|
||||
mbr_length = 0x200
|
||||
bootcode_length = 0x1B8
|
||||
|
||||
@@ -120,9 +125,7 @@ class MBRScan(interfaces.plugins.PluginInterface):
|
||||
renderers.NotApplicableValue(),
|
||||
renderers.NotApplicableValue(),
|
||||
renderers.NotApplicableValue(),
|
||||
interfaces.renderers.Disassembly(
|
||||
bootcode, 0, architecture
|
||||
),
|
||||
renderers.Disassembly(bootcode, 0, architecture),
|
||||
),
|
||||
)
|
||||
else:
|
||||
@@ -146,10 +149,14 @@ class MBRScan(interfaces.plugins.PluginInterface):
|
||||
renderers.NotApplicableValue(),
|
||||
renderers.NotApplicableValue(),
|
||||
renderers.NotApplicableValue(),
|
||||
interfaces.renderers.Disassembly(
|
||||
bootcode, 0, architecture
|
||||
renderers.Disassembly(bootcode, 0, architecture),
|
||||
renderers.LayerData(
|
||||
context=self.context,
|
||||
layer_name=layer.name,
|
||||
offset=mbr_start_offset,
|
||||
length=bootcode_length,
|
||||
no_surrounding=True,
|
||||
),
|
||||
format_hints.HexBytes(bootcode),
|
||||
),
|
||||
)
|
||||
|
||||
@@ -232,7 +239,7 @@ class MBRScan(interfaces.plugins.PluginInterface):
|
||||
("Bootable", bool),
|
||||
("PartitionType", str),
|
||||
("SectorInSize", format_hints.Hex),
|
||||
("Disasm", interfaces.renderers.Disassembly),
|
||||
("Disasm", renderers.Disassembly),
|
||||
],
|
||||
self._generator(),
|
||||
)
|
||||
@@ -256,8 +263,8 @@ class MBRScan(interfaces.plugins.PluginInterface):
|
||||
("EndingCHS", int),
|
||||
("EndingSector", int),
|
||||
("SectorInSize", format_hints.Hex),
|
||||
("Disasm", interfaces.renderers.Disassembly),
|
||||
("Bootcode", format_hints.HexBytes),
|
||||
("Disasm", renderers.Disassembly),
|
||||
("Bootcode", renderers.LayerData),
|
||||
],
|
||||
self._generator(),
|
||||
)
|
||||
|
||||
@@ -231,7 +231,7 @@ class MFTScan(interfaces.plugins.PluginInterface, timeliner.TimeLinerInterface):
|
||||
|
||||
content = attr.get_resident_filecontent()
|
||||
if content:
|
||||
content = format_hints.HexBytes(content)
|
||||
content = renderers.LayerData.from_object(content)
|
||||
else:
|
||||
content = renderers.NotAvailableValue()
|
||||
|
||||
@@ -334,9 +334,9 @@ class MFTScan(interfaces.plugins.PluginInterface, timeliner.TimeLinerInterface):
|
||||
class ADS(interfaces.plugins.PluginInterface):
|
||||
"""Scans for Alternate Data Stream"""
|
||||
|
||||
_required_framework_version = (2, 7, 0)
|
||||
_required_framework_version = (2, 22, 0)
|
||||
|
||||
_version = (1, 0, 1)
|
||||
_version = (1, 0, 2)
|
||||
|
||||
@classmethod
|
||||
def get_requirements(cls):
|
||||
@@ -395,7 +395,7 @@ class ADS(interfaces.plugins.PluginInterface):
|
||||
("MFT Type", str),
|
||||
("Filename", str),
|
||||
("ADS Filename", str),
|
||||
("Hexdump", format_hints.HexBytes),
|
||||
("Hexdump", renderers.LayerData),
|
||||
],
|
||||
self._generator(),
|
||||
)
|
||||
@@ -404,9 +404,9 @@ class ADS(interfaces.plugins.PluginInterface):
|
||||
class ResidentData(interfaces.plugins.PluginInterface):
|
||||
"""Scans for MFT Records with Resident Data"""
|
||||
|
||||
_required_framework_version = (2, 7, 0)
|
||||
_required_framework_version = (2, 22, 0)
|
||||
|
||||
_version = (1, 0, 1)
|
||||
_version = (1, 0, 2)
|
||||
|
||||
@classmethod
|
||||
def get_requirements(cls):
|
||||
@@ -461,7 +461,7 @@ class ResidentData(interfaces.plugins.PluginInterface):
|
||||
("Record Number", int),
|
||||
("MFT Type", str),
|
||||
("Filename", str),
|
||||
("Hexdump", format_hints.HexBytes),
|
||||
("Hexdump", renderers.LayerData),
|
||||
],
|
||||
self._generator(),
|
||||
)
|
||||
|
||||
@@ -17,7 +17,7 @@ from volatility3.framework.symbols import intermed
|
||||
from volatility3.framework.symbols.windows import pdbutil
|
||||
from volatility3.framework.symbols.windows.extensions import pe
|
||||
from volatility3.plugins.windows import pslist, modules
|
||||
from volatility3.framework.constants.windows import KERNEL_MODULE_NAMES
|
||||
from volatility3.framework.constants import windows
|
||||
|
||||
vollog = logging.getLogger(__name__)
|
||||
|
||||
@@ -533,7 +533,7 @@ class PESymbols(interfaces.plugins.PluginInterface):
|
||||
# a `ntoskrnl.exe` can have an internal PDB name of any of the ones in the following list
|
||||
# The code attempts to find all possible PDBs to ensure the best chance of recovery
|
||||
if mod_name == PESymbols.os_module_name:
|
||||
pdb_names = [fn + ".pdb" for fn in KERNEL_MODULE_NAMES]
|
||||
pdb_names = [fn + ".pdb" for fn in windows.KERNEL_MODULE_NAMES]
|
||||
|
||||
# for non-kernel files, replace the exe, sys, or dll extension with pdb
|
||||
else:
|
||||
|
||||
@@ -55,6 +55,8 @@ class PoolConstraint:
|
||||
|
||||
|
||||
class PoolHeaderScanner(interfaces.layers.ScannerInterface):
|
||||
_version = (1, 0, 0)
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
module: interfaces.context.ModuleInterface,
|
||||
@@ -142,6 +144,11 @@ class PoolScanner(plugins.PluginInterface):
|
||||
requirements.VersionRequirement(
|
||||
name="handles", component=handles.Handles, version=(3, 0, 0)
|
||||
),
|
||||
requirements.VersionRequirement(
|
||||
name="pool_header_scanner",
|
||||
component=PoolHeaderScanner,
|
||||
version=(1, 0, 0),
|
||||
),
|
||||
]
|
||||
|
||||
def _generator(self):
|
||||
|
||||
@@ -4,10 +4,10 @@ import string
|
||||
from itertools import chain
|
||||
from typing import Dict, Iterable, List
|
||||
|
||||
from volatility3.framework import constants, exceptions
|
||||
from volatility3.framework import constants, exceptions, renderers
|
||||
from volatility3.framework.configuration import requirements
|
||||
from volatility3.framework.interfaces import plugins
|
||||
from volatility3.framework.renderers import TreeGrid, format_hints
|
||||
from volatility3.framework.renderers import format_hints
|
||||
from volatility3.framework.symbols.windows import extensions
|
||||
from volatility3.plugins.windows import (
|
||||
handles,
|
||||
@@ -231,7 +231,7 @@ We recommend using -r pretty if you are looking at this plugin's output in a ter
|
||||
offset_type = "(Physical)" if self.config["physical-offsets"] else "(Virtual)"
|
||||
offset_str = "Offset" + offset_type
|
||||
|
||||
return TreeGrid(
|
||||
return renderers.TreeGrid(
|
||||
[
|
||||
(offset_str, format_hints.Hex),
|
||||
("Name", str),
|
||||
|
||||
@@ -11,8 +11,7 @@ from Crypto.Cipher import AES, ARC4, DES
|
||||
|
||||
from volatility3.framework import interfaces, renderers, exceptions, constants
|
||||
from volatility3.framework.configuration import requirements
|
||||
from volatility3.framework.exceptions import InvalidAddressException
|
||||
from volatility3.framework.layers import registry as registrylayer
|
||||
from volatility3.framework.layers import registry as registry_layer
|
||||
from volatility3.framework.symbols.windows.extensions import registry
|
||||
from volatility3.plugins.windows.registry import hivelist
|
||||
|
||||
@@ -329,13 +328,13 @@ class Hashdump(interfaces.plugins.PluginInterface):
|
||||
|
||||
@classmethod
|
||||
def get_hive_key(
|
||||
cls, hive: registry.RegistryHive, key: str
|
||||
cls, hive: registry_layer.RegistryHive, key: str
|
||||
) -> Optional["registry.CM_KEY_NODE"]:
|
||||
result = None
|
||||
try:
|
||||
if hive:
|
||||
result = hive.get_key(key)
|
||||
except (KeyError, registrylayer.RegistryException):
|
||||
except (KeyError, registry_layer.RegistryException):
|
||||
vollog.info(
|
||||
f"Unable to load the required registry key {hive.get_name()}\\{key} from this memory image"
|
||||
)
|
||||
@@ -343,7 +342,7 @@ class Hashdump(interfaces.plugins.PluginInterface):
|
||||
|
||||
@classmethod
|
||||
def get_user_keys(
|
||||
cls, samhive: registry.RegistryHive
|
||||
cls, samhive: registry_layer.RegistryHive
|
||||
) -> List[interfaces.objects.ObjectInterface]:
|
||||
user_key_path = "SAM\\Domains\\Account\\Users"
|
||||
|
||||
@@ -354,7 +353,7 @@ class Hashdump(interfaces.plugins.PluginInterface):
|
||||
return [k for k in user_key.get_subkeys() if k.Name != "Names"]
|
||||
|
||||
@classmethod
|
||||
def get_bootkey(cls, syshive: registry.RegistryHive) -> Optional[bytes]:
|
||||
def get_bootkey(cls, syshive: registry_layer.RegistryHive) -> Optional[bytes]:
|
||||
"""
|
||||
Returns the scrambled bootkey necesary to decrypt hashes
|
||||
"""
|
||||
@@ -382,8 +381,8 @@ class Hashdump(interfaces.plugins.PluginInterface):
|
||||
return None
|
||||
bootkey += class_data.decode("utf-16-le")
|
||||
except (
|
||||
InvalidAddressException,
|
||||
registrylayer.RegistryException,
|
||||
exceptions.InvalidAddressException,
|
||||
registry_layer.RegistryException,
|
||||
) as excp:
|
||||
vollog.log(
|
||||
constants.LOGLEVEL_VVV, f"Unable to read Lsa key {lk}: {excp}"
|
||||
@@ -398,7 +397,7 @@ class Hashdump(interfaces.plugins.PluginInterface):
|
||||
|
||||
@classmethod
|
||||
def get_hbootkey(
|
||||
cls, samhive: registry.RegistryHive, bootkey: bytes
|
||||
cls, samhive: registry_layer.RegistryHive, bootkey: bytes
|
||||
) -> Optional[bytes]:
|
||||
sam_account_path = "SAM\\Domains\\Account"
|
||||
|
||||
@@ -456,7 +455,10 @@ class Hashdump(interfaces.plugins.PluginInterface):
|
||||
|
||||
@classmethod
|
||||
def get_user_hashes(
|
||||
cls, user: registry.CM_KEY_NODE, samhive: registry.RegistryHive, hbootkey: bytes
|
||||
cls,
|
||||
user: registry.CM_KEY_NODE,
|
||||
samhive: registry_layer.RegistryHive,
|
||||
hbootkey: bytes,
|
||||
) -> Optional[Tuple[bytes, bytes]]:
|
||||
## Will sometimes find extra user with rid = NAMES, returns empty strings right now
|
||||
try:
|
||||
@@ -470,7 +472,7 @@ class Hashdump(interfaces.plugins.PluginInterface):
|
||||
sam_data = samhive.read(v.Data + 4, v.DataLength)
|
||||
except (
|
||||
exceptions.InvalidAddressException,
|
||||
registrylayer.RegistryException,
|
||||
registry_layer.RegistryException,
|
||||
):
|
||||
return None
|
||||
|
||||
@@ -570,7 +572,7 @@ class Hashdump(interfaces.plugins.PluginInterface):
|
||||
|
||||
@classmethod
|
||||
def get_user_name(
|
||||
cls, user: registry.CM_KEY_NODE, samhive: registry.RegistryHive
|
||||
cls, user: registry.CM_KEY_NODE, samhive: registry_layer.RegistryHive
|
||||
) -> Optional[bytes]:
|
||||
value = None
|
||||
for v in user.get_values():
|
||||
@@ -593,7 +595,7 @@ class Hashdump(interfaces.plugins.PluginInterface):
|
||||
|
||||
# replaces the dump_hashes method in vol2
|
||||
def _generator(
|
||||
self, syshive: registry.RegistryHive, samhive: registry.RegistryHive
|
||||
self, syshive: registry_layer.RegistryHive, samhive: registry_layer.RegistryHive
|
||||
):
|
||||
if syshive is None:
|
||||
vollog.debug("SYSTEM address is None: No system hive found")
|
||||
|
||||
@@ -10,9 +10,8 @@ from Crypto.Cipher import ARC4, DES, AES
|
||||
|
||||
from volatility3.framework import interfaces, renderers, exceptions
|
||||
from volatility3.framework.configuration import requirements
|
||||
from volatility3.framework.exceptions import InvalidAddressException
|
||||
|
||||
from volatility3.framework.layers import registry
|
||||
from volatility3.framework.layers import registry as registry_layer
|
||||
from volatility3.framework.symbols.windows import versions
|
||||
from volatility3.plugins.windows.registry import hashdump, hivelist
|
||||
from volatility3.framework.renderers import format_hints
|
||||
@@ -65,7 +64,7 @@ class Lsadump(interfaces.plugins.PluginInterface):
|
||||
|
||||
@classmethod
|
||||
def get_lsa_key(
|
||||
cls, sechive: registry.RegistryHive, bootkey: bytes, vista_or_later: bool
|
||||
cls, sechive: registry_layer.RegistryHive, bootkey: bytes, vista_or_later: bool
|
||||
) -> Optional[bytes]:
|
||||
if not bootkey:
|
||||
return None
|
||||
@@ -109,7 +108,7 @@ class Lsadump(interfaces.plugins.PluginInterface):
|
||||
@classmethod
|
||||
def get_secret_by_name(
|
||||
cls,
|
||||
sechive: registry.RegistryHive,
|
||||
sechive: registry_layer.RegistryHive,
|
||||
name: str,
|
||||
lsakey: bytes,
|
||||
is_vista_or_later: bool,
|
||||
@@ -123,8 +122,8 @@ class Lsadump(interfaces.plugins.PluginInterface):
|
||||
try:
|
||||
enc_secret_value = next(enc_secret_key.get_values(), None)
|
||||
except (
|
||||
InvalidAddressException,
|
||||
registry.RegistryException,
|
||||
exceptions.InvalidAddressException,
|
||||
registry_layer.RegistryException,
|
||||
):
|
||||
enc_secret_value = None
|
||||
|
||||
@@ -171,7 +170,9 @@ class Lsadump(interfaces.plugins.PluginInterface):
|
||||
return decrypted_data[8 : 8 + dec_data_len]
|
||||
|
||||
def _generator(
|
||||
self, syshive: registry.RegistryHive, sechive: registry.RegistryHive
|
||||
self,
|
||||
syshive: registry_layer.RegistryHive,
|
||||
sechive: registry_layer.RegistryHive,
|
||||
):
|
||||
kernel = self.context.modules[self.config["kernel"]]
|
||||
|
||||
@@ -206,8 +207,8 @@ class Lsadump(interfaces.plugins.PluginInterface):
|
||||
enc_secret_value = next(sec_val_key.get_values(), None)
|
||||
except (
|
||||
StopIteration,
|
||||
InvalidAddressException,
|
||||
registry.RegistryException,
|
||||
exceptions.InvalidAddressException,
|
||||
registry_layer.RegistryException,
|
||||
):
|
||||
enc_secret_value = None
|
||||
|
||||
@@ -229,8 +230,8 @@ class Lsadump(interfaces.plugins.PluginInterface):
|
||||
try:
|
||||
key_name = key.get_name()
|
||||
except (
|
||||
InvalidAddressException,
|
||||
registry.RegistryException,
|
||||
exceptions.InvalidAddressException,
|
||||
registry_layer.RegistryException,
|
||||
):
|
||||
key_name = renderers.UnreadableValue()
|
||||
|
||||
|
||||
@@ -4,18 +4,13 @@
|
||||
|
||||
import datetime
|
||||
import logging
|
||||
from typing import List, Optional, Sequence, Iterable, Tuple, Union
|
||||
from typing import Iterable, List, Optional, Sequence, Tuple, Union
|
||||
|
||||
from volatility3.framework import objects, renderers, exceptions, interfaces, constants
|
||||
from volatility3.framework import constants, exceptions, interfaces, objects, renderers
|
||||
from volatility3.framework.configuration import requirements
|
||||
from volatility3.framework.layers.registry import (
|
||||
RegistryHive,
|
||||
RegistryFormatException,
|
||||
InvalidAddressException,
|
||||
RegistryException,
|
||||
)
|
||||
from volatility3.framework.renderers import TreeGrid, conversion, format_hints
|
||||
from volatility3.framework.symbols.windows.extensions.registry import RegValueTypes
|
||||
from volatility3.framework.layers import registry as registry_layer
|
||||
from volatility3.framework.renderers import conversion, format_hints
|
||||
from volatility3.framework.symbols.windows.extensions import registry
|
||||
from volatility3.plugins.windows.registry import hivelist
|
||||
|
||||
vollog = logging.getLogger(__name__)
|
||||
@@ -55,7 +50,7 @@ class PrintKey(interfaces.plugins.PluginInterface):
|
||||
@classmethod
|
||||
def key_iterator(
|
||||
cls,
|
||||
hive: RegistryHive,
|
||||
hive: registry_layer.RegistryHive,
|
||||
node_path: Optional[Sequence[objects.StructType]] = None,
|
||||
recurse: bool = False,
|
||||
) -> Iterable[
|
||||
@@ -87,14 +82,14 @@ class PrintKey(interfaces.plugins.PluginInterface):
|
||||
try:
|
||||
key_path_names.append(k.get_name())
|
||||
except (
|
||||
InvalidAddressException,
|
||||
RegistryException,
|
||||
registry_layer.InvalidAddressException,
|
||||
registry_layer.RegistryException,
|
||||
):
|
||||
key_path_names.append("-")
|
||||
key_path = "\\".join([k for k in key_path_names])
|
||||
|
||||
if node.vol.type_name.endswith(constants.BANG + "_CELL_DATA"):
|
||||
raise RegistryFormatException(
|
||||
raise registry_layer.RegistryFormatException(
|
||||
hive.name, "Encountered _CELL_DATA instead of _CM_KEY_NODE"
|
||||
)
|
||||
last_write_time = conversion.wintime_to_datetime(node.LastWriteTime.QuadPart)
|
||||
@@ -116,7 +111,7 @@ class PrintKey(interfaces.plugins.PluginInterface):
|
||||
key_node.get_name()
|
||||
except (
|
||||
exceptions.InvalidAddressException,
|
||||
RegistryException,
|
||||
registry_layer.RegistryException,
|
||||
) as excp:
|
||||
vollog.debug(excp)
|
||||
continue
|
||||
@@ -138,7 +133,7 @@ class PrintKey(interfaces.plugins.PluginInterface):
|
||||
|
||||
def _printkey_iterator(
|
||||
self,
|
||||
hive: RegistryHive,
|
||||
hive: registry_layer.RegistryHive,
|
||||
node_path: Optional[Sequence[objects.StructType]] = None,
|
||||
recurse: bool = False,
|
||||
):
|
||||
@@ -166,7 +161,7 @@ class PrintKey(interfaces.plugins.PluginInterface):
|
||||
key_node_name = node.get_name()
|
||||
except (
|
||||
exceptions.InvalidAddressException,
|
||||
RegistryException,
|
||||
registry_layer.RegistryException,
|
||||
) as excp:
|
||||
vollog.debug(excp)
|
||||
key_node_name = renderers.UnreadableValue()
|
||||
@@ -193,16 +188,16 @@ class PrintKey(interfaces.plugins.PluginInterface):
|
||||
value_node_name = node.get_name() or "(Default)"
|
||||
except (
|
||||
exceptions.InvalidAddressException,
|
||||
RegistryException,
|
||||
registry_layer.RegistryException,
|
||||
) as excp:
|
||||
vollog.debug(excp)
|
||||
value_node_name = renderers.UnreadableValue()
|
||||
|
||||
try:
|
||||
value_type = RegValueTypes(node.Type).name
|
||||
value_type = registry.RegValueTypes(node.Type).name
|
||||
except (
|
||||
exceptions.InvalidAddressException,
|
||||
RegistryException,
|
||||
registry_layer.RegistryException,
|
||||
) as excp:
|
||||
vollog.debug(excp)
|
||||
value_type = renderers.UnreadableValue()
|
||||
@@ -222,11 +217,17 @@ class PrintKey(interfaces.plugins.PluginInterface):
|
||||
value_data = format_hints.MultiTypeData(
|
||||
value_data, encoding="utf-8"
|
||||
)
|
||||
elif RegValueTypes(node.Type) == RegValueTypes.REG_BINARY:
|
||||
elif (
|
||||
registry.RegValueTypes(node.Type)
|
||||
== registry.RegValueTypes.REG_BINARY
|
||||
):
|
||||
value_data = format_hints.MultiTypeData(
|
||||
value_data, show_hex=True
|
||||
)
|
||||
elif RegValueTypes(node.Type) == RegValueTypes.REG_MULTI_SZ:
|
||||
elif (
|
||||
registry.RegValueTypes(node.Type)
|
||||
== registry.RegValueTypes.REG_MULTI_SZ
|
||||
):
|
||||
value_data = format_hints.MultiTypeData(
|
||||
value_data, encoding="utf-16-le", split_nulls=True
|
||||
)
|
||||
@@ -237,7 +238,7 @@ class PrintKey(interfaces.plugins.PluginInterface):
|
||||
except (
|
||||
ValueError,
|
||||
exceptions.InvalidAddressException,
|
||||
RegistryException,
|
||||
registry_layer.RegistryException,
|
||||
) as excp:
|
||||
vollog.debug(excp)
|
||||
value_data = renderers.UnreadableValue()
|
||||
@@ -279,13 +280,13 @@ class PrintKey(interfaces.plugins.PluginInterface):
|
||||
except (
|
||||
exceptions.InvalidAddressException,
|
||||
KeyError,
|
||||
RegistryException,
|
||||
registry_layer.RegistryException,
|
||||
) as excp:
|
||||
if isinstance(excp, KeyError):
|
||||
vollog.debug(
|
||||
f"Key '{key}' not found in Hive at offset {hex(hive.hive_offset)}."
|
||||
)
|
||||
elif isinstance(excp, RegistryException):
|
||||
elif isinstance(excp, registry_layer.RegistryException):
|
||||
vollog.debug(excp)
|
||||
elif isinstance(excp, exceptions.InvalidAddressException):
|
||||
vollog.debug(
|
||||
@@ -308,7 +309,7 @@ class PrintKey(interfaces.plugins.PluginInterface):
|
||||
def run(self):
|
||||
offset = self.config.get("offset", None)
|
||||
|
||||
return TreeGrid(
|
||||
return renderers.TreeGrid(
|
||||
columns=[
|
||||
("Last Write Time", datetime.datetime),
|
||||
("Hive Offset", format_hints.Hex),
|
||||
|
||||
@@ -1216,7 +1216,7 @@ class ScheduledTasks(interfaces.plugins.PluginInterface, timeliner.TimeLinerInte
|
||||
|
||||
@classmethod
|
||||
def _get_task_keys(
|
||||
cls, software_hive: reg_extensions.RegistryHive
|
||||
cls, software_hive: registry.RegistryHive
|
||||
) -> Tuple[
|
||||
Optional[reg_extensions.CM_KEY_NODE], Optional[reg_extensions.CM_KEY_NODE]
|
||||
]:
|
||||
|
||||
@@ -12,11 +12,8 @@ from typing import Any, Generator, List, Tuple
|
||||
|
||||
from volatility3.framework import constants, exceptions, interfaces, renderers
|
||||
from volatility3.framework.configuration import requirements
|
||||
from volatility3.framework.layers.physical import BufferDataLayer
|
||||
from volatility3.framework.layers.registry import (
|
||||
RegistryHive,
|
||||
RegistryException,
|
||||
)
|
||||
from volatility3.framework.layers import physical
|
||||
from volatility3.framework.layers import registry as registry_layer
|
||||
from volatility3.framework.renderers import conversion, format_hints
|
||||
from volatility3.framework.symbols import intermed
|
||||
from volatility3.plugins.windows.registry import hivelist
|
||||
@@ -94,7 +91,7 @@ class UserAssist(interfaces.plugins.PluginInterface, timeliner.TimeLinerInterfac
|
||||
return item
|
||||
|
||||
userassist_layer_name = self.context.layers.free_layer_name("userassist_buffer")
|
||||
buffer = BufferDataLayer(
|
||||
buffer = physical.BufferDataLayer(
|
||||
self.context, self._config_path, userassist_layer_name, userassist_data
|
||||
)
|
||||
self.context.add_layer(buffer)
|
||||
@@ -158,7 +155,7 @@ class UserAssist(interfaces.plugins.PluginInterface, timeliner.TimeLinerInterfac
|
||||
).has_member("CookiePad")
|
||||
|
||||
def list_userassist(
|
||||
self, hive: RegistryHive
|
||||
self, hive: registry_layer.RegistryHive
|
||||
) -> Generator[Tuple[int, Tuple], None, None]:
|
||||
"""Generate userassist data for a registry hive."""
|
||||
|
||||
@@ -180,7 +177,7 @@ class UserAssist(interfaces.plugins.PluginInterface, timeliner.TimeLinerInterfac
|
||||
"software\\microsoft\\windows\\currentversion\\explorer\\userassist",
|
||||
return_list=True,
|
||||
)
|
||||
except RegistryException as e:
|
||||
except registry_layer.RegistryException as e:
|
||||
vollog.warning(
|
||||
f"Error accessing UserAssist key in {hive_name} at {hive.hive_offset:#x}: {e}"
|
||||
)
|
||||
@@ -250,7 +247,7 @@ class UserAssist(interfaces.plugins.PluginInterface, timeliner.TimeLinerInterfac
|
||||
subkey_name = subkey.get_name()
|
||||
except (
|
||||
exceptions.InvalidAddressException,
|
||||
RegistryException,
|
||||
registry_layer.RegistryException,
|
||||
):
|
||||
subkey_name = renderers.UnreadableValue()
|
||||
|
||||
@@ -279,7 +276,7 @@ class UserAssist(interfaces.plugins.PluginInterface, timeliner.TimeLinerInterfac
|
||||
value_name = value.get_name()
|
||||
except (
|
||||
exceptions.InvalidAddressException,
|
||||
RegistryException,
|
||||
registry_layer.RegistryException,
|
||||
):
|
||||
value_name = renderers.UnreadableValue()
|
||||
|
||||
|
||||
@@ -63,6 +63,11 @@ class Skeleton_Key_Check(interfaces.plugins.PluginInterface):
|
||||
requirements.VersionRequirement(
|
||||
name="pe_symbols", component=pe_symbols.PESymbols, version=(3, 0, 0)
|
||||
),
|
||||
requirements.VersionRequirement(
|
||||
name="bytes_scanner",
|
||||
component=scanners.BytesScanner,
|
||||
version=(1, 0, 0),
|
||||
),
|
||||
]
|
||||
|
||||
def _check_for_skeleton_key_vad(
|
||||
|
||||
@@ -42,6 +42,11 @@ class SvcList(svcscan.SvcScan):
|
||||
description="Windows kernel",
|
||||
architectures=["Intel32", "Intel64"],
|
||||
),
|
||||
requirements.VersionRequirement(
|
||||
name="bytes_scanner",
|
||||
component=scanners.BytesScanner,
|
||||
version=(1, 0, 0),
|
||||
),
|
||||
]
|
||||
|
||||
@classmethod
|
||||
|
||||
@@ -56,6 +56,11 @@ class SvcScan(interfaces.plugins.PluginInterface):
|
||||
requirements.VersionRequirement(
|
||||
name="hivelist", component=hivelist.HiveList, version=(2, 0, 0)
|
||||
),
|
||||
requirements.VersionRequirement(
|
||||
name="bytes_scanner",
|
||||
component=scanners.BytesScanner,
|
||||
version=(1, 0, 0),
|
||||
),
|
||||
]
|
||||
|
||||
@classmethod
|
||||
|
||||
@@ -3,20 +3,15 @@
|
||||
#
|
||||
|
||||
import logging
|
||||
from typing import Generator, Iterable, List, Tuple
|
||||
|
||||
from typing import Iterable, Generator, List, Tuple
|
||||
|
||||
from volatility3.framework import constants, interfaces, renderers
|
||||
from volatility3.framework import constants, interfaces, objects, renderers
|
||||
from volatility3.framework.configuration import requirements
|
||||
from volatility3.framework.interfaces.configuration import RequirementInterface
|
||||
from volatility3.framework.interfaces.objects import ObjectInterface
|
||||
from volatility3.framework.objects import Bytes, DataFormatInfo, Integer, StructType
|
||||
from volatility3.framework.objects.templates import ObjectTemplate
|
||||
from volatility3.framework.interfaces import configuration
|
||||
from volatility3.framework.objects.utility import array_to_string
|
||||
from volatility3.framework.renderers import format_hints
|
||||
from volatility3.framework.symbols import intermed
|
||||
from volatility3.framework.symbols.windows.extensions import pe
|
||||
|
||||
from volatility3.plugins.windows import modules
|
||||
|
||||
vollog = logging.getLogger(__name__)
|
||||
@@ -29,7 +24,7 @@ class Passphrase(interfaces.plugins.PluginInterface):
|
||||
_required_framework_version = (2, 5, 2)
|
||||
|
||||
@classmethod
|
||||
def get_requirements(cls) -> List[RequirementInterface]:
|
||||
def get_requirements(cls) -> List[configuration.RequirementInterface]:
|
||||
return [
|
||||
requirements.ModuleRequirement(
|
||||
"kernel",
|
||||
@@ -67,7 +62,7 @@ class Passphrase(interfaces.plugins.PluginInterface):
|
||||
layer_name,
|
||||
module_base,
|
||||
)
|
||||
data_section: StructType = next(
|
||||
data_section: objects.StructType = next(
|
||||
sec
|
||||
for sec in dos_header.get_nt_header().get_sections()
|
||||
if array_to_string(sec.Name) == ".data"
|
||||
@@ -76,11 +71,11 @@ class Passphrase(interfaces.plugins.PluginInterface):
|
||||
size: int = data_section.Misc.VirtualSize
|
||||
# Looking at `Length` in TrueCrypt/Common/Password.h::Password struct
|
||||
DWORD_SIZE_BYTES: int = 4
|
||||
format = DataFormatInfo(
|
||||
format = objects.DataFormatInfo(
|
||||
length=DWORD_SIZE_BYTES, byteorder="little", signed=True
|
||||
)
|
||||
int32 = ObjectTemplate(
|
||||
Integer, pe_table_name + constants.BANG + "int", data_format=format
|
||||
int32 = objects.templates.ObjectTemplate(
|
||||
objects.Integer, pe_table_name + constants.BANG + "int", data_format=format
|
||||
)
|
||||
count, not_aligned = divmod(size, DWORD_SIZE_BYTES)
|
||||
if not_aligned:
|
||||
@@ -99,7 +94,7 @@ class Passphrase(interfaces.plugins.PluginInterface):
|
||||
if not min_length <= length <= 64:
|
||||
continue
|
||||
offset = length.vol["offset"] + DWORD_SIZE_BYTES
|
||||
passphrase: Bytes = self.context.object(
|
||||
passphrase: objects.Bytes = self.context.object(
|
||||
pe_table_name + constants.BANG + "bytes",
|
||||
layer_name,
|
||||
offset,
|
||||
@@ -111,7 +106,7 @@ class Passphrase(interfaces.plugins.PluginInterface):
|
||||
continue
|
||||
# TrueCrypt/Common/Password.h::Password struct is padded with
|
||||
# 3 zero bytes to keep 64-byte alignment.
|
||||
buf: Bytes = self.context.object(
|
||||
buf: objects.Bytes = self.context.object(
|
||||
pe_table_name + constants.BANG + "bytes",
|
||||
layer_name,
|
||||
offset + length + 1, # +1 for '\0'-terminated password string
|
||||
@@ -124,8 +119,8 @@ class Passphrase(interfaces.plugins.PluginInterface):
|
||||
|
||||
def _generator(self):
|
||||
kernel = self.context.modules[self.config["kernel"]]
|
||||
mods: Iterable[ObjectInterface] = modules.Modules.list_modules(
|
||||
self.context, self.config["kernel"]
|
||||
mods: Iterable[interfaces.objects.ObjectInterface] = (
|
||||
modules.Modules.list_modules(self.context, self.config["kernel"])
|
||||
)
|
||||
try:
|
||||
truecrypt_module_base = next(
|
||||
|
||||
@@ -41,6 +41,11 @@ class VadRegExScan(plugins.PluginInterface):
|
||||
element_type=int,
|
||||
optional=True,
|
||||
),
|
||||
requirements.VersionRequirement(
|
||||
name="regex_scanner",
|
||||
component=scanners.RegExScanner,
|
||||
version=(1, 0, 0),
|
||||
),
|
||||
requirements.StringRequirement(
|
||||
name="pattern", description="RegEx pattern", optional=False
|
||||
),
|
||||
|
||||
@@ -17,8 +17,8 @@ vollog = logging.getLogger(__name__)
|
||||
class VadYaraScan(interfaces.plugins.PluginInterface):
|
||||
"""Scans all the Virtual Address Descriptor memory maps using yara."""
|
||||
|
||||
_required_framework_version = (2, 4, 0)
|
||||
_version = (1, 1, 2)
|
||||
_required_framework_version = (2, 22, 0)
|
||||
_version = (1, 1, 3)
|
||||
|
||||
@classmethod
|
||||
def get_requirements(cls) -> List[interfaces.configuration.RequirementInterface]:
|
||||
@@ -93,12 +93,18 @@ class VadYaraScan(interfaces.plugins.PluginInterface):
|
||||
for offset, rule_name, name, value in scanner(
|
||||
layer.read(start, size, pad=True), start
|
||||
):
|
||||
layer_data = renderers.LayerData(
|
||||
context=self.context,
|
||||
offset=offset,
|
||||
layer_name=layer.name,
|
||||
length=len(value),
|
||||
)
|
||||
yield 0, (
|
||||
format_hints.Hex(offset),
|
||||
task.UniqueProcessId,
|
||||
rule_name,
|
||||
name,
|
||||
value,
|
||||
layer_data,
|
||||
)
|
||||
|
||||
@classmethod
|
||||
@@ -126,7 +132,7 @@ class VadYaraScan(interfaces.plugins.PluginInterface):
|
||||
("PID", int),
|
||||
("Rule", str),
|
||||
("Component", str),
|
||||
("Value", bytes),
|
||||
("Value", renderers.LayerData),
|
||||
],
|
||||
self._generator(),
|
||||
)
|
||||
|
||||
@@ -48,6 +48,11 @@ class VerInfo(interfaces.plugins.PluginInterface):
|
||||
requirements.VersionRequirement(
|
||||
name="modules", component=modules.Modules, version=(3, 0, 0)
|
||||
),
|
||||
requirements.VersionRequirement(
|
||||
name="page_start_scanner",
|
||||
component=scanners.BytesScanner,
|
||||
version=(1, 0, 0),
|
||||
),
|
||||
requirements.BooleanRequirement(
|
||||
name="extensive",
|
||||
description="Search physical layer for version information",
|
||||
|
||||
@@ -105,8 +105,8 @@ class YaraScanner(interfaces.layers.ScannerInterface):
|
||||
class YaraScan(plugins.PluginInterface):
|
||||
"""Scans kernel memory using yara rules (string or file)."""
|
||||
|
||||
_required_framework_version = (2, 0, 0)
|
||||
_version = (2, 0, 0)
|
||||
_required_framework_version = (2, 22, 0)
|
||||
_version = (2, 0, 1)
|
||||
_yara_x = USE_YARA_X
|
||||
|
||||
@classmethod
|
||||
@@ -118,7 +118,12 @@ class YaraScan(plugins.PluginInterface):
|
||||
name="primary",
|
||||
description="Memory layer for the kernel",
|
||||
architectures=["Intel32", "Intel64"],
|
||||
)
|
||||
),
|
||||
requirements.VersionRequirement(
|
||||
name="yarascanner",
|
||||
component=YaraScanner,
|
||||
version=(2, 1, 1),
|
||||
),
|
||||
]
|
||||
|
||||
@classmethod
|
||||
@@ -201,7 +206,13 @@ class YaraScan(plugins.PluginInterface):
|
||||
for offset, rule_name, name, value in layer.scan(
|
||||
context=self.context, scanner=YaraScanner(rules=rules)
|
||||
):
|
||||
yield 0, (format_hints.Hex(offset), rule_name, name, value)
|
||||
layer_data = renderers.LayerData(
|
||||
context=self.context,
|
||||
offset=offset,
|
||||
layer_name=layer.name,
|
||||
length=len(value),
|
||||
)
|
||||
yield 0, (format_hints.Hex(offset), rule_name, name, layer_data)
|
||||
|
||||
def run(self):
|
||||
return renderers.TreeGrid(
|
||||
@@ -209,7 +220,7 @@ class YaraScan(plugins.PluginInterface):
|
||||
("Offset", format_hints.Hex),
|
||||
("Rule", str),
|
||||
("Component", str),
|
||||
("Value", bytes),
|
||||
("Value", renderers.LayerData),
|
||||
],
|
||||
self._generator(),
|
||||
)
|
||||
|
||||
@@ -8,6 +8,7 @@ or file or graphical output
|
||||
"""
|
||||
import collections
|
||||
import collections.abc
|
||||
import dataclasses
|
||||
import datetime
|
||||
import logging
|
||||
from typing import Any, Callable, Dict, Iterable, List, Optional, Tuple, TypeVar, Union
|
||||
@@ -22,16 +23,28 @@ class UnreadableValue(interfaces.renderers.BaseAbsentValue):
|
||||
"""Class that represents values which are empty because the data cannot be
|
||||
read."""
|
||||
|
||||
def __str__(self) -> str:
|
||||
"""Fallback method for rendering basic types"""
|
||||
return "-"
|
||||
|
||||
|
||||
class UnparsableValue(interfaces.renderers.BaseAbsentValue):
|
||||
"""Class that represents values which are empty because the data cannot be
|
||||
interpreted correctly."""
|
||||
|
||||
def __str__(self) -> str:
|
||||
"""Fallback method for rendering basic types"""
|
||||
return "-"
|
||||
|
||||
|
||||
class NotApplicableValue(interfaces.renderers.BaseAbsentValue):
|
||||
"""Class that represents values which are empty because they don't make
|
||||
sense for this node."""
|
||||
|
||||
def __str__(self) -> str:
|
||||
"""Fallback method for rendering basic types"""
|
||||
return "N/A"
|
||||
|
||||
|
||||
class NotAvailableValue(interfaces.renderers.BaseAbsentValue):
|
||||
"""Class that represents values which cannot be provided now (but might in
|
||||
@@ -45,6 +58,70 @@ class NotAvailableValue(interfaces.renderers.BaseAbsentValue):
|
||||
in preference, and only if neither fits should this be used.
|
||||
"""
|
||||
|
||||
def __str__(self) -> str:
|
||||
"""Fallback method for rendering basic types"""
|
||||
return "N/A"
|
||||
|
||||
|
||||
##########
|
||||
### Basic Types
|
||||
|
||||
|
||||
class Disassembly(interfaces.renderers.BasicType):
|
||||
"""A class to indicate that the bytes provided should be disassembled
|
||||
(based on the architecture)"""
|
||||
|
||||
possible_architectures = ["intel", "intel64", "arm", "arm64"]
|
||||
|
||||
def __init__(
|
||||
self, data: bytes, offset: int = 0, architecture: str = "intel64"
|
||||
) -> None:
|
||||
self.data = data
|
||||
self.architecture = None
|
||||
if architecture in self.possible_architectures:
|
||||
self.architecture = architecture
|
||||
if not isinstance(offset, int):
|
||||
raise TypeError("Offset must be an integer type")
|
||||
self.offset = offset
|
||||
|
||||
def __str__(self) -> str:
|
||||
"""Fallback method of rendering"""
|
||||
return str(self.data)
|
||||
|
||||
|
||||
@dataclasses.dataclass
|
||||
class LayerData(interfaces.renderers.BasicType):
|
||||
"""Layer data
|
||||
|
||||
This requires the contex to be passed in, in case plugins want to use multiple contexts
|
||||
and to ensure the TreeGrid interface doesn't change, since this would break all existing plugins
|
||||
"""
|
||||
|
||||
context: "interfaces.context.ContextInterface"
|
||||
layer_name: str
|
||||
offset: int
|
||||
length: int
|
||||
no_surrounding: bool = False
|
||||
|
||||
@staticmethod
|
||||
def from_object(
|
||||
object: "interfaces.objects.ObjectInterface",
|
||||
size: Optional[int] = None,
|
||||
no_surrounding: bool = True,
|
||||
):
|
||||
return LayerData(
|
||||
context=object._context,
|
||||
layer_name=object.vol.layer_name,
|
||||
offset=object.vol.offset,
|
||||
length=size or object.vol.size,
|
||||
no_surrounding=no_surrounding,
|
||||
)
|
||||
|
||||
def __str__(self) -> str:
|
||||
"""Fallback method of rendering"""
|
||||
data = self.context.layers[self.layer_name].read(self.offset, self.length, True)
|
||||
return str(data)
|
||||
|
||||
|
||||
class TreeNode(interfaces.renderers.TreeNode):
|
||||
"""Class representing a particular node in a tree grid."""
|
||||
|
||||
@@ -1,9 +1,9 @@
|
||||
from volatility3.framework.symbols import intermed
|
||||
from volatility3.framework.symbols.linux.extensions import network
|
||||
from volatility3.framework.interfaces.configuration import VersionableInterface
|
||||
from volatility3.framework.interfaces import configuration
|
||||
|
||||
|
||||
class NetSymbols(VersionableInterface):
|
||||
class NetSymbols(configuration.VersionableInterface):
|
||||
_version = (1, 0, 0)
|
||||
|
||||
@classmethod
|
||||
|
||||
@@ -18,7 +18,6 @@ from volatility3.framework import (
|
||||
renderers,
|
||||
symbols,
|
||||
)
|
||||
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
|
||||
@@ -413,7 +412,9 @@ class DEVICE_OBJECT(objects.StructType, pool.ExecutiveObject):
|
||||
header = self.get_object_header()
|
||||
return header.NameInfo.Name.String # type: ignore
|
||||
|
||||
def get_attached_devices(self) -> Generator[ObjectInterface, None, None]:
|
||||
def get_attached_devices(
|
||||
self,
|
||||
) -> Generator[interfaces.objects.ObjectInterface, None, None]:
|
||||
"""Enumerate the attached device's objects"""
|
||||
seen = set()
|
||||
|
||||
@@ -443,7 +444,7 @@ class DRIVER_OBJECT(objects.StructType, pool.ExecutiveObject):
|
||||
header = self.get_object_header()
|
||||
return header.NameInfo.Name.String # type: ignore
|
||||
|
||||
def get_devices(self) -> Generator[ObjectInterface, None, None]:
|
||||
def get_devices(self) -> Generator[interfaces.objects.ObjectInterface, None, None]:
|
||||
"""Enumerate the driver's device objects"""
|
||||
seen = set()
|
||||
|
||||
|
||||
@@ -4,17 +4,15 @@
|
||||
|
||||
import logging
|
||||
import socket
|
||||
from typing import Dict, Tuple, List, Union, Optional
|
||||
from typing import Dict, List, Optional, Tuple, Union
|
||||
|
||||
from volatility3.framework import exceptions
|
||||
from volatility3.framework import objects, interfaces
|
||||
from volatility3.framework.objects import Array
|
||||
from volatility3.framework import exceptions, interfaces, objects
|
||||
from volatility3.framework.renderers import conversion
|
||||
|
||||
vollog = logging.getLogger(__name__)
|
||||
|
||||
|
||||
def inet_ntop(address_family: int, packed_ip: Union[List[int], Array]) -> str:
|
||||
def inet_ntop(address_family: int, packed_ip: Union[List[int], objects.Array]) -> str:
|
||||
if address_family in [socket.AF_INET6, socket.AF_INET]:
|
||||
try:
|
||||
return socket.inet_ntop(address_family, bytes(packed_ip))
|
||||
|
||||
@@ -4,7 +4,7 @@ import logging
|
||||
import struct
|
||||
from typing import Dict, List, Optional, Tuple, Union
|
||||
|
||||
from volatility3.plugins.windows.poolscanner import PoolConstraint
|
||||
from volatility3.plugins.windows import poolscanner
|
||||
|
||||
from volatility3.framework import (
|
||||
constants,
|
||||
@@ -28,7 +28,7 @@ class POOL_HEADER(objects.StructType):
|
||||
|
||||
def get_object(
|
||||
self,
|
||||
constraint: PoolConstraint,
|
||||
constraint: poolscanner.PoolConstraint,
|
||||
use_top_down: bool,
|
||||
kernel_symbol_table: Optional[str] = None,
|
||||
native_layer_name: Optional[str] = None,
|
||||
|
||||
@@ -8,10 +8,7 @@ import struct
|
||||
from typing import Iterator, Optional, Union, cast
|
||||
|
||||
from volatility3.framework import constants, exceptions, interfaces, objects
|
||||
from volatility3.framework.layers.registry import (
|
||||
RegistryException,
|
||||
RegistryHive,
|
||||
)
|
||||
from volatility3.framework.layers import registry
|
||||
|
||||
vollog = logging.getLogger(__name__)
|
||||
|
||||
@@ -102,7 +99,9 @@ class CMHIVE(objects.StructType):
|
||||
|
||||
for attr in ["FileFullPath", "FileUserName", "HiveRootPath"]:
|
||||
with contextlib.suppress(
|
||||
AttributeError, exceptions.InvalidAddressException, RegistryException
|
||||
AttributeError,
|
||||
exceptions.InvalidAddressException,
|
||||
registry.RegistryException,
|
||||
):
|
||||
name = getattr(self, attr)
|
||||
if name.Length > 0:
|
||||
@@ -172,7 +171,9 @@ class CM_KEY_NODE(objects.StructType):
|
||||
|
||||
Raises TypeError if the key was not instantiated on a RegistryHive layer
|
||||
"""
|
||||
if not isinstance(self._context.layers[self.vol.layer_name], RegistryHive):
|
||||
if not isinstance(
|
||||
self._context.layers[self.vol.layer_name], registry.RegistryHive
|
||||
):
|
||||
raise TypeError("CM_KEY_NODE was not instantiated on a RegistryHive layer")
|
||||
return bool(self.vol.offset & 0x80000000)
|
||||
|
||||
@@ -182,7 +183,7 @@ class CM_KEY_NODE(objects.StructType):
|
||||
Raises TypeError if the key was not instantiated on a RegistryHive layer
|
||||
"""
|
||||
hive = self._context.layers[self.vol.layer_name]
|
||||
if not isinstance(hive, RegistryHive):
|
||||
if not isinstance(hive, registry.RegistryHive):
|
||||
raise TypeError("CM_KEY_NODE was not instantiated on a RegistryHive layer")
|
||||
for index in range(2):
|
||||
# Use get_cell because it should *always* be a KeyIndex
|
||||
@@ -190,7 +191,7 @@ class CM_KEY_NODE(objects.StructType):
|
||||
yield from self._get_subkeys_recursive(hive, subkey_node)
|
||||
|
||||
def _get_subkeys_recursive(
|
||||
self, hive: RegistryHive, node: interfaces.objects.ObjectInterface
|
||||
self, hive: "registry.RegistryHive", node: interfaces.objects.ObjectInterface
|
||||
) -> Iterator["CM_KEY_NODE"]:
|
||||
"""Recursively descend a node returning subkeys."""
|
||||
# The keylist appears to include 4 bytes of key name after each value
|
||||
@@ -200,7 +201,7 @@ class CM_KEY_NODE(objects.StructType):
|
||||
signature = node.cast("string", max_length=2, encoding="latin-1")
|
||||
except (
|
||||
exceptions.InvalidAddressException,
|
||||
RegistryException,
|
||||
registry.RegistryException,
|
||||
):
|
||||
return None
|
||||
|
||||
@@ -229,7 +230,7 @@ class CM_KEY_NODE(objects.StructType):
|
||||
subnode = hive.get_node(subnode_offset)
|
||||
except (
|
||||
exceptions.InvalidAddressException,
|
||||
RegistryException,
|
||||
registry.RegistryException,
|
||||
):
|
||||
vollog.log(
|
||||
constants.LOGLEVEL_VVV,
|
||||
@@ -244,7 +245,7 @@ class CM_KEY_NODE(objects.StructType):
|
||||
Raises TypeError if the key was not instantiated on a RegistryHive layer
|
||||
"""
|
||||
hive = self._context.layers[self.vol.layer_name]
|
||||
if not isinstance(hive, RegistryHive):
|
||||
if not isinstance(hive, registry.RegistryHive):
|
||||
raise TypeError("CM_KEY_NODE was not instantiated on a RegistryHive layer")
|
||||
|
||||
try:
|
||||
@@ -255,7 +256,7 @@ class CM_KEY_NODE(objects.StructType):
|
||||
if v != 0:
|
||||
try:
|
||||
node = hive.get_node(v)
|
||||
except (RegistryException,) as excp:
|
||||
except (registry.RegistryException,) as excp:
|
||||
vollog.debug(f"Invalid address {excp}")
|
||||
continue
|
||||
if isinstance(node, CM_KEY_VALUE):
|
||||
@@ -263,7 +264,7 @@ class CM_KEY_NODE(objects.StructType):
|
||||
|
||||
except (
|
||||
exceptions.InvalidAddressException,
|
||||
RegistryException,
|
||||
registry.RegistryException,
|
||||
) as excp:
|
||||
vollog.debug(f"Invalid address in get_values iteration: {excp}")
|
||||
return None
|
||||
@@ -281,7 +282,7 @@ class CM_KEY_NODE(objects.StructType):
|
||||
Raises TypeError if the key was not instantiated on a RegistryHive layer
|
||||
"""
|
||||
reg = self._context.layers[self.vol.layer_name]
|
||||
if not isinstance(reg, RegistryHive):
|
||||
if not isinstance(reg, registry.RegistryHive):
|
||||
raise TypeError("Key was not instantiated on a RegistryHive layer")
|
||||
# Using the offset adds a significant delay (since it cannot be cached easily)
|
||||
# if self.vol.offset == reg.get_node(reg.root_cell_offset).vol.offset:
|
||||
@@ -320,7 +321,7 @@ class CM_KEY_VALUE(objects.StructType):
|
||||
data = b""
|
||||
# Check if the data is stored inline
|
||||
layer = self._context.layers[self.vol.layer_name]
|
||||
if not isinstance(layer, RegistryHive):
|
||||
if not isinstance(layer, registry.RegistryHive):
|
||||
raise TypeError("Key value was not instantiated on a RegistryHive layer")
|
||||
|
||||
# If the high-bit is set
|
||||
@@ -353,7 +354,10 @@ class CM_KEY_VALUE(objects.StructType):
|
||||
offset=layer.get_cell(block_offset).vol.offset,
|
||||
length=amount,
|
||||
)
|
||||
except (exceptions.InvalidAddressException, RegistryException):
|
||||
except (
|
||||
exceptions.InvalidAddressException,
|
||||
registry.RegistryException,
|
||||
):
|
||||
vollog.debug(
|
||||
f"Failed to read {amount:x} bytes of data, padding with {amount:x}"
|
||||
)
|
||||
@@ -363,7 +367,7 @@ class CM_KEY_VALUE(objects.StructType):
|
||||
# but the length at the start could be negative so just adding 4 to jump past it
|
||||
try:
|
||||
data = layer.read(self.Data + 4, datalen)
|
||||
except (exceptions.InvalidAddressException, RegistryException):
|
||||
except (exceptions.InvalidAddressException, registry.RegistryException):
|
||||
vollog.debug(
|
||||
f"Failed to read {datalen:x} bytes of data, returning {datalen:x} null bytes"
|
||||
)
|
||||
|
||||
@@ -4,7 +4,7 @@
|
||||
|
||||
from volatility3.framework import objects, interfaces
|
||||
from volatility3.framework import exceptions
|
||||
from volatility3.framework.symbols.wrappers import Flags
|
||||
from volatility3.framework.symbols import wrappers
|
||||
from volatility3.framework import renderers
|
||||
from typing import Union
|
||||
|
||||
@@ -91,7 +91,7 @@ class SERVICE_RECORD(objects.StructType):
|
||||
"SERVICE_INTERACTIVE_PROCESS": 256,
|
||||
}
|
||||
|
||||
type_flags = Flags(choices=SERVICE_TYPE_FLAGS)
|
||||
type_flags = wrappers.Flags(choices=SERVICE_TYPE_FLAGS)
|
||||
return "|".join(type_flags(self.Type))
|
||||
|
||||
def traverse(self):
|
||||
|
||||
@@ -16,7 +16,6 @@ from volatility3 import symbols
|
||||
from volatility3.framework import constants, contexts, exceptions, interfaces
|
||||
from volatility3.framework.automagic import symbol_cache
|
||||
from volatility3.framework.configuration import requirements
|
||||
from volatility3.framework.configuration.requirements import SymbolTableRequirement
|
||||
from volatility3.framework.symbols import intermed
|
||||
from volatility3.framework.symbols.windows import pdbconv
|
||||
|
||||
@@ -140,7 +139,7 @@ class PDBUtility(interfaces.configuration.VersionableInterface):
|
||||
requirement_name = interfaces.configuration.path_head(config_path)
|
||||
|
||||
# Construct the appropriate symbol table
|
||||
requirement = SymbolTableRequirement(
|
||||
requirement = requirements.SymbolTableRequirement(
|
||||
name=requirement_name, description="PDBUtility generated symbol table"
|
||||
)
|
||||
requirement.construct(context, parent_config_path)
|
||||
|
||||
@@ -4,6 +4,7 @@ import struct
|
||||
from typing import Iterator, List, Optional, Tuple, Type
|
||||
|
||||
from volatility3.framework import exceptions, interfaces, renderers
|
||||
from volatility3.framework.layers import registry as registry_layer
|
||||
from volatility3.framework.configuration import requirements
|
||||
from volatility3.framework.symbols.windows.extensions import registry
|
||||
from volatility3.plugins.windows.registry import hivelist, printkey
|
||||
@@ -80,7 +81,7 @@ class Certificates(interfaces.plugins.PluginInterface):
|
||||
]:
|
||||
with contextlib.suppress(
|
||||
KeyError,
|
||||
registry.RegistryException,
|
||||
registry_layer.RegistryException,
|
||||
exceptions.InvalidAddressException,
|
||||
):
|
||||
# Walk it
|
||||
|
||||
Reference in New Issue
Block a user