Merge pull request #1555 from volatilityfoundation/release/v2.11.0

Release/v2.11.0
This commit is contained in:
ikelos
2025-01-16 20:12:52 +00:00
committed by GitHub
104 changed files with 17382 additions and 849 deletions
+1 -1
View File
@@ -18,7 +18,7 @@ jobs:
runs-on: ubuntu-20.04
strategy:
matrix:
python-version: ["3.7"]
python-version: ["3.8"]
steps:
- uses: actions/checkout@v4
- name: Set up Python ${{ matrix.python-version }}
+1 -1
View File
@@ -8,7 +8,7 @@ jobs:
fail-fast: false
matrix:
host: [ ubuntu-latest, windows-latest ]
python-version: [ "3.7", "3.8", "3.9", "3.10", "3.11" ]
python-version: [ "3.8", "3.9", "3.10", "3.11" ]
steps:
- uses: actions/checkout@v4
+2 -2
View File
@@ -6,7 +6,7 @@ jobs:
runs-on: ubuntu-20.04
strategy:
matrix:
python-version: ["3.7"]
python-version: ["3.8"]
steps:
- uses: actions/checkout@v4
- name: Set up Python ${{ matrix.python-version }}
@@ -46,7 +46,7 @@ jobs:
- name: Clean up post-test
run: |
rm -rf *.lime
rm -rf *.bin
rm -rf *.img
cd volatility3/symbols
rm -rf linux
-261
View File
@@ -1,261 +0,0 @@
[style]
# Align closing bracket with visual indentation.
align_closing_bracket_with_visual_indent=True
# Allow dictionary keys to exist on multiple lines. For example:
#
# x = {
# ('this is the first element of a tuple',
# 'this is the second element of a tuple'):
# value,
# }
allow_multiline_dictionary_keys=False
# Allow lambdas to be formatted on more than one line.
allow_multiline_lambdas=False
# Allow splits before the dictionary value.
allow_split_before_dict_value=True
# Number of blank lines surrounding top-level function and class
# definitions.
blank_lines_around_top_level_definition=2
# Insert a blank line before a class-level docstring.
blank_line_before_class_docstring=False
# Insert a blank line before a module docstring.
blank_line_before_module_docstring=False
# Insert a blank line before a 'def' or 'class' immediately nested
# within another 'def' or 'class'. For example:
#
# class Foo:
# # <------ this blank line
# def method():
# ...
blank_line_before_nested_class_or_def=True
# Do not split consecutive brackets. Only relevant when
# dedent_closing_brackets is set. For example:
#
# call_func_that_takes_a_dict(
# {
# 'key1': 'value1',
# 'key2': 'value2',
# }
# )
#
# would reformat to:
#
# call_func_that_takes_a_dict({
# 'key1': 'value1',
# 'key2': 'value2',
# })
coalesce_brackets=False
# The column limit.
column_limit=120
# The style for continuation alignment. Possible values are:
#
# - SPACE: Use spaces for continuation alignment. This is default behavior.
# - FIXED: Use fixed number (CONTINUATION_INDENT_WIDTH) of columns
# (ie: CONTINUATION_INDENT_WIDTH/INDENT_WIDTH tabs) for continuation
# alignment.
# - LESS: Slightly left if cannot vertically align continuation lines with
# indent characters.
# - VALIGN-RIGHT: Vertically align continuation lines with indent
# characters. Slightly right (one more indent character) if cannot
# vertically align continuation lines with indent characters.
#
# For options FIXED, and VALIGN-RIGHT are only available when USE_TABS is
# enabled.
continuation_align_style=SPACE
# Indent width used for line continuations.
continuation_indent_width=4
# Put closing brackets on a separate line, dedented, if the bracketed
# expression can't fit in a single line. Applies to all kinds of brackets,
# including function definitions and calls. For example:
#
# config = {
# 'key1': 'value1',
# 'key2': 'value2',
# } # <--- this bracket is dedented and on a separate line
#
# time_series = self.remote_client.query_entity_counters(
# entity='dev3246.region1',
# key='dns.query_latency_tcp',
# transform=Transformation.AVERAGE(window=timedelta(seconds=60)),
# start_ts=now()-timedelta(days=3),
# end_ts=now(),
# ) # <--- this bracket is dedented and on a separate line
dedent_closing_brackets=False
# Disable the heuristic which places each list element on a separate line
# if the list is comma-terminated.
disable_ending_comma_heuristic=False
# Place each dictionary entry onto its own line.
each_dict_entry_on_separate_line=True
# The regex for an i18n comment. The presence of this comment stops
# reformatting of that line, because the comments are required to be
# next to the string they translate.
i18n_comment=
# The i18n function call names. The presence of this function stops
# reformatting on that line, because the string it has cannot be moved
# away from the i18n comment.
i18n_function_call=
# Indent the dictionary value if it cannot fit on the same line as the
# dictionary key. For example:
#
# config = {
# 'key1':
# 'value1',
# 'key2': value1 +
# value2,
# }
indent_dictionary_value=False
# The number of columns to use for indentation.
indent_width=4
# Join short lines into one line. E.g., single line 'if' statements.
join_multiple_lines=True
# Do not include spaces around selected binary operators. For example:
#
# 1 + 2 * 3 - 4 / 5
#
# will be formatted as follows when configured with "*,/":
#
# 1 + 2*3 - 4/5
#
no_spaces_around_selected_binary_operators=
# Use spaces around default or named assigns.
spaces_around_default_or_named_assign=True
# Use spaces around the power operator.
spaces_around_power_operator=True
# The number of spaces required before a trailing comment.
spaces_before_comment=2
# Insert a space between the ending comma and closing bracket of a list,
# etc.
space_between_ending_comma_and_closing_bracket=True
# Split before arguments
split_all_comma_separated_values=False
# Split before arguments if the argument list is terminated by a
# comma.
split_arguments_when_comma_terminated=False
# Set to True to prefer splitting before '&', '|' or '^' rather than
# after.
split_before_bitwise_operator=True
# Split before the closing bracket if a list or dict literal doesn't fit on
# a single line.
split_before_closing_bracket=True
# Split before a dictionary or set generator (comp_for). For example, note
# the split before the 'for':
#
# foo = {
# variable: 'Hello world, have a nice day!'
# for variable in bar if variable != 42
# }
split_before_dict_set_generator=True
# Split before the '.' if we need to split a longer expression:
#
# foo = ('This is a really long string: {}, {}, {}, {}'.format(a, b, c, d))
#
# would reformat to something like:
#
# foo = ('This is a really long string: {}, {}, {}, {}'
# .format(a, b, c, d))
split_before_dot=False
# Split after the opening paren which surrounds an expression if it doesn't
# fit on a single line.
split_before_expression_after_opening_paren=False
# If an argument / parameter list is going to be split, then split before
# the first argument.
split_before_first_argument=False
# Set to True to prefer splitting before 'and' or 'or' rather than
# after.
split_before_logical_operator=True
# Split named assignments onto individual lines.
split_before_named_assigns=True
# Set to True to split list comprehensions and generators that have
# non-trivial expressions and multiple clauses before each of these
# clauses. For example:
#
# result = [
# a_long_var + 100 for a_long_var in xrange(1000)
# if a_long_var % 10]
#
# would reformat to something like:
#
# result = [
# a_long_var + 100
# for a_long_var in xrange(1000)
# if a_long_var % 10]
split_complex_comprehension=True
# The penalty for splitting right after the opening bracket.
split_penalty_after_opening_bracket=200
# The penalty for splitting the line after a unary operator.
split_penalty_after_unary_operator=10000
# The penalty for splitting right before an if expression.
split_penalty_before_if_expr=0
# The penalty of splitting the line around the '&', '|', and '^'
# operators.
split_penalty_bitwise_operator=300
# The penalty for splitting a list comprehension or generator
# expression.
split_penalty_comprehension=80
# The penalty for characters over the column limit.
split_penalty_excess_character=7000
# The penalty incurred by adding a line split to the unwrapped line. The
# more line splits added the higher the penalty.
split_penalty_for_added_line_split=30
# The penalty of splitting a list of "import as" names. For example:
#
# from a_very_long_or_indented_module_name_yada_yad import (long_argument_1,
# long_argument_2,
# long_argument_3)
#
# would reformat to something like:
#
# from a_very_long_or_indented_module_name_yada_yad import (
# long_argument_1, long_argument_2, long_argument_3)
split_penalty_import_names=0
# The penalty of splitting the line around the 'and' and 'or'
# operators.
split_penalty_logical_operator=300
# Use the Tab character for indentation.
use_tabs=False
+2 -2
View File
@@ -20,7 +20,7 @@ more details.
## Requirements
Volatility 3 requires Python 3.7.3 or later. To install the most minimal set of dependencies (some plugins will not work) use a command such as:
Volatility 3 requires Python 3.8.0 or later. To install the most minimal set of dependencies (some plugins will not work) use a command such as:
```shell
pip3 install -r requirements-minimal.txt
@@ -106,7 +106,7 @@ The latest generated copy of the documentation can be found at: <https://volatil
## Licensing and Copyright
Copyright (C) 2007-2024 Volatility Foundation
Copyright (C) 2007-2025 Volatility Foundation
All Rights Reserved
+1
View File
@@ -4,5 +4,6 @@ sphinx_autodoc_typehints>=1.4.0
sphinx-rtd-theme>=0.4.3
yara-python
yara-x
pycryptodome
pefile
+1 -1
View File
@@ -169,7 +169,7 @@ master_doc = "index"
# General information about the project.
project = "Volatility 3"
copyright = "2012-2024, Volatility Foundation"
copyright = "2012-2025, Volatility Foundation"
# The version info for the project you're documenting, acts as replacement for
# |version| and |release|, also used in various other places throughout the
@@ -11,6 +11,7 @@ Volatility3 does not provide the ability to acquire memory. Below are some exam
* `AVML - Acquire Volatile Memory for Linux <https://github.com/microsoft/avml>`_
* `LiME - Linux Memory Extract <https://github.com/504ensicsLabs/LiME>`_
Be aware that LiME raw format is not supported by volatility3, the padded or lime option should be used instead. `This issue contains further information <https://github.com/504ensicsLabs/LiME/issues/111>`_.
Procedure to create symbol tables for linux
--------------------------------------------
+1 -1
View File
@@ -6,7 +6,7 @@ readme = "README.md"
authors = [
{ name = "Volatility Foundation", email = "volatility@volatilityfoundation.org" },
]
requires-python = ">=3.7.3"
requires-python = ">=3.8.0"
license = { text = "VSL" }
dynamic = ["dependencies", "optional-dependencies", "version"]
+3 -2
View File
@@ -9,7 +9,8 @@ yara-python>=3.8.0
# This is required for several plugins that perform malware analysis and disassemble code.
# It can also improve accuracy of Windows 8 and later memory samples.
capstone>=3.0.5
# FIXME: Version 6.0.0 is incompatible (#1336) so we'll need an adaptor at some point
capstone>=3.0.5,<6.0.0
# This is required by plugins that decrypt passwords, password hashes, etc.
pycryptodome
@@ -19,4 +20,4 @@ leechcorepyc>=2.4.0; sys_platform != 'darwin'
# This is required for memory analysis on a Amazon/MinIO S3 and Google Cloud object storage
gcsfs>=2023.1.0
s3fs>=2023.1.0
s3fs>=2023.1.0
View File
View File
View File
@@ -0,0 +1,387 @@
import sys
import struct
import traceback
import unittest
sys.path.insert(0, "../../volatility3")
from volatility3.plugins.windows import scheduled_tasks
class TestActionsDecoding(unittest.TestCase):
def test_decode_exe_action(self):
# fmt: off
buf = struct.pack(
"512B",
*[
0x03, 0x00, 0x16, 0x00, 0x00, 0x00, 0x4c, 0x00,
0x6f, 0x00, 0x63, 0x00, 0x61, 0x00, 0x6c, 0x00,
0x53, 0x00, 0x79, 0x00, 0x73, 0x00, 0x74, 0x00,
0x65, 0x00, 0x6d, 0x00, 0x66, 0x66, 0x00, 0x00,
0x00, 0x00, 0x6e, 0x00, 0x00, 0x00, 0x25, 0x00,
0x77, 0x00, 0x69, 0x00, 0x6e, 0x00, 0x64, 0x00,
0x69, 0x00, 0x72, 0x00, 0x25, 0x00, 0x5c, 0x00,
0x73, 0x00, 0x79, 0x00, 0x73, 0x00, 0x74, 0x00,
0x65, 0x00, 0x6d, 0x00, 0x33, 0x00, 0x32, 0x00,
0x5c, 0x00, 0x57, 0x00, 0x69, 0x00, 0x6e, 0x00,
0x64, 0x00, 0x6f, 0x00, 0x77, 0x00, 0x73, 0x00,
0x50, 0x00, 0x6f, 0x00, 0x77, 0x00, 0x65, 0x00,
0x72, 0x00, 0x53, 0x00, 0x68, 0x00, 0x65, 0x00,
0x6c, 0x00, 0x6c, 0x00, 0x5c, 0x00, 0x76, 0x00,
0x31, 0x00, 0x2e, 0x00, 0x30, 0x00, 0x5c, 0x00,
0x70, 0x00, 0x6f, 0x00, 0x77, 0x00, 0x65, 0x00,
0x72, 0x00, 0x73, 0x00, 0x68, 0x00, 0x65, 0x00,
0x6c, 0x00, 0x6c, 0x00, 0x2e, 0x00, 0x65, 0x00,
0x78, 0x00, 0x65, 0x00, 0x62, 0x01, 0x00, 0x00,
0x2d, 0x00, 0x45, 0x00, 0x78, 0x00, 0x65, 0x00,
0x63, 0x00, 0x75, 0x00, 0x74, 0x00, 0x69, 0x00,
0x6f, 0x00, 0x6e, 0x00, 0x50, 0x00, 0x6f, 0x00,
0x6c, 0x00, 0x69, 0x00, 0x63, 0x00, 0x79, 0x00,
0x20, 0x00, 0x55, 0x00, 0x6e, 0x00, 0x72, 0x00,
0x65, 0x00, 0x73, 0x00, 0x74, 0x00, 0x72, 0x00,
0x69, 0x00, 0x63, 0x00, 0x74, 0x00, 0x65, 0x00,
0x64, 0x00, 0x20, 0x00, 0x2d, 0x00, 0x4e, 0x00,
0x6f, 0x00, 0x6e, 0x00, 0x49, 0x00, 0x6e, 0x00,
0x74, 0x00, 0x65, 0x00, 0x72, 0x00, 0x61, 0x00,
0x63, 0x00, 0x74, 0x00, 0x69, 0x00, 0x76, 0x00,
0x65, 0x00, 0x20, 0x00, 0x2d, 0x00, 0x4e, 0x00,
0x6f, 0x00, 0x50, 0x00, 0x72, 0x00, 0x6f, 0x00,
0x66, 0x00, 0x69, 0x00, 0x6c, 0x00, 0x65, 0x00,
0x20, 0x00, 0x2d, 0x00, 0x57, 0x00, 0x69, 0x00,
0x6e, 0x00, 0x64, 0x00, 0x6f, 0x00, 0x77, 0x00,
0x53, 0x00, 0x74, 0x00, 0x79, 0x00, 0x6c, 0x00,
0x65, 0x00, 0x20, 0x00, 0x48, 0x00, 0x69, 0x00,
0x64, 0x00, 0x64, 0x00, 0x65, 0x00, 0x6e, 0x00,
0x20, 0x00, 0x22, 0x00, 0x26, 0x00, 0x20, 0x00,
0x25, 0x00, 0x77, 0x00, 0x69, 0x00, 0x6e, 0x00,
0x64, 0x00, 0x69, 0x00, 0x72, 0x00, 0x25, 0x00,
0x5c, 0x00, 0x73, 0x00, 0x79, 0x00, 0x73, 0x00,
0x74, 0x00, 0x65, 0x00, 0x6d, 0x00, 0x33, 0x00,
0x32, 0x00, 0x5c, 0x00, 0x57, 0x00, 0x69, 0x00,
0x6e, 0x00, 0x64, 0x00, 0x6f, 0x00, 0x77, 0x00,
0x73, 0x00, 0x50, 0x00, 0x6f, 0x00, 0x77, 0x00,
0x65, 0x00, 0x72, 0x00, 0x53, 0x00, 0x68, 0x00,
0x65, 0x00, 0x6c, 0x00, 0x6c, 0x00, 0x5c, 0x00,
0x76, 0x00, 0x31, 0x00, 0x2e, 0x00, 0x30, 0x00,
0x5c, 0x00, 0x4d, 0x00, 0x6f, 0x00, 0x64, 0x00,
0x75, 0x00, 0x6c, 0x00, 0x65, 0x00, 0x73, 0x00,
0x5c, 0x00, 0x53, 0x00, 0x6d, 0x00, 0x62, 0x00,
0x53, 0x00, 0x68, 0x00, 0x61, 0x00, 0x72, 0x00,
0x65, 0x00, 0x5c, 0x00, 0x44, 0x00, 0x69, 0x00,
0x73, 0x00, 0x61, 0x00, 0x62, 0x00, 0x6c, 0x00,
0x65, 0x00, 0x55, 0x00, 0x6e, 0x00, 0x75, 0x00,
0x73, 0x00, 0x65, 0x00, 0x64, 0x00, 0x53, 0x00,
0x6d, 0x00, 0x62, 0x00, 0x31, 0x00, 0x2e, 0x00,
0x70, 0x00, 0x73, 0x00, 0x31, 0x00, 0x20, 0x00,
0x2d, 0x00, 0x53, 0x00, 0x63, 0x00, 0x65, 0x00,
0x6e, 0x00, 0x61, 0x00, 0x72, 0x00, 0x69, 0x00,
0x6f, 0x00, 0x20, 0x00, 0x43, 0x00, 0x6c, 0x00,
0x69, 0x00, 0x65, 0x00, 0x6e, 0x00, 0x74, 0x00,
0x22, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
]
)
try:
actions = scheduled_tasks.ActionSet.decode(buf).actions # type: ignore
self.assertEqual(len(actions), 1)
self.assertEqual(actions[0].action_type, scheduled_tasks.ActionType.Exe)
except Exception:
self.fail(
"ActionDecoder.decode should not raise exception:\n%s"
% traceback.format_exc()
)
class TestTriggersDecoding(unittest.TestCase):
def test_decode_all_triggers(self):
"""
Tests decoding a set of all triggers that can be constructed via the
Task Scheduler GUI interface. Ensures that the correct number of bytes
is being consumed for each trigger structure.
"""
buf = struct.pack(
"1808B",
# fmt: off
*[
0x17, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0xda, 0xaf, 0x8d, 0x09, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0xda, 0xaf, 0x8d, 0x09, 0x00, 0x00, 0x00,
0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
0x38, 0x21, 0x41, 0x42, 0x48, 0x48, 0x48, 0x48,
0xa0, 0x12, 0xa0, 0xa4, 0x48, 0x48, 0x48, 0x48,
0x0e, 0x00, 0x00, 0x00, 0x48, 0x48, 0x48, 0x48,
0x41, 0x00, 0x75, 0x00, 0x74, 0x00, 0x68, 0x00,
0x6f, 0x00, 0x72, 0x00, 0x00, 0x00, 0x48, 0x48,
0x00, 0x00, 0x00, 0x00, 0x48, 0x48, 0x48, 0x48,
0x00, 0x48, 0x48, 0x48, 0x48, 0x48, 0x48, 0x48,
0x00, 0x48, 0x48, 0x48, 0x48, 0x48, 0x48, 0x48,
0x01, 0x00, 0x00, 0x00, 0x48, 0x48, 0x48, 0x48,
0x1c, 0x00, 0x00, 0x00, 0x48, 0x48, 0x48, 0x48,
0x01, 0x05, 0x00, 0x00, 0x00, 0x00, 0x00, 0x05,
0x15, 0x00, 0x00, 0x00, 0x69, 0xce, 0x28, 0x2a,
0xce, 0xd8, 0x1f, 0x77, 0x37, 0x9c, 0xe2, 0x44,
0xf4, 0x01, 0x00, 0x00, 0x48, 0x48, 0x48, 0x48,
0x40, 0x00, 0x00, 0x00, 0x48, 0x48, 0x48, 0x48,
0x44, 0x00, 0x45, 0x00, 0x53, 0x00, 0x4b, 0x00,
0x54, 0x00, 0x4f, 0x00, 0x50, 0x00, 0x2d, 0x00,
0x45, 0x00, 0x33, 0x00, 0x38, 0x00, 0x38, 0x00,
0x44, 0x00, 0x38, 0x00, 0x50, 0x00, 0x5c, 0x00,
0x41, 0x00, 0x64, 0x00, 0x6d, 0x00, 0x69, 0x00,
0x6e, 0x00, 0x69, 0x00, 0x73, 0x00, 0x74, 0x00,
0x72, 0x00, 0x61, 0x00, 0x74, 0x00, 0x6f, 0x00,
0x72, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x2c, 0x00, 0x00, 0x00, 0x48, 0x48, 0x48, 0x48,
0x00, 0x00, 0x00, 0x00, 0xff, 0xff, 0xff, 0xff,
0x80, 0xf4, 0x03, 0x00, 0xff, 0xff, 0xff, 0xff,
0x07, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x48, 0x48, 0x48, 0x48,
0xdd, 0xdd, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x01, 0x07, 0x0a, 0x00, 0x00, 0x00, 0x09, 0x00,
0x80, 0x48, 0x11, 0xf8, 0x36, 0x1a, 0xdb, 0x01,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0xff, 0xff, 0xff, 0xff, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x01, 0x2e, 0xe2, 0x01, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0xc2, 0x31, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x48, 0x48, 0x48, 0x48,
0xaa, 0xaa, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0xda, 0xaf, 0x8d, 0x09, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0xda, 0xaf, 0x8d, 0x09, 0x00, 0x00, 0x00,
0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
0x00, 0x00, 0x00, 0x00, 0xff, 0xff, 0xff, 0xff,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00,
0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x48, 0x48, 0x48, 0x48,
0x01, 0x48, 0x48, 0x48, 0x48, 0x48, 0x48, 0x48,
0xff, 0xff, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0xda, 0xaf, 0x8d, 0x09, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0xda, 0xaf, 0x8d, 0x09, 0x00, 0x00, 0x00,
0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
0x00, 0x00, 0x00, 0x00, 0xff, 0xff, 0xff, 0xff,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x48, 0x48, 0x48, 0x48,
0xee, 0xee, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0xda, 0xaf, 0x8d, 0x09, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0xda, 0xaf, 0x8d, 0x09, 0x00, 0x00, 0x00,
0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
0x00, 0x00, 0x00, 0x00, 0xff, 0xff, 0xff, 0xff,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x48, 0x48, 0x48, 0x48,
0xcc, 0xcc, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
0x00, 0x00, 0x00, 0x00, 0xff, 0xff, 0xff, 0xff,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x01, 0x00, 0x65, 0x00, 0x78, 0x00, 0x65, 0x00,
0x22, 0x00, 0x20, 0x00, 0x53, 0x00, 0x74, 0x00,
0x00, 0x00, 0x00, 0x00, 0x48, 0x48, 0x48, 0x48,
0x84, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x3c, 0x00, 0x51, 0x00, 0x75, 0x00, 0x65, 0x00,
0x72, 0x00, 0x79, 0x00, 0x4c, 0x00, 0x69, 0x00,
0x73, 0x00, 0x74, 0x00, 0x3e, 0x00, 0x3c, 0x00,
0x51, 0x00, 0x75, 0x00, 0x65, 0x00, 0x72, 0x00,
0x79, 0x00, 0x20, 0x00, 0x49, 0x00, 0x64, 0x00,
0x3d, 0x00, 0x22, 0x00, 0x30, 0x00, 0x22, 0x00,
0x20, 0x00, 0x50, 0x00, 0x61, 0x00, 0x74, 0x00,
0x68, 0x00, 0x3d, 0x00, 0x22, 0x00, 0x49, 0x00,
0x6e, 0x00, 0x74, 0x00, 0x65, 0x00, 0x72, 0x00,
0x6e, 0x00, 0x65, 0x00, 0x74, 0x00, 0x20, 0x00,
0x45, 0x00, 0x78, 0x00, 0x70, 0x00, 0x6c, 0x00,
0x6f, 0x00, 0x72, 0x00, 0x65, 0x00, 0x72, 0x00,
0x22, 0x00, 0x3e, 0x00, 0x3c, 0x00, 0x53, 0x00,
0x65, 0x00, 0x6c, 0x00, 0x65, 0x00, 0x63, 0x00,
0x74, 0x00, 0x20, 0x00, 0x50, 0x00, 0x61, 0x00,
0x74, 0x00, 0x68, 0x00, 0x3d, 0x00, 0x22, 0x00,
0x49, 0x00, 0x6e, 0x00, 0x74, 0x00, 0x65, 0x00,
0x72, 0x00, 0x6e, 0x00, 0x65, 0x00, 0x74, 0x00,
0x20, 0x00, 0x45, 0x00, 0x78, 0x00, 0x70, 0x00,
0x6c, 0x00, 0x6f, 0x00, 0x72, 0x00, 0x65, 0x00,
0x72, 0x00, 0x22, 0x00, 0x3e, 0x00, 0x2a, 0x00,
0x5b, 0x00, 0x53, 0x00, 0x79, 0x00, 0x73, 0x00,
0x74, 0x00, 0x65, 0x00, 0x6d, 0x00, 0x5b, 0x00,
0x45, 0x00, 0x76, 0x00, 0x65, 0x00, 0x6e, 0x00,
0x74, 0x00, 0x49, 0x00, 0x44, 0x00, 0x3d, 0x00,
0x32, 0x00, 0x5d, 0x00, 0x5d, 0x00, 0x3c, 0x00,
0x2f, 0x00, 0x53, 0x00, 0x65, 0x00, 0x6c, 0x00,
0x65, 0x00, 0x63, 0x00, 0x74, 0x00, 0x3e, 0x00,
0x3c, 0x00, 0x2f, 0x00, 0x51, 0x00, 0x75, 0x00,
0x65, 0x00, 0x72, 0x00, 0x79, 0x00, 0x3e, 0x00,
0x3c, 0x00, 0x2f, 0x00, 0x51, 0x00, 0x75, 0x00,
0x65, 0x00, 0x72, 0x00, 0x79, 0x00, 0x4c, 0x00,
0x69, 0x00, 0x73, 0x00, 0x74, 0x00, 0x3e, 0x00,
0x00, 0x00, 0x48, 0x48, 0x48, 0x48, 0x48, 0x48,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x88, 0x88, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
0x00, 0x00, 0x00, 0x00, 0xff, 0xff, 0xff, 0xff,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x48, 0x48, 0x48, 0x48,
0x77, 0x77, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
0x00, 0x00, 0x00, 0x00, 0xff, 0xff, 0xff, 0xff,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00,
0x01, 0xff, 0xff, 0xff, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x48, 0x48, 0x48, 0x48,
0x03, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x01, 0x48, 0x48, 0x48, 0x48, 0x48, 0x48, 0x48,
0x77, 0x77, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
0x00, 0x00, 0x00, 0x00, 0xff, 0xff, 0xff, 0xff,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00,
0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x48, 0x48, 0x48, 0x48,
0x04, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x48, 0x48, 0x48, 0x48, 0x48, 0x48, 0x48,
0x00, 0x48, 0x48, 0x48, 0x48, 0x48, 0x48, 0x48,
0x01, 0x00, 0x00, 0x00, 0x48, 0x48, 0x48, 0x48,
0x1c, 0x00, 0x00, 0x00, 0x48, 0x48, 0x48, 0x48,
0x01, 0x05, 0x00, 0x00, 0x00, 0x00, 0x00, 0x05,
0x15, 0x00, 0x00, 0x00, 0x69, 0xce, 0x28, 0x2a,
0xce, 0xd8, 0x1f, 0x77, 0x37, 0x9c, 0xe2, 0x44,
0xf4, 0x01, 0x00, 0x00, 0x48, 0x48, 0x48, 0x48,
0x40, 0x00, 0x00, 0x00, 0x48, 0x48, 0x48, 0x48,
0x44, 0x00, 0x45, 0x00, 0x53, 0x00, 0x4b, 0x00,
0x54, 0x00, 0x4f, 0x00, 0x50, 0x00, 0x2d, 0x00,
0x45, 0x00, 0x33, 0x00, 0x38, 0x00, 0x38, 0x00,
0x44, 0x00, 0x38, 0x00, 0x50, 0x00, 0x5c, 0x00,
0x41, 0x00, 0x64, 0x00, 0x6d, 0x00, 0x69, 0x00,
0x6e, 0x00, 0x69, 0x00, 0x73, 0x00, 0x74, 0x00,
0x72, 0x00, 0x61, 0x00, 0x74, 0x00, 0x6f, 0x00,
0x72, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x77, 0x77, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
0x00, 0x00, 0x00, 0x00, 0xff, 0xff, 0xff, 0xff,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00,
0x01, 0xff, 0xff, 0xff, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x48, 0x48, 0x48, 0x48,
0x07, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x01, 0x48, 0x48, 0x48, 0x48, 0x48, 0x48, 0x48,
0x77, 0x77, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
0x00, 0x00, 0x00, 0x00, 0xff, 0xff, 0xff, 0xff,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00,
0x01, 0xff, 0xff, 0xff, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x48, 0x48, 0x48, 0x48,
0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x48, 0x48, 0x48, 0x48, 0x48, 0x48, 0x48,
0x00, 0x48, 0x48, 0x48, 0x48, 0x48, 0x48, 0x48,
0x01, 0x00, 0x00, 0x00, 0x48, 0x48, 0x48, 0x48,
0x1c, 0x00, 0x00, 0x00, 0x48, 0x48, 0x48, 0x48,
0x01, 0x05, 0x00, 0x00, 0x00, 0x00, 0x00, 0x05,
0x15, 0x00, 0x00, 0x00, 0x69, 0xce, 0x28, 0x2a,
0xce, 0xd8, 0x1f, 0x77, 0x37, 0x9c, 0xe2, 0x44,
0xf4, 0x01, 0x00, 0x00, 0x48, 0x48, 0x48, 0x48,
0x40, 0x00, 0x00, 0x00, 0x48, 0x48, 0x48, 0x48,
0x44, 0x00, 0x45, 0x00, 0x53, 0x00, 0x4b, 0x00,
0x54, 0x00, 0x4f, 0x00, 0x50, 0x00, 0x2d, 0x00,
0x45, 0x00, 0x33, 0x00, 0x38, 0x00, 0x38, 0x00,
0x44, 0x00, 0x38, 0x00, 0x50, 0x00, 0x5c, 0x00,
0x41, 0x00, 0x64, 0x00, 0x6d, 0x00, 0x69, 0x00,
0x6e, 0x00, 0x69, 0x00, 0x73, 0x00, 0x74, 0x00,
0x72, 0x00, 0x61, 0x00, 0x74, 0x00, 0x6f, 0x00,
0x72, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00
]
# fmt: on
)
triggers = scheduled_tasks.TriggerSet.decode(buf)
self.assertIsNotNone(triggers)
def test_decode_triggers(self):
# fmt: off
buf = struct.pack(
"320B",
*[
0x17, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0xB9, 0x61, 0x1A, 0xA8, 0xB9, 0x61, 0x1A,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0xB9, 0x61, 0x1A, 0xA8, 0xB9, 0x61, 0x1A,
0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF,
0x08, 0xA1, 0x40, 0x42, 0x48, 0x48, 0x48, 0x48,
0x7A, 0x7F, 0x59, 0xDC, 0x48, 0x48, 0x48, 0x48,
0x22, 0x00, 0x00, 0x00, 0x48, 0x48, 0x48, 0x48,
0x49, 0x00, 0x6E, 0x00, 0x74, 0x00, 0x65, 0x00,
0x72, 0x00, 0x61, 0x00, 0x63, 0x00, 0x74, 0x00,
0x69, 0x00, 0x76, 0x00, 0x65, 0x00, 0x55, 0x00,
0x73, 0x00, 0x65, 0x00, 0x72, 0x00, 0x73, 0x00,
0x00, 0x00, 0x48, 0x48, 0x48, 0x48, 0x48, 0x48,
0x00, 0x00, 0x00, 0x00, 0x48, 0x48, 0x48, 0x48,
0x00, 0x48, 0x48, 0x48, 0x48, 0x48, 0x48, 0x48,
0x00, 0x48, 0x48, 0x48, 0x48, 0x48, 0x48, 0x48,
0x05, 0x00, 0x00, 0x00, 0x48, 0x48, 0x48, 0x48,
0x0C, 0x00, 0x00, 0x00, 0x48, 0x48, 0x48, 0x48,
0x01, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x05,
0x04, 0x00, 0x00, 0x00, 0x48, 0x48, 0x48, 0x48,
0x00, 0x00, 0x00, 0x00, 0x48, 0x48, 0x48, 0x48,
0x2C, 0x00, 0x00, 0x00, 0x48, 0x48, 0x48, 0x48,
0x00, 0x00, 0x00, 0x00, 0xFF, 0xFF, 0xFF, 0xFF,
0x80, 0x51, 0x01, 0x00, 0xFF, 0xFF, 0xFF, 0xFF,
0x07, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x48, 0x48, 0x48, 0x48,
0xAA, 0xAA, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0xB9, 0x61, 0x1A, 0xA8, 0xB9, 0x61, 0x1A,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0xB9, 0x61, 0x1A, 0xA8, 0xB9, 0x61, 0x1A,
0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF,
0x2C, 0x01, 0x00, 0x00, 0xFF, 0xFF, 0xFF, 0xFF,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0xC1, 0xD9, 0x04,
0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x0F, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x48, 0x48, 0x48, 0x48,
0x01, 0x48, 0x48, 0x48, 0x48, 0x48, 0x48, 0x48,
]
)
# fmt: on
triggers = scheduled_tasks.TriggerSet.decode(buf)
self.assertIsNotNone(triggers)
if not triggers:
return
self.assertGreater(len(triggers.triggers), 0)
+1
View File
@@ -6,5 +6,6 @@ pefile>=2017.8.1 #foo
# This is required for the yara plugins
yara-python>=3.8.0
yara-x>=0.5.0
pytest>=7.0.0
+11
View File
@@ -341,6 +341,17 @@ def test_linux_tty_check(image, volatility, python):
assert out.count(b"\n") >= 5
assert rc == 0
def test_linux_sockstat(image, volatility, python):
rc, out, err = runvol_plugin("linux.sockstat.Sockstat", image, volatility, python)
assert out.count(b"AF_UNIX") >= 354
assert out.count(b"AF_BLUETOOTH") >= 5
assert out.count(b"AF_INET") >= 32
assert out.count(b"AF_INET6") >= 20
assert out.count(b"AF_PACKET") >= 1
assert out.count(b"AF_NETLINK") >= 43
assert rc == 0
def test_linux_library_list(image, volatility, python):
rc, out, err = runvol_plugin(
+28 -2
View File
@@ -235,18 +235,35 @@ class CommandLine:
default=constants.CACHE_PATH,
type=str,
)
parser.add_argument(
isf_group = parser.add_mutually_exclusive_group()
isf_group.add_argument(
"--offline",
help="Do not search online for additional JSON files",
default=False,
action="store_true",
)
isf_group.add_argument(
"-u",
"--remote-isf-url",
metavar="URL",
help="Search online for ISF json files",
default=constants.REMOTE_ISF_URL,
type=str,
)
parser.add_argument(
"--filters",
help="List of filters to apply to the output (in the form of [+-]columname,pattern[!])",
default=[],
action="append",
)
parser.add_argument(
"--hide-columns",
help="Case-insensitive space separated list of prefixes to determine which columns to hide in the output if provided",
default=None,
action="extend",
nargs="*",
type=str,
)
parser.set_defaults(**default_config)
@@ -313,6 +330,8 @@ class CommandLine:
if partial_args.offline:
constants.OFFLINE = partial_args.offline
elif partial_args.remote_isf_url:
constants.REMOTE_ISF_URL = partial_args.remote_isf_url
# Do the initialization
ctx = contexts.Context() # Construct a blank context
@@ -348,7 +367,9 @@ class CommandLine:
)
for plugin in sorted(plugin_list):
plugin_parser = subparser.add_parser(
plugin, help=plugin_list[plugin].__doc__
plugin,
help=plugin_list[plugin].__doc__,
description=plugin_list[plugin].__doc__,
)
self.populate_requirements_argparse(plugin_parser, plugin_list[plugin])
@@ -477,6 +498,7 @@ class CommandLine:
grid = constructed.run()
renderer = renderers[args.renderer]()
renderer.filter = text_filter.CLIFilter(grid, args.filters)
renderer.column_hide_list = args.hide_columns
renderer.render(grid)
except exceptions.VolatilityException as excp:
self.process_exceptions(excp)
@@ -604,6 +626,10 @@ class CommandLine:
caused_by = [
"A required python module is not installed (install the module and re-run)"
]
elif isinstance(excp, exceptions.RenderException):
general = "Volatility experienced an issue when rendering the output:"
detail = f"{excp}"
caused_by = ["An invalid renderer option, such as no visible columns"]
else:
general = "Volatility encountered an unexpected situation."
detail = ""
+2 -2
View File
@@ -53,7 +53,7 @@ class CLIFilter:
"""Filters the row based on each of the column_filters"""
if not self._filters:
return False
found = any([column_filter.found(row) for column_filter in self._filters])
found = any(column_filter.found(row) for column_filter in self._filters)
return not found
@@ -86,7 +86,7 @@ class ColumnFilter:
otherwise it is filtered.
"""
if self.column_num is None:
found = any([self.find(x) for x in row])
found = any(self.find(x) for x in row)
else:
found = self.find(row[self.column_num])
if self.exclude:
+96 -41
View File
@@ -12,7 +12,7 @@ from functools import wraps
from typing import Any, Callable, Dict, List, Tuple
from volatility3.cli import text_filter
from volatility3.framework import interfaces, renderers
from volatility3.framework import exceptions, interfaces, renderers
from volatility3.framework.renderers import format_hints
vollog = logging.getLogger(__name__)
@@ -141,6 +141,30 @@ class CLIRenderer(interfaces.renderers.Renderer):
name = "unnamed"
structured_output = False
filter: text_filter.CLIFilter = None
column_hide_list: list = None
def ignored_columns(
self,
grid: interfaces.renderers.TreeGrid,
) -> List[interfaces.renderers.Column]:
ignored_column_list = []
if self.column_hide_list:
for column in grid.columns:
accept = True
for column_prefix in self.column_hide_list:
if column.name.lower().startswith(column_prefix.lower()):
accept = False
if not accept:
ignored_column_list.append(column)
elif self.column_hide_list is None:
return []
if len(ignored_column_list) == len(grid.columns):
raise exceptions.RenderException("No visible columns to render")
vollog.info(
f"Hiding columns: {[column.name for column in ignored_column_list]}"
)
return ignored_column_list
class QuickTextRenderer(CLIRenderer):
@@ -173,13 +197,23 @@ class QuickTextRenderer(CLIRenderer):
outfd = sys.stdout
line = []
ignore_columns = self.ignored_columns(grid)
for column in grid.columns:
# Ignore the type because namedtuples don't realize they have accessible attributes
line.append(f"{column.name}")
if column not in ignore_columns:
line.append(f"{column.name}")
outfd.write("\n{}\n".format("\t".join(line)))
def visitor(node: interfaces.renderers.TreeNode, accumulator):
if self.filter and self.filter.filter(node.values):
line = []
for column_index, column in enumerate(grid.columns):
renderer = self._type_renderers.get(
column.type, self._type_renderers["default"]
)
if column not in ignore_columns:
line.append(renderer(node.values[column_index]))
if self.filter and self.filter.filter(line):
return accumulator
accumulator.write("\n")
@@ -188,13 +222,6 @@ class QuickTextRenderer(CLIRenderer):
"*" * max(0, node.path_depth - 1)
+ ("" if (node.path_depth <= 1) else " ")
)
line = []
for column_index in range(len(grid.columns)):
column = grid.columns[column_index]
renderer = self._type_renderers.get(
column.type, self._type_renderers["default"]
)
line.append(renderer(node.values[column_index]))
accumulator.write("{}".format("\t".join(line)))
accumulator.flush()
return accumulator
@@ -245,11 +272,13 @@ class CSVRenderer(CLIRenderer):
grid: The TreeGrid object to render
"""
outfd = sys.stdout
ignore_columns = self.ignored_columns(grid)
header_list = ["TreeDepth"]
for column in grid.columns:
# Ignore the type because namedtuples don't realize they have accessible attributes
header_list.append(f"{column.name}")
if column not in ignore_columns:
header_list.append(f"{column.name}")
writer = csv.DictWriter(
outfd, header_list, lineterminator="\n", escapechar="\\"
@@ -259,12 +288,20 @@ class CSVRenderer(CLIRenderer):
def visitor(node: interfaces.renderers.TreeNode, accumulator):
# Nodes always have a path value, giving them a path_depth of at least 1, we use max just in case
row = {"TreeDepth": str(max(0, node.path_depth - 1))}
for column_index in range(len(grid.columns)):
column = grid.columns[column_index]
line = []
for column_index, column in enumerate(grid.columns):
renderer = self._type_renderers.get(
column.type, self._type_renderers["default"]
)
row[f"{column.name}"] = renderer(node.values[column_index])
if column not in ignore_columns:
line.append(row[f"{column.name}"])
else:
del row[f"{column.name}"]
if self.filter and self.filter.filter(line):
return accumulator
accumulator.writerow(row)
return accumulator
@@ -298,6 +335,7 @@ class PrettyTextRenderer(CLIRenderer):
sys.stderr.write("Formatting...\n")
ignore_columns = self.ignored_columns(grid)
display_alignment = ">"
column_separator = " | "
@@ -317,12 +355,9 @@ class PrettyTextRenderer(CLIRenderer):
max_column_widths.get(tree_indent_column, 0), node.path_depth
)
if self.filter and self.filter.filter(node.values):
return accumulator
line = {}
for column_index in range(len(grid.columns)):
column = grid.columns[column_index]
rendered_line = []
for column_index, column in enumerate(grid.columns):
renderer = self._type_renderers.get(
column.type, self._type_renderers["default"]
)
@@ -333,7 +368,13 @@ class PrettyTextRenderer(CLIRenderer):
max_column_widths[column.name] = max(
max_column_widths.get(column.name, len(column.name)), field_width
)
line[column] = data.split("\n")
if column not in ignore_columns:
line[column] = data.split("\n")
rendered_line.append(data)
if self.filter and self.filter.filter(rendered_line):
return accumulator
accumulator.append((node.path_depth, line))
return accumulator
@@ -347,44 +388,49 @@ class PrettyTextRenderer(CLIRenderer):
format_string_list = [
"{0:<" + str(max_column_widths.get(tree_indent_column, 0)) + "s}"
]
for column_index in range(len(grid.columns)):
column = grid.columns[column_index]
format_string_list.append(
"{"
+ str(column_index + 1)
+ ":"
+ display_alignment
+ str(max_column_widths[column.name])
+ "s}"
)
column_offset = 0
for column_index, column in enumerate(grid.columns):
if column not in ignore_columns:
format_string_list.append(
"{"
+ str(column_index - column_offset + 1)
+ ":"
+ display_alignment
+ str(max_column_widths[column.name])
+ "s}"
)
else:
column_offset += 1
format_string = column_separator.join(format_string_list) + "\n"
column_titles = [""] + [column.name for column in grid.columns]
column_titles = [""] + [
column.name for column in grid.columns if column not in ignore_columns
]
outfd.write(format_string.format(*column_titles))
for depth, line in final_output:
nums_line = max([len(line[column]) for column in line])
for column in line:
line[column] = line[column] + ([""] * (nums_line - len(line[column])))
if column in ignore_columns:
del line[column]
else:
line[column] = line[column] + (
[""] * (nums_line - len(line[column]))
)
for index in range(nums_line):
if index == 0:
outfd.write(
format_string.format(
"*" * depth,
*[
self.tab_stop(line[column][index])
for column in grid.columns
],
*[self.tab_stop(line[column][index]) for column in line],
)
)
else:
outfd.write(
format_string.format(
" " * depth,
*[
self.tab_stop(line[column][index])
for column in grid.columns
],
*[self.tab_stop(line[column][index]) for column in line],
)
)
@@ -430,6 +476,8 @@ class JsonRenderer(CLIRenderer):
List[interfaces.renderers.TreeNode],
] = ({}, [])
ignore_columns = self.ignored_columns(grid)
def visitor(
node: interfaces.renderers.TreeNode,
accumulator: Tuple[Dict[str, Dict[str, Any]], List[Dict[str, Any]]],
@@ -437,8 +485,10 @@ class JsonRenderer(CLIRenderer):
# Nodes always have a path value, giving them a path_depth of at least 1, we use max just in case
acc_map, final_tree = accumulator
node_dict: Dict[str, Any] = {"__children": []}
for column_index in range(len(grid.columns)):
column = grid.columns[column_index]
line = []
for column_index, column in enumerate(grid.columns):
if column in ignore_columns:
continue
renderer = self._type_renderers.get(
column.type, self._type_renderers["default"]
)
@@ -446,6 +496,11 @@ class JsonRenderer(CLIRenderer):
if isinstance(data, interfaces.renderers.BaseAbsentValue):
data = None
node_dict[column.name] = data
line.append(data)
if self.filter and self.filter.filter(line):
return accumulator
if node.parent:
acc_map[node.parent.path]["__children"].append(node_dict)
else:
+12 -1
View File
@@ -159,12 +159,21 @@ class VolShell(cli.CommandLine):
default=constants.CACHE_PATH,
type=str,
)
parser.add_argument(
isf_group = parser.add_mutually_exclusive_group()
isf_group.add_argument(
"--offline",
help="Do not search online for additional JSON files",
default=False,
action="store_true",
)
isf_group.add_argument(
"-u",
"--remote-isf-url",
metavar="URL",
help="Search online for ISF json files",
default=constants.REMOTE_ISF_URL,
type=str,
)
# Volshell specific flags
os_specific = parser.add_mutually_exclusive_group(required=False)
@@ -236,6 +245,8 @@ class VolShell(cli.CommandLine):
if partial_args.offline:
constants.OFFLINE = partial_args.offline
elif partial_args.remote_isf_url:
constants.REMOTE_ISF_URL = partial_args.remote_isf_url
# Do the initialization
ctx = contexts.Context() # Construct a blank context
+42 -12
View File
@@ -14,7 +14,7 @@ from urllib import parse, request
from volatility3.cli import text_renderer, volshell
from volatility3.framework import exceptions, interfaces, objects, plugins, renderers
from volatility3.framework.configuration import requirements
from volatility3.framework.layers import intel, physical, resources
from volatility3.framework.layers import intel, physical, resources, scanners
try:
import capstone
@@ -29,6 +29,8 @@ class Volshell(interfaces.plugins.PluginInterface):
_required_framework_version = (2, 0, 0)
DEFAULT_NUM_DISPLAY_BYTES = 128
def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
self.__current_layer: Optional[str] = None
@@ -58,7 +60,7 @@ class Volshell(interfaces.plugins.PluginInterface):
]
def run(
self, additional_locals: Dict[str, Any] = None
self, additional_locals: Dict[str, Any] = {}
) -> interfaces.renderers.TreeGrid:
"""Runs the interactive volshell plugin.
@@ -94,7 +96,10 @@ class Volshell(interfaces.plugins.PluginInterface):
"""
sys.ps1 = f"({self.current_layer}) >>> "
self.__console = code.InteractiveConsole(locals=self._construct_locals_dict())
# Dict self._construct_locals_dict() will have priority on keys
combined_locals = additional_locals.copy()
combined_locals.update(self._construct_locals_dict())
self.__console = code.InteractiveConsole(locals=combined_locals)
# Since we have to do work to add the option only once for all different modes of volshell, we can't
# rely on the default having been set
if self.config.get("script", None) is not None:
@@ -112,7 +117,7 @@ class Volshell(interfaces.plugins.PluginInterface):
variables = []
print("\nMethods:")
for aliases, item in self.construct_locals():
for aliases, item in sorted(self.construct_locals()):
name = ", ".join(aliases)
if item.__doc__ and callable(item):
print(f"* {name}")
@@ -125,8 +130,7 @@ class Volshell(interfaces.plugins.PluginInterface):
print(f" {var}")
def construct_locals(self) -> List[Tuple[List[str], Any]]:
"""Returns a dictionary listing the functions to be added to the
environment."""
"""Returns a listing of the functions to be added to the environment."""
return [
(["dt", "display_type"], self.display_type),
(["db", "display_bytes"], self.display_bytes),
@@ -147,6 +151,7 @@ class Volshell(interfaces.plugins.PluginInterface):
(["cc", "create_configurable"], self.create_configurable),
(["lf", "load_file"], self.load_file),
(["rs", "run_script"], self.run_script),
(["rx", "regex_scan"], self.regex_scan),
]
def _construct_locals_dict(self) -> Dict[str, Any]:
@@ -266,27 +271,52 @@ class Volshell(interfaces.plugins.PluginInterface):
self.__current_kernel_name = kernel_name
print(f"Current kernel : {self.current_kernel_name}")
def display_bytes(self, offset, count=128, layer_name=None):
def display_bytes(self, offset, count=DEFAULT_NUM_DISPLAY_BYTES, layer_name=None):
"""Displays byte values and ASCII characters"""
remaining_data = self._read_data(offset, count=count, layer_name=layer_name)
self._display_data(offset, remaining_data)
def display_quadwords(self, offset, count=128, layer_name=None):
def display_quadwords(
self, offset, count=DEFAULT_NUM_DISPLAY_BYTES, layer_name=None
):
"""Displays quad-word values (8 bytes) and corresponding ASCII characters"""
remaining_data = self._read_data(offset, count=count, layer_name=layer_name)
self._display_data(offset, remaining_data, format_string="Q")
def display_doublewords(self, offset, count=128, layer_name=None):
def display_doublewords(
self, offset, count=DEFAULT_NUM_DISPLAY_BYTES, layer_name=None
):
"""Displays double-word values (4 bytes) and corresponding ASCII characters"""
remaining_data = self._read_data(offset, count=count, layer_name=layer_name)
self._display_data(offset, remaining_data, format_string="I")
def display_words(self, offset, count=128, layer_name=None):
def display_words(self, offset, count=DEFAULT_NUM_DISPLAY_BYTES, layer_name=None):
"""Displays word values (2 bytes) and corresponding ASCII characters"""
remaining_data = self._read_data(offset, count=count, layer_name=layer_name)
self._display_data(offset, remaining_data, format_string="H")
def disassemble(self, offset, count=128, layer_name=None, architecture=None):
def regex_scan(self, pattern, count=DEFAULT_NUM_DISPLAY_BYTES, layer_name=None):
"""Scans for regex pattern in layer using RegExScanner."""
if not isinstance(pattern, bytes):
raise TypeError("pattern must be bytes, e.g. rx(b'pattern')")
layer_name_to_scan = layer_name or self.current_layer
for offset in self.context.layers[layer_name_to_scan].scan(
scanner=scanners.RegExScanner(pattern),
context=self.context,
):
remaining_data = self._read_data(
offset, count=count, layer_name=layer_name_to_scan
)
self._display_data(offset, remaining_data)
print("")
def disassemble(
self,
offset,
count=DEFAULT_NUM_DISPLAY_BYTES,
layer_name=None,
architecture=None,
):
"""Disassembles a number of instructions from the code at offset"""
remaining_data = self._read_data(offset, count=count, layer_name=layer_name)
if not has_capstone:
@@ -529,7 +559,7 @@ class Volshell(interfaces.plugins.PluginInterface):
val, interfaces.configuration.BasicTypes
) and not isinstance(val, list):
if not isinstance(val, list) or all(
[isinstance(x, interfaces.configuration.BasicTypes) for x in val]
isinstance(x, interfaces.configuration.BasicTypes) for x in val
):
raise TypeError(
"Configurable values must be simple types (int, bool, str, bytes)"
+1 -1
View File
@@ -7,7 +7,7 @@ import glob
import sys
import zipfile
required_python_version = (3, 7, 3)
required_python_version = (3, 8, 0)
if (
sys.version_info.major != required_python_version[0]
or sys.version_info.minor < required_python_version[1]
+2 -4
View File
@@ -209,10 +209,8 @@ class KernelPDBScanner(interfaces.automagic.AutomagicInterface):
try:
kvp = vlayer.mapping(kvo, 0)
if any(
[
(p == kernel["mz_offset"] and layer_name == physical_layer_name)
for (_, _, p, _, layer_name) in kvp
]
(p == kernel["mz_offset"] and layer_name == physical_layer_name)
for (_, _, p, _, layer_name) in kvp
):
return (virtual_layer_name, kvo, kernel)
else:
@@ -161,7 +161,7 @@ class ListRequirement(interfaces.configuration.RequirementInterface):
"TypeError - Too many values provided to list option.",
)
return {config_path: self}
if not all([isinstance(element, self.element_type) for element in value]):
if not all(isinstance(element, self.element_type) for element in value):
vollog.log(
constants.LOGLEVEL_V,
"TypeError - At least one element in the list is not of the correct type.",
@@ -181,7 +181,7 @@ class ChoiceRequirement(interfaces.configuration.RequirementInterface):
"""
super().__init__(*args, **kwargs)
if not isinstance(choices, list) or any(
[not isinstance(choice, str) for choice in choices]
not isinstance(choice, str) for choice in choices
):
raise TypeError("ChoiceRequirement takes a list of strings as choices")
self.choices = choices
@@ -410,11 +410,9 @@ class TranslationLayerRequirement(
args = {"context": context, "config_path": config_path, "name": name}
if any(
[
subreq.unsatisfied(context, config_path)
for subreq in self.requirements.values()
if not subreq.optional
]
subreq.unsatisfied(context, config_path)
for subreq in self.requirements.values()
if not subreq.optional
):
return None
@@ -485,11 +483,9 @@ class SymbolTableRequirement(
args = {"context": context, "config_path": config_path, "name": name}
if any(
[
subreq.unsatisfied(context, config_path)
for subreq in self.requirements.values()
if not subreq.optional
]
subreq.unsatisfied(context, config_path)
for subreq in self.requirements.values()
if not subreq.optional
):
return None
@@ -527,12 +523,14 @@ class VersionRequirement(interfaces.configuration.RequirementInterface):
def __init__(
self,
name: str,
description: str = None,
description: Optional[str] = None,
default: bool = False,
optional: bool = False,
component: Type[interfaces.configuration.VersionableInterface] = None,
version: Optional[Tuple[int, ...]] = None,
) -> None:
if description is None:
description = f"Version {'.'.join([str(x) for x in version])} dependency on {component.__module__}.{component.__name__} unmet"
super().__init__(
name=name, description=description, default=default, optional=optional
)
@@ -544,15 +542,51 @@ class VersionRequirement(interfaces.configuration.RequirementInterface):
self._version = version
def unsatisfied(
self, context: interfaces.context.ContextInterface, config_path: str
self,
context: interfaces.context.ContextInterface,
config_path: str,
accumulator: Optional[
List[interfaces.configuration.VersionableInterface]
] = None,
) -> Dict[str, interfaces.configuration.RequirementInterface]:
# Mypy doesn't appreciate our classproperty implementation, self._plugin.version has no type
config_path = interfaces.configuration.path_join(config_path, self.name)
if not self.matches_required(self._version, self._component.version):
return {config_path: self}
recurse = True
if accumulator is None:
accumulator = set([self._component])
else:
if self._component in accumulator:
recurse = False
else:
accumulator.add(self._component)
# Check for child requirements
if (
issubclass(self._component, interfaces.configuration.ConfigurableInterface)
and recurse
):
result = {}
for requirement in self._component.get_requirements():
if not requirement.optional and isinstance(
requirement, VersionRequirement
):
result.update(
requirement.unsatisfied(
context, config_path, accumulator.copy()
)
)
if result:
result.update({config_path: self})
return result
context.config[interfaces.configuration.path_join(config_path, self.name)] = (
True
)
return {}
@classmethod
@@ -672,11 +706,9 @@ class ModuleRequirement(
args = {"context": context, "config_path": config_path, "name": name}
if any(
[
subreq.unsatisfied(context, config_path)
for subreq in self.requirements.values()
if not subreq.optional
]
subreq.unsatisfied(context, config_path)
for subreq in self.requirements.values()
if not subreq.optional
):
return None
+2 -1
View File
@@ -134,4 +134,5 @@ def __getattr__(name):
]:
warnings.warn(f"{name} is deprecated", FutureWarning)
return globals()[f"{deprecated_tag}{name}"]
return None
return getattr(__import__(__name__), name)
+1 -1
View File
@@ -1,6 +1,6 @@
# We use the SemVer 2.0.0 versioning scheme
VERSION_MAJOR = 2 # Number of releases of the library with a breaking change
VERSION_MINOR = 8 # Number of changes that only add to the interface
VERSION_MINOR = 11 # Number of changes that only add to the interface
VERSION_PATCH = 0 # Number of changes that do not change the interface
VERSION_SUFFIX = ""
@@ -0,0 +1,21 @@
from volatility3.framework.layers import intel
WIN_ARCHS = ["Intel32", "Intel64"]
"""Windows supported architectures"""
WIN_ARCHS_LAYERS = [intel.Intel]
"""Windows supported architectures layers"""
LINUX_ARCHS = ["Intel32", "Intel64"]
"""Linux supported architectures"""
LINUX_ARCHS_LAYERS = [intel.Intel]
"""Linux supported architectures layers"""
MAC_ARCHS = ["Intel32", "Intel64"]
"""Mac supported architectures"""
MAC_ARCHS_LAYERS = [intel.Intel]
"""Mac supported architectures layers"""
FRAMEWORK_ARCHS = ["Intel32", "Intel64"]
"""Framework supported architectures"""
FRAMEWORK_ARCHS_LAYERS = [intel.Intel]
"""Framework supported architectures layers"""
@@ -5,7 +5,7 @@
Linux-specific values that aren't found in debug symbols
"""
from enum import IntEnum
from enum import IntEnum, Flag
KERNEL_NAME = "__kernel__"
@@ -302,3 +302,53 @@ class ELF_CLASS(IntEnum):
ELFCLASSNONE = 0
ELFCLASS32 = 1
ELFCLASS64 = 2
# PTrace
PT_OPT_FLAG_SHIFT = 3
PTRACE_EVENT_FORK = 1
PTRACE_EVENT_VFORK = 2
PTRACE_EVENT_CLONE = 3
PTRACE_EVENT_EXEC = 4
PTRACE_EVENT_VFORK_DONE = 5
PTRACE_EVENT_EXIT = 6
PTRACE_EVENT_SECCOMP = 7
PTRACE_O_EXITKILL = 1 << 20
PTRACE_O_SUSPEND_SECCOMP = 1 << 21
class PT_FLAGS(Flag):
"PTrace flags"
PT_PTRACED = 0x00001
PT_SEIZED = 0x10000
PT_TRACESYSGOOD = 1 << (PT_OPT_FLAG_SHIFT + 0)
PT_TRACE_FORK = 1 << (PT_OPT_FLAG_SHIFT + PTRACE_EVENT_FORK)
PT_TRACE_VFORK = 1 << (PT_OPT_FLAG_SHIFT + PTRACE_EVENT_VFORK)
PT_TRACE_CLONE = 1 << (PT_OPT_FLAG_SHIFT + PTRACE_EVENT_CLONE)
PT_TRACE_EXEC = 1 << (PT_OPT_FLAG_SHIFT + PTRACE_EVENT_EXEC)
PT_TRACE_VFORK_DONE = 1 << (PT_OPT_FLAG_SHIFT + PTRACE_EVENT_VFORK_DONE)
PT_TRACE_EXIT = 1 << (PT_OPT_FLAG_SHIFT + PTRACE_EVENT_EXIT)
PT_TRACE_SECCOMP = 1 << (PT_OPT_FLAG_SHIFT + PTRACE_EVENT_SECCOMP)
PT_EXITKILL = PTRACE_O_EXITKILL << PT_OPT_FLAG_SHIFT
PT_SUSPEND_SECCOMP = PTRACE_O_SUSPEND_SECCOMP << PT_OPT_FLAG_SHIFT
@property
def flags(self) -> str:
"""Returns the ptrace flags string"""
return str(self).replace(self.__class__.__name__ + ".", "")
# Boot time
NSEC_PER_SEC = 1e9
# Valid sizes for modules. Note that the Linux kernel does not define these values; they
# are based on empirical observations of typical memory allocations for kernel modules.
# We use this to verify that the found module falls within reasonable limits.
MODULE_MAXIMUM_CORE_SIZE = 20000000
MODULE_MAXIMUM_CORE_TEXT_SIZE = 20000000
MODULE_MINIMUM_SIZE = 4096
+1 -1
View File
@@ -245,7 +245,7 @@ class Module(interfaces.context.ModuleInterface):
"""
if constants.BANG not in object_type:
object_type = self.symbol_table_name + constants.BANG + object_type
else:
elif not object_type.startswith(self.symbol_table_name + constants.BANG):
raise ValueError(
"Cannot reference another module when constructing an object"
)
+4
View File
@@ -126,3 +126,7 @@ class OfflineException(VolatilityException):
def __str__(self):
return f"Volatility 3 is offline: unable to access {self._url}"
class RenderException(VolatilityException):
"""Thrown if there is an error during rendering"""
@@ -494,8 +494,7 @@ class SimpleTypeRequirement(RequirementInterface):
"""Validates the instance requirement based upon its
`instance_type`."""
config_path = path_join(config_path, self.name)
value = self.config_value(context, config_path, None)
value = self.config_value(context, config_path, self.default)
if not isinstance(value, self.instance_type):
vollog.log(
constants.LOGLEVEL_V,
@@ -536,7 +535,7 @@ class ClassRequirement(RequirementInterface):
"""Checks to see if a class can be recovered."""
config_path = path_join(config_path, self.name)
value = self.config_value(context, config_path, None)
value = self.config_value(context, config_path, self.default)
self._cls = None
if value is not None and isinstance(value, str):
if "." in value:
@@ -191,6 +191,9 @@ class ModuleInterface(interfaces.configuration.ConfigurableInterface):
self._native_layer_name
].build_configuration()
# Modules are constructable, and therefore require a class configuration variable
config["class"] = self.__class__.__module__ + "." + self.__class__.__name__
for subconfig in subconfigs:
for req in subconfigs[subconfig]:
config[interfaces.configuration.path_join(subconfig, req)] = subconfigs[
+1 -1
View File
@@ -216,7 +216,7 @@ class ObjectInterface(metaclass=abc.ABCMeta):
Args:
member_names: List of names to test as to members with those names validity
"""
return all([self.has_valid_member(member_name) for member_name in member_names])
return all(self.has_valid_member(member_name) for member_name in member_names)
class VolTemplateProxy(metaclass=abc.ABCMeta):
"""A container for proxied methods that the ObjectTemplate of this
+2 -4
View File
@@ -270,10 +270,8 @@ class Intel(linear.LinearlyMappedLayer):
try:
# TODO: Consider reimplementing this, since calls to mapping can call is_valid
return all(
[
self._context.layers[layer].is_valid(mapped_offset)
for _, _, mapped_offset, _, layer in self.mapping(offset, length)
]
self._context.layers[layer].is_valid(mapped_offset)
for _, _, mapped_offset, _, layer in self.mapping(offset, length)
)
except exceptions.InvalidAddressException:
return False
+5 -4
View File
@@ -170,6 +170,9 @@ class RegistryHive(linear.LinearlyMappedLayer):
return_list specifies whether the return result will be a single
node (default) or a list of nodes from root to the current node
(if return_list is true).
Raises RegistryFormatException if an invalid structure is encountered
Raises KeyError if the key is not found
"""
root_node = self.get_node(self.root_cell_offset)
if not root_node.vol.type_name.endswith(constants.BANG + "_CM_KEY_NODE"):
@@ -318,10 +321,8 @@ class RegistryHive(linear.LinearlyMappedLayer):
with contextlib.suppress(exceptions.InvalidAddressException):
# Pass this to the lower layers for now
return all(
[
self.context.layers[layer].is_valid(offset, length)
for (_, _, offset, length, layer) in self.mapping(offset, length)
]
self.context.layers[layer].is_valid(offset, length)
for (_, _, offset, length, layer) in self.mapping(offset, length)
)
return False
+2 -4
View File
@@ -51,10 +51,8 @@ class NonLinearlySegmentedLayer(
try:
base_layer = self._context.layers[self._base_layer]
return all(
[
base_layer.is_valid(mapped_offset)
for _i, _i, mapped_offset, _i, _s in self.mapping(offset, length)
]
base_layer.is_valid(mapped_offset)
for _i, _i, mapped_offset, _i, _s in self.mapping(offset, length)
)
except exceptions.InvalidAddressException:
return False
+2 -4
View File
@@ -928,10 +928,8 @@ class AggregateType(interfaces.objects.ObjectInterface):
members, collections.abc.Mapping
), f"{agg_name} members parameter must be a mapping: {type(members)}"
assert all(
[
(isinstance(member, tuple) and len(member) == 2)
for member in members.values()
]
(isinstance(member, tuple) and len(member) == 2)
for member in members.values()
), f"{agg_name} members must be a tuple of relative_offsets and templates"
def member(self, attr: str = "member") -> object:
@@ -0,0 +1,98 @@
# This file is Copyright 2024 Volatility Foundation and licensed under the Volatility Software License 1.0
# which is available at https://www.volatilityfoundation.org/license/vsl-v1.0
#
import datetime
from typing import List, Tuple, Iterable
from volatility3.framework import interfaces, renderers
from volatility3.framework.configuration import requirements
from volatility3.plugins import timeliner
from volatility3.plugins.linux import pslist
class Boottime(interfaces.plugins.PluginInterface, timeliner.TimeLinerInterface):
"""Shows the time the system was started"""
_required_framework_version = (2, 11, 0)
_version = (1, 0, 0)
@classmethod
def get_requirements(cls) -> List[interfaces.configuration.RequirementInterface]:
return [
requirements.ModuleRequirement(
name="kernel",
description="Linux kernel",
architectures=["Intel32", "Intel64"],
),
requirements.PluginRequirement(
name="pslist", plugin=pslist.PsList, version=(2, 3, 0)
),
]
@classmethod
def get_time_namespaces_bootime(
cls,
context: interfaces.context.ContextInterface,
vmlinux_module_name: str,
) -> Iterable[Tuple[int, int, int, str, datetime.datetime]]:
"""Enumerates tasks' boot times based on their time namespaces.
Args:
context: The context to retrieve required elements (layers, symbol tables) from
vmlinux_module_name: The name of the kernel module on which to operate
pids: Pid list
unique: Filter unique time namespaces
Yields:
A tuple with the fields to show in the plugin output.
"""
time_namespace_ids = set()
for task in pslist.PsList.list_tasks(context, vmlinux_module_name):
time_namespace_id = task.get_time_namespace_id()
# If it cannot get the time namespace i.e. kernels < 5.6, this still works
# using None to just get the first tasks
if time_namespace_id in time_namespace_ids:
continue
time_namespace_ids.add(time_namespace_id)
boottime = task.get_boottime(root_time_namespace=False)
fields = (
time_namespace_id,
boottime,
)
yield fields
def _generator(self):
for (
time_namespace_id,
boottime,
) in self.get_time_namespaces_bootime(
self.context,
self.config["kernel"],
):
fields = [
time_namespace_id or renderers.NotAvailableValue(),
boottime,
]
yield 0, fields
def generate_timeline(self):
for (
time_namespace_id,
boottime,
) in self.get_time_namespaces_bootime(
self.context,
self.config["kernel"],
):
description = f"System boot time for time namespace {time_namespace_id}"
yield description, timeliner.TimeLinerType.CREATED, boottime
def run(self):
columns = [
("TIME NS", int),
("Boot Time", datetime.datetime),
]
return renderers.TreeGrid(columns, self._generator())
@@ -53,9 +53,7 @@ class Check_afinfo(plugins.PluginInterface):
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]
)
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])}"
@@ -2,20 +2,19 @@
# which is available at https://www.volatilityfoundation.org/license/vsl-v1.0
#
import logging
from volatility3.framework import interfaces, renderers
from volatility3.framework.renderers import format_hints
from volatility3.framework.configuration import requirements
from volatility3.plugins.linux import pslist
vollog = logging.getLogger(__name__)
class Check_creds(interfaces.plugins.PluginInterface):
"""Checks if any processes are sharing credential structures"""
_required_framework_version = (2, 0, 0)
_version = (2, 0, 0)
@classmethod
def get_requirements(cls):
return [
@@ -46,20 +45,28 @@ class Check_creds(interfaces.plugins.PluginInterface):
tasks = pslist.PsList.list_tasks(self.context, vmlinux.name)
for task in tasks:
cred_addr = task.cred.dereference().vol.offset
task_cred_ptr = task.cred
if not (task_cred_ptr and task_cred_ptr.is_readable()):
continue
if cred_addr not in creds:
creds[cred_addr] = []
cred_addr = task_cred_ptr.dereference().vol.offset
creds.setdefault(cred_addr, [])
creds[cred_addr].append(task.pid)
for _, pids in creds.items():
for cred_addr, pids in creds.items():
if len(pids) > 1:
pid_str = ""
for pid in pids:
pid_str = pid_str + f"{pid:d}, "
pid_str = pid_str[:-2]
yield (0, [str(pid_str)])
pid_str = ", ".join([str(pid) for pid in pids])
fields = [
format_hints.Hex(cred_addr),
pid_str,
]
yield (0, fields)
def run(self):
return renderers.TreeGrid([("PIDs", str)], self._generator())
headers = [
("CredVAddr", format_hints.Hex),
("PIDs", str),
]
return renderers.TreeGrid(headers, self._generator())
@@ -0,0 +1,73 @@
# This file is Copyright 2024 Volatility Foundation and licensed under the Volatility Software License 1.0
# which is available at https://www.volatilityfoundation.org/license/vsl-v1.0
#
import logging
from typing import List
from volatility3.framework import renderers, interfaces, exceptions
from volatility3.framework.renderers import format_hints
from volatility3.framework.interfaces import plugins
from volatility3.framework.configuration import requirements
vollog = logging.getLogger(__name__)
class EBPF(plugins.PluginInterface):
"""Enumerate eBPF programs"""
_required_framework_version = (2, 0, 0)
_version = (1, 0, 0)
@classmethod
def get_requirements(cls) -> List[interfaces.configuration.RequirementInterface]:
return [
requirements.ModuleRequirement(
name="kernel",
description="Linux kernel",
architectures=["Intel32", "Intel64"],
),
]
def get_ebpf_programs(
self,
context: interfaces.context.ContextInterface,
vmlinux_module_name: str,
) -> interfaces.objects.ObjectInterface:
"""Enumerate eBPF programs walking its IDR.
Args:
context: The context to retrieve required elements (layers, symbol tables) from
vmlinux_module_name: The name of the kernel module on which to operate
Yields:
eBPF program objects
"""
vmlinux = context.modules[vmlinux_module_name]
if not vmlinux.has_symbol("prog_idr"):
raise exceptions.VolatilityException(
"Cannot find the eBPF prog idr. Unsupported kernel"
)
prog_idr = vmlinux.object_from_symbol("prog_idr")
for page_addr in prog_idr.get_entries():
bpf_prog = vmlinux.object("bpf_prog", offset=page_addr, absolute=True)
yield bpf_prog
def _generator(self):
for prog in self.get_ebpf_programs(self.context, self.config["kernel"]):
prog_addr = prog.vol.offset
prog_type = prog.get_type() or renderers.NotAvailableValue()
prog_tag = prog.get_tag() or renderers.NotAvailableValue()
prog_name = prog.get_name() or renderers.NotAvailableValue()
fields = (format_hints.Hex(prog_addr), prog_name, prog_tag, prog_type)
yield (0, fields)
def run(self):
headers = [
("Address", format_hints.Hex),
("Name", str),
("Tag", str),
("Type", str),
]
return renderers.TreeGrid(headers, self._generator())
@@ -0,0 +1,246 @@
# This file is Copyright 2024 Volatility Foundation and licensed under the Volatility Software License 1.0
# which is available at https://www.volatilityfoundation.org/license/vsl-v1.0
#
import logging
from typing import List, Set, Tuple, Iterable
from volatility3.framework import renderers, interfaces, exceptions, objects
from volatility3.framework.constants import architectures
from volatility3.framework.renderers import format_hints
from volatility3.framework.configuration import requirements
from volatility3.plugins.linux import lsmod
vollog = logging.getLogger(__name__)
class Hidden_modules(interfaces.plugins.PluginInterface):
"""Carves memory to find hidden kernel modules"""
_required_framework_version = (2, 10, 0)
_version = (1, 0, 0)
@classmethod
def get_requirements(cls) -> List[interfaces.configuration.RequirementInterface]:
return [
requirements.ModuleRequirement(
name="kernel",
description="Linux kernel",
architectures=architectures.LINUX_ARCHS,
),
requirements.PluginRequirement(
name="lsmod", plugin=lsmod.Lsmod, version=(2, 0, 0)
),
]
@staticmethod
def get_modules_memory_boundaries(
context: interfaces.context.ContextInterface,
vmlinux_module_name: str,
) -> Tuple[int]:
"""Determine the boundaries of the module allocation area
Args:
context: The context to retrieve required elements (layers, symbol tables) from
vmlinux_module_name: The name of the kernel module on which to operate
Returns:
A tuple containing the minimum and maximum addresses for the module allocation area.
"""
vmlinux = context.modules[vmlinux_module_name]
if vmlinux.has_symbol("mod_tree"):
# Kernel >= 5.19 58d208de3e8d87dbe196caf0b57cc58c7a3836ca
mod_tree = vmlinux.object_from_symbol("mod_tree")
modules_addr_min = mod_tree.addr_min
modules_addr_max = mod_tree.addr_max
elif vmlinux.has_symbol("module_addr_min"):
# 2.6.27 <= kernel < 5.19 3a642e99babe0617febb6f402e1e063479f489db
modules_addr_min = vmlinux.object_from_symbol("module_addr_min")
modules_addr_max = vmlinux.object_from_symbol("module_addr_max")
if isinstance(modules_addr_min, objects.Void):
raise exceptions.VolatilityException(
"Your ISF symbols lack type information. You may need to update the"
"ISF using the latest version of dwarf2json"
)
else:
raise exceptions.VolatilityException(
"Cannot find the module memory allocation area. Unsupported kernel"
)
return modules_addr_min, modules_addr_max
@classmethod
def _get_module_address_alignment(
cls,
context: interfaces.context.ContextInterface,
vmlinux_module_name: str,
) -> int:
"""Obtain the module memory address alignment.
struct module is aligned to the L1 cache line, which is typically 64 bytes for most
common i386/AMD64/ARM64 configurations. In some cases, it can be 128 bytes, but this
will still work.
Args:
context: The context to retrieve required elements (layers, symbol tables) from
vmlinux_module_name: The name of the kernel module on which to operate
Returns:
The struct module alignment
"""
# FIXME: When dwarf2json/ISF supports type alignments. Read it directly from the type metadata
# Additionally, while 'context' and 'vmlinux_module_name' are currently unused, they will be
# essential for retrieving type metadata in the future.
return 64
@staticmethod
def _validate_alignment_patterns(
addresses: Iterable[int],
address_alignment: int,
) -> bool:
"""Check if the memory addresses meet our alignments patterns
Args:
addresses: Iterable with the address values
address_alignment: Number of bytes for alignment validation
Returns:
True if all the addresses meet the alignment
"""
return all(addr % address_alignment == 0 for addr in addresses)
@classmethod
def get_hidden_modules(
cls,
context: interfaces.context.ContextInterface,
vmlinux_module_name: str,
known_module_addresses: Set[int],
modules_memory_boundaries: Tuple,
) -> Iterable[interfaces.objects.ObjectInterface]:
"""Enumerate hidden modules by taking advantage of memory address alignment patterns
This technique is much faster and uses less memory than the traditional scan method
in Volatility2, but it doesn't work with older kernels.
From kernels 4.2 struct module allocation are aligned to the L1 cache line size.
In i386/amd64/arm64 this is typically 64 bytes. However, this can be changed in
the Linux kernel configuration via CONFIG_X86_L1_CACHE_SHIFT. The alignment can
also be obtained from the DWARF info i.e. DW_AT_alignment<64>, but dwarf2json
doesn't support this feature yet.
In kernels < 4.2, alignment attributes are absent in the struct module, meaning
alignment cannot be guaranteed. Therefore, for older kernels, it's better to use
the traditional scan technique.
Args:
context: The context to retrieve required elements (layers, symbol tables) from
vmlinux_module_name: The name of the kernel module on which to operate
known_module_addresses: Set with known module addresses
modules_memory_boundaries: Minimum and maximum address boundaries for module allocation.
Yields:
module objects
"""
vmlinux = context.modules[vmlinux_module_name]
vmlinux_layer = context.layers[vmlinux.layer_name]
module_addr_min, module_addr_max = modules_memory_boundaries
module_address_alignment = cls._get_module_address_alignment(
context, vmlinux_module_name
)
if not cls._validate_alignment_patterns(
known_module_addresses, module_address_alignment
):
vollog.warning(
f"Module addresses aren't aligned to {module_address_alignment} bytes. "
"Switching to 1 byte aligment scan method."
)
module_address_alignment = 1
mkobj_offset = vmlinux.get_type("module").relative_child_offset("mkobj")
mod_offset = vmlinux.get_type("module_kobject").relative_child_offset("mod")
offset_to_mkobj_mod = mkobj_offset + mod_offset
mod_member_template = vmlinux.get_type("module_kobject").child_template("mod")
mod_size = mod_member_template.size
mod_member_data_format = mod_member_template.data_format
for module_addr in range(
module_addr_min, module_addr_max, module_address_alignment
):
if module_addr in known_module_addresses:
continue
try:
# This is just a pre-filter. Module readability and consistency are verified in module.is_valid()
self_referential_bytes = vmlinux_layer.read(
module_addr + offset_to_mkobj_mod, mod_size
)
self_referential = objects.convert_data_to_value(
self_referential_bytes, int, mod_member_data_format
)
if self_referential != module_addr:
continue
except (
exceptions.PagedInvalidAddressException,
exceptions.InvalidAddressException,
):
continue
module = vmlinux.object("module", offset=module_addr, absolute=True)
if module and module.is_valid():
yield module
@classmethod
def get_lsmod_module_addresses(
cls,
context: interfaces.context.ContextInterface,
vmlinux_module_name: str,
) -> Set[int]:
"""Obtain a set the known module addresses from linux.lsmod plugin
Args:
context: The context to retrieve required elements (layers, symbol tables) from
vmlinux_module_name: The name of the kernel module on which to operate
Returns:
A set containing known kernel module addresses
"""
vmlinux = context.modules[vmlinux_module_name]
vmlinux_layer = context.layers[vmlinux.layer_name]
known_module_addresses = {
vmlinux_layer.canonicalize(module.vol.offset)
for module in lsmod.Lsmod.list_modules(context, vmlinux_module_name)
}
return known_module_addresses
def _generator(self):
vmlinux_module_name = self.config["kernel"]
known_module_addresses = self.get_lsmod_module_addresses(
self.context, vmlinux_module_name
)
modules_memory_boundaries = self.get_modules_memory_boundaries(
self.context, vmlinux_module_name
)
for module in self.get_hidden_modules(
self.context,
vmlinux_module_name,
known_module_addresses,
modules_memory_boundaries,
):
module_addr = module.vol.offset
module_name = module.get_name() or renderers.NotAvailableValue()
fields = (format_hints.Hex(module_addr), module_name)
yield (0, fields)
def run(self):
if self.context.symbol_space.verify_table_versions(
"dwarf2json", lambda version, _: (not version) or version < (0, 8, 0)
):
raise exceptions.SymbolSpaceError(
"Invalid symbol table, please ensure the ISF table produced by dwarf2json was created with version 0.8.0 or later"
)
headers = [
("Address", format_hints.Hex),
("Name", str),
]
return renderers.TreeGrid(headers, self._generator())
@@ -0,0 +1,115 @@
# This file is Copyright 2024 Volatility Foundation and licensed under the Volatility Software License 1.0
# which is available at https://www.volatilityfoundation.org/license/vsl-v1.0
#
import logging
from typing import List
from volatility3.framework import constants, exceptions, interfaces, renderers
from volatility3.framework.configuration import requirements
from volatility3.framework.interfaces import plugins
from volatility3.framework.renderers import format_hints
from volatility3.framework.symbols import linux
from volatility3.framework.constants import architectures
from volatility3.framework.objects import utility
from volatility3.plugins.linux import pslist, lsmod
vollog = logging.getLogger(__name__)
class Kthreads(plugins.PluginInterface):
"""Enumerates kthread functions"""
_required_framework_version = (2, 11, 0)
_version = (1, 0, 0)
@classmethod
def get_requirements(cls) -> List[interfaces.configuration.RequirementInterface]:
return [
requirements.ModuleRequirement(
name="kernel",
description="Linux kernel",
architectures=architectures.LINUX_ARCHS,
),
requirements.VersionRequirement(
name="linuxutils", component=linux.LinuxUtilities, version=(2, 1, 0)
),
requirements.PluginRequirement(
name="pslist", plugin=pslist.PsList, version=(2, 3, 0)
),
requirements.PluginRequirement(
name="lsmod", plugin=lsmod.Lsmod, version=(2, 0, 0)
),
]
def _generator(self):
vmlinux = self.context.modules[self.config["kernel"]]
modules = lsmod.Lsmod.list_modules(self.context, vmlinux.name)
handlers = linux.LinuxUtilities.generate_kernel_handler_info(
self.context, vmlinux.name, modules
)
kthread_type = vmlinux.get_type(
vmlinux.symbol_table_name + constants.BANG + "kthread"
)
if not kthread_type.has_member("threadfn"):
raise exceptions.VolatilityException(
"Unsupported kthread implementation. This plugin only works with kernels >= 5.8"
)
for task in pslist.PsList.list_tasks(
self.context, vmlinux.name, include_threads=True
):
if not task.is_kernel_thread:
continue
if task.has_member("worker_private"):
# kernels >= 5.17 e32cf5dfbe227b355776948b2c9b5691b84d1cbd
ktread_base_pointer = task.worker_private
else:
# 5.8 <= kernels < 5.17 in 52782c92ac85c4e393eb4a903a62e6c24afa633f threadfn
# was added to struct kthread. task.set_child_tid is safe on those versions.
ktread_base_pointer = task.set_child_tid
if not ktread_base_pointer.is_readable():
continue
kthread = ktread_base_pointer.dereference().cast("kthread")
threadfn = kthread.threadfn
if not (threadfn and threadfn.is_readable()):
continue
task_name = utility.array_to_string(task.comm)
# kernels >= 5.17 in d6986ce24fc00b0638bd29efe8fb7ba7619ed2aa full_name was added to kthread
thread_name = (
utility.pointer_to_string(kthread.full_name, count=255)
if kthread.has_member("full_name")
else task_name
)
module_name, symbol_name = linux.LinuxUtilities.lookup_module_address(
vmlinux, handlers, threadfn
)
fields = [
task.pid,
thread_name,
format_hints.Hex(threadfn),
module_name,
symbol_name,
]
yield 0, fields
def run(self):
return renderers.TreeGrid(
[
("TID", int),
("Thread Name", str),
("Handler Address", format_hints.Hex),
("Module", str),
("Symbol", str),
],
self._generator(),
)
+159 -29
View File
@@ -1,10 +1,10 @@
# This file is Copyright 2019 Volatility Foundation and licensed under the Volatility Software License 1.0
# This file is Copyright 2024 Volatility Foundation and licensed under the Volatility Software License 1.0
# which is available at https://www.volatilityfoundation.org/license/vsl-v1.0
#
"""A module containing a collection of plugins that produce data typically
found in Linux's /proc file system."""
import logging
from typing import List, Callable
import datetime
import dataclasses
from typing import List, Callable, Tuple, Iterable
from volatility3.framework import renderers, interfaces, constants
from volatility3.framework.configuration import requirements
@@ -12,16 +12,105 @@ from volatility3.framework.interfaces import plugins
from volatility3.framework.objects import utility
from volatility3.framework.symbols import linux
from volatility3.plugins.linux import pslist
from volatility3.plugins import timeliner
vollog = logging.getLogger(__name__)
class Lsof(plugins.PluginInterface):
"""Lists all memory maps for all processes."""
@dataclasses.dataclass
class FDUser:
"""FD user representation, featuring augmented information and formatted fields.
This is the data the plugin will eventually display.
"""
task_tgid: int
task_tid: int
task_comm: str
fd_num: int
full_path: str
device: str = dataclasses.field(default=renderers.NotAvailableValue())
inode_num: int = dataclasses.field(default=renderers.NotAvailableValue())
inode_type: str = dataclasses.field(default=renderers.NotAvailableValue())
file_mode: str = dataclasses.field(default=renderers.NotAvailableValue())
change_time: datetime.datetime = dataclasses.field(
default=renderers.NotAvailableValue()
)
modification_time: datetime.datetime = dataclasses.field(
default=renderers.NotAvailableValue()
)
access_time: datetime.datetime = dataclasses.field(
default=renderers.NotAvailableValue()
)
inode_size: int = dataclasses.field(default=renderers.NotAvailableValue())
@dataclasses.dataclass
class FDInternal:
"""FD internal representation containing only the core objects
Fields:
task: 'task_struct' object
fd_fields: FD fields as obtained from LinuxUtilities.files_descriptors_for_process()
"""
task: interfaces.objects.ObjectInterface
fd_fields: Tuple[int, int, str]
def to_user(self) -> FDUser:
"""Augment the FD information to be presented to the user
Returns:
An InodeUser dataclass
"""
# Ensure all types are atomic immutable. Otherwise, astuple() will take a long
# time doing a deepcopy of the Volatility objects.
task_tgid = int(self.task.tgid)
task_tid = int(self.task.pid)
task_comm = utility.array_to_string(self.task.comm)
fd_num, filp, full_path = self.fd_fields
fd_num = int(fd_num)
full_path = str(full_path)
inode = filp.get_inode()
if inode:
superblock_ptr = inode.i_sb
if superblock_ptr and superblock_ptr.is_readable():
device = f"{superblock_ptr.major}:{superblock_ptr.minor}"
else:
device = renderers.NotAvailableValue()
fd_user = FDUser(
task_tgid=task_tgid,
task_tid=task_tid,
task_comm=task_comm,
fd_num=fd_num,
full_path=full_path,
device=device,
inode_num=int(inode.i_ino),
inode_type=inode.get_inode_type() or renderers.UnparsableValue(),
file_mode=inode.get_file_mode(),
change_time=inode.get_change_time(),
modification_time=inode.get_modification_time(),
access_time=inode.get_access_time(),
inode_size=int(inode.i_size),
)
else:
# We use the dataclasses' default values
fd_user = FDUser(
task_tgid=task_tgid,
task_tid=task_tid,
task_comm=task_comm,
fd_num=fd_num,
full_path=full_path,
)
return fd_user
class Lsof(plugins.PluginInterface, timeliner.TimeLinerInterface):
"""Lists open files for each processes."""
_required_framework_version = (2, 0, 0)
_version = (1, 1, 0)
_version = (2, 0, 0)
@classmethod
def get_requirements(cls) -> List[interfaces.configuration.RequirementInterface]:
@@ -49,41 +138,82 @@ class Lsof(plugins.PluginInterface):
def list_fds(
cls,
context: interfaces.context.ContextInterface,
symbol_table: str,
vmlinux_module_name: str,
filter_func: Callable[[int], bool] = lambda _: False,
):
linuxutils_symbol_table = None # type: ignore
for task in pslist.PsList.list_tasks(context, symbol_table, filter_func):
) -> Iterable[FDInternal]:
"""Enumerates open file descriptors in tasks
Args:
context: The context to retrieve required elements (layers, symbol tables) from
vmlinux_module_name: The name of the kernel module on which to operate
filter_func: A function which takes a process object and returns True if the process
should be ignored/filtered
Yields:
A FDInternal object
"""
linuxutils_symbol_table = None
for task in pslist.PsList.list_tasks(
context, vmlinux_module_name, filter_func, include_threads=True
):
if linuxutils_symbol_table is None:
if constants.BANG not in task.vol.type_name:
raise ValueError("Task is not part of a symbol table")
linuxutils_symbol_table = task.vol.type_name.split(constants.BANG)[0]
task_comm = utility.array_to_string(task.comm)
pid = int(task.pid)
fd_generator = linux.LinuxUtilities.files_descriptors_for_process(
context, linuxutils_symbol_table, task
)
for fd_fields in fd_generator:
yield pid, task_comm, task, fd_fields
yield FDInternal(task=task, fd_fields=fd_fields)
def _generator(self, pids, symbol_table):
def _generator(self, pids, vmlinux_module_name):
filter_func = pslist.PsList.create_pid_filter(pids)
fds_generator = self.list_fds(
self.context, symbol_table, filter_func=filter_func
)
for pid, task_comm, _task, fd_fields in fds_generator:
fd_num, _filp, full_path = fd_fields
fields = (pid, task_comm, fd_num, full_path)
yield (0, fields)
for fd_internal in self.list_fds(
self.context, vmlinux_module_name, filter_func=filter_func
):
fd_user = fd_internal.to_user()
yield (0, dataclasses.astuple(fd_user))
def run(self):
pids = self.config.get("pid", None)
symbol_table = self.config["kernel"]
vmlinux_module_name = self.config["kernel"]
tree_grid_args = [("PID", int), ("Process", str), ("FD", int), ("Path", str)]
return renderers.TreeGrid(tree_grid_args, self._generator(pids, symbol_table))
tree_grid_args = [
("PID", int),
("TID", int),
("Process", str),
("FD", int),
("Path", str),
("Device", str),
("Inode", int),
("Type", str),
("Mode", str),
("Changed", datetime.datetime),
("Modified", datetime.datetime),
("Accessed", datetime.datetime),
("Size", int),
]
return renderers.TreeGrid(
tree_grid_args, self._generator(pids, vmlinux_module_name)
)
def generate_timeline(self):
pids = self.config.get("pid", None)
vmlinux_module_name = self.config["kernel"]
filter_func = pslist.PsList.create_pid_filter(pids)
for fd_internal in self.list_fds(
self.context, vmlinux_module_name, filter_func=filter_func
):
fd_user = fd_internal.to_user()
description = (
f"Process {fd_user.task_comm} ({fd_user.task_tgid}/{fd_user.task_tid}) "
f"Open '{fd_user.full_path}'"
)
yield description, timeliner.TimeLinerType.CHANGED, fd_user.change_time
yield description, timeliner.TimeLinerType.MODIFIED, fd_user.modification_time
yield description, timeliner.TimeLinerType.ACCESSED, fd_user.access_time
+4 -10
View File
@@ -5,7 +5,7 @@
from typing import List
import logging
from volatility3.framework import constants, interfaces
from volatility3.framework import renderers
from volatility3.framework import renderers, symbols
from volatility3.framework.configuration import requirements
from volatility3.framework.objects import utility
from volatility3.framework.renderers import format_hints
@@ -63,15 +63,9 @@ class Malfind(interfaces.plugins.PluginInterface):
def _generator(self, tasks):
# determine if we're on a 32 or 64 bit kernel
vmlinux = self.context.modules[self.config["kernel"]]
if (
self.context.symbol_space.get_type(
vmlinux.symbol_table_name + constants.BANG + "pointer"
).size
== 4
):
is_32bit_arch = True
else:
is_32bit_arch = False
is_32bit_arch = not symbols.symbol_table_is_64bit(
self.context, vmlinux.symbol_table_name
)
for task in tasks:
process_name = utility.array_to_string(task.comm)
@@ -37,7 +37,7 @@ class MountInfo(plugins.PluginInterface):
_required_framework_version = (2, 2, 0)
_version = (1, 0, 0)
_version = (1, 2, 1)
@classmethod
def get_requirements(cls) -> List[interfaces.configuration.RequirementInterface]:
@@ -143,10 +143,10 @@ class MountInfo(plugins.PluginInterface):
sb_opts,
)
@staticmethod
def _get_tasks_mountpoints(
self,
tasks: Iterable[interfaces.objects.ObjectInterface],
filtered_by_pids: bool,
filtered_by_pids: bool = False,
):
seen_mountpoints = set()
for task in tasks:
@@ -184,8 +184,8 @@ class MountInfo(plugins.PluginInterface):
self,
tasks: Iterable[interfaces.objects.ObjectInterface],
mnt_ns_ids: List[int],
mount_format: bool,
filtered_by_pids: bool,
mount_format: bool = False,
filtered_by_pids: bool = False,
) -> Iterable[Tuple[int, Tuple]]:
show_filter_warning = False
for task, mnt, mnt_ns_id in self._get_tasks_mountpoints(
@@ -247,6 +247,42 @@ class MountInfo(plugins.PluginInterface):
"Could not filter by mount namespace id. This field is not available in this kernel."
)
@classmethod
def get_superblocks(
cls,
context: interfaces.context.ContextInterface,
vmlinux_module_name: str,
) -> Iterable[interfaces.objects.ObjectInterface]:
"""Yield file system superblocks based on the task's mounted filesystems.
Args:
context: The context to retrieve required elements (layers, symbol tables) from
vmlinux_module_name: The name of the kernel module on which to operate
Yields:
super_block: Kernel's struct super_block object
"""
# No filter so that we get all the mount namespaces from all tasks
tasks = pslist.PsList.list_tasks(context, vmlinux_module_name)
seen_sb_ptr = set()
for task, mnt, _mnt_ns_id in cls._get_tasks_mountpoints(tasks):
path_root = linux.LinuxUtilities.get_path_mnt(task, mnt)
if not path_root:
continue
sb_ptr = mnt.get_mnt_sb()
if not (sb_ptr and sb_ptr.is_readable()):
continue
if sb_ptr in seen_sb_ptr:
continue
seen_sb_ptr.add(sb_ptr)
superblock = sb_ptr.dereference()
yield superblock, path_root
def run(self):
pids = self.config.get("pids")
mount_ns_ids = self.config.get("mntns")
@@ -174,7 +174,7 @@ class AbstractNetfilter(ABC):
priority [int]: Priority
hook_ops_hook [int]: Hook address
module_name [str]: Linux kernel module name
hooked [bool]: hooked?
hooked [bool]: "True" if the network stack has been hijacked
"""
for netns, net in self.get_net_namespaces():
for proto_idx, proto_name, hook_idx, hook_name in self._proto_hook_loop():
@@ -190,7 +190,7 @@ class AbstractNetfilter(ABC):
priority = int(hook_ops.priority)
hook_ops_hook = hook_ops.hook
module_name = self.get_module_name_for_address(hook_ops_hook)
hooked = module_name is not None
hooked = module_name is None
yield netns, proto_name, hook_name, priority, hook_ops_hook, module_name, hooked
@@ -675,7 +675,7 @@ class Netfilter(interfaces.plugins.PluginInterface):
_required_framework_version = (2, 0, 0)
_version = (1, 0, 0)
_version = (1, 1, 0)
_required_linuxutils_version = (2, 1, 0)
_required_lsmod_version = (2, 0, 0)
@@ -714,7 +714,7 @@ class Netfilter(interfaces.plugins.PluginInterface):
hook_name,
priority,
format_hints.Hex(hook_func),
module_name,
module_name or renderers.NotAvailableValue(),
str(hooked),
)
@@ -0,0 +1,538 @@
# This file is Copyright 2024 Volatility Foundation and licensed under the Volatility Software License 1.0
# which is available at https://www.volatilityfoundation.org/license/vsl-v1.0
#
import math
import logging
import datetime
from dataclasses import dataclass, astuple
from typing import List, Set, Type, Iterable
from volatility3.framework import renderers, interfaces
from volatility3.framework.renderers import format_hints
from volatility3.framework.interfaces import plugins
from volatility3.framework.configuration import requirements
from volatility3.plugins import timeliner
from volatility3.plugins.linux import mountinfo
vollog = logging.getLogger(__name__)
@dataclass
class InodeUser:
"""Inode user representation, featuring augmented information and formatted fields.
This is the data the plugin will eventually display.
"""
superblock_addr: int
mountpoint: str
device: str
inode_num: int
inode_addr: int
type: str
inode_pages: int
cached_pages: int
file_mode: str
access_time: str
modification_time: str
change_time: str
path: str
@dataclass
class InodeInternal:
"""Inode internal representation containing only the core objects
Fields:
superblock: 'super_block' struct
mountpoint: Superblock mountpoint path
inode: 'inode' struct
path: Dentry full path
"""
superblock: interfaces.objects.ObjectInterface
mountpoint: str
inode: interfaces.objects.ObjectInterface
path: str
def to_user(
self, kernel_layer: interfaces.layers.TranslationLayerInterface
) -> InodeUser:
"""Augment the inode information to be presented to the user
Args:
kernel_layer: The kernel layer to obtain the page size
Returns:
An InodeUser dataclass
"""
# Ensure all types are atomic immutable. Otherwise, astuple() will take a long
# time doing a deepcopy of the Volatility objects.
superblock_addr = self.superblock.vol.offset
device = f"{self.superblock.major}:{self.superblock.minor}"
inode_num = int(self.inode.i_ino)
inode_addr = self.inode.vol.offset
inode_type = self.inode.get_inode_type() or renderers.UnparsableValue()
# Round up the number of pages to fit the inode's size
inode_pages = int(math.ceil(self.inode.i_size / float(kernel_layer.page_size)))
cached_pages = int(self.inode.i_mapping.nrpages)
file_mode = self.inode.get_file_mode()
access_time_dt = self.inode.get_access_time()
modification_time_dt = self.inode.get_modification_time()
change_time_dt = self.inode.get_change_time()
inode_user = InodeUser(
superblock_addr=superblock_addr,
mountpoint=self.mountpoint,
device=device,
inode_num=inode_num,
inode_addr=inode_addr,
type=inode_type,
inode_pages=inode_pages,
cached_pages=cached_pages,
file_mode=file_mode,
access_time=access_time_dt,
modification_time=modification_time_dt,
change_time=change_time_dt,
path=self.path,
)
return inode_user
class Files(plugins.PluginInterface, timeliner.TimeLinerInterface):
"""Lists files from memory"""
_required_framework_version = (2, 0, 0)
_version = (1, 0, 1)
@classmethod
def get_requirements(cls) -> List[interfaces.configuration.RequirementInterface]:
return [
requirements.ModuleRequirement(
name="kernel",
description="Linux kernel",
architectures=["Intel32", "Intel64"],
),
requirements.PluginRequirement(
name="mountinfo", plugin=mountinfo.MountInfo, version=(1, 2, 0)
),
requirements.ListRequirement(
name="type",
description="List of space-separated file type filters i.e. --type REG DIR",
element_type=str,
optional=True,
),
requirements.StringRequirement(
name="find",
description="Filename (full path) to find",
optional=True,
),
]
@staticmethod
def _follow_symlink(
inode: interfaces.objects.ObjectInterface,
symlink_path: str,
) -> str:
"""Follows (fast) symlinks (kernels >= 4.2.x).
Fast symlinks are filesystem agnostic.
Args:
inode: The inode (or pointer) to dump
symlink_path: The symlink name
Returns:
If it can resolve the symlink, it returns a string "symlink_path -> target_path"
Otherwise, it returns the same symlink_path
"""
# i_link (fast symlinks) were introduced in 4.2
if inode and inode.is_link and inode.has_member("i_link") and inode.i_link:
i_link_str = inode.i_link.dereference().cast(
"string", max_length=255, encoding="utf-8", errors="replace"
)
symlink_path = f"{symlink_path} -> {i_link_str}"
return symlink_path
@classmethod
def _walk_dentry(
cls,
seen_dentries: Set[int],
root_dentry: interfaces.objects.ObjectInterface,
parent_dir: str,
):
"""Walks dentries recursively
Args:
seen_dentries: A set to ensure each dentry is processed only once
root_dentry: Root dentry object
parent_dir: Parent directory path
Yields:
file_path: Filename including path
dentry: Dentry object
"""
for dentry in root_dentry.get_subdirs():
dentry_addr = dentry.vol.offset
# corruption
if dentry_addr == root_dentry.vol.offset:
continue
if dentry_addr in seen_dentries:
continue
seen_dentries.add(dentry_addr)
inode_ptr = dentry.d_inode
if not (inode_ptr and inode_ptr.is_readable()):
continue
inode = inode_ptr.dereference()
if not inode.is_valid():
continue
# This allows us to have consistent paths
if dentry.d_name.name:
basename = dentry.d_name.name_as_str()
# Do NOT use os.path.join() below
file_path = parent_dir + "/" + basename
else:
continue
yield file_path, dentry
if inode.is_dir:
yield from cls._walk_dentry(seen_dentries, dentry, parent_dir=file_path)
@classmethod
def get_inodes(
cls,
context: interfaces.context.ContextInterface,
vmlinux_module_name: str,
) -> Iterable[InodeInternal]:
"""Retrieves the inodes from the superblocks
Args:
context: The context that the plugin will operate within
vmlinux_module_name: The name of the kernel module on which to operate
Yields:
An InodeInternal object
"""
superblocks_iter = mountinfo.MountInfo.get_superblocks(
context=context,
vmlinux_module_name=vmlinux_module_name,
)
seen_inodes = set()
seen_dentries = set()
for superblock, mountpoint in superblocks_iter:
parent_dir = "" if mountpoint == "/" else mountpoint
# Superblock root dentry
root_dentry_ptr = superblock.s_root
if not root_dentry_ptr:
continue
root_dentry = root_dentry_ptr.dereference()
# Dentry sanity check
if not root_dentry.is_root():
continue
# More dentry/inode sanity checks
root_inode_ptr = root_dentry.d_inode
if not (root_inode_ptr and root_inode_ptr.is_readable()):
continue
root_inode = root_inode_ptr.dereference()
if not root_inode.is_valid():
continue
# Inode already processed?
if root_inode_ptr in seen_inodes:
continue
seen_inodes.add(root_inode_ptr)
root_path = mountpoint
inode_in = InodeInternal(
superblock=superblock,
mountpoint=mountpoint,
inode=root_inode,
path=root_path,
)
yield inode_in
# Children
for file_path, file_dentry in cls._walk_dentry(
seen_dentries, root_dentry, parent_dir
):
if not file_dentry:
continue
# Dentry/inode sanity checks
file_inode_ptr = file_dentry.d_inode
if not (file_inode_ptr and file_inode_ptr.is_readable()):
continue
file_inode = file_inode_ptr.dereference()
if not file_inode.is_valid():
continue
# Inode already processed?
if file_inode_ptr in seen_inodes:
continue
seen_inodes.add(file_inode_ptr)
file_path = cls._follow_symlink(file_inode_ptr, file_path)
inode_in = InodeInternal(
superblock=superblock,
mountpoint=mountpoint,
inode=file_inode,
path=file_path,
)
yield inode_in
def _generator(self):
vmlinux_module_name = self.config["kernel"]
vmlinux = self.context.modules[vmlinux_module_name]
vmlinux_layer = self.context.layers[vmlinux.layer_name]
inodes_iter = self.get_inodes(
context=self.context,
vmlinux_module_name=vmlinux_module_name,
)
types_filter = self.config["type"]
for inode_in in inodes_iter:
if types_filter and inode_in.inode.get_inode_type() not in types_filter:
continue
if self.config["find"]:
if inode_in.path == self.config["find"]:
inode_out = inode_in.to_user(vmlinux_layer)
yield (0, astuple(inode_out))
break # Only the first match
else:
inode_out = inode_in.to_user(vmlinux_layer)
yield (0, astuple(inode_out))
def generate_timeline(self):
"""Generates tuples of (description, timestamp_type, timestamp)
These need not be generated in any particular order, sorting
will be done later
"""
vmlinux_module_name = self.config["kernel"]
vmlinux = self.context.modules[vmlinux_module_name]
vmlinux_layer = self.context.layers[vmlinux.layer_name]
inodes_iter = self.get_inodes(
context=self.context,
vmlinux_module_name=vmlinux_module_name,
)
for inode_in in inodes_iter:
inode_out = inode_in.to_user(vmlinux_layer)
description = f"Cached Inode for {inode_out.path}"
yield description, timeliner.TimeLinerType.ACCESSED, inode_out.access_time
yield description, timeliner.TimeLinerType.MODIFIED, inode_out.modification_time
yield description, timeliner.TimeLinerType.CHANGED, inode_out.change_time
@staticmethod
def format_fields_with_headers(headers, generator):
"""Uses the headers type to cast the fields obtained from the generator"""
for level, fields in generator:
formatted_fields = []
for header, field in zip(headers, fields):
header_type = header[1]
if isinstance(
field, (header_type, interfaces.renderers.BaseAbsentValue)
):
formatted_field = field
else:
formatted_field = header_type(field)
formatted_fields.append(formatted_field)
yield level, formatted_fields
def run(self):
headers = [
("SuperblockAddr", format_hints.Hex),
("MountPoint", str),
("Device", str),
("InodeNum", int),
("InodeAddr", format_hints.Hex),
("FileType", str),
("InodePages", int),
("CachedPages", int),
("FileMode", str),
("AccessTime", datetime.datetime),
("ModificationTime", datetime.datetime),
("ChangeTime", datetime.datetime),
("FilePath", str),
]
return renderers.TreeGrid(
headers, self.format_fields_with_headers(headers, self._generator())
)
class InodePages(plugins.PluginInterface):
"""Lists and recovers cached inode pages"""
_required_framework_version = (2, 0, 0)
_version = (1, 0, 1)
@classmethod
def get_requirements(cls) -> List[interfaces.configuration.RequirementInterface]:
return [
requirements.ModuleRequirement(
name="kernel",
description="Linux kernel",
architectures=["Intel32", "Intel64"],
),
requirements.PluginRequirement(
name="files", plugin=Files, version=(1, 0, 0)
),
requirements.StringRequirement(
name="find",
description="Filename (full path) to find ",
optional=True,
),
requirements.IntRequirement(
name="inode",
description="Inode address",
optional=True,
),
requirements.StringRequirement(
name="dump",
description="Output file path",
optional=True,
),
]
@staticmethod
def write_inode_content_to_file(
inode: interfaces.objects.ObjectInterface,
filename: str,
open_method: Type[interfaces.plugins.FileHandlerInterface],
vmlinux_layer: interfaces.layers.TranslationLayerInterface,
) -> None:
"""Extracts the inode's contents from the page cache and saves them to a file
Args:
inode: The inode to dump
filename: Filename for writing the inode content
open_method: class for constructing output files
vmlinux_layer: The kernel layer to obtain the page size
"""
if not inode.is_reg:
vollog.error("The inode is not a regular file")
return
# By using truncate/seek, provided the filesystem supports it, a sparse file will be
# created, saving both disk space and I/O time.
# Additionally, using the page index will guarantee that each page is written at the
# appropriate file position.
try:
with open_method(filename) as f:
inode_size = inode.i_size
f.truncate(inode_size)
for page_idx, page_content in inode.get_contents():
current_fp = page_idx * vmlinux_layer.page_size
max_length = inode_size - current_fp
page_bytes = page_content[:max_length]
if current_fp + len(page_bytes) > inode_size:
vollog.error(
"Page out of file bounds: inode 0x%x, inode size %d, page index %d",
inode.vol.offset,
inode_size,
page_idx,
)
f.seek(current_fp)
f.write(page_bytes)
except IOError as e:
vollog.error("Unable to write to file (%s): %s", filename, e)
def _generator(self):
vmlinux_module_name = self.config["kernel"]
vmlinux = self.context.modules[vmlinux_module_name]
vmlinux_layer = self.context.layers[vmlinux.layer_name]
if self.config["inode"] and self.config["find"]:
vollog.error("Cannot use --inode and --find simultaneously")
return
if self.config["find"]:
inodes_iter = Files.get_inodes(
context=self.context,
vmlinux_module_name=vmlinux_module_name,
)
for inode_in in inodes_iter:
if inode_in.path == self.config["find"]:
inode = inode_in.inode
break # Only the first match
elif self.config["inode"]:
inode = vmlinux.object("inode", self.config["inode"], absolute=True)
else:
vollog.error("You must use either --inode or --find")
return
if not inode.is_valid():
vollog.error("Invalid inode at 0x%x", inode.vol.offset)
return
if not inode.is_reg:
vollog.error("The inode is not a regular file")
return
inode_size = inode.i_size
for page_obj in inode.get_pages():
page_vaddr = page_obj.vol.offset
page_paddr = page_obj.to_paddr()
page_mapping_addr = page_obj.mapping
page_index = int(page_obj.index)
page_file_offset = page_index * vmlinux_layer.page_size
dump_safe = page_file_offset < inode_size
page_flags_list = page_obj.get_flags_list()
page_flags = ",".join([x.replace("PG_", "") for x in page_flags_list])
fields = (
page_vaddr,
page_paddr,
page_mapping_addr,
page_index,
dump_safe,
page_flags,
)
yield 0, fields
if self.config["dump"]:
filename = self.config["dump"]
vollog.info("[*] Writing inode at 0x%x to '%s'", inode.vol.offset, filename)
self.write_inode_content_to_file(inode, filename, self.open, vmlinux_layer)
def run(self):
headers = [
("PageVAddr", format_hints.Hex),
("PagePAddr", format_hints.Hex),
("MappingAddr", format_hints.Hex),
("Index", int),
("DumpSafe", bool),
("Flags", str),
]
return renderers.TreeGrid(
headers, Files.format_fields_with_headers(headers, self._generator())
)
@@ -0,0 +1,255 @@
# This file is Copyright 2024 Volatility Foundation and licensed under the Volatility Software License 1.0
# which is available at https://www.volatilityfoundation.org/license/vsl-v1.0
#
import logging
from typing import List
from volatility3.framework import renderers, interfaces, constants
from volatility3.framework.symbols import linux
from volatility3.framework.renderers import format_hints
from volatility3.framework.interfaces import plugins
from volatility3.framework.configuration import requirements
from volatility3.plugins.linux import pslist
vollog = logging.getLogger(__name__)
class PIDHashTable(plugins.PluginInterface):
"""Enumerates processes through the PID hash table"""
_required_framework_version = (2, 0, 0)
_version = (1, 0, 1)
@classmethod
def get_requirements(cls) -> List[interfaces.configuration.RequirementInterface]:
return [
requirements.ModuleRequirement(
name="kernel",
description="Linux kernel",
architectures=["Intel32", "Intel64"],
),
requirements.PluginRequirement(
name="pslist", plugin=pslist.PsList, version=(2, 0, 0)
),
requirements.VersionRequirement(
name="linuxutils", component=linux.LinuxUtilities, version=(2, 1, 0)
),
requirements.BooleanRequirement(
name="decorate_comm",
description="Show `user threads` comm in curly brackets, and `kernel threads` comm in square brackets",
optional=True,
default=False,
),
]
def _is_valid_task(self, task) -> bool:
return bool(task and task.pid > 0 and task.parent.is_readable())
def _get_pidtype_pid(self):
vmlinux = self.context.modules[self.config["kernel"]]
# The pid_type enumeration is present since 2.5.37, just in case
pid_type_enum = vmlinux.get_enumeration("pid_type")
if not pid_type_enum:
vollog.error("Cannot find pid_type enum. Unsupported kernel")
return None
pidtype_pid = pid_type_enum.choices.get("PIDTYPE_PID")
if pidtype_pid is None:
vollog.error("Cannot find PIDTYPE_PID. Unsupported kernel")
return None
# Typically PIDTYPE_PID = 0
return pidtype_pid
def _get_pidhash_array(self):
vmlinux = self.context.modules[self.config["kernel"]]
pidhash_shift = vmlinux.object_from_symbol("pidhash_shift")
pidhash_size = 1 << pidhash_shift
array_type_name = vmlinux.symbol_table_name + constants.BANG + "array"
pidhash_ptr = vmlinux.object_from_symbol("pid_hash")
# pidhash is an array of hlist_heads
pidhash = self._context.object(
array_type_name,
offset=pidhash_ptr,
subtype=vmlinux.get_type("hlist_head"),
count=pidhash_size,
layer_name=vmlinux.layer_name,
)
return pidhash
def _walk_upid(self, seen_upids, upid):
vmlinux = self.context.modules[self.config["kernel"]]
vmlinux_layer = self.context.layers[vmlinux.layer_name]
while upid and vmlinux_layer.is_valid(upid.vol.offset):
if upid.vol.offset in seen_upids:
break
seen_upids.add(upid.vol.offset)
pid_chain = upid.pid_chain
if not (pid_chain.next and pid_chain.next.is_readable()):
break
upid = linux.LinuxUtilities.container_of(
pid_chain.next, "upid", "pid_chain", vmlinux
)
def _get_upids(self):
vmlinux = self.context.modules[self.config["kernel"]]
# 2.6.24 <= kernels < 4.15
pidhash = self._get_pidhash_array()
seen_upids = set()
for hlist in pidhash:
# each entry in the hlist is a upid which is wrapped in a pid
ent = hlist.first
while ent and ent.is_readable():
# upid->pid_chain exists 2.6.24 <= kernel < 4.15
upid = linux.LinuxUtilities.container_of(
ent.vol.offset, "upid", "pid_chain", vmlinux
)
if upid.vol.offset in seen_upids:
break
self._walk_upid(seen_upids, upid)
ent = ent.next
return seen_upids
def _pid_hash_implementation(self):
vmlinux = self.context.modules[self.config["kernel"]]
# 2.6.24 <= kernels < 4.15
task_pids_off = vmlinux.get_type("task_struct").relative_child_offset("pids")
pidtype_pid = self._get_pidtype_pid()
for upid in self._get_upids():
pid = linux.LinuxUtilities.container_of(upid, "pid", "numbers", vmlinux)
if not pid:
continue
pid_tasks_0 = pid.tasks[pidtype_pid].first
if not (pid_tasks_0 and pid_tasks_0.is_readable()):
continue
task = vmlinux.object(
"task_struct", offset=pid_tasks_0 - task_pids_off, absolute=True
)
if self._is_valid_task(task):
yield task
def _task_for_radix_pid_node(self, nodep):
vmlinux = self.context.modules[self.config["kernel"]]
# kernels >= 4.15
pid = vmlinux.object("pid", offset=nodep, absolute=True)
pidtype_pid = self._get_pidtype_pid()
pid_tasks_0 = pid.tasks[pidtype_pid].first
if not (pid_tasks_0 and pid_tasks_0.is_readable()):
return None
task_struct_type = vmlinux.get_type("task_struct")
if task_struct_type.has_member("pids"):
member = "pids"
elif task_struct_type.has_member("pid_links"):
member = "pid_links"
else:
return None
task_pids_off = task_struct_type.relative_child_offset(member)
task = vmlinux.object(
"task_struct", offset=pid_tasks_0 - task_pids_off, absolute=True
)
return task
def _pid_namespace_idr(self):
vmlinux = self.context.modules[self.config["kernel"]]
# kernels >= 4.15
ns_addr = vmlinux.get_symbol("init_pid_ns").address
ns = vmlinux.object("pid_namespace", offset=ns_addr)
for page_addr in ns.idr.get_entries():
task = self._task_for_radix_pid_node(page_addr)
if self._is_valid_task(task):
yield task
def _determine_pid_func(self):
vmlinux = self.context.modules[self.config["kernel"]]
pid_hash = vmlinux.has_symbol("pid_hash") and vmlinux.has_symbol(
"pidhash_shift"
) # 2.5.55 <= kernels < 4.15
has_pid_numbers = vmlinux.has_type("pid") and vmlinux.get_type(
"pid"
).has_member(
"numbers"
) # kernels >= 2.6.24
has_pid_chain = vmlinux.has_type("upid") and vmlinux.get_type(
"upid"
).has_member(
"pid_chain"
) # 2.6.24 <= kernels < 4.15
# kernels >= 4.15
pid_idr = vmlinux.has_type("pid_namespace") and vmlinux.get_type(
"pid_namespace"
).has_member("idr")
if pid_idr:
# kernels >= 4.15
return self._pid_namespace_idr
elif pid_hash and has_pid_numbers and has_pid_numbers and has_pid_chain:
# 2.6.24 <= kernels < 4.15
return self._pid_hash_implementation
return None
def get_tasks(self) -> interfaces.objects.ObjectInterface:
"""Enumerates processes through the PID hash table
Yields:
task_struct objects
"""
pid_func = self._determine_pid_func()
if not pid_func:
vollog.error("Cannot determine which PID hash table this kernel is using")
return
yield from sorted(pid_func(), key=lambda t: (t.tgid, t.pid))
def _generator(
self, decorate_comm: bool = False
) -> interfaces.objects.ObjectInterface:
for task in self.get_tasks():
offset, pid, tid, ppid, name = pslist.PsList.get_task_fields(
task, decorate_comm
)
fields = format_hints.Hex(offset), pid, tid, ppid, name
yield 0, fields
def run(self):
decorate_comm = self.config.get("decorate_comm")
headers = [
("OFFSET", format_hints.Hex),
("PID", int),
("TID", int),
("PPID", int),
("COMM", str),
]
return renderers.TreeGrid(headers, self._generator(decorate_comm=decorate_comm))
+26 -5
View File
@@ -1,6 +1,7 @@
# This file is Copyright 2021 Volatility Foundation and licensed under the Volatility Software License 1.0
# which is available at https://www.volatilityfoundation.org/license/vsl-v1.0
#
import datetime
from typing import Any, Callable, Iterable, List, Tuple
from volatility3.framework import interfaces, renderers
@@ -9,15 +10,16 @@ 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.plugins import timeliner
from volatility3.plugins.linux import elfs
class PsList(interfaces.plugins.PluginInterface):
class PsList(interfaces.plugins.PluginInterface, timeliner.TimeLinerInterface):
"""Lists the processes present in a particular linux memory image."""
_required_framework_version = (2, 0, 0)
_version = (2, 2, 1)
_version = (2, 3, 0)
@classmethod
def get_requirements(cls) -> List[interfaces.configuration.RequirementInterface]:
@@ -81,7 +83,7 @@ class PsList(interfaces.plugins.PluginInterface):
@classmethod
def get_task_fields(
cls, task: interfaces.objects.ObjectInterface, decorate_comm: bool = False
) -> Tuple[int, int, int, str]:
) -> Tuple[int, int, int, int, str, datetime.datetime]:
"""Extract the fields needed for the final output
Args:
@@ -96,13 +98,14 @@ class PsList(interfaces.plugins.PluginInterface):
tid = task.pid
ppid = task.parent.tgid if task.parent else 0
name = utility.array_to_string(task.comm)
start_time = task.get_create_time()
if decorate_comm:
if task.is_kernel_thread:
name = f"[{name}]"
elif task.is_user_thread:
name = f"{{{name}}}"
task_fields = (task.vol.offset, pid, tid, ppid, name)
task_fields = (task.vol.offset, pid, tid, ppid, name, start_time)
return task_fields
def _get_file_output(self, task: interfaces.objects.ObjectInterface) -> str:
@@ -177,7 +180,9 @@ class PsList(interfaces.plugins.PluginInterface):
else:
file_output = "Disabled"
offset, pid, tid, ppid, name = self.get_task_fields(task, decorate_comm)
offset, pid, tid, ppid, name, creation_time = self.get_task_fields(
task, decorate_comm
)
yield 0, (
format_hints.Hex(offset),
@@ -185,6 +190,7 @@ class PsList(interfaces.plugins.PluginInterface):
tid,
ppid,
name,
creation_time or renderers.NotAvailableValue(),
file_output,
)
@@ -233,8 +239,23 @@ class PsList(interfaces.plugins.PluginInterface):
("TID", int),
("PPID", int),
("COMM", str),
("CREATION TIME", datetime.datetime),
("File output", str),
]
return renderers.TreeGrid(
columns, self._generator(filter_func, include_threads, decorate_comm, dump)
)
def generate_timeline(self):
pids = self.config.get("pid")
filter_func = self.create_pid_filter(pids)
for task in self.list_tasks(
self.context, self.config["kernel"], filter_func, include_threads=True
):
offset, user_pid, user_tid, _user_ppid, name, creation_time = (
self.get_task_fields(task)
)
description = f"Process {user_pid}/{user_tid} {name} ({offset})"
yield (description, timeliner.TimeLinerType.CREATED, creation_time)
@@ -0,0 +1,97 @@
# This file is Copyright 2024 Volatility Foundation and licensed under the Volatility Software License 1.0
# which is available at https://www.volatilityfoundation.org/license/vsl-v1.0
#
import logging
from typing import List, Iterator
from volatility3.framework import renderers, interfaces
from volatility3.framework.constants import architectures
from volatility3.framework.objects import utility
from volatility3.framework.configuration import requirements
from volatility3.framework.interfaces import plugins
from volatility3.plugins.linux import pslist
vollog = logging.getLogger(__name__)
class Ptrace(plugins.PluginInterface):
"""Enumerates ptrace's tracer and tracee tasks"""
_required_framework_version = (2, 10, 0)
_version = (1, 0, 0)
@classmethod
def get_requirements(cls) -> List[interfaces.configuration.RequirementInterface]:
return [
requirements.ModuleRequirement(
name="kernel",
description="Linux kernel",
architectures=architectures.LINUX_ARCHS,
),
requirements.PluginRequirement(
name="pslist", plugin=pslist.PsList, version=(2, 2, 0)
),
]
@classmethod
def enumerate_ptrace_tasks(
cls,
context: interfaces.context.ContextInterface,
vmlinux_module_name: str,
) -> Iterator[interfaces.objects.ObjectInterface]:
"""Enumerates ptrace's tracer and tracee tasks
Args:
context: The context to retrieve required elements (layers, symbol tables) from
vmlinux_module_name: The name of the kernel module on which to operate
Yields:
A task_struct object
"""
tasks = pslist.PsList.list_tasks(
context,
vmlinux_module_name,
filter_func=pslist.PsList.create_pid_filter(),
include_threads=True,
)
for task in tasks:
if task.is_being_ptraced or task.is_ptracing:
yield task
def _generator(self, vmlinux_module_name):
for task in self.enumerate_ptrace_tasks(self.context, vmlinux_module_name):
task_comm = utility.array_to_string(task.comm)
user_pid = task.tgid
user_tid = task.pid
tracer_tid = task.get_ptrace_tracer_tid() or renderers.NotAvailableValue()
tracee_tids = task.get_ptrace_tracee_tids() or [
renderers.NotAvailableValue()
]
flags = task.get_ptrace_tracee_flags() or renderers.NotAvailableValue()
for level, tracee_tid in enumerate(tracee_tids):
fields = [
task_comm,
user_pid,
user_tid,
tracer_tid,
tracee_tid,
flags,
]
yield (level, fields)
def run(self):
vmlinux_module_name = self.config["kernel"]
headers = [
("Process", str),
("PID", int),
("TID", int),
("Tracer TID", int),
("Tracee TID", int),
("Flags", str),
]
return renderers.TreeGrid(headers, self._generator(vmlinux_module_name))
+22 -16
View File
@@ -22,9 +22,10 @@ class SockHandlers(interfaces.configuration.VersionableInterface):
_required_framework_version = (2, 0, 0)
_version = (1, 0, 0)
_version = (3, 0, 0)
def __init__(self, vmlinux, task):
def __init__(self, vmlinux, task, *args, **kwargs):
super().__init__(*args, **kwargs)
self._vmlinux = vmlinux
self._task = task
@@ -151,17 +152,15 @@ class SockHandlers(interfaces.configuration.VersionableInterface):
bpfprog = sock_filter.prog
BPF_PROG_TYPE_UNSPEC = 0 # cBPF filter
try:
bpfprog_type = bpfprog.get_type()
if bpfprog_type == BPF_PROG_TYPE_UNSPEC:
return # cBPF filter
except AttributeError:
bpfprog_type = bpfprog.get_type()
if not bpfprog_type:
# kernel < 3.18.140, it's a cBPF filter
return None
BPF_PROG_TYPE_SOCKET_FILTER = 1 # eBPF filter
if bpfprog_type != BPF_PROG_TYPE_SOCKET_FILTER:
if bpfprog_type == "BPF_PROG_TYPE_UNSPEC":
return None # cBPF filter
if bpfprog_type != "BPF_PROG_TYPE_SOCKET_FILTER":
socket_filter["bpf_filter_type"] = f"UNK({bpfprog_type})"
vollog.warning(f"Unexpected BPF type {bpfprog_type} for a socket")
return None
@@ -440,7 +439,7 @@ class Sockstat(plugins.PluginInterface):
_required_framework_version = (2, 0, 0)
_version = (1, 0, 0)
_version = (3, 0, 0)
@classmethod
def get_requirements(cls):
@@ -451,10 +450,10 @@ class Sockstat(plugins.PluginInterface):
architectures=["Intel32", "Intel64"],
),
requirements.VersionRequirement(
name="SockHandlers", component=SockHandlers, version=(1, 0, 0)
name="SockHandlers", component=SockHandlers, version=(3, 0, 0)
),
requirements.PluginRequirement(
name="lsof", plugin=lsof.Lsof, version=(1, 1, 0)
name="lsof", plugin=lsof.Lsof, version=(2, 0, 0)
),
requirements.VersionRequirement(
name="linuxutils", component=linux.LinuxUtilities, version=(2, 0, 0)
@@ -509,8 +508,9 @@ class Sockstat(plugins.PluginInterface):
dfop_addr = vmlinux.object_from_symbol("sockfs_dentry_operations").vol.offset
fd_generator = lsof.Lsof.list_fds(context, vmlinux.name, filter_func)
for _pid, _task_comm, task, fd_fields in fd_generator:
fd_num, filp, _full_path = fd_fields
for fd_internal in fd_generator:
fd_num, filp, _full_path = fd_internal.fd_fields
task = fd_internal.task
if filp.f_op not in (sfop_addr, dfop_addr):
continue
@@ -617,8 +617,12 @@ class Sockstat(plugins.PluginInterface):
else NotAvailableValue()
)
task_comm = utility.array_to_string(task.comm)
fields = (
netns_id,
task_comm,
task.tgid,
task.pid,
fd_num,
format_hints.Hex(sock.vol.offset),
@@ -638,7 +642,9 @@ class Sockstat(plugins.PluginInterface):
tree_grid_args = [
("NetNS", int),
("Pid", int),
("Process Name", str),
("PID", int),
("TID", int),
("FD", int),
("Sock Offset", format_hints.Hex),
("Family", str),
@@ -2,6 +2,7 @@
# which is available at https://www.volatilityfoundation.org/license/vsl-v1.0
#
import logging
from typing import Iterable, List, Tuple
from volatility3.framework import interfaces, renderers
@@ -10,12 +11,14 @@ from volatility3.framework.renderers import format_hints
from volatility3.plugins import yarascan
from volatility3.plugins.linux import pslist
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, 0)
_version = (1, 0, 2)
@classmethod
def get_requirements(cls) -> List[interfaces.configuration.RequirementInterface]:
@@ -31,7 +34,7 @@ class VmaYaraScan(interfaces.plugins.PluginInterface):
name="pslist", plugin=pslist.PsList, version=(2, 0, 0)
),
requirements.PluginRequirement(
name="yarascan", plugin=yarascan.YaraScan, version=(1, 2, 0)
name="yarascan", plugin=yarascan.YaraScan, version=(2, 0, 0)
),
requirements.VersionRequirement(
name="yarascanner", component=yarascan.YaraScanner, version=(2, 0, 0)
@@ -53,6 +56,8 @@ class VmaYaraScan(interfaces.plugins.PluginInterface):
# use yarascan to parse the yara options provided and create the rules
rules = yarascan.YaraScan.process_yara_options(dict(self.config))
sanity_check = 1024 * 1024 * 1024 # 1 GB
# filter based on the pid option if provided
filter_func = pslist.PsList.create_pid_filter(self.config.get("pid", None))
for task in pslist.PsList.list_tasks(
@@ -69,19 +74,36 @@ class VmaYaraScan(interfaces.plugins.PluginInterface):
# get the proc_layer object from the context
proc_layer = self.context.layers[proc_layer_name]
# scan the process layer with the yarascanner
for offset, rule_name, name, value in proc_layer.scan(
context=self.context,
scanner=yarascan.YaraScanner(rules=rules),
sections=self.get_vma_maps(task),
):
yield 0, (
format_hints.Hex(offset),
task.tgid,
rule_name,
name,
value,
)
max_vma_size = 0
vma_maps_to_scan = []
for start, size in self.get_vma_maps(task):
if size > sanity_check:
vollog.debug(
f"VMA at 0x{start:x} over sanity-check size, not scanning"
)
continue
max_vma_size = max(max_vma_size, size)
vma_maps_to_scan.append((start, size))
if not vma_maps_to_scan:
vollog.warning(f"No VMAs were found for task {task.tgid}, not scanning")
continue
scanner = yarascan.YaraScanner(rules=rules)
scanner.chunk_size = max_vma_size
# scan the VMA data (in one contiguous block) with the yarascanner
for start, size in vma_maps_to_scan:
for offset, rule_name, name, value in scanner(
proc_layer.read(start, size, pad=True), start
):
yield 0, (
format_hints.Hex(offset),
task.tgid,
rule_name,
name,
value,
)
@staticmethod
def get_vma_maps(
+17 -12
View File
@@ -45,6 +45,7 @@ class Timeliner(interfaces.plugins.PluginInterface):
orders the results by time."""
_required_framework_version = (2, 0, 0)
_version = (1, 1, 0)
def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
@@ -65,7 +66,7 @@ class Timeliner(interfaces.plugins.PluginInterface):
if selected_list:
def filter_plugins(name: str, selected: List[str]) -> bool:
return any([s in name for s in selected])
return any(s in name for s in selected)
filter_func = filter_plugins
else:
@@ -198,9 +199,10 @@ class Timeliner(interfaces.plugins.PluginInterface):
),
)
)
except Exception:
except Exception as e:
vollog.log(
logging.INFO, f"Exception occurred running plugin: {plugin_name}"
logging.INFO,
f"Exception occurred running plugin: {plugin_name}: {e}",
)
vollog.log(logging.DEBUG, traceback.format_exc())
@@ -245,6 +247,16 @@ class Timeliner(interfaces.plugins.PluginInterface):
filter_list = self.config["plugin-filter"]
# Identify plugins that we can run which output datetimes
for plugin_class in self.usable_plugins:
if not issubclass(plugin_class, TimeLinerInterface):
# get_usable_plugins() should filter this, but adding a safeguard just in case
continue
if filter_list and not any(
filter in plugin_class.__module__ + "." + plugin_class.__name__
for filter in filter_list
):
continue
try:
automagics = automagic.choose_automagic(self.automagics, plugin_class)
@@ -276,15 +288,8 @@ class Timeliner(interfaces.plugins.PluginInterface):
config_value,
)
if isinstance(plugin, TimeLinerInterface):
if not len(filter_list) or any(
[
filter
in plugin.__module__ + "." + plugin.__class__.__name__
for filter in filter_list
]
):
plugins_to_run.append(plugin)
plugins_to_run.append(plugin)
except exceptions.UnsatisfiedException as excp:
# Remove the failed plugin from the list and continue
vollog.debug(
@@ -0,0 +1,650 @@
# This file is Copyright 2024 Volatility Foundation and licensed under the Volatility Software License 1.0
# which is available at https://www.volatilityfoundation.org/license/vsl-v1.0
#
import dataclasses
import datetime
import enum
import itertools
import logging
from typing import Dict, Iterable, Iterator, List, Optional, Tuple, Union
from volatility3.framework import interfaces, renderers
from volatility3.framework.configuration import requirements
from volatility3.framework.layers import registry
from volatility3.framework.renderers import conversion
from volatility3.framework.symbols.windows.extensions import registry as reg_extensions
from volatility3.plugins import timeliner
from volatility3.plugins.windows.registry import hivelist
vollog = logging.getLogger(__name__)
#######################################################################
# More information about the following enums can be found in the report
# 'Analysis of the AmCache` by Blanche Lagny, 2019
#######################################################################
class Win8FileValName(enum.Enum):
"""
An enumeration that creates a helpful mapping of opaque Windows 8 Amcache
'File' subkey value names to their human-readable equivalent.
"""
ProgramID = "100"
SHA1Hash = "101"
Product = "0"
Company = "1"
Size = "6"
SizeOfImage = "7"
PEHeaderChecksum = "9"
LastModTime = "11" # REG_QWORD FILETIME
CreateTime = "12" # REG_QWORD FILETIME
Path = "15"
LastModTime2 = "17" # REG_QWORD FILETIME
Version = "d"
CompileTime = "f" # REG_QWORD UNIX EPOCH
class Win8ProgramValName(enum.Enum):
"""
An enumeration that creates a helpful mapping of opaque Windows 8 Amcache
'Program' subkey value names to their human-readable equivalent.
"""
Product = "0"
Version = "1"
Publisher = "2"
InstallTime = "a"
MSIProductCode = "11"
MSIPackageCode = "12"
ProductCode = "f"
PackageCode = "10"
class Win10InvAppFileValName(enum.Enum):
"""
An enumeration containing the most useful Windows 10 Amcache
'InventoryApplicationFile' subkey value names.
"""
FileId = "FileId"
LinkDate = "LinkDate"
LowerCaseLongPath = "LowerCaseLongPath"
ProductName = "ProductName"
ProductVersion = "ProductVersion"
ProgramID = "ProgramId"
Publisher = "Publisher"
class Win10InvAppValName(enum.Enum):
"""
An enumeration containing the most useful Windows 10 Amcache
'InventoryApplication' subkey value names.
"""
InstallDate = "InstallDate"
Name = "Name"
Publisher = "Publisher"
RootDirPath = "RootDirPath"
Version = "Version"
class Win10DriverBinaryValName(enum.Enum):
"""
An enumeration containing the most useful Windows 10 Amcache
'InventoryDriverBinary' subkey value names.
"""
DriverId = "DriverId"
DriverName = "DriverName"
DriverCompany = "DriverCompany"
Product = "Product"
Service = "Service"
DriverTimeStamp = "DriverTimeStamp"
class AmcacheEntryType(enum.IntEnum):
Driver = 1
Program = 2
File = 3
NullableString = Union[str, None, interfaces.renderers.BaseAbsentValue]
NullableDatetime = Union[datetime.datetime, None, interfaces.renderers.BaseAbsentValue]
@dataclasses.dataclass
class _AmcacheEntry:
"""
A class containing all information about an entry from the Amcache registry hive.
Because all values could potentially be paged out of memory or malformed, they are all
a union between their expected value and `interfaces.renderers.BaseAbsentValue`.
"""
entry_type: str
path: NullableString = renderers.NotApplicableValue()
company: NullableString = renderers.NotApplicableValue()
last_modify_time: NullableDatetime = renderers.NotApplicableValue()
last_modify_time_2: NullableDatetime = renderers.NotApplicableValue()
install_time: NullableDatetime = renderers.NotApplicableValue()
compile_time: NullableDatetime = renderers.NotApplicableValue()
sha1_hash: NullableString = renderers.NotApplicableValue()
service: NullableString = renderers.NotApplicableValue()
product_name: NullableString = renderers.NotApplicableValue()
product_version: NullableString = renderers.NotApplicableValue()
def _entry_sort_key(entry_tuple: Tuple[NullableString, _AmcacheEntry]) -> str:
"""Sorts entries by program ID. This is broken out as a function here
to ensure consistency in sorting between the `group_by` and `sorted` function
invocations.
"""
program_id, _ = entry_tuple
key = program_id if isinstance(program_id, str) else ""
return key
def _get_string_value(
values: Dict[str, reg_extensions.CM_KEY_VALUE], name: str
) -> NullableString:
try:
value = values[name]
except KeyError:
return renderers.NotAvailableValue()
data = value.decode_data()
if not isinstance(data, bytes):
return renderers.UnparsableValue()
return data.decode("utf-16le", errors="replace").rstrip("\u0000")
def _get_datetime_filetime_value(
values: Dict[str, reg_extensions.CM_KEY_VALUE], name: str
) -> NullableDatetime:
try:
value = values[name]
except KeyError:
return renderers.NotAvailableValue()
data = value.decode_data()
if not isinstance(data, int):
return renderers.UnparsableValue()
return conversion.wintime_to_datetime(data)
def _get_datetime_utc_epoch_value(
values: Dict[str, reg_extensions.CM_KEY_VALUE], name: str
) -> NullableDatetime:
try:
value = values[name]
except KeyError:
return renderers.NotAvailableValue()
data = value.decode_data()
if not isinstance(data, (int, float)):
return renderers.UnparsableValue()
try:
return datetime.datetime.fromtimestamp(float(data), datetime.timezone.utc)
except (ValueError, OverflowError, OSError):
return renderers.UnparsableValue()
def _get_datetime_str_value(
values: Dict[str, reg_extensions.CM_KEY_VALUE], name: str
) -> NullableDatetime:
try:
value = values[name]
except KeyError:
return renderers.NotAvailableValue()
data = value.decode_data()
if not isinstance(data, int):
return renderers.UnparsableValue()
if isinstance(data, str):
try:
return datetime.datetime.strptime(data, "%m/%d/%Y %H:%M:%S")
except ValueError:
return renderers.UnparsableValue()
else:
return renderers.UnparsableValue()
class Amcache(interfaces.plugins.PluginInterface, timeliner.TimeLinerInterface):
"""Extract information on executed applications from the AmCache."""
_required_framework_version = (2, 0, 0)
_version = (1, 0, 0)
@classmethod
def get_requirements(cls) -> List[interfaces.configuration.RequirementInterface]:
# Since we're calling the plugin, make sure we have the plugin's requirements
return [
requirements.ModuleRequirement(
name="kernel",
description="Windows kernel",
architectures=["Intel32", "Intel64"],
),
requirements.PluginRequirement(
name="hivelist", plugin=hivelist.HiveList, version=(1, 0, 0)
),
]
def generate_timeline(
self,
) -> Iterator[Tuple[str, timeliner.TimeLinerType, datetime.datetime]]:
for _, entry in self._generator():
if isinstance(entry.last_modify_time, datetime.datetime):
yield f"Amcache: {entry.entry_type} {entry.path} registry key modified", timeliner.TimeLinerType.MODIFIED, entry.last_modify_time
if isinstance(entry.last_modify_time_2, datetime.datetime):
yield f"Amcache: {entry.entry_type} {entry.path} STANDARD_INFORMATION create time", timeliner.TimeLinerType.CREATED, entry.last_modify_time_2
if isinstance(entry.install_time, datetime.datetime):
yield f"Amcache: {entry.entry_type} {entry.path} installed", timeliner.TimeLinerType.CREATED, entry.install_time
if isinstance(entry.compile_time, datetime.datetime):
yield f"Amcache: {entry.entry_type} {entry.path} compiled (PE metadata)", timeliner.TimeLinerType.MODIFIED, entry.compile_time
@classmethod
def get_amcache_hive(
cls,
context: interfaces.context.ContextInterface,
config_path: str,
kernel: interfaces.context.ModuleInterface,
) -> Optional[registry.RegistryHive]:
"""Retrieves the `Amcache.hve` registry hive from the kernel module, if it can be located."""
return next(
hivelist.HiveList.list_hives(
context=context,
base_config_path=interfaces.configuration.path_join(
config_path, "hivelist"
),
layer_name=kernel.layer_name,
symbol_table=kernel.symbol_table_name,
filter_string="amcache",
),
None,
)
@classmethod
def parse_file_key(
cls, file_key: reg_extensions.CM_KEY_NODE
) -> Iterator[Tuple[NullableString, _AmcacheEntry]]:
"""Parses File entries from the Windows 8 `Root\\File` key.
:param programs_key: The `Root\\File` registry key.
:return: An iterator of tuples, where the first member is the program ID string for
correlating `Root\\Program` entries, and the second member is the `AmcacheEntry`.
"""
val_enum = Win8FileValName
wanted_values = [key.value for key in val_enum]
for file_entry_key in itertools.chain(
*(key.get_subkeys() for key in file_key.get_subkeys())
):
vollog.debug(f"Checking Win8 File key {file_entry_key.get_name()}")
values = {
str(value.get_name()): value
for value in file_entry_key.get_values()
if value.get_name() in wanted_values
}
program_id = _get_string_value(values, val_enum.ProgramID.value)
path = _get_string_value(values, val_enum.Path.value)
company = _get_string_value(values, val_enum.Company.value)
last_mod_time = _get_datetime_filetime_value(
values, val_enum.LastModTime.value
)
last_mod_time_2 = _get_datetime_filetime_value(
values, val_enum.LastModTime2.value
)
install_time = _get_datetime_filetime_value(
values, val_enum.CreateTime.value
)
compile_time = _get_datetime_utc_epoch_value(
values, val_enum.CompileTime.value
)
sha1_hash = _get_string_value(values, val_enum.SHA1Hash.value)
vollog.debug(f"Found sha1hash {sha1_hash}")
product_name = _get_string_value(values, val_enum.Product.value)
yield program_id, _AmcacheEntry(
AmcacheEntryType.File.name,
path=path,
company=company,
last_modify_time=last_mod_time,
last_modify_time_2=last_mod_time_2,
install_time=install_time,
compile_time=compile_time,
sha1_hash=(
sha1_hash.lstrip("0000")
if isinstance(sha1_hash, str)
else sha1_hash
),
product_name=product_name,
)
@classmethod
def parse_programs_key(
cls, programs_key: reg_extensions.CM_KEY_NODE
) -> Iterator[Tuple[str, _AmcacheEntry]]:
"""Parses Program entries from the Windows 8 `Root\\Programs` key.
:param programs_key: The `Root\\Programs` registry key.
:return: An iterator of tuples, where the first member is the program ID string for
correlating `Root\\File` entries, and the second member is the `AmcacheEntry`.
"""
val_enum = Win8ProgramValName
wanted_values = [key.value for key in val_enum]
for program_key in programs_key.get_subkeys():
values = {
str(value.get_name()): value
for value in program_key.get_values()
if value.get_name() in wanted_values
}
vollog.debug(f"Parsing Win8 Program key {program_key.get_name()}")
program_id = program_key.get_name().strip().strip("\u0000")
product = _get_string_value(values, val_enum.Product.value)
company = _get_string_value(values, val_enum.Publisher.value)
install_time = _get_datetime_utc_epoch_value(
values, val_enum.InstallTime.value
)
version = _get_string_value(values, val_enum.Version.value)
yield program_id, _AmcacheEntry(
AmcacheEntryType.Program.name,
company=company,
last_modify_time=conversion.wintime_to_datetime(
program_key.LastWriteTime.QuadPart
),
install_time=install_time,
product_name=product,
product_version=version,
)
@classmethod
def parse_inventory_app_key(
cls, inv_app_key: reg_extensions.CM_KEY_NODE
) -> Iterator[Tuple[str, _AmcacheEntry]]:
"""Parses InventoryApplication entries from the Windows 10 `Root\\InventoryApplication` key.
:param programs_key: The `Root\\InventoryApplication` registry key.
:return: An iterator of tuples, where the first member is the program ID string for
correlating `Root\\InventoryApplicationFile` entries, and the second member is the `AmcacheEntry`.
"""
val_enum = Win10InvAppValName
wanted_values = [key.value for key in val_enum]
for program_key in inv_app_key.get_subkeys():
program_id = program_key.get_name()
values = {
str(value.get_name()): value
for value in program_key.get_values()
if value.get_name() in wanted_values
}
name = _get_string_value(values, val_enum.Name.value)
version = _get_string_value(values, val_enum.Version.value)
publisher = _get_string_value(values, val_enum.Publisher.value)
path = _get_string_value(values, val_enum.RootDirPath.value)
install_date = _get_datetime_str_value(values, val_enum.InstallDate.value)
last_mod = conversion.wintime_to_datetime(
program_key.LastWriteTime.QuadPart
)
product: str = name if isinstance(name, str) else "UNKNOWN" # type: ignore
yield program_id.strip().strip("\u0000"), _AmcacheEntry(
AmcacheEntryType.Program.name,
path=path,
last_modify_time=last_mod,
install_time=install_date,
product_name=product,
company=publisher,
product_version=version,
)
@classmethod
def parse_inventory_app_file_key(
cls, inv_app_file_key: reg_extensions.CM_KEY_NODE
) -> Iterator[Tuple[NullableString, _AmcacheEntry]]:
"""Parses executable file entries from the `Root\\InventoryApplicationFile` registry key.
:param inv_app_file_key: The `Root\\InventoryApplicationFile` registry key.
:return: An iterator of tuples, where the first member is the program ID string for correlating
with it's parent `InventoryApplication` program entry, and the second member is the `Amcache` entry.
"""
val_enum = Win10InvAppFileValName
wanted_values = [key.value for key in val_enum]
for file_key in inv_app_file_key.get_subkeys():
vollog.debug(
f"Parsing Win10 InventoryApplicationFile key {file_key.get_name()}"
)
values = {
str(value.get_name()): value
for value in file_key.get_values()
if value.get_name() in wanted_values
}
last_mod = conversion.wintime_to_datetime(file_key.LastWriteTime.QuadPart)
path = _get_string_value(values, val_enum.LowerCaseLongPath.value)
linkdate = _get_datetime_str_value(values, val_enum.LinkDate.value)
sha1_hash = _get_string_value(values, val_enum.FileId.value)
publisher = _get_string_value(values, val_enum.Publisher.value)
prod_name = _get_string_value(values, val_enum.ProductName.value)
prod_ver = _get_string_value(values, val_enum.ProductVersion.value)
program_id = _get_string_value(values, val_enum.ProgramID.value)
yield program_id, _AmcacheEntry(
AmcacheEntryType.File.name,
path=path,
company=publisher,
last_modify_time=last_mod,
compile_time=linkdate,
sha1_hash=(
sha1_hash.lstrip("0000")
if isinstance(sha1_hash, str)
else sha1_hash
),
product_name=prod_name,
product_version=prod_ver,
)
@classmethod
def parse_driver_binary_key(
cls, driver_binary_key: reg_extensions.CM_KEY_NODE
) -> Iterator[_AmcacheEntry]:
"""Parses information about installed drivers from the `Root\\InventoryDriverBinary` registry key.
:param driver_binary_key: The `Root\\InventoryDriverBinary` registry key
:return: An iterator of `AmcacheEntry`s
"""
val_enum = Win10DriverBinaryValName
wanted_values = [key.value for key in val_enum]
for binary_key in driver_binary_key.get_subkeys():
values = {
str(value.get_name()): value
for value in binary_key.get_values()
if value.get_name() in wanted_values
}
# Depending on the Windows version, the key name will be either the name
# of the driver, or its SHA1 hash.
if "/" in str(binary_key.get_name()):
driver_name = str(binary_key.get_name())
sha1_hash = _get_string_value(values, val_enum.DriverId.name)
else:
sha1_hash = str(binary_key.get_name())
driver_name = _get_string_value(values, val_enum.DriverName.name)
if isinstance(sha1_hash, str):
sha1_hash = sha1_hash[4:] if sha1_hash.startswith("0000") else sha1_hash
company, product, service, last_write_time, driver_timestamp = (
_get_string_value(values, val_enum.DriverCompany.name),
_get_string_value(values, val_enum.Product.name),
_get_string_value(values, val_enum.Service.name),
conversion.wintime_to_datetime(binary_key.LastWriteTime.QuadPart),
_get_datetime_utc_epoch_value(values, val_enum.DriverTimeStamp.name),
)
yield _AmcacheEntry(
entry_type=AmcacheEntryType.Driver.name,
path=driver_name,
company=company,
last_modify_time=last_write_time,
compile_time=driver_timestamp,
sha1_hash=(
sha1_hash.lstrip("0000")
if isinstance(sha1_hash, str)
else sha1_hash
),
service=service,
product_name=product,
)
def _generator(self) -> Iterator[Tuple[int, _AmcacheEntry]]:
kernel = self.context.modules[self.config["kernel"]]
def indented(
entry_gen: Iterable[_AmcacheEntry], indent: int = 0
) -> Iterator[Tuple[int, _AmcacheEntry]]:
for item in entry_gen:
yield indent, item
# Building the dictionary ahead of time is much better for performance
# vs looking up each service's DLL individually.
amcache = self.get_amcache_hive(self.context, self.config_path, kernel)
if amcache is None:
return
try:
yield from indented(
self.parse_driver_binary_key(
amcache.get_key("Root\\InventoryDriverBinary") # type: ignore
)
)
except KeyError:
# Registry key not found
pass
try:
programs: Dict[str, _AmcacheEntry] = {
program_id: entry
for program_id, entry in self.parse_programs_key(
amcache.get_key("Root\\Programs")
) # type: ignore
}
except KeyError:
programs = {}
try:
files = sorted(
list(
self.parse_file_key(amcache.get_key("Root\\File")), # type: ignore
),
key=_entry_sort_key,
)
except KeyError:
files = []
for program_id, file_entries in itertools.groupby(
files,
key=_entry_sort_key,
):
files_indent = 0
if isinstance(program_id, str):
try:
program_entry = programs.pop(program_id.strip().strip("\u0000"))
yield (0, program_entry)
files_indent = 1
except KeyError:
# No parent program for this file entry
pass
for _, entry in file_entries:
yield files_indent, entry
for empty_program in programs.values():
yield 0, empty_program
try:
programs: Dict[str, _AmcacheEntry] = dict(
self.parse_inventory_app_key(
amcache.get_key("Root\\InventoryApplication") # type: ignore
)
)
except KeyError:
programs = {}
try:
files = sorted(
list(
self.parse_inventory_app_file_key(amcache.get_key("Root\\InventoryApplicationFile")), # type: ignore
),
key=_entry_sort_key,
)
except KeyError:
files = []
for program_id, file_entries in itertools.groupby(
files,
key=_entry_sort_key,
):
files_indent = 0
if isinstance(program_id, str):
try:
program_entry = programs.pop(program_id.strip().strip("\u0000"))
yield (0, program_entry)
files_indent = 1
except KeyError:
# No parent program for this file entry
pass
for _, entry in file_entries:
yield files_indent, entry
for empty_program in programs.values():
yield 0, empty_program
def run(self):
return renderers.TreeGrid(
[
("EntryType", str),
("Path", str),
("Company", str),
("LastModifyTime", datetime.datetime),
("LastModifyTime2", datetime.datetime),
("InstallTime", datetime.datetime),
("CompileTime", datetime.datetime),
("SHA1", str),
("Service", str),
("ProductName", str),
("ProductVersion", str),
],
(
(indent, dataclasses.astuple(entry))
for indent, entry in self._generator()
),
)
@@ -248,8 +248,12 @@ class Callbacks(interfaces.plugins.PluginInterface):
context, layer_name, nt_symbol_table, constraints
):
try:
if hasattr(mem_object, "is_valid") and not mem_object.is_valid():
continue
if isinstance(mem_object, callbacks._SHUTDOWN_PACKET):
if not mem_object.is_parseable(type_map):
continue
elif hasattr(mem_object, "is_valid"):
if not mem_object.is_valid():
continue
yield cls._process_scanned_callback(mem_object, type_map)
except exceptions.InvalidAddressException:
@@ -0,0 +1,381 @@
# This file is Copyright 2024 Volatility Foundation and licensed under the Volatility Software License 1.0
# which is available at https://www.volatilityfoundation.org/license/vsl-v1.0
#
# This module attempts to locate windows console histories.
import logging
import struct
from typing import Tuple, Generator, Set, Dict, Any, Optional
from volatility3.framework import interfaces
from volatility3.framework import renderers
from volatility3.framework.configuration import requirements
from volatility3.framework.layers import scanners
from volatility3.framework.objects import utility
from volatility3.framework.renderers import format_hints
from volatility3.plugins.windows import pslist, consoles
vollog = logging.getLogger(__name__)
class CmdScan(interfaces.plugins.PluginInterface):
"""Looks for Windows Command History lists"""
_required_framework_version = (2, 4, 0)
_version = (1, 0, 0)
@classmethod
def get_requirements(cls):
# Since we're calling the plugin, make sure we have the plugin's requirements
return [
requirements.ModuleRequirement(
name="kernel",
description="Windows kernel",
architectures=["Intel32", "Intel64"],
),
requirements.VersionRequirement(
name="pslist", component=pslist.PsList, version=(2, 0, 0)
),
requirements.PluginRequirement(
name="consoles", plugin=consoles.Consoles, version=(1, 0, 0)
),
requirements.BooleanRequirement(
name="no_registry",
description="Don't search the registry for possible values of CommandHistorySize",
optional=True,
default=False,
),
requirements.ListRequirement(
name="max_history",
element_type=int,
description="CommandHistorySize values to search for.",
optional=True,
default=[50],
),
]
@classmethod
def get_filtered_vads(
cls,
conhost_proc: interfaces.context.ContextInterface,
size_filter: Optional[int] = 0x40000000,
) -> Generator[Tuple[int, int], None, None]:
"""
Returns vads of a process with size smaller than size_filter
Args:
conhost_proc: the process object for conhost.exe
Returns:
A list of tuples of:
vad_base: the base address
vad_size: the size of the VAD
"""
for vad in conhost_proc.get_vad_root().traverse():
base = vad.get_start()
if vad.get_size() < size_filter:
yield (base, vad.get_size())
@classmethod
def get_command_history(
cls,
context: interfaces.context.ContextInterface,
kernel_layer_name: str,
kernel_symbol_table_name: str,
config_path: str,
procs: Generator[interfaces.objects.ObjectInterface, None, None],
max_history: Set[int],
) -> Tuple[
interfaces.context.ContextInterface,
interfaces.context.ContextInterface,
Dict[str, Any],
]:
"""Gets the list of commands from each Command History structure
Args:
context: The context to retrieve required elements (layers, symbol tables) from
kernel_layer_name: The name of the layer on which to operate
kernel_symbol_table_name: The name of the table containing the kernel symbols
config_path: The config path where to find symbol files
procs: list of process objects
max_history: an initial set of CommandHistorySize values
Returns:
The conhost process object, the command history structure, a dictionary of properties for
that command history structure.
"""
conhost_symbol_table = None
for conhost_proc, proc_layer_name in consoles.Consoles.find_conhost_proc(procs):
if not conhost_proc:
vollog.info(
"Unable to find a valid conhost.exe process in the process list. Analysis cannot proceed."
)
continue
vollog.debug(
f"Found conhost process {conhost_proc} with pid {conhost_proc.UniqueProcessId}"
)
conhostexe_base, conhostexe_size = consoles.Consoles.find_conhostexe(
conhost_proc
)
if not conhostexe_base:
vollog.info(
"Unable to find the location of conhost.exe. Analysis cannot proceed."
)
continue
vollog.debug(f"Found conhost.exe base at {conhostexe_base:#x}")
proc_layer = context.layers[proc_layer_name]
if conhost_symbol_table is None:
conhost_symbol_table = consoles.Consoles.create_conhost_symbol_table(
context,
kernel_layer_name,
kernel_symbol_table_name,
config_path,
proc_layer_name,
conhostexe_base,
)
conhost_module = context.module(
conhost_symbol_table, proc_layer_name, offset=conhostexe_base
)
command_count_max_offset = conhost_module.get_type(
"_COMMAND_HISTORY"
).relative_child_offset("CommandCountMax")
sections = cls.get_filtered_vads(conhost_proc)
found_history_for_proc = False
# scan for potential _COMMAND_HISTORY structures by using the CommandHistorySize
for max_history_value in max_history:
max_history_bytes = struct.pack("H", max_history_value)
vollog.debug(
f"Scanning for CommandHistorySize value: {max_history_bytes}"
)
for address in proc_layer.scan(
context,
scanners.BytesScanner(max_history_bytes),
sections=sections,
):
command_history = None
command_history_properties = []
try:
command_history = conhost_module.object(
"_COMMAND_HISTORY",
offset=address - command_count_max_offset,
absolute=True,
)
if not command_history.is_valid(max_history_value):
continue
vollog.debug(
f"Getting Command History properties for {command_history}"
)
command_history_properties.append(
{
"level": 0,
"name": "_COMMAND_HISTORY",
"address": command_history.vol.offset,
"data": None,
}
)
command_history_properties.append(
{
"level": 1,
"name": "_COMMAND_HISTORY.Application",
"address": command_history.Application.vol.offset,
"data": command_history.get_application(),
}
)
command_history_properties.append(
{
"level": 1,
"name": "_COMMAND_HISTORY.ProcessHandle",
"address": command_history.ConsoleProcessHandle.ProcessHandle.vol.offset,
"data": hex(
command_history.ConsoleProcessHandle.ProcessHandle
),
}
)
command_history_properties.append(
{
"level": 1,
"name": "_COMMAND_HISTORY.CommandCount",
"address": None,
"data": command_history.CommandCount,
}
)
command_history_properties.append(
{
"level": 1,
"name": "_COMMAND_HISTORY.LastDisplayed",
"address": command_history.LastDisplayed.vol.offset,
"data": command_history.LastDisplayed,
}
)
command_history_properties.append(
{
"level": 1,
"name": "_COMMAND_HISTORY.CommandCountMax",
"address": command_history.CommandCountMax.vol.offset,
"data": command_history.CommandCountMax,
}
)
command_history_properties.append(
{
"level": 1,
"name": "_COMMAND_HISTORY.CommandBucket",
"address": command_history.CommandBucket.vol.offset,
"data": "",
}
)
for (
cmd_index,
bucket_cmd,
) in command_history.scan_command_bucket():
try:
command_history_properties.append(
{
"level": 2,
"name": f"_COMMAND_HISTORY.CommandBucket_Command_{cmd_index}",
"address": bucket_cmd.vol.offset,
"data": bucket_cmd.get_command_string(),
}
)
except Exception as e:
vollog.debug(
f"reading {bucket_cmd} encountered exception {e}"
)
except Exception as e:
vollog.debug(
f"reading {command_history} encountered exception {e}"
)
if command_history and command_history_properties:
found_history_for_proc = True
yield conhost_proc, command_history, command_history_properties
# if found_history_for_proc is still False, then none of the scanned locations found
# a valid _COMMAND_HISTORY for the process, so yield the process and some empty data
# so the process can at least be reported that it was found with no history
if not found_history_for_proc:
yield conhost_proc, command_history or None, []
def _generator(
self, procs: Generator[interfaces.objects.ObjectInterface, None, None]
):
"""
Generates the command history to use in rendering
Args:
procs: the process list filtered to conhost.exe instances
"""
kernel = self.context.modules[self.config["kernel"]]
max_history = set(self.config.get("max_history", [50]))
no_registry = self.config.get("no_registry")
if no_registry is False:
max_history, _ = consoles.Consoles.get_console_settings_from_registry(
self.context,
self.config_path,
kernel.layer_name,
kernel.symbol_table_name,
max_history,
[],
)
vollog.debug(f"Possible CommandHistorySize values: {max_history}")
proc = None
for (
proc,
command_history,
command_history_properties,
) in self.get_command_history(
self.context,
kernel.layer_name,
kernel.symbol_table_name,
self.config_path,
procs,
max_history,
):
process_name = utility.array_to_string(proc.ImageFileName)
process_pid = proc.UniqueProcessId
if command_history and command_history_properties:
for command_history_property in command_history_properties:
yield (
command_history_property["level"],
(
process_pid,
process_name,
format_hints.Hex(command_history.vol.offset),
command_history_property["name"],
(
renderers.NotApplicableValue()
if command_history_property["address"] is None
else format_hints.Hex(
command_history_property["address"]
)
),
str(command_history_property["data"]),
),
)
else:
yield (
0,
(
process_pid,
process_name,
(
format_hints.Hex(command_history.vol.offset)
if command_history
else renderers.NotApplicableValue()
),
"_COMMAND_HISTORY",
renderers.NotApplicableValue(),
"History Not Found",
),
)
if proc is None:
vollog.warn("No conhost.exe processes found.")
def _conhost_proc_filter(self, proc: interfaces.objects.ObjectInterface):
"""
Used to filter to only conhost.exe processes
"""
process_name = utility.array_to_string(proc.ImageFileName)
return process_name != "conhost.exe"
def run(self):
kernel = self.context.modules[self.config["kernel"]]
return renderers.TreeGrid(
[
("PID", int),
("Process", str),
("ConsoleInfo", format_hints.Hex),
("Property", str),
("Address", format_hints.Hex),
("Data", str),
],
self._generator(
pslist.PsList.list_processes(
context=self.context,
layer_name=kernel.layer_name,
symbol_table=kernel.symbol_table_name,
filter_func=self._conhost_proc_filter,
)
),
)
@@ -0,0 +1,953 @@
# This file is Copyright 2024 Volatility Foundation and licensed under the Volatility Software License 1.0
# which is available at https://www.volatilityfoundation.org/license/vsl-v1.0
#
# This module attempts to locate windows console histories.
import logging
import os
import struct
from typing import Tuple, Generator, Set, Dict, Any, Type
from volatility3.framework import interfaces, symbols, exceptions
from volatility3.framework import renderers
from volatility3.framework.interfaces import configuration
from volatility3.framework.configuration import requirements
from volatility3.framework.layers import scanners
from volatility3.framework.objects import utility
from volatility3.framework.renderers import format_hints
from volatility3.framework.symbols import intermed
from volatility3.framework.symbols.windows.extensions import pe, consoles
from volatility3.plugins.windows import pslist, info, verinfo
from volatility3.plugins.windows.registry import hivelist
vollog = logging.getLogger(__name__)
class Consoles(interfaces.plugins.PluginInterface):
"""Looks for Windows console buffers"""
_required_framework_version = (2, 4, 0)
_version = (1, 0, 0)
@classmethod
def get_requirements(cls):
# Since we're calling the plugin, make sure we have the plugin's requirements
return [
requirements.ModuleRequirement(
name="kernel",
description="Windows kernel",
architectures=["Intel32", "Intel64"],
),
requirements.VersionRequirement(
name="pslist", component=pslist.PsList, version=(2, 0, 0)
),
requirements.VersionRequirement(
name="verinfo", component=verinfo.VerInfo, version=(1, 0, 0)
),
requirements.PluginRequirement(
name="hivelist", plugin=hivelist.HiveList, version=(1, 0, 0)
),
requirements.BooleanRequirement(
name="no_registry",
description="Don't search the registry for possible values of CommandHistorySize and HistoryBufferMax",
optional=True,
default=False,
),
requirements.ListRequirement(
name="max_history",
element_type=int,
description="CommandHistorySize values to search for.",
optional=True,
default=[50],
),
requirements.ListRequirement(
name="max_buffers",
element_type=int,
description="HistoryBufferMax values to search for.",
optional=True,
default=[4],
),
]
@classmethod
def find_conhost_proc(
cls, proc_list: Generator[interfaces.objects.ObjectInterface, None, None]
) -> Tuple[interfaces.context.ContextInterface, str]:
"""
Walks the process list and returns the conhost instances.
Args:
proc_list: The process list generator
Return:
The process object and layer name for conhost
"""
for proc in proc_list:
if utility.array_to_string(proc.ImageFileName).lower() == "conhost.exe":
try:
proc_id = proc.UniqueProcessId
proc_layer_name = proc.add_process_layer()
yield proc, proc_layer_name
except exceptions.InvalidAddressException as excp:
vollog.debug(
"Process {}: invalid address {} in layer {}".format(
proc_id, excp.invalid_address, excp.layer_name
)
)
@classmethod
def find_conhostexe(
cls, conhost_proc: interfaces.context.ContextInterface
) -> Tuple[int, int]:
"""
Finds the base address of conhost.exe
Args:
conhost_proc: the process object for conhost.exe
Returns:
A tuple of:
conhostexe_base: the base address of conhost.exe
conhostexe_size: the size of the VAD for conhost.exe
"""
for vad in conhost_proc.get_vad_root().traverse():
filename = vad.get_file_name()
if isinstance(filename, str) and filename.lower().endswith("conhost.exe"):
base = vad.get_start()
return base, vad.get_size()
return None, None
@classmethod
def determine_conhost_version(
cls,
context: interfaces.context.ContextInterface,
layer_name: str,
nt_symbol_table: str,
config_path: str,
conhost_layer_name: str,
conhost_base: int,
) -> Tuple[str, Type]:
"""Tries to determine which symbol filename to use for the image's console information. This is similar to the
netstat plugin.
Args:
context: The context to retrieve required elements (layers, symbol tables) from
layer_name: The name of the layer on which to operate
nt_symbol_table: The name of the table containing the kernel symbols
config_path: The config path where to find symbol files
conhost_layer_name: The name of the conhot process memory layer
conhost_base: the base address of conhost.exe
Returns:
The filename of the symbol table to use and the associated class types.
"""
is_64bit = symbols.symbol_table_is_64bit(context, nt_symbol_table)
if is_64bit:
arch = "x64"
else:
arch = "x86"
vers = info.Info.get_version_structure(context, layer_name, nt_symbol_table)
kuser = info.Info.get_kuser_structure(context, layer_name, nt_symbol_table)
try:
vers_minor_version = int(vers.MinorVersion)
nt_major_version = int(kuser.NtMajorVersion)
nt_minor_version = int(kuser.NtMinorVersion)
except ValueError:
# vers struct exists, but is not an int anymore?
raise NotImplementedError(
"Kernel Debug Structure version format not supported!"
)
except Exception:
# unsure what to raise here. Also, it might be useful to add some kind of fallback,
# either to a user-provided version or to another method to determine conhost.exe's version
raise exceptions.VolatilityException(
"Kernel Debug Structure missing VERSION/KUSER structure, unable to determine Windows version!"
)
vollog.debug(
"Determined OS Version: {}.{} {}.{}".format(
kuser.NtMajorVersion,
kuser.NtMinorVersion,
vers.MajorVersion,
vers.MinorVersion,
)
)
if nt_major_version == 10 and arch == "x64":
# win10 x64 has an additional class type we have to include.
class_types = consoles.win10_x64_class_types
else:
# default to general class types
class_types = consoles.class_types
# these versions are listed explicitly because symbol files differ based on
# version *and* architecture. this is currently the clearest way to show
# the differences, even if it introduces a fair bit of redundancy.
# furthermore, it is easy to append new versions.
if arch == "x86":
version_dict = {}
else:
version_dict = {
(10, 0, 17763, 1): "consoles-win10-17763-x64",
(10, 0, 17763, 3232): "consoles-win10-17763-3232-x64",
(10, 0, 18362, 0): "consoles-win10-18362-x64",
(10, 0, 19041, 0): "consoles-win10-19041-x64",
(10, 0, 20348, 1): "consoles-win10-20348-x64",
(10, 0, 20348, 1970): "consoles-win10-20348-1970-x64",
(10, 0, 20348, 2461): "consoles-win10-20348-2461-x64",
(10, 0, 20348, 2520): "consoles-win10-20348-2461-x64",
(10, 0, 22000, 0): "consoles-win10-22000-x64",
(10, 0, 22621, 1): "consoles-win10-22621-x64",
(10, 0, 22621, 3527): "consoles-win10-22621-3527-x64",
(10, 0, 25398, 0): "consoles-win10-22000-x64",
}
# we do not need to check for conhost's specific FileVersion in every case
conhost_mod_version = 0 # keep it 0 as a default
# we need to define additional version numbers (which are then found via conhost.exe's FileVersion header) in case there is
# ambiguity _within_ an OS version. If such a version number (last number of the tuple) is defined for the current OS
# we need to inspect conhost.exe's headers to see if we can grab the precise version
if [
(a, b, c, d)
for a, b, c, d in version_dict
if (a, b, c) == (nt_major_version, nt_minor_version, vers_minor_version)
and d != 0
]:
vollog.debug(
"Requiring further version inspection due to OS version by checking conhost.exe's FileVersion header"
)
pe_table_name = intermed.IntermediateSymbolTable.create(
context,
configuration.path_join(config_path, "conhost"),
"windows",
"pe",
class_types=pe.class_types,
)
try:
(major, minor, product, build) = (
verinfo.VerInfo.get_version_information(
context, pe_table_name, conhost_layer_name, conhost_base
)
)
conhost_mod_version = build
vollog.debug(
f"Found conhost.exe version {major}.{minor}.{product}.{build} in {conhost_layer_name} at base {conhost_base:#x}"
)
except (exceptions.InvalidAddressException, TypeError, AttributeError):
# the following is IntelLayer specific and might need to be adapted to other architectures.
physical_layer_name = context.layers[layer_name].config.get(
"memory_layer", None
)
if physical_layer_name:
ver = verinfo.VerInfo.find_version_info(
context, physical_layer_name, "CONHOST.EXE"
)
if ver:
conhost_mod_version = ver[3]
vollog.debug(
"Determined conhost.exe's FileVersion: {}".format(
conhost_mod_version
)
)
else:
vollog.debug("Could not determine conhost.exe's FileVersion.")
else:
vollog.debug(
"Unable to retrieve physical memory layer, skipping FileVersion check."
)
# when determining the symbol file we have to consider the following cases:
# the determined version's symbol file is found by intermed.create -> proceed
# the determined version's symbol file is not found by intermed -> intermed will throw an exc and abort
# the determined version has no mapped symbol file -> if win10 use latest, otherwise throw exc
# windows version cannot be determined -> throw exc
filename = version_dict.get(
(
nt_major_version,
nt_minor_version,
vers_minor_version,
conhost_mod_version,
)
)
if not filename:
# no match on filename means that we possibly have a version newer than those listed here.
# try to grab the latest supported version of the current image NT version. If that symbol
# version does not work, support has to be added manually.
current_versions = [
(nt_maj, nt_min, vers_min, conhost_ver)
for nt_maj, nt_min, vers_min, conhost_ver in version_dict
if nt_maj == nt_major_version
and nt_min == nt_minor_version
and vers_min <= vers_minor_version
and conhost_ver <= conhost_mod_version
]
current_versions.sort()
if current_versions:
latest_version = current_versions[-1]
filename = version_dict.get(latest_version)
vollog.debug(
f"Unable to find exact matching symbol file, going with latest: {filename}"
)
else:
raise NotImplementedError(
"This version of Windows is not supported: {}.{} {}.{}!".format(
nt_major_version,
nt_minor_version,
vers.MajorVersion,
vers_minor_version,
)
)
vollog.debug(f"Determined symbol filename: {filename}")
return filename, class_types
@classmethod
def create_conhost_symbol_table(
cls,
context: interfaces.context.ContextInterface,
layer_name: str,
nt_symbol_table: str,
config_path: str,
conhost_layer_name: str,
conhost_base: int,
) -> str:
"""Creates a symbol table for conhost structures.
Args:
context: The context to retrieve required elements (layers, symbol tables) from
layer_name: The name of the layer on which to operate
nt_symbol_table: The name of the table containing the kernel symbols
config_path: The config path where to find symbol files
Returns:
The name of the constructed symbol table
"""
table_mapping = {"nt_symbols": nt_symbol_table}
symbol_filename, class_types = cls.determine_conhost_version(
context,
layer_name,
nt_symbol_table,
config_path,
conhost_layer_name,
conhost_base,
)
vollog.debug(f"Using symbol file '{symbol_filename}' and types {class_types}")
return intermed.IntermediateSymbolTable.create(
context,
configuration.path_join(config_path, "conhost"),
os.path.join("windows", "consoles"),
symbol_filename,
class_types=class_types,
table_mapping=table_mapping,
)
@classmethod
def get_console_info(
cls,
context: interfaces.context.ContextInterface,
kernel_layer_name: str,
kernel_table_name: str,
config_path: str,
procs: Generator[interfaces.objects.ObjectInterface, None, None],
max_history: Set[int],
max_buffers: Set[int],
) -> Tuple[
interfaces.context.ContextInterface,
interfaces.context.ContextInterface,
Dict[str, Any],
]:
"""Gets the Console Information structure and its related properties for each conhost process
Args:
context: The context to retrieve required elements (layers, symbol tables) from
kernel_layer_name: The name of the layer on which to operate
kernel_table_name: The name of the table containing the kernel symbols
config_path: The config path where to find symbol files
procs: list of process objects
max_history: an initial set of CommandHistorySize values
max_buffers: an initial list of HistoryBufferMax values
Returns:
The conhost process object, the console information structure, a dictionary of properties for
that console information structure.
"""
conhost_symbol_table = None
for conhost_proc, proc_layer_name in cls.find_conhost_proc(procs):
if not conhost_proc:
vollog.info(
"Unable to find a valid conhost.exe process in the process list. Analysis cannot proceed."
)
continue
vollog.debug(
f"Found conhost process {conhost_proc} with pid {conhost_proc.UniqueProcessId}"
)
conhostexe_base, conhostexe_size = cls.find_conhostexe(conhost_proc)
if not conhostexe_base:
vollog.info(
"Unable to find the location of conhost.exe. Analysis cannot proceed."
)
continue
vollog.debug(f"Found conhost.exe base at {conhostexe_base:#x}")
proc_layer = context.layers[proc_layer_name]
if conhost_symbol_table is None:
conhost_symbol_table = cls.create_conhost_symbol_table(
context,
kernel_layer_name,
kernel_table_name,
config_path,
proc_layer_name,
conhostexe_base,
)
conhost_module = context.module(
conhost_symbol_table, proc_layer_name, offset=conhostexe_base
)
found_console_info_for_proc = False
# scan for potential _CONSOLE_INFORMATION structures by using the CommandHistorySize
for max_history_value in max_history:
max_history_bytes = struct.pack("H", max_history_value)
vollog.debug(
f"Scanning for CommandHistorySize value: {max_history_bytes}"
)
for address in proc_layer.scan(
context,
scanners.BytesScanner(max_history_bytes),
sections=[(conhostexe_base, conhostexe_size)],
):
console_properties = []
try:
console_info = conhost_module.object(
"_CONSOLE_INFORMATION",
offset=address
- conhost_module.get_type(
"_CONSOLE_INFORMATION"
).relative_child_offset("CommandHistorySize"),
absolute=True,
)
if not any(
[
console_info.is_valid(max_buffer)
for max_buffer in max_buffers
]
):
continue
vollog.debug(
f"Getting Console Information properties for {console_info}"
)
console_properties.append(
{
"level": 0,
"name": "_CONSOLE_INFORMATION",
"address": console_info.vol.offset,
"data": "",
}
)
console_properties.append(
{
"level": 1,
"name": "_CONSOLE_INFORMATION.ScreenX",
"address": console_info.ScreenX.vol.offset,
"data": console_info.ScreenX,
}
)
console_properties.append(
{
"level": 1,
"name": "_CONSOLE_INFORMATION.ScreenY",
"address": console_info.ScreenY.vol.offset,
"data": console_info.ScreenY,
}
)
console_properties.append(
{
"level": 1,
"name": "_CONSOLE_INFORMATION.CommandHistorySize",
"address": console_info.CommandHistorySize.vol.offset,
"data": console_info.CommandHistorySize,
}
)
console_properties.append(
{
"level": 1,
"name": "_CONSOLE_INFORMATION.HistoryBufferCount",
"address": console_info.HistoryBufferCount.vol.offset,
"data": console_info.HistoryBufferCount,
}
)
console_properties.append(
{
"level": 1,
"name": "_CONSOLE_INFORMATION.HistoryBufferMax",
"address": console_info.HistoryBufferMax.vol.offset,
"data": console_info.HistoryBufferMax,
}
)
console_properties.append(
{
"level": 1,
"name": "_CONSOLE_INFORMATION.Title",
"address": console_info.Title.vol.offset,
"data": console_info.get_title(),
}
)
console_properties.append(
{
"level": 1,
"name": "_CONSOLE_INFORMATION.OriginalTitle",
"address": console_info.OriginalTitle.vol.offset,
"data": console_info.get_original_title(),
}
)
vollog.debug(
f"Getting ConsoleProcessList entries for {console_info.ConsoleProcessList}"
)
console_properties.append(
{
"level": 1,
"name": "_CONSOLE_INFORMATION.ProcessCount",
"address": console_info.ProcessCount.vol.offset,
"data": console_info.ProcessCount,
}
)
console_properties.append(
{
"level": 1,
"name": "_CONSOLE_INFORMATION.ConsoleProcessList",
"address": console_info.ConsoleProcessList.vol.offset,
"data": "",
}
)
for index, attached_proc in enumerate(
console_info.get_processes()
):
console_properties.append(
{
"level": 2,
"name": f"_CONSOLE_INFORMATION.ConsoleProcessList.ConsoleProcess_{index}",
"address": attached_proc.ConsoleProcess.dereference().vol.offset,
"data": "",
}
)
console_properties.append(
{
"level": 2,
"name": f"_CONSOLE_INFORMATION.ConsoleProcessList.ConsoleProcess_{index}_ProcessId",
"address": attached_proc.ConsoleProcess.ProcessId.vol.offset,
"data": attached_proc.ConsoleProcess.ProcessId,
}
)
console_properties.append(
{
"level": 2,
"name": f"_CONSOLE_INFORMATION.ConsoleProcessList.ConsoleProcess_{index}_ProcessHandle",
"address": attached_proc.ConsoleProcess.ProcessHandle.vol.offset,
"data": hex(
attached_proc.ConsoleProcess.ProcessHandle
),
}
)
vollog.debug(
f"Getting ExeAliasList entries for {console_info.ExeAliasList}"
)
console_properties.append(
{
"level": 1,
"name": "_CONSOLE_INFORMATION.ExeAliasList",
"address": console_info.ExeAliasList.vol.offset,
"data": "",
}
)
if console_info.ExeAliasList:
for index, exe_alias_list in enumerate(
console_info.get_exe_aliases()
):
try:
console_properties.append(
{
"level": 2,
"name": f"_CONSOLE_INFORMATION.ExeAliasList.AliasList_{index}",
"address": exe_alias_list.vol.offset,
"data": "",
}
)
console_properties.append(
{
"level": 2,
"name": f"_CONSOLE_INFORMATION.ExeAliasList.AliasList_{index}.ExeName",
"address": exe_alias_list.ExeName.vol.offset,
"data": exe_alias_list.get_exename(),
}
)
for alias_index, alias in enumerate(
exe_alias_list.get_aliases()
):
console_properties.append(
{
"level": 3,
"name": f"_CONSOLE_INFORMATION.ExeAliasList.AliasList_{index}.Alias_{alias_index}.Source",
"address": alias.Source.vol.offset,
"data": alias.get_source(),
}
)
console_properties.append(
{
"level": 3,
"name": f"_CONSOLE_INFORMATION.ExeAliasList.AliasList_{index}.Alias_{alias_index}.Target",
"address": alias.Target.vol.offset,
"data": alias.get_target(),
}
)
except Exception as e:
vollog.debug(
f"reading {exe_alias_list} encountered exception {e}"
)
vollog.debug(
f"Getting HistoryList entries for {console_info.HistoryList}"
)
console_properties.append(
{
"level": 1,
"name": "_CONSOLE_INFORMATION.HistoryList",
"address": console_info.HistoryList.vol.offset,
"data": "",
}
)
for index, command_history in enumerate(
console_info.get_histories()
):
try:
console_properties.append(
{
"level": 2,
"name": f"_CONSOLE_INFORMATION.HistoryList.CommandHistory_{index}",
"address": command_history.vol.offset,
"data": "",
}
)
console_properties.append(
{
"level": 2,
"name": f"_CONSOLE_INFORMATION.HistoryList.CommandHistory_{index}_Application",
"address": command_history.Application.vol.offset,
"data": command_history.get_application(),
}
)
console_properties.append(
{
"level": 2,
"name": f"_CONSOLE_INFORMATION.HistoryList.CommandHistory_{index}_ProcessHandle",
"address": command_history.ConsoleProcessHandle.ProcessHandle.vol.offset,
"data": hex(
command_history.ConsoleProcessHandle.ProcessHandle
),
}
)
console_properties.append(
{
"level": 2,
"name": f"_CONSOLE_INFORMATION.HistoryList.CommandHistory_{index}_CommandCount",
"address": None,
"data": command_history.CommandCount,
}
)
console_properties.append(
{
"level": 2,
"name": f"_CONSOLE_INFORMATION.HistoryList.CommandHistory_{index}_LastDisplayed",
"address": command_history.LastDisplayed.vol.offset,
"data": command_history.LastDisplayed,
}
)
for (
cmd_index,
bucket_cmd,
) in command_history.get_commands():
try:
console_properties.append(
{
"level": 3,
"name": f"_CONSOLE_INFORMATION.HistoryList.CommandHistory_{index}_Command_{cmd_index}",
"address": bucket_cmd.vol.offset,
"data": bucket_cmd.get_command_string(),
}
)
except Exception as e:
vollog.debug(
f"reading {bucket_cmd} encountered exception {e}"
)
except Exception as e:
vollog.debug(
f"reading {command_history} encountered exception {e}"
)
try:
vollog.debug(
f"Getting ScreenBuffer entries for {console_info}"
)
console_properties.append(
{
"level": 1,
"name": "_CONSOLE_INFORMATION.CurrentScreenBuffer",
"address": console_info.CurrentScreenBuffer.vol.offset,
"data": "",
}
)
for screen_index, screen_info in enumerate(
console_info.get_screens()
):
try:
console_properties.append(
{
"level": 2,
"name": f"_CONSOLE_INFORMATION.ScreenBuffer_{screen_index}",
"address": screen_info,
"data": "",
}
)
console_properties.append(
{
"level": 2,
"name": f"_CONSOLE_INFORMATION.ScreenBuffer_{screen_index}.ScreenX",
"address": None,
"data": screen_info.ScreenX,
}
)
console_properties.append(
{
"level": 2,
"name": f"_CONSOLE_INFORMATION.ScreenBuffer_{screen_index}.ScreenY",
"address": None,
"data": screen_info.ScreenY,
}
)
console_properties.append(
{
"level": 2,
"name": f"_CONSOLE_INFORMATION.ScreenBuffer_{screen_index}.Dump",
"address": None,
"data": "\n".join(screen_info.get_buffer()),
}
)
except Exception as e:
vollog.debug(
f"reading {screen_info} encountered exception {e}"
)
except Exception as e:
vollog.debug(
f"reading _CONSOLE_INFORMATION.CurrentScreenBuffer encountered exception {e}"
)
except exceptions.PagedInvalidAddressException as exp:
vollog.debug(
f"Required memory at {exp.invalid_address:#x} is not valid"
)
continue
if console_info and console_properties:
found_console_info_for_proc = True
yield conhost_proc, console_info, console_properties
if not found_console_info_for_proc:
yield conhost_proc, console_info or None, []
@classmethod
def get_console_settings_from_registry(
cls,
context: interfaces.context.ContextInterface,
config_path: str,
kernel_layer_name: str,
kernel_symbol_table_name: str,
max_history: Set[int],
max_buffers: Set[int],
) -> Tuple[Set[int], Set[int]]:
"""
Walks the Registry user hives and extracts any CommandHistorySize and HistoryBufferMax values
for scanning
Args:
context: The context to retrieve required elements (layers, symbol tables) from
config_path: The config path where to find symbol files
kernel_layer_name: The name of the layer on which to operate
kernel_symbol_table_name: The name of the table containing the kernel symbols
max_history: an initial set of CommandHistorySize values
max_buffers: an initial list of HistoryBufferMax values
Returns:
The updated max_history and max_buffers sets.
"""
vollog.debug(
f"Possible CommandHistorySize values before checking Registry: {max_history}"
)
vollog.debug(
f"Possible HistoryBufferMax values before checking Registry: {max_buffers}"
)
for hive in hivelist.HiveList.list_hives(
context=context,
base_config_path=config_path,
layer_name=kernel_layer_name,
symbol_table=kernel_symbol_table_name,
hive_offsets=None,
):
try:
for value in hive.get_key("Console").get_values():
val_name = str(value.get_name())
if val_name == "HistoryBufferSize":
max_history.add(value.decode_data())
elif val_name == "NumberOfHistoryBuffers":
max_buffers.add(value.decode_data())
except Exception:
continue
return max_history, max_buffers
def _generator(
self, procs: Generator[interfaces.objects.ObjectInterface, None, None]
):
"""
Generates the console information to use in rendering
Args:
procs: the process list filtered to conhost.exe instances
"""
kernel = self.context.modules[self.config["kernel"]]
max_history = set(self.config.get("max_history", [50]))
max_buffers = set(self.config.get("max_buffers", [4]))
no_registry = self.config.get("no_registry")
if no_registry is False:
max_history, max_buffers = self.get_console_settings_from_registry(
self.context,
self.config_path,
kernel.layer_name,
kernel.symbol_table_name,
max_history,
max_buffers,
)
vollog.debug(f"Possible CommandHistorySize values: {max_history}")
vollog.debug(f"Possible HistoryBufferMax values: {max_buffers}")
proc = None
for proc, console_info, console_properties in self.get_console_info(
self.context,
kernel.layer_name,
kernel.symbol_table_name,
self.config_path,
procs,
max_history,
max_buffers,
):
process_name = utility.array_to_string(proc.ImageFileName)
process_pid = proc.UniqueProcessId
if console_info and console_properties:
for console_property in console_properties:
yield (
console_property["level"],
(
process_pid,
process_name,
format_hints.Hex(console_info.vol.offset),
console_property["name"],
(
renderers.NotApplicableValue()
if console_property["address"] is None
else format_hints.Hex(console_property["address"])
),
(
str(console_property["data"])
if console_property["data"]
else renderers.NotAvailableValue()
),
),
)
else:
yield (
0,
(
process_pid,
process_name,
(
format_hints.Hex(console_info.vol.offset)
if console_info
else renderers.NotApplicableValue()
),
"_CONSOLE_INFORMATION",
renderers.NotApplicableValue(),
"Console Information Not Found",
),
)
if proc is None:
vollog.warn("No conhost.exe processes found.")
def _conhost_proc_filter(self, proc: interfaces.objects.ObjectInterface) -> bool:
"""
Used to filter to only conhost.exe processes
"""
process_name = utility.array_to_string(proc.ImageFileName)
return process_name.lower() != "conhost.exe"
def run(self):
kernel = self.context.modules[self.config["kernel"]]
return renderers.TreeGrid(
[
("PID", int),
("Process", str),
("ConsoleInfo", format_hints.Hex),
("Property", str),
("Address", format_hints.Hex),
("Data", str),
],
self._generator(
pslist.PsList.list_processes(
context=self.context,
layer_name=kernel.layer_name,
symbol_table=kernel.symbol_table_name,
filter_func=self._conhost_proc_filter,
)
),
)
@@ -0,0 +1,211 @@
# This file is Copyright 2024 Volatility Foundation and licensed under the Volatility Software License 1.0
# which is available at https://www.volatilityfoundation.org/license/vsl-v1.0
# Full details on the techniques used in these plugins to detect EDR-evading malware
# can be found in our 20 page whitepaper submitted to DEFCON along with the presentation
# https://www.volexity.com/wp-content/uploads/2024/08/Defcon24_EDR_Evasion_Detection_White-Paper_Andrew-Case.pdf
import logging
from typing import Tuple, Optional, Generator, List, Dict
from functools import partial
from volatility3.framework import renderers, interfaces, exceptions
from volatility3.framework.configuration import requirements
from volatility3.framework.renderers import format_hints
import volatility3.plugins.windows.pslist as pslist
import volatility3.plugins.windows.threads as threads
import volatility3.plugins.windows.pe_symbols as pe_symbols
vollog = logging.getLogger(__name__)
class DebugRegisters(interfaces.plugins.PluginInterface):
# version 2.6.0 adds support for scanning for 'Ethread' structures by pool tags
_required_framework_version = (2, 6, 0)
_version = (1, 0, 0)
@classmethod
def get_requirements(cls) -> List:
return [
requirements.ModuleRequirement(
name="kernel",
description="Windows kernel",
architectures=["Intel32", "Intel64"],
),
requirements.VersionRequirement(
name="pslist", component=pslist.PsList, version=(2, 0, 0)
),
requirements.VersionRequirement(
name="pe_symbols", component=pe_symbols.PESymbols, version=(1, 0, 0)
),
]
@staticmethod
def _get_debug_info(
ethread: interfaces.objects.ObjectInterface,
) -> Optional[Tuple[interfaces.objects.ObjectInterface, int, int, int, int, int]]:
"""
Gathers information related to the debug registers for the given thread
Args:
ethread: the thread (_ETHREAD) to examine
Returns:
Tuple[interfaces.objects.ObjectInterface, int, int, int, int, int]: The owner process of the thread and the values for dr7, dr0, dr1, dr2, dr3
"""
try:
dr7 = ethread.Tcb.TrapFrame.Dr7
state = ethread.Tcb.State
except exceptions.InvalidAddressException:
return None
# 0 = debug registers not active
# 4 = terminated
if dr7 == 0 or state == 4:
return None
try:
owner_proc = ethread.owning_process()
except (AttributeError, exceptions.InvalidAddressException):
return None
dr0 = ethread.Tcb.TrapFrame.Dr0
dr1 = ethread.Tcb.TrapFrame.Dr1
dr2 = ethread.Tcb.TrapFrame.Dr2
dr3 = ethread.Tcb.TrapFrame.Dr3
# bail if all are 0
if not (dr0 or dr1 or dr2 or dr3):
return None
return owner_proc, dr7, dr0, dr1, dr2, dr3
def _generator(
self,
) -> Generator[
Tuple[
int,
Tuple[
str,
int,
int,
int,
int,
format_hints.Hex,
str,
str,
format_hints.Hex,
str,
str,
format_hints.Hex,
str,
str,
format_hints.Hex,
str,
str,
],
],
None,
None,
]:
kernel = self.context.modules[self.config["kernel"]]
vads_cache: Dict[int, pe_symbols.ranges_type] = {}
proc_modules = None
procs = pslist.PsList.list_processes(
context=self.context,
layer_name=kernel.layer_name,
symbol_table=kernel.symbol_table_name,
)
for proc in procs:
for thread in threads.Threads.list_threads(kernel, proc):
debug_info = self._get_debug_info(thread)
if not debug_info:
continue
owner_proc, dr7, dr0, dr1, dr2, dr3 = debug_info
vads = pe_symbols.PESymbols.get_vads_for_process_cache(
vads_cache, owner_proc
)
if not vads:
continue
# this lookup takes a while, so only perform if we need to
if not proc_modules:
proc_modules = pe_symbols.PESymbols.get_process_modules(
self.context, kernel.layer_name, kernel.symbol_table_name, None
)
path_and_symbol = partial(
pe_symbols.PESymbols.path_and_symbol_for_address,
self.context,
self.config_path,
proc_modules,
)
file0, sym0 = path_and_symbol(vads, dr0)
file1, sym1 = path_and_symbol(vads, dr1)
file2, sym2 = path_and_symbol(vads, dr2)
file3, sym3 = path_and_symbol(vads, dr3)
# if none map to an actual file VAD then bail
if not (file0 or file1 or file2 or file3):
continue
process_name = owner_proc.ImageFileName.cast(
"string",
max_length=owner_proc.ImageFileName.vol.count,
errors="replace",
)
thread_tid = thread.Cid.UniqueThread
yield (
0,
(
process_name,
owner_proc.UniqueProcessId,
thread_tid,
thread.Tcb.State,
dr7,
format_hints.Hex(dr0),
file0 or renderers.NotApplicableValue(),
sym0 or renderers.NotApplicableValue(),
format_hints.Hex(dr1),
file1 or renderers.NotApplicableValue(),
sym1 or renderers.NotApplicableValue(),
format_hints.Hex(dr2),
file2 or renderers.NotApplicableValue(),
sym2 or renderers.NotApplicableValue(),
format_hints.Hex(dr3),
file3 or renderers.NotApplicableValue(),
sym3 or renderers.NotApplicableValue(),
),
)
def run(self) -> renderers.TreeGrid:
return renderers.TreeGrid(
[
("Process", str),
("PID", int),
("TID", int),
("State", int),
("Dr7", int),
("Dr0", format_hints.Hex),
("Range0", str),
("Symbol0", str),
("Dr1", format_hints.Hex),
("Range1", str),
("Symbol1", str),
("Dr2", format_hints.Hex),
("Range2", str),
("Symbol2", str),
("Dr3", format_hints.Hex),
("Range3", str),
("Symbol3", str),
],
self._generator(),
)
@@ -122,10 +122,30 @@ class Malfind(interfaces.plugins.PluginInterface):
vadinfo.winnt_protections,
)
write_exec = "EXECUTE" in protection_string and "WRITE" in protection_string
dirty_page_check = False
# the write/exec check applies to everything
if not write_exec:
continue
"""
# Inspect "PAGE_EXECUTE_READ" VAD pages to detect
# non writable memory regions having been injected
# using elevated WriteProcessMemory().
"""
if "EXECUTE" in protection_string:
for page in range(
vad.get_start(), vad.get_end(), proc_layer.page_size
):
try:
# If we have a dirty page in a non writable "EXECUTE" region, it is suspicious.
if proc_layer.is_dirty(page):
dirty_page_check = True
break
except exceptions.InvalidAddressException:
# Abort as it is likely that other addresses in the same range will also fail.
break
if not dirty_page_check:
continue
else:
continue
if (vad.get_private_memory() == 1 and vad.get_tag() == "VadS") or (
vad.get_private_memory() == 0
@@ -134,6 +154,11 @@ class Malfind(interfaces.plugins.PluginInterface):
if cls.is_vad_empty(proc_layer, vad):
continue
if dirty_page_check:
# Useful information to investigate the page content with volshell afterwards.
vollog.warning(
f"[proc_id {proc_id}] Found suspicious DIRTY + {protection_string} page at {hex(page)}",
)
data = proc_layer.read(vad.get_start(), 64, pad=True)
yield vad, data
@@ -29,7 +29,7 @@ class MFTScan(interfaces.plugins.PluginInterface, timeliner.TimeLinerInterface):
architectures=["Intel32", "Intel64"],
),
requirements.VersionRequirement(
name="yarascanner", component=yarascan.YaraScanner, version=(2, 0, 0)
name="yarascanner", component=yarascan.YaraScanner, version=(2, 1, 0)
),
]
@@ -38,7 +38,7 @@ class MFTScan(interfaces.plugins.PluginInterface, timeliner.TimeLinerInterface):
# Yara Rule to scan for MFT Header Signatures
rules = yarascan.YaraScan.process_yara_options(
{"yara_rules": "/FILE0|FILE\\*|BAAD/"}
{"yara_string": "/FILE0|FILE\\*|BAAD/"}
)
# Read in the Symbol File
@@ -197,7 +197,7 @@ class ADS(interfaces.plugins.PluginInterface):
# Yara Rule to scan for MFT Header Signatures
rules = yarascan.YaraScan.process_yara_options(
{"yara_rules": "/FILE0|FILE\\*|BAAD/"}
{"yara_string": "/FILE0|FILE\\*|BAAD/"}
)
# Read in the Symbol File
@@ -76,7 +76,7 @@ class NetScan(interfaces.plugins.PluginInterface, timeliner.TimeLinerInterface):
# ~ vollog.debug("Using pool size constraints: TcpL {}, TcpE {}, UdpA {}".format(tcpl_size, tcpe_size, udpa_size))
return [
constraints = [
# TCP listener
poolscanner.PoolConstraint(
b"TcpL",
@@ -100,6 +100,19 @@ class NetScan(interfaces.plugins.PluginInterface, timeliner.TimeLinerInterface):
),
]
if symbol_table.startswith("netscan-win10-20348"):
vollog.debug("Adding additional pool constraint for `TTcb` tags")
constraints.append(
poolscanner.PoolConstraint(
b"TTcb",
type_name=symbol_table + constants.BANG + "_TCP_ENDPOINT",
size=(tcpe_size, None),
page_type=poolscanner.PoolType.NONPAGED | poolscanner.PoolType.FREE,
)
)
return constraints
@classmethod
def determine_tcpip_version(
cls,
@@ -0,0 +1,96 @@
# This file is Copyright 2024 Volatility Foundation and licensed under the Volatility Software License 1.0
# which is available at https://www.volatilityfoundation.org/license/vsl-v1.0
#
import logging
from typing import List, Generator
from volatility3.framework import interfaces, symbols
from volatility3.framework.configuration import requirements
from volatility3.plugins.windows import thrdscan, ssdt
vollog = logging.getLogger(__name__)
class Threads(thrdscan.ThrdScan):
"""Lists process threads"""
_required_framework_version = (2, 4, 0)
_version = (1, 0, 0)
def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
self.implementation = self.list_orphan_kernel_threads
@classmethod
def get_requirements(cls) -> List[interfaces.configuration.RequirementInterface]:
# Since we're calling the plugin, make sure we have the plugin's requirements
return [
requirements.ModuleRequirement(
name="kernel",
description="Windows kernel",
architectures=["Intel32", "Intel64"],
),
requirements.PluginRequirement(
name="thrdscan", plugin=thrdscan.ThrdScan, version=(1, 1, 0)
),
requirements.PluginRequirement(
name="ssdt", plugin=ssdt.SSDT, version=(1, 0, 0)
),
]
@classmethod
def list_orphan_kernel_threads(
cls,
context: interfaces.context.ContextInterface,
module_name: str,
) -> Generator[interfaces.objects.ObjectInterface, None, None]:
"""Yields thread objects of kernel threads that do not map to a module
Args:
cls
context: the context to operate upon
module_name: name of the module to use for scanning
Returns:
A generator of thread objects of orphaned threads
"""
module = context.modules[module_name]
layer_name = module.layer_name
symbol_table = module.symbol_table_name
collection = ssdt.SSDT.build_module_collection(
context, layer_name, symbol_table
)
# FIXME - use a proper constant once established
# used to filter out smeared pointers
if symbols.symbol_table_is_64bit(context, symbol_table):
kernel_start = 0xFFFFF80000000000
else:
kernel_start = 0x80000000
for thread in thrdscan.ThrdScan.scan_threads(context, module_name):
# we don't want smeared or terminated threads
try:
proc = thread.owning_process()
except AttributeError:
continue
# we only care about kernel threads, 4 = System
# previous methods for determining if a thread was a kernel thread
# such as bit fields and flags are not stable in Win10+
# so we check if the thread is from the kernel itself or one its child
# kernel processes (MemCompression, Regsitry, ...)
if proc.UniqueProcessId != 4 and proc.InheritedFromUniqueProcessId != 4:
continue
if thread.StartAddress < kernel_start:
continue
module_symbols = list(
collection.get_module_symbols_by_absolute_location(thread.StartAddress)
)
# alert on threads that do not map to a module
if not module_symbols:
yield thread
File diff suppressed because it is too large Load Diff
@@ -4,7 +4,7 @@
import datetime
import logging
from typing import Callable, Iterable, List, Type
from typing import Callable, Iterator, List, Type
from volatility3.framework import renderers, interfaces, layers, exceptions, constants
from volatility3.framework.configuration import requirements
@@ -12,6 +12,7 @@ from volatility3.framework.objects import utility
from volatility3.framework.renderers import format_hints
from volatility3.framework.symbols import intermed
from volatility3.framework.symbols.windows.extensions import pe
from volatility3.framework.symbols.windows import extensions
from volatility3.plugins import timeliner
vollog = logging.getLogger(__name__)
@@ -197,7 +198,7 @@ class PsList(interfaces.plugins.PluginInterface, timeliner.TimeLinerInterface):
filter_func: Callable[
[interfaces.objects.ObjectInterface], bool
] = lambda _: False,
) -> Iterable[interfaces.objects.ObjectInterface]:
) -> Iterator["extensions.EPROCESS"]:
"""Lists all the processes in the primary layer that are in the pid
config option.
@@ -1,9 +1,14 @@
import datetime, logging, string
import datetime
import logging
import string
from itertools import chain
from typing import Dict, Iterable, List
from volatility3.framework import constants, exceptions
from volatility3.framework.interfaces import plugins
from volatility3.framework.configuration import requirements
from volatility3.framework.renderers import format_hints, TreeGrid
from volatility3.framework.interfaces import plugins
from volatility3.framework.renderers import TreeGrid, format_hints
from volatility3.framework.symbols.windows import extensions
from volatility3.plugins.windows import (
handles,
info,
@@ -71,20 +76,19 @@ class PsXView(plugins.PluginInterface):
"string", max_length=proc.ImageFileName.vol.count, errors="replace"
)
def _is_valid_proc_name(self, str):
for c in str:
if not c in self.valid_proc_name_chars:
return False
return True
def _is_valid_proc_name(self, string: str) -> bool:
return all(c in self.valid_proc_name_chars for c in string)
def _filter_garbage_procs(self, proc_list):
def _filter_garbage_procs(
self, proc_list: Iterable[extensions.EPROCESS]
) -> List[extensions.EPROCESS]:
return [
p
for p in proc_list
if p.is_valid() and self._is_valid_proc_name(self._proc_name_to_string(p))
]
def _translate_offset(self, offset):
def _translate_offset(self, offset: int) -> int:
if not self.config["physical-offsets"]:
return offset
@@ -100,21 +104,25 @@ class PsXView(plugins.PluginInterface):
return offset
def _proc_list_to_dict(self, tasks):
def _proc_list_to_dict(
self, tasks: Iterable[extensions.EPROCESS]
) -> Dict[int, extensions.EPROCESS]:
tasks = self._filter_garbage_procs(tasks)
return {self._translate_offset(proc.vol.offset): proc for proc in tasks}
def _check_pslist(self, tasks):
return self._proc_list_to_dict(tasks)
def _check_psscan(self, layer_name, symbol_table):
def _check_psscan(
self, layer_name: str, symbol_table: str
) -> Dict[int, extensions.EPROCESS]:
res = psscan.PsScan.scan_processes(
context=self.context, layer_name=layer_name, symbol_table=symbol_table
)
return self._proc_list_to_dict(res)
def _check_thrdscan(self):
def _check_thrdscan(self) -> Dict[int, extensions.EPROCESS]:
ret = []
for ethread in thrdscan.ThrdScan.scan_threads(
@@ -135,33 +143,38 @@ class PsXView(plugins.PluginInterface):
return self._proc_list_to_dict(ret)
def _check_csrss_handles(self, tasks, layer_name, symbol_table):
ret = []
def _check_csrss_handles(
self, tasks: Iterable[extensions.EPROCESS], layer_name: str, symbol_table: str
) -> Dict[int, extensions.EPROCESS]:
ret: List[extensions.EPROCESS] = []
handles_plugin = handles.Handles(
context=self.context, config_path=self.config_path
)
type_map = handles_plugin.get_type_map(self.context, layer_name, symbol_table)
cookie = handles_plugin.find_cookie(
context=self.context,
layer_name=layer_name,
symbol_table=symbol_table,
)
for p in tasks:
name = self._proc_name_to_string(p)
if name == "csrss.exe":
try:
if p.has_member("ObjectTable"):
handles_plugin = handles.Handles(
context=self.context, config_path=self.config_path
)
hndls = list(handles_plugin.handles(p.ObjectTable))
for h in hndls:
if (
h.get_object_type(
handles_plugin.get_type_map(
self.context, layer_name, symbol_table
)
)
== "Process"
):
ret.append(h.Body.cast("_EPROCESS"))
if name != "csrss.exe":
continue
except exceptions.InvalidAddressException:
vollog.log(
constants.LOGLEVEL_VVV, "Cannot access eprocess object table"
)
try:
ret += [
handle.Body.cast("_EPROCESS")
for handle in handles_plugin.handles(p.ObjectTable)
if handle.get_object_type(type_map, cookie) == "Process"
]
except exceptions.InvalidAddressException:
vollog.log(
constants.LOGLEVEL_VVV, "Cannot access eprocess object table"
)
return self._proc_list_to_dict(ret)
@@ -178,7 +191,7 @@ class PsXView(plugins.PluginInterface):
)
# get processes from each source
processes = {}
processes: Dict[str, Dict[int, extensions.EPROCESS]] = {}
processes["pslist"] = self._check_pslist(kdbg_list_processes)
processes["psscan"] = self._check_psscan(layer_name, symbol_table)
@@ -187,27 +200,20 @@ class PsXView(plugins.PluginInterface):
kdbg_list_processes, layer_name, symbol_table
)
# print results
# list of lists of offsets
offsets = [list(processes[source].keys()) for source in processes]
# flatten to one list
offsets = sum(offsets, [])
# remove duplicates
offsets = set(offsets)
# Unique set of all offsets from all sources
offsets = set(chain(*(mapping.keys() for mapping in processes.values())))
for offset in offsets:
proc = None
# We know there will be at least one process mapped to each offset
proc: extensions.EPROCESS = next(
mapping[offset] for mapping in processes.values() if offset in mapping
)
in_sources = {src: False for src in processes}
for source in processes:
if offset in processes[source]:
for source, process_mapping in processes.items():
if offset in process_mapping:
in_sources[source] = True
if not proc:
proc = processes[source][offset]
pid = proc.UniqueProcessId
name = self._proc_name_to_string(proc)
@@ -2,9 +2,9 @@
# which is available at https://www.volatilityfoundation.org/license/vsl-v1.0
#
import logging
from typing import Iterator, List, Tuple, Iterable, Optional
from typing import Iterator, List, Optional, Tuple
from volatility3.framework import renderers, interfaces, exceptions
from volatility3.framework import exceptions, interfaces, renderers
from volatility3.framework.configuration import requirements
from volatility3.framework.layers import registry
from volatility3.framework.renderers import format_hints
@@ -140,8 +140,8 @@ class HiveList(interfaces.plugins.PluginInterface):
layer_name: str,
symbol_table: str,
filter_string: Optional[str] = None,
hive_offsets: List[int] = None,
) -> Iterable[registry.RegistryHive]:
hive_offsets: Optional[List[int]] = None,
) -> Iterator[registry.RegistryHive]:
"""Walks through a registry, hive by hive returning the constructed
registry layer name.
@@ -200,7 +200,7 @@ class HiveList(interfaces.plugins.PluginInterface):
context: interfaces.context.ContextInterface,
layer_name: str,
symbol_table: str,
filter_string: str = None,
filter_string: Optional[str] = None,
) -> Iterator[interfaces.objects.ObjectInterface]:
"""Lists all the hives in the primary layer.
@@ -20,7 +20,7 @@ class PrintKey(interfaces.plugins.PluginInterface):
"""Lists the registry keys under a hive or specific key value."""
_required_framework_version = (2, 0, 0)
_version = (1, 0, 0)
_version = (1, 1, 0)
@classmethod
def get_requirements(cls) -> List[interfaces.configuration.RequirementInterface]:
@@ -282,7 +282,7 @@ class PrintKey(interfaces.plugins.PluginInterface):
renderers.UnreadableValue(),
format_hints.Hex(hive.hive_offset),
"Key",
"?\\" + (key or ""),
f"{hive.get_name()}\\" + (key or ""),
renderers.UnreadableValue(),
renderers.UnreadableValue(),
renderers.UnreadableValue(),
File diff suppressed because it is too large Load Diff
@@ -30,7 +30,7 @@ class SSDT(plugins.PluginInterface):
architectures=["Intel32", "Intel64"],
),
requirements.PluginRequirement(
name="modules", plugin=modules.Modules, version=(1, 0, 0)
name="modules", plugin=modules.Modules, version=(2, 0, 0)
),
]
@@ -13,11 +13,11 @@ from volatility3.plugins.windows import pslist, threads, vadinfo, thrdscan
vollog = logging.getLogger(__name__)
class SupsiciousThreads(interfaces.plugins.PluginInterface):
class SuspiciousThreads(interfaces.plugins.PluginInterface):
"""Lists suspicious userland process threads"""
_required_framework_version = (2, 4, 0)
_version = (2, 0, 0)
_version = (2, 0, 1)
@classmethod
def get_requirements(cls) -> List[interfaces.configuration.RequirementInterface]:
@@ -20,7 +20,7 @@ from volatility3.framework.renderers import format_hints
from volatility3.framework.symbols import intermed
from volatility3.framework.symbols.windows import versions
from volatility3.framework.symbols.windows.extensions import services as services_types
from volatility3.plugins.windows import poolscanner, pslist, vadyarascan
from volatility3.plugins.windows import poolscanner, pslist
from volatility3.plugins.windows.registry import hivelist
vollog = logging.getLogger(__name__)
@@ -39,7 +39,7 @@ class SvcScan(interfaces.plugins.PluginInterface):
"""Scans for windows services."""
_required_framework_version = (2, 0, 0)
_version = (3, 0, 0)
_version = (3, 0, 1)
def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
@@ -60,9 +60,6 @@ class SvcScan(interfaces.plugins.PluginInterface):
requirements.PluginRequirement(
name="poolscanner", plugin=poolscanner.PoolScanner, version=(1, 0, 0)
),
requirements.PluginRequirement(
name="vadyarascan", plugin=vadyarascan.VadYaraScan, version=(1, 0, 0)
),
requirements.PluginRequirement(
name="hivelist", plugin=hivelist.HiveList, version=(1, 0, 0)
),
@@ -317,10 +314,17 @@ class SvcScan(interfaces.plugins.PluginInterface):
layer = context.layers[proc_layer_name]
# get process sections for scanning
sections = []
for vad in task.get_vad_root().traverse():
base = vad.get_start()
if vad.get_size():
sections.append((base, vad.get_size()))
for offset in layer.scan(
context=context,
scanner=scanners.BytesScanner(needle=service_tag),
sections=vadyarascan.VadYaraScan.get_vad_maps(task),
sections=sections,
):
if not is_vista_or_later:
service_record = context.object(
@@ -22,8 +22,8 @@ class ThrdScan(interfaces.plugins.PluginInterface, timeliner.TimeLinerInterface)
_version = (1, 1, 0)
def __init__(self, *args, **kwargs):
self.implementation = self.scan_threads
super().__init__(*args, **kwargs)
self.implementation = self.scan_threads
@classmethod
def get_requirements(cls):
@@ -48,8 +48,7 @@ class ThrdScan(interfaces.plugins.PluginInterface, timeliner.TimeLinerInterface)
Args:
context: The context to retrieve required elements (layers, symbol tables) from
layer_name: The name of the layer on which to operate
symbol_table: The name of the table containing the kernel symbols
module_name: Name of the module to use for scanning
Returns:
A list of _ETHREAD objects found by scanning memory for the "Thre" / "Thr\\xE5" pool signatures
@@ -19,8 +19,8 @@ class Threads(thrdscan.ThrdScan):
_version = (1, 0, 0)
def __init__(self, *args, **kwargs):
self.implementation = self.list_process_threads
super().__init__(*args, **kwargs)
self.implementation = self.list_process_threads
@classmethod
def get_requirements(cls) -> List[interfaces.configuration.RequirementInterface]:
@@ -50,7 +50,6 @@ class Threads(thrdscan.ThrdScan):
Args:
proc: _EPROCESS object from which to list the VADs
filter_func: Function to take a virtual address descriptor value and return True if it should be filtered out
Returns:
A list of threads based on the process and filtered based on the filter function
@@ -64,22 +63,19 @@ class Threads(thrdscan.ThrdScan):
seen.add(thread.vol.offset)
yield thread
@classmethod
def filter_func(cls, config: interfaces.configuration.HierarchicalDict) -> Callable:
return pslist.PsList.create_pid_filter(config.get("pid", None))
@classmethod
def list_process_threads(
cls,
context: interfaces.context.ContextInterface,
module_name: str,
filter_func: Callable,
) -> Iterable[interfaces.objects.ObjectInterface]:
"""Runs through all processes and lists threads for each process"""
module = context.modules[module_name]
layer_name = module.layer_name
symbol_table_name = module.symbol_table_name
filter_func = pslist.PsList.create_pid_filter(context.config.get("pid", None))
for proc in pslist.PsList.list_processes(
context=context,
layer_name=layer_name,
@@ -0,0 +1,208 @@
# This file is Copyright 2024 Volatility Foundation and licensed under the Volatility Software License 1.0
# which is available at https://www.volatilityfoundation.org/license/vsl-v1.0
# Full details on the techniques used in these plugins to detect EDR-evading malware
# can be found in our 20 page whitepaper submitted to DEFCON along with the presentation
# https://www.volexity.com/wp-content/uploads/2024/08/Defcon24_EDR_Evasion_Detection_White-Paper_Andrew-Case.pdf
import logging
from typing import Dict, Tuple, List, Generator
from volatility3.framework import interfaces, exceptions
from volatility3.framework import renderers
from volatility3.framework.configuration import requirements
from volatility3.framework.objects import utility
from volatility3.plugins.windows import pslist, pe_symbols
vollog = logging.getLogger(__name__)
class unhooked_system_calls(interfaces.plugins.PluginInterface):
"""Looks for signs of Skeleton Key malware"""
_required_framework_version = (2, 4, 0)
system_calls = {
"ntdll.dll": {
pe_symbols.wanted_names_identifier: [
"NtCreateThread",
"NtProtectVirtualMemory",
"NtReadVirtualMemory",
"NtOpenProcess",
"NtWriteFile",
"NtQueryVirtualMemory",
"NtAllocateVirtualMemory",
"NtWorkerFactoryWorkerReady",
"NtAcceptConnectPort",
"NtAddDriverEntry",
"NtAdjustPrivilegesToken",
"NtAlpcCreatePort",
"NtClose",
"NtCreateFile",
"NtCreateMutant",
"NtOpenFile",
"NtOpenIoCompletion",
"NtOpenJobObject",
"NtOpenKey",
"NtOpenKeyEx",
"NtOpenThread",
"NtOpenThreadToken",
"NtOpenThreadTokenEx",
"NtWriteVirtualMemory",
"NtTraceEvent",
"NtTranslateFilePath",
"NtUmsThreadYield",
"NtUnloadDriver",
"NtUnloadKey",
"NtUnloadKey2",
"NtUnloadKeyEx",
"NtCreateKey",
"NtCreateSection",
"NtDeleteKey",
"NtDeleteValueKey",
"NtDuplicateObject",
"NtQueryValueKey",
"NtReplaceKey",
"NtRequestWaitReplyPort",
"NtRestoreKey",
"NtSetContextThread",
"NtSetSecurityObject",
"NtSetValueKey",
"NtSystemDebugControl",
"NtTerminateProcess",
]
}
}
# This data structure is used to track unique implementations of functions across processes
# The outer dictionary holds the module name (e.g., ntdll.dll)
# The next dictionary holds the function names (NtTerminateProcess, NtSetValueKey, etc.) inside a module
# The innermost dictionary holds the unique implementation (bytes) of a function across processes
# Each implementation is tracked along with the process(es) that host it
# For systems without malware, all functions should have the same implementation
# When API hooking/module unhooking is done, the victim (infected) processes will have unique implementations
_code_bytes_type = Dict[str, Dict[str, Dict[bytes, List[Tuple[int, str]]]]]
@classmethod
def get_requirements(cls) -> List:
# Since we're calling the plugin, make sure we have the plugin's requirements
return [
requirements.ModuleRequirement(
name="kernel",
description="Windows kernel",
architectures=["Intel32", "Intel64"],
),
requirements.VersionRequirement(
name="pslist", component=pslist.PsList, version=(2, 0, 0)
),
requirements.PluginRequirement(
name="pe_symbols", plugin=pe_symbols.PESymbols, version=(1, 0, 0)
),
]
def _gather_code_bytes(
self,
kernel: interfaces.context.ModuleInterface,
found_symbols: pe_symbols.found_symbols_type,
) -> _code_bytes_type:
"""
Enumerates the desired DLLs and function implementations in each process
Groups based on unique implementations of each DLLs' functions
The purpose is to detect when a function has different implementations (code)
in different processes.
This very effectively detects code injection.
"""
code_bytes: unhooked_system_calls._code_bytes_type = {}
procs = pslist.PsList.list_processes(
context=self.context,
layer_name=kernel.layer_name,
symbol_table=kernel.symbol_table_name,
)
for proc in procs:
try:
proc_id = proc.UniqueProcessId
proc_name = utility.array_to_string(proc.ImageFileName)
proc_layer_name = proc.add_process_layer()
except exceptions.InvalidAddressException:
continue
for dll_name, functions in found_symbols.items():
for func_name, func_addr in functions:
try:
fbytes = self.context.layers[proc_layer_name].read(
func_addr, 0x20
)
except exceptions.InvalidAddressException:
continue
# see the definition of _code_bytes_type for details of this data structure
if dll_name not in code_bytes:
code_bytes[dll_name] = {}
if func_name not in code_bytes[dll_name]:
code_bytes[dll_name][func_name] = {}
if fbytes not in code_bytes[dll_name][func_name]:
code_bytes[dll_name][func_name][fbytes] = []
code_bytes[dll_name][func_name][fbytes].append((proc_id, proc_name))
return code_bytes
def _generator(self) -> Generator[Tuple[int, Tuple[str, str, int]], None, None]:
kernel = self.context.modules[self.config["kernel"]]
found_symbols = pe_symbols.PESymbols.addresses_for_process_symbols(
self.context,
self.config_path,
kernel.layer_name,
kernel.symbol_table_name,
unhooked_system_calls.system_calls,
)
# code_bytes[dll_name][func_name][func_bytes]
code_bytes = self._gather_code_bytes(kernel, found_symbols)
# walk the functions that were evaluated
for functions in code_bytes.values():
# cbb is the distinct groups of bytes (instructions)
# for this function across processes
for func_name, cbb in functions.items():
# the dict key here is the raw instructions, which is not helpful to look at
# the values are the list of tuples for the (proc_id, proc_name) pairs for this set of bytes (instructions)
cb = list(cbb.values())
# if all processes map to the same implementation, then no malware is present
if len(cb) == 1:
yield 0, (func_name, "", len(cb[0]))
else:
# if there are differing implementations then it means
# that malware has overwritten system call(s) in infected processes
# max_idx and small_idx find which implementation of a system call has the least processes
# as all observed malware and open source projects only infected a few targets, leaving the
# rest with the original EDR hooks in place
max_idx = 0 if len(cb[0]) > len(cb[1]) else 1
small_idx = (~max_idx) & 1
ps = []
# gather processes on small_idx since these are the malware infected ones
for pid, pname in cb[small_idx]:
ps.append("{:d}:{}".format(pid, pname))
proc_names = ", ".join(ps)
yield 0, (func_name, proc_names, len(cb[max_idx]))
def run(self) -> renderers.TreeGrid:
return renderers.TreeGrid(
[
("Function", str),
("Distinct Implementations", str),
("Total Implementations", int),
],
self._generator(),
)
@@ -7,7 +7,7 @@ import datetime
from typing import List, Iterable
from volatility3.framework import constants
from volatility3.framework import interfaces, symbols
from volatility3.framework import interfaces, symbols, exceptions
from volatility3.framework import renderers
from volatility3.framework.configuration import requirements
from volatility3.framework.interfaces import configuration
@@ -132,10 +132,15 @@ class UnloadedModules(interfaces.plugins.PluginInterface, timeliner.TimeLinerInt
kernel.symbol_table_name,
unloadedmodule_table_name,
):
try:
name = mod.Name.String
except exceptions.InvalidAddressException:
name = renderers.UnreadableValue()
yield (
0,
(
mod.Name.String,
name,
format_hints.Hex(mod.StartAddress),
format_hints.Hex(mod.EndAddress),
conversion.wintime_to_datetime(mod.CurrentTime),
@@ -3,7 +3,7 @@
#
import logging
from typing import Callable, List, Generator, Iterable, Type, Optional
from typing import Callable, List, Generator, Iterable, Type, Optional, Tuple
from volatility3.framework import renderers, interfaces, exceptions
from volatility3.framework.configuration import requirements
@@ -196,11 +196,31 @@ class VadInfo(interfaces.plugins.PluginInterface):
return file_handle
def _generator(self, procs):
def _generator(self, procs: List[interfaces.objects.ObjectInterface]) -> Generator[
Tuple[
int,
Tuple[
int,
str,
format_hints.Hex,
format_hints.Hex,
format_hints.Hex,
str,
str,
int,
int,
format_hints.Hex,
str,
str,
],
],
None,
None,
]:
kernel = self.context.modules[self.config["kernel"]]
kernel_layer = self.context.layers[kernel.layer_name]
def passthrough(_: interfaces.objects.ObjectInterface) -> bool:
def passthrough(x: interfaces.objects.ObjectInterface) -> bool:
return False
filter_func = passthrough
@@ -250,7 +270,7 @@ class VadInfo(interfaces.plugins.PluginInterface):
),
)
def run(self):
def run(self) -> renderers.TreeGrid:
kernel = self.context.modules[self.config["kernel"]]
filter_func = pslist.PsList.create_pid_filter(self.config.get("pid", None))
@@ -18,7 +18,7 @@ class VadYaraScan(interfaces.plugins.PluginInterface):
"""Scans all the Virtual Address Descriptor memory maps using yara."""
_required_framework_version = (2, 4, 0)
_version = (1, 1, 0)
_version = (1, 1, 1)
@classmethod
def get_requirements(cls) -> List[interfaces.configuration.RequirementInterface]:
@@ -32,8 +32,11 @@ class VadYaraScan(interfaces.plugins.PluginInterface):
requirements.PluginRequirement(
name="pslist", plugin=pslist.PsList, version=(2, 0, 0)
),
requirements.VersionRequirement(
name="yarascanner", component=yarascan.YaraScanner, version=(2, 0, 0)
),
requirements.PluginRequirement(
name="yarascan", plugin=yarascan.YaraScan, version=(1, 3, 0)
name="yarascan", plugin=yarascan.YaraScan, version=(2, 0, 0)
),
requirements.ListRequirement(
name="pid",
@@ -66,33 +69,40 @@ class VadYaraScan(interfaces.plugins.PluginInterface):
):
layer_name = task.add_process_layer()
layer = self.context.layers[layer_name]
max_vad_size = 0
vad_maps_to_scan = []
for start, size in self.get_vad_maps(task):
if size > sanity_check:
vollog.warn(
vollog.debug(
f"VAD at 0x{start:x} over sanity-check size, not scanning"
)
continue
max_vad_size = max(max_vad_size, size)
vad_maps_to_scan.append((start, size))
for match in rules.match(data=layer.read(start, size, True)):
if yarascan.YaraScan.yara_returns_instances():
for match_string in match.strings:
for instance in match_string.instances:
yield 0, (
format_hints.Hex(instance.offset + start),
task.UniqueProcessId,
match.rule,
match_string.identifier,
instance.matched_data,
)
else:
for offset, name, value in match.strings:
yield 0, (
format_hints.Hex(offset + start),
task.UniqueProcessId,
match.rule,
name,
value,
)
if not vad_maps_to_scan:
vollog.warning(
f"No VADs were found for task {task.UniqueProcessID}, not scanning"
)
continue
scanner = yarascan.YaraScanner(rules=rules)
scanner.chunk_size = max_vad_size
# scan the VAD data (in one contiguous block) with the yarascanner
for start, size in vad_maps_to_scan:
for offset, rule_name, name, value in scanner(
layer.read(start, size, pad=True), start
):
yield 0, (
format_hints.Hex(offset),
task.UniqueProcessId,
rule_name,
name,
value,
)
@staticmethod
def get_vad_maps(
@@ -13,7 +13,7 @@ from volatility3.framework.layers import scanners
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 pslist, modules, dlllist
from volatility3.plugins.windows import pslist, modules
vollog = logging.getLogger(__name__)
@@ -46,10 +46,7 @@ class VerInfo(interfaces.plugins.PluginInterface):
name="pslist", plugin=pslist.PsList, version=(2, 0, 0)
),
requirements.PluginRequirement(
name="modules", plugin=modules.Modules, version=(1, 0, 0)
),
requirements.VersionRequirement(
name="dlllist", component=dlllist.DllList, version=(2, 0, 0)
name="modules", plugin=modules.Modules, version=(2, 0, 0)
),
requirements.BooleanRequirement(
name="extensive",
+85 -48
View File
@@ -13,20 +13,31 @@ from volatility3.framework.renderers import format_hints
vollog = logging.getLogger(__name__)
try:
import yara
USE_YARA_X = False
try:
import yara_x
USE_YARA_X = True
if tuple([int(x) for x in yara.__version__.split(".")]) < (3, 8):
raise ImportError
except ImportError:
vollog.info(
"Python Yara (>3.8.0) module not found, plugin (and dependent plugins) not available"
)
raise
try:
import yara
if tuple(int(x) for x in yara.__version__.split(".")) < (3, 8):
raise ImportError
vollog.debug("Using yara-python module")
except ImportError:
vollog.info(
"Neither yara-x nor yara-python (>3.8.0) module not found, plugin (and dependent plugins) not available"
)
raise
class YaraScanner(interfaces.layers.ScannerInterface):
_version = (2, 0, 0)
_version = (2, 1, 0)
# yara.Rules isn't exposed, so we can't type this properly
def __init__(self, rules) -> None:
@@ -34,37 +45,69 @@ class YaraScanner(interfaces.layers.ScannerInterface):
if rules is None:
raise ValueError("No rules provided to YaraScanner")
self._rules = rules
self.st_object = not tuple([int(x) for x in yara.__version__.split(".")]) < (
4,
3,
self.st_object = (
None
if USE_YARA_X
else not tuple(int(x) for x in yara.__version__.split(".")) < (4, 3)
)
def __call__(
self, data: bytes, data_offset: int
) -> Iterable[Tuple[int, str, str, bytes]]:
for match in self._rules.match(data=data):
if YaraScan.yara_returns_instances():
for match_string in match.strings:
for instance in match_string.instances:
if USE_YARA_X:
for match in self._rules.scan(data).matching_rules:
for match_string in match.patterns:
for instance in match_string.matches:
yield (
instance.offset + data_offset,
match.rule,
f"{match.namespace}.{match.identifier}",
match_string.identifier,
instance.matched_data,
data[instance.offset : instance.offset + instance.length],
)
else:
for offset, name, value in match.strings:
yield (offset + data_offset, match.rule, name, value)
else:
for match in self._rules.match(data=data):
if YaraScan.yara_returns_instances():
for match_string in match.strings:
for instance in match_string.instances:
yield (
instance.offset + data_offset,
match.rule,
match_string.identifier,
instance.matched_data,
)
else:
for offset, name, value in match.strings:
yield (offset + data_offset, match.rule, name, value)
@staticmethod
def get_rule(rule):
if USE_YARA_X:
return yara_x.compile(f"rule r1 {{strings: $a = {rule} condition: $a}}")
return yara.compile(
sources={"n": f"rule r1 {{strings: $a = {rule} condition: $a}}"}
)
@staticmethod
def from_compiled_file(filepath):
with resources.ResourceAccessor().open(filepath, "rb") as fp:
if USE_YARA_X:
return yara_x.Rules.deserialize_from(file=fp)
return yara.load(file=fp)
@staticmethod
def from_file(filepath):
with resources.ResourceAccessor().open(filepath, "rb") as fp:
if USE_YARA_X:
return yara_x.compile(fp.read().decode())
return yara.compile(file=fp)
class YaraScan(plugins.PluginInterface):
"""Scans kernel memory using yara rules (string or file)."""
_required_framework_version = (2, 0, 0)
_version = (1, 3, 0)
# TODO: When the major version is bumped, take the opportunity to rename the yara_rules config to yara_string
# or something that makes more sense
_version = (2, 0, 0)
_yara_x = USE_YARA_X
@classmethod
def get_requirements(cls) -> List[interfaces.configuration.RequirementInterface]:
@@ -99,10 +142,14 @@ class YaraScan(plugins.PluginInterface):
optional=True,
),
requirements.StringRequirement(
name="yara_rules", description="Yara rules (as a string)", optional=True
name="yara_string",
description="Yara rules (as a string)",
optional=True,
),
requirements.URIRequirement(
name="yara_file", description="Yara rules (as a file)", optional=True
name="yara_file",
description="Yara rules (as a file)",
optional=True,
),
# This additional requirement is to follow suit with upstream, who feel that compiled rules could potentially be used to execute malicious code
# As such, there's a separate option to run compiled files, as happened with yara-3.9 and later
@@ -121,38 +168,28 @@ class YaraScan(plugins.PluginInterface):
@classmethod
def yara_returns_instances(cls) -> bool:
st_object = not tuple([int(x) for x in yara.__version__.split(".")]) < (
4,
3,
)
return st_object
return not tuple(int(x) for x in yara.__version__.split(".")) < (4, 3)
@classmethod
def process_yara_options(cls, config: Dict[str, Any]):
rules = None
if config.get("yara_rules", None) is not None:
rule = config["yara_rules"]
if config.get("yara_string") is not None:
rule = config["yara_string"]
if rule[0] not in ["{", "/"]:
rule = f'"{rule}"'
if config.get("case", False):
rule += " nocase"
if config.get("wide", False):
rule += " wide ascii"
rules = yara.compile(
sources={"n": f"rule r1 {{strings: $a = {rule} condition: $a}}"}
)
elif config.get("yara_source", None) is not None:
rules = yara.compile(source=config["yara_source"])
elif config.get("yara_file", None) is not None:
rules = yara.compile(
file=resources.ResourceAccessor().open(config["yara_file"], "rb")
)
elif config.get("yara_compiled_file", None) is not None:
rules = yara.load(
file=resources.ResourceAccessor().open(
config["yara_compiled_file"], "rb"
)
rules = YaraScanner.get_rule(rule)
elif config.get("yara_file") is not None:
vollog.debug(f"Plain file: {config['yara_file']} - yara-x: {USE_YARA_X}")
rules = YaraScanner.from_file(config["yara_file"])
elif config.get("yara_compiled_file") is not None:
vollog.debug(
f"Compiled file: {config['yara_compiled_file']} - yara-x: {USE_YARA_X}"
)
rules = YaraScanner.from_compiled_file(config["yara_compiled_file"])
else:
vollog.error("No yara rules, nor yara rules file were specified")
return rules
@@ -11,6 +11,7 @@ from typing import Union
from volatility3.framework import interfaces, renderers
# FIXME: Move wintime_to_datetime() and unixtime_to_datetime() out of renderers, possibly framework.objects.utility
def wintime_to_datetime(
wintime: int,
) -> Union[interfaces.renderers.BaseAbsentValue, datetime.datetime]:
@@ -27,6 +28,27 @@ def wintime_to_datetime(
return renderers.UnparsableValue()
def windows_bytes_to_guid(buf: bytes) -> str:
"""
Converts 16 raw bytes to a windows GUID.
Raises ValueError if the provided buffer is not exactly 16 bytes.
"""
if len(buf) != 16:
raise ValueError("Expected 16 bytes for GUID")
head_components = [format(v, "x") for v in struct.unpack("<IHH", buf[:8])]
tail_component = [
format(v, "x")
for v in struct.unpack(
">HQ",
buf[8:10] + b"\x00\x00" + buf[10:16],
)
]
combined = head_components + tail_component
return "{" + "-".join(combined) + "}"
def unixtime_to_datetime(
unixtime: int,
) -> Union[interfaces.renderers.BaseAbsentValue, datetime.datetime]:
+420 -13
View File
@@ -1,6 +1,9 @@
# This file is Copyright 2019 Volatility Foundation and licensed under the Volatility Software License 1.0
# which is available at https://www.volatilityfoundation.org/license/vsl-v1.0
#
import math
import contextlib
from abc import ABC, abstractmethod
from typing import Iterator, List, Tuple, Optional, Union
from volatility3 import framework
@@ -19,6 +22,7 @@ class LinuxKernelIntermedSymbols(intermed.IntermediateSymbolTable):
# Set-up Linux specific types
self.set_type_class("file", extensions.struct_file)
self.set_type_class("list_head", extensions.list_head)
self.set_type_class("hlist_head", extensions.hlist_head)
self.set_type_class("mm_struct", extensions.mm_struct)
self.set_type_class("super_block", extensions.super_block)
self.set_type_class("task_struct", extensions.task_struct)
@@ -30,9 +34,13 @@ class LinuxKernelIntermedSymbols(intermed.IntermediateSymbolTable):
self.set_type_class("kobject", extensions.kobject)
self.set_type_class("cred", extensions.cred)
self.set_type_class("inode", extensions.inode)
self.set_type_class("idr", extensions.IDR)
self.set_type_class("address_space", extensions.address_space)
self.set_type_class("page", extensions.page)
# Might not exist in the current symbols
self.optional_set_type_class("module", extensions.module)
self.optional_set_type_class("bpf_prog", extensions.bpf_prog)
self.optional_set_type_class("bpf_prog_aux", extensions.bpf_prog_aux)
self.optional_set_type_class("kernel_cap_struct", extensions.kernel_cap_struct)
self.optional_set_type_class("kernel_cap_t", extensions.kernel_cap_t)
@@ -46,6 +54,7 @@ class LinuxKernelIntermedSymbols(intermed.IntermediateSymbolTable):
# Might not exist in older kernels or the current symbols
self.optional_set_type_class("mount", extensions.mount)
self.optional_set_type_class("mnt_namespace", extensions.mnt_namespace)
self.optional_set_type_class("rb_root", extensions.rb_root)
# Network
self.set_type_class("net", extensions.net)
@@ -67,7 +76,7 @@ class LinuxKernelIntermedSymbols(intermed.IntermediateSymbolTable):
class LinuxUtilities(interfaces.configuration.VersionableInterface):
"""Class with multiple useful linux functions."""
_version = (2, 1, 0)
_version = (2, 1, 1)
_required_framework_version = (2, 0, 0)
framework.require_interface_version(*_required_framework_version)
@@ -162,13 +171,30 @@ class LinuxUtilities(interfaces.configuration.VersionableInterface):
Returns:
str: Sock pipe pathname relative to the task's root directory.
"""
# FIXME: This function must be moved to the 'dentry' object extension
# Also, the scope of this function went beyond the sock pipe path, so we need to rename this.
# Once https://github.com/volatilityfoundation/volatility3/pull/1263 is merged, replace the
# dentry inode getters
if not (filp and filp.is_readable()):
return f"<invalid file pointer> {filp:x}"
dentry = filp.get_dentry()
if not (dentry and dentry.is_readable()):
return f"<invalid dentry pointer> {dentry:x}"
kernel_module = cls.get_module_from_volobj_type(context, dentry)
sym_addr = dentry.d_op.d_dname
if not (sym_addr and sym_addr.is_readable()):
return f"<invalid d_dname pointer> {sym_addr:x}"
symbs = list(kernel_module.get_symbols_by_absolute_location(sym_addr))
inode = dentry.d_inode
if not (inode and inode.is_readable() and inode.is_valid()):
return f"<invalid dentry inode> {inode:x}"
if len(symbs) == 1:
sym = symbs[0].split(constants.BANG)[1]
@@ -182,17 +208,50 @@ class LinuxUtilities(interfaces.configuration.VersionableInterface):
pre_name = "pipe"
elif sym == "simple_dname":
pre_name = cls._get_path_file(task, filp)
name = dentry.d_name.name
if name:
pre_name = name.dereference().cast(
"string", max_length=255, errors="replace"
)
return "/" + pre_name + " (deleted)"
else:
pre_name = ""
elif sym == "ns_dname":
# From Kernels 3.19
# In Kernels >= 6.9, see Linux kernel commit 1fa08aece42512be072351f482096d5796edf7ca
# ns_common->stashed change from 'atomic64_t' to 'dentry*'
try:
ns_common_type = kernel_module.get_type("ns_common")
stashed_template = ns_common_type.child_template("stashed")
stashed_type_full_name = stashed_template.vol.type_name
stashed_type_name = stashed_type_full_name.split(constants.BANG)[1]
if stashed_type_name == "atomic64_t":
# 3.19 <= Kernels < 6.9
fsdata_ptr = dentry.d_fsdata
if not (fsdata_ptr and fsdata_ptr.is_readable()):
raise IndexError
ns_ops = fsdata_ptr.dereference().cast("proc_ns_operations")
else:
# Kernels >= 6.9
private_ptr = inode.i_private
if not (private_ptr and private_ptr.is_readable()):
raise IndexError
ns_common = private_ptr.dereference().cast("ns_common")
ns_ops = ns_common.ops
pre_name = utility.pointer_to_string(ns_ops.name, 255)
except IndexError:
pre_name = "<unsupported ns_dname implementation>"
else:
pre_name = f"<unsupported d_op symbol: {sym}>"
ret = f"{pre_name}:[{dentry.d_inode.i_ino:d}]"
pre_name = f"<unsupported d_op symbol> {sym}"
else:
ret = f"<invalid d_dname pointer> {sym_addr:x}"
pre_name = f"<unknown d_dname pointer> {sym_addr:x}"
return ret
return f"{pre_name}:[{inode.i_ino:d}]"
@classmethod
def path_for_file(cls, context, task, filp) -> str:
@@ -249,7 +308,7 @@ class LinuxUtilities(interfaces.configuration.VersionableInterface):
task: interfaces.objects.ObjectInterface,
):
# task.files can be null
if not task.files:
if not (task.files and task.files.is_readable()):
return None
fd_table = task.files.get_fds()
@@ -269,7 +328,7 @@ class LinuxUtilities(interfaces.configuration.VersionableInterface):
)
for fd_num, filp in enumerate(fds):
if filp != 0:
if filp and filp.is_readable():
full_path = LinuxUtilities.path_for_file(context, task, filp)
yield fd_num, filp, full_path
@@ -412,9 +471,7 @@ class LinuxUtilities(interfaces.configuration.VersionableInterface):
Returns:
A kernel object (vmlinux)
"""
symbol_table_arr = volobj.vol.type_name.split("!", 1)
symbol_table = symbol_table_arr[0] if len(symbol_table_arr) == 2 else None
symbol_table = volobj.get_symbol_table_name()
module_names = context.modules.get_modules_by_symbol_tables(symbol_table)
module_names = list(module_names)
@@ -425,3 +482,353 @@ class LinuxUtilities(interfaces.configuration.VersionableInterface):
kernel = context.modules[kernel_module_name]
return kernel
class IDStorage(ABC):
"""Abstraction to support both XArray and RadixTree"""
# Dynamic values, these will be initialized later
CHUNK_SHIFT = None
CHUNK_SIZE = None
CHUNK_MASK = None
def __init__(
self,
context: interfaces.context.ContextInterface,
kernel_module_name: str,
):
self.vmlinux = context.modules[kernel_module_name]
self.vmlinux_layer = self.vmlinux.context.layers[self.vmlinux.layer_name]
self.pointer_size = self.vmlinux.get_type("pointer").size
# Dynamically work out the (XA_CHUNK|RADIX_TREE_MAP)_SHIFT values based on
# the node.slots[] array size
node_type = self.vmlinux.get_type(self.node_type_name)
slots_array_size = node_type.child_template("slots").count
# Calculate the LSB index - 1
self.CHUNK_SHIFT = slots_array_size.bit_length() - 1
self.CHUNK_SIZE = 1 << self.CHUNK_SHIFT
self.CHUNK_MASK = self.CHUNK_SIZE - 1
@classmethod
def choose_id_storage(
cls,
context: interfaces.context.ContextInterface,
kernel_module_name: str,
) -> "IDStorage":
"""Returns the appropriate ID storage data structure instance for the current kernel implementation.
This is used by the IDR and the PageCache to choose between the XArray and RadixTree.
Args:
context: The context to retrieve required elements (layers, symbol tables) from
kernel_module_name: The name of the kernel module on which to operate
Returns:
The appropriate ID storage instance for the current kernel
"""
vmlinux = context.modules[kernel_module_name]
address_space_type = vmlinux.get_type("address_space")
address_space_has_i_pages = address_space_type.has_member("i_pages")
i_pages_type_name = (
address_space_type.child_template("i_pages").vol.type_name
if address_space_has_i_pages
else ""
)
i_pages_is_xarray = i_pages_type_name.endswith(constants.BANG + "xarray")
i_pages_is_radix_tree_root = i_pages_type_name.endswith(
constants.BANG + "radix_tree_root"
) and vmlinux.get_type("radix_tree_root").has_member("xa_head")
if i_pages_is_xarray or i_pages_is_radix_tree_root:
return XArray(context, kernel_module_name)
else:
return RadixTree(context, kernel_module_name)
@property
@abstractmethod
def node_type_name(self) -> str:
"""Returns the Tree implementation node type name
Returns:
A string with the node type name
"""
raise NotImplementedError()
@property
def tag_internal_value(self) -> int:
"""Returns the internal node flag for the tree"""
raise NotImplementedError()
@abstractmethod
def node_is_internal(self, nodep) -> bool:
"""Checks if the node is internal"""
raise NotImplementedError
@abstractmethod
def is_node_tagged(self, nodep) -> bool:
"""Checks if the node pointer is tagged"""
raise NotImplementedError
@abstractmethod
def untag_node(self, nodep) -> int:
"""Untags a node pointer"""
raise NotImplementedError
@abstractmethod
def get_tree_height(self, treep) -> int:
"""Returns the tree height"""
raise NotImplementedError
@abstractmethod
def get_node_height(self, nodep) -> int:
"""Returns the node height"""
raise NotImplementedError
@abstractmethod
def get_head_node(self, tree) -> int:
"""Returns a pointer to the tree's head"""
raise NotImplementedError
@abstractmethod
def is_valid_node(self, nodep) -> bool:
"""Validates a node pointer"""
raise NotImplementedError
def nodep_to_node(self, nodep) -> interfaces.objects.ObjectInterface:
"""Instanciates a tree node from its pointer
Args:
nodep: Pointer to the XArray/RadixTree node
Returns:
A XArray/RadixTree node instance
"""
node = self.vmlinux.object(self.node_type_name, offset=nodep, absolute=True)
return node
def _slot_to_nodep(self, slot) -> int:
if self.node_is_internal(slot):
nodep = slot & ~self.tag_internal_value
else:
nodep = slot
return nodep
def _iter_node(self, nodep, height) -> Iterator[int]:
node = self.nodep_to_node(nodep)
node_slots = node.slots
for off in range(self.CHUNK_SIZE):
slot = node_slots[off]
if slot == 0:
continue
nodep = self._slot_to_nodep(slot)
if height == 1:
if self.is_valid_node(nodep):
yield nodep
else:
for child_node in self._iter_node(nodep, height - 1):
yield child_node
def get_entries(self, root: interfaces.objects.ObjectInterface) -> Iterator[int]:
"""Walks the tree data structure
Args:
root: The tree root object
Yields:
A tree node pointer
"""
height = self.get_tree_height(root.vol.offset)
nodep = self.get_head_node(root)
if not nodep:
return
# Keep the internal flag before untagging it
is_internal = self.node_is_internal(nodep)
if self.is_node_tagged(nodep):
nodep = self.untag_node(nodep)
if is_internal:
height = self.get_node_height(nodep)
if height == 0:
if self.is_valid_node(nodep):
yield nodep
else:
for child_node in self._iter_node(nodep, height):
yield child_node
class XArray(IDStorage):
XARRAY_TAG_MASK = 3
XARRAY_TAG_INTERNAL = 2
def get_tree_height(self, treep) -> int:
return 0
@property
def node_type_name(self) -> str:
return "xa_node"
@property
def tag_internal_value(self) -> int:
return self.XARRAY_TAG_INTERNAL
def get_node_height(self, nodep) -> int:
node = self.nodep_to_node(nodep)
return (node.shift / self.CHUNK_SHIFT) + 1
def get_head_node(self, tree) -> int:
return tree.xa_head
def node_is_internal(self, nodep) -> bool:
return (nodep & self.XARRAY_TAG_MASK) == self.XARRAY_TAG_INTERNAL
def is_node_tagged(self, nodep) -> bool:
return (nodep & self.XARRAY_TAG_MASK) != 0
def untag_node(self, nodep) -> int:
return nodep & (~self.XARRAY_TAG_MASK)
def is_valid_node(self, nodep) -> bool:
# It should have the tag mask clear
return not self.is_node_tagged(nodep)
class RadixTree(IDStorage):
RADIX_TREE_INTERNAL_NODE = 1
RADIX_TREE_EXCEPTIONAL_ENTRY = 2
RADIX_TREE_ENTRY_MASK = 3
# Dynamic values. These will be initialized later
RADIX_TREE_INDEX_BITS = None
RADIX_TREE_MAX_PATH = None
RADIX_TREE_HEIGHT_SHIFT = None
RADIX_TREE_HEIGHT_MASK = None
def __init__(self, *args, **kwargs) -> None:
super().__init__(*args, **kwargs)
char_bits = 8
self.RADIX_TREE_INDEX_BITS = char_bits * self.pointer_size
self.RADIX_TREE_MAX_PATH = int(
math.ceil(self.RADIX_TREE_INDEX_BITS / float(self.CHUNK_SHIFT))
)
self.RADIX_TREE_HEIGHT_SHIFT = self.RADIX_TREE_MAX_PATH + 1
self.RADIX_TREE_HEIGHT_MASK = (1 << self.RADIX_TREE_HEIGHT_SHIFT) - 1
if not self.vmlinux.has_type("radix_tree_root"):
# In kernels 4.20, RADIX_TREE_INTERNAL_NODE flag took RADIX_TREE_EXCEPTIONAL_ENTRY's
# value. RADIX_TREE_EXCEPTIONAL_ENTRY was removed but that's managed in is_valid_node()
# Note that the Radix Tree is still in use for IDR, even after kernels 4.20 when XArray
# mostly replace it
self.RADIX_TREE_INTERNAL_NODE = 2
@property
def node_type_name(self) -> str:
return "radix_tree_node"
@property
def tag_internal_value(self) -> int:
return self.RADIX_TREE_INTERNAL_NODE
def get_tree_height(self, treep) -> int:
with contextlib.suppress(exceptions.SymbolError):
if self.vmlinux.get_type("radix_tree_root").has_member("height"):
# kernels < 4.7.10
radix_tree_root = self.vmlinux.object(
"radix_tree_root", offset=treep, absolute=True
)
return radix_tree_root.height
# kernels >= 4.7.10
return 0
def _radix_tree_maxindex(self, node, height) -> int:
"""Return the maximum key which can be store into a radix tree with this height."""
if not self.vmlinux.has_symbol("height_to_maxindex"):
# Kernels >= 4.7
return (self.CHUNK_SIZE << node.shift) - 1
else:
# Kernels < 4.7
height_to_maxindex_array = self.vmlinux.object_from_symbol(
"height_to_maxindex"
)
maxindex = height_to_maxindex_array[height]
return maxindex
def get_node_height(self, nodep) -> int:
node = self.nodep_to_node(nodep)
if hasattr(node, "shift"):
# 4.7 <= Kernels < 4.20
return (node.shift / self.CHUNK_SHIFT) + 1
elif hasattr(node, "path"):
# 3.15 <= Kernels < 4.7
return node.path & self.RADIX_TREE_HEIGHT_MASK
elif hasattr(node, "height"):
# Kernels < 3.15
return node.height
else:
raise exceptions.VolatilityException("Cannot find radix-tree node height")
def get_head_node(self, tree) -> int:
return tree.rnode
def node_is_internal(self, nodep) -> bool:
return (nodep & self.RADIX_TREE_INTERNAL_NODE) != 0
def is_node_tagged(self, nodep) -> bool:
return self.node_is_internal(nodep)
def untag_node(self, nodep) -> int:
return nodep & (~self.RADIX_TREE_ENTRY_MASK)
def is_valid_node(self, nodep) -> bool:
# In kernels 4.20, exceptional nodes were removed and internal entries took their bitmask
if self.vmlinux.has_type("radix_tree_root"):
return (
nodep & self.RADIX_TREE_ENTRY_MASK
) != self.RADIX_TREE_EXCEPTIONAL_ENTRY
return True
class PageCache(object):
"""Linux Page Cache abstraction"""
def __init__(
self,
context: interfaces.context.ContextInterface,
kernel_module_name: str,
page_cache: interfaces.objects.ObjectInterface,
):
"""
Args:
context: interfaces.context.ContextInterface,
kernel_module_name: The name of the kernel module on which to operate
page_cache: Page cache address space
"""
self.vmlinux = context.modules[kernel_module_name]
self._page_cache = page_cache
self._idstorage = IDStorage.choose_id_storage(context, kernel_module_name)
def get_cached_pages(self) -> Iterator[interfaces.objects.ObjectInterface]:
"""Returns all page cache contents
Yields:
Page objects
"""
for page_addr in self._idstorage.get_entries(self._page_cache.i_pages):
if not page_addr:
continue
page = self.vmlinux.object("page", offset=page_addr, absolute=True)
if page:
yield page
File diff suppressed because it is too large Load Diff
+2 -2
View File
@@ -24,8 +24,8 @@ class ProducerMetadata(interfaces.symbols.MetadataInterface):
version = self._json_data.get("version", None)
if not version:
return None
if all([x in "0123456789." for x in version]):
return tuple([int(x) for x in version.split(".")])
if all(x in "0123456789." for x in version):
return tuple(int(x) for x in version.split("."))
vollog.log(
constants.LOGLEVEL_VVVV,
f"Metadata version contains unexpected characters: '{version}'",
@@ -0,0 +1,659 @@
{
"symbols": {},
"enums": {},
"base_types": {
"unsigned long": {
"kind": "int",
"size": 4,
"signed": false,
"endian": "little"
},
"unsigned char": {
"kind": "char",
"size": 1,
"signed": false,
"endian": "little"
},
"pointer": {
"kind": "int",
"size": 8,
"signed": false,
"endian": "little"
},
"unsigned int": {
"kind": "int",
"size": 4,
"signed": false,
"endian": "little"
},
"unsigned short": {
"kind": "int",
"size": 2,
"signed": false,
"endian": "little"
},
"short": {
"kind": "int",
"size": 2,
"signed": true,
"endian": "little"
},
"long": {
"kind": "int",
"size": 4,
"signed": false,
"endian": "little"
}
},
"user_types": {
"_CONSOLE_INFORMATION": {
"fields": {
"ScreenX": {
"type": {
"kind": "base",
"name": "short"
},
"offset": 32
},
"ScreenY": {
"type": {
"kind": "base",
"name": "short"
},
"offset": 34
},
"CommandHistorySize": {
"type": {
"kind": "base",
"name": "unsigned short"
},
"offset": 144
},
"HistoryBufferMax": {
"type": {
"kind": "base",
"name": "unsigned short"
},
"offset": 148
},
"OriginalTitle": {
"type": {
"kind": "pointer",
"subtype": {
"kind": "base",
"name": "string"
}
},
"offset": 1736
},
"Title": {
"type": {
"kind": "pointer",
"subtype": {
"kind": "base",
"name": "string"
}
},
"offset": 1672
},
"GetScreenBuffer": {
"type": {
"kind": "pointer",
"subtype": {
"kind": "struct",
"name": "_SCREEN_INFORMATION"
}
},
"offset": 1800
},
"CurrentScreenBuffer": {
"type": {
"kind": "pointer",
"subtype": {
"kind": "struct",
"name": "_SCREEN_INFORMATION"
}
},
"offset": 1384
},
"ConsoleProcessList": {
"type": {
"kind": "pointer",
"subtype": {
"kind": "struct",
"name": "nt_symbols!_LIST_ENTRY"
}
},
"offset": 1360
},
"ProcessCount": {
"type": {
"kind": "base",
"name": "unsigned short"
},
"offset": 1368
},
"HistoryList": {
"type": {
"kind": "pointer",
"subtype": {
"kind": "struct",
"name": "nt_symbols!_LIST_ENTRY"
}
},
"offset": -760
},
"HistoryBufferCount": {
"type": {
"kind": "base",
"name": "unsigned short"
},
"offset": -752
},
"ExeAliasList": {
"type": {
"kind": "struct",
"name": "nt_symbols!_LIST_ENTRY"
},
"offset": 1392
}
},
"kind": "struct",
"size": 140
},
"_VECTOR": {
"fields": {
"Begin": {
"type": {
"kind": "pointer",
"subtype": {
"kind": "struct",
"name": "_COMMAND"
}
},
"offset": 0
},
"End": {
"type": {
"kind": "pointer",
"subtype": {
"kind": "struct",
"name": "_COMMAND"
}
},
"offset": 8
},
"EndCapacity": {
"type": {
"kind": "pointer",
"subtype": {
"kind": "base",
"name": "unsigned long"
}
},
"offset": 16
}
},
"kind": "struct",
"size": 24
},
"_COMMAND": {
"fields": {
"Chars": {
"type": {
"count": 1,
"kind": "array",
"subtype": {
"kind": "base",
"name": "unsigned char"
}
},
"offset": 0
},
"Pointer": {
"type": {
"kind": "pointer",
"subtype": {
"kind": "base",
"name": "string"
}
},
"offset": 0
},
"Length": {
"type": {
"kind": "base",
"name": "unsigned int"
},
"offset": 16
},
"Allocated": {
"type": {
"kind": "base",
"name": "unsigned int"
},
"offset": 24
}
},
"kind": "struct",
"size": 32
},
"_CONSOLE_PROCESS_LIST": {
"fields": {
"ListEntry": {
"type": {
"kind": "struct",
"name": "nt_symbols!_LIST_ENTRY"
},
"offset": 0
},
"ConsoleProcess": {
"type": {
"kind": "pointer",
"subtype": {
"kind": "struct",
"name": "_CONSOLE_PROCESS"
}
},
"offset": 16
}
},
"kind": "struct",
"size": 24
},
"_CONSOLE_PROCESS_HANDLE": {
"fields": {
"ProcessHandle": {
"type": {
"kind": "base",
"name": "unsigned int"
},
"offset": 48
}
},
"kind": "struct",
"size": 52
},
"_CONSOLE_PROCESS": {
"fields": {
"ProcessId": {
"type": {
"kind": "base",
"name": "unsigned int"
},
"offset": 28
},
"ThreadId": {
"type": {
"kind": "base",
"name": "unsigned int"
},
"offset": 32
},
"ProcessHandle": {
"type": {
"kind": "base",
"name": "unsigned int"
},
"offset": 48
}
},
"kind": "struct",
"size": 24
},
"_COMMAND_HISTORY": {
"fields": {
"ListEntry": {
"type": {
"kind": "struct",
"name": "nt_symbols!_LIST_ENTRY"
},
"offset": 0
},
"CommandBucket": {
"type": {
"kind": "struct",
"name": "_VECTOR"
},
"offset": 16
},
"CommandCountMax": {
"type": {
"kind": "base",
"name": "unsigned short"
},
"offset": 40
},
"Application": {
"type": {
"kind": "struct",
"name": "_COMMAND"
},
"offset": 48
},
"ConsoleProcessHandle": {
"type": {
"kind": "pointer",
"subtype": {
"kind": "struct",
"name": "_CONSOLE_PROCESS_HANDLE"
}
},
"offset": 80
},
"Flags": {
"type": {
"kind": "base",
"name": "unsigned short"
},
"offset": 88
},
"LastDisplayed": {
"type": {
"kind": "base",
"name": "short"
},
"offset": 92
}
},
"kind": "struct",
"size": 96
},
"_SCREEN_INFORMATION": {
"fields": {
"TextBufferInfo": {
"type": {
"kind": "pointer",
"subtype": {
"kind": "struct",
"name": "_TEXT_BUFFER_INFO"
}
},
"offset": 56
},
"Next": {
"type": {
"kind": "pointer",
"subtype": {
"kind": "struct",
"name": "_SCREEN_INFORMATION"
}
},
"offset": 64
}
},
"kind": "struct",
"size": 72
},
"_ROW_POINTER": {
"fields": {
"Row": {
"type": {
"kind": "pointer",
"subtype": {
"kind": "struct",
"name": "_ROW"
}
},
"offset": 0
}
},
"kind": "struct",
"size": 8
},
"_ROWS_ARRAY": {
"fields": {
"Rows": {
"type": {
"count": 1,
"kind": "array",
"subtype": {
"kind": "struct",
"name": "_ROW_POINTER"
}
},
"offset": 0
}
},
"kind": "struct",
"size": 8
},
"_TEXT_BUFFER_INFO": {
"fields": {
"BufferDeque": {
"type": {
"kind": "pointer",
"subtype": {
"kind": "struct",
"name": "_DEQUE"
}
},
"offset": 0
},
"BufferRows": {
"type": {
"kind": "pointer",
"subtype": {
"kind": "struct",
"name": "_ROWS_ARRAY"
}
},
"offset": 8
},
"BufferCapacity": {
"type": {
"kind": "base",
"name": "short"
},
"offset": 32
},
"ThisBuffer": {
"type": {
"kind": "pointer",
"subtype": {
"kind": "struct",
"name": "_TEXT_BUFFER_INFO"
}
},
"offset": 48
},
"BufferEnd": {
"type": {
"kind": "base",
"name": "short"
},
"offset": 56
},
"BufferLastIndex": {
"type": {
"kind": "base",
"name": "short"
},
"offset": 58
},
"BufferStart": {
"type": {
"kind": "base",
"name": "short"
},
"offset": 112
}
},
"kind": "struct",
"size": 72
},
"_CHAR_ROW_CELL": {
"fields": {
"Text": {
"type": {
"kind": "struct",
"name": "nt_symbols!_UNICODE_STRING"
},
"offset": 0
},
"DbcsAttribute": {
"type": {
"kind": "base",
"name": "unsigned int"
},
"offset": 2
}
},
"kind": "struct",
"size": 3
},
"_CHAR_ROW_CELL_ARRAY": {
"fields": {
"Chars": {
"type": {
"count": 1,
"kind": "array",
"subtype": {
"kind": "struct",
"name": "_CHAR_ROW_CELL"
}
},
"offset": 0
}
},
"kind": "struct",
"size": 8
},
"_ROW": {
"fields": {
"CharRow": {
"type": {
"kind": "pointer",
"subtype": {
"kind": "struct",
"name": "_CHAR_ROW_CELL_ARRAY"
}
},
"offset": 8
},
"RowLength": {
"type": {
"kind": "base",
"name": "short"
},
"offset": 64
},
"Index": {
"type": {
"kind": "base",
"name": "short"
},
"offset": 72
},
"RowLength2": {
"type": {
"kind": "base",
"name": "short"
},
"offset": 80
},
"TextBuffer": {
"type": {
"kind": "pointer",
"subtype": {
"kind": "struct",
"name": "_TEXT_BUFFER_INFO"
}
},
"offset": 88
}
},
"kind": "struct",
"size": 96
},
"_DEQUE": {
"fields": {
"Blink": {
"type": {
"kind": "pointer",
"subtype": {
"kind": "base",
"name": "void"
}
},
"offset": 0
}
},
"kind": "struct",
"size": 8
},
"_EXE_ALIAS_LIST": {
"fields": {
"ListEntry": {
"type": {
"kind": "struct",
"name": "nt_symbols!_LIST_ENTRY"
},
"offset": 0
},
"ExeLength": {
"type": {
"kind": "base",
"name": "short"
},
"offset": 8
},
"ExeName": {
"type": {
"kind": "pointer",
"subtype": {
"kind": "base",
"name": "string"
}
},
"offset": 24
},
"AliasList": {
"type": {
"kind": "struct",
"name": "nt_symbols!_LIST_ENTRY"
},
"offset": 32
}
},
"kind": "struct",
"size": 48
},
"_ALIAS": {
"fields": {
"ListEntry": {
"type": {
"kind": "struct",
"name": "nt_symbols!_LIST_ENTRY"
},
"offset": 0
},
"Source": {
"type": {
"kind": "struct",
"name": "_COMMAND"
},
"offset": 16
},
"Target": {
"type": {
"kind": "struct",
"name": "_COMMAND"
},
"offset": 48
}
},
"kind": "struct",
"size": 32
}
},
"metadata": {
"producer": {
"version": "0.0.1",
"name": "Dave Lassalle",
"datetime": "2024-07-31T15:05:35-06:00"
},
"format": "4.1.0"
}
}
@@ -0,0 +1,659 @@
{
"symbols": {},
"enums": {},
"base_types": {
"unsigned long": {
"kind": "int",
"size": 4,
"signed": false,
"endian": "little"
},
"unsigned char": {
"kind": "char",
"size": 1,
"signed": false,
"endian": "little"
},
"pointer": {
"kind": "int",
"size": 8,
"signed": false,
"endian": "little"
},
"unsigned int": {
"kind": "int",
"size": 4,
"signed": false,
"endian": "little"
},
"unsigned short": {
"kind": "int",
"size": 2,
"signed": false,
"endian": "little"
},
"short": {
"kind": "int",
"size": 2,
"signed": true,
"endian": "little"
},
"long": {
"kind": "int",
"size": 4,
"signed": false,
"endian": "little"
}
},
"user_types": {
"_CONSOLE_INFORMATION": {
"fields": {
"ScreenX": {
"type": {
"kind": "base",
"name": "short"
},
"offset": 32
},
"ScreenY": {
"type": {
"kind": "base",
"name": "short"
},
"offset": 34
},
"CommandHistorySize": {
"type": {
"kind": "base",
"name": "unsigned short"
},
"offset": 144
},
"HistoryBufferMax": {
"type": {
"kind": "base",
"name": "unsigned short"
},
"offset": 148
},
"OriginalTitle": {
"type": {
"kind": "pointer",
"subtype": {
"kind": "base",
"name": "string"
}
},
"offset": 1736
},
"Title": {
"type": {
"kind": "pointer",
"subtype": {
"kind": "base",
"name": "string"
}
},
"offset": 1672
},
"GetScreenBuffer": {
"type": {
"kind": "pointer",
"subtype": {
"kind": "struct",
"name": "_SCREEN_INFORMATION"
}
},
"offset": 1800
},
"CurrentScreenBuffer": {
"type": {
"kind": "pointer",
"subtype": {
"kind": "struct",
"name": "_SCREEN_INFORMATION"
}
},
"offset": 1384
},
"ConsoleProcessList": {
"type": {
"kind": "pointer",
"subtype": {
"kind": "struct",
"name": "nt_symbols!_LIST_ENTRY"
}
},
"offset": 1360
},
"ProcessCount": {
"type": {
"kind": "base",
"name": "unsigned short"
},
"offset": 1368
},
"HistoryList": {
"type": {
"kind": "pointer",
"subtype": {
"kind": "struct",
"name": "nt_symbols!_LIST_ENTRY"
}
},
"offset": -760
},
"HistoryBufferCount": {
"type": {
"kind": "base",
"name": "unsigned short"
},
"offset": -752
},
"ExeAliasList": {
"type": {
"kind": "struct",
"name": "nt_symbols!_LIST_ENTRY"
},
"offset": 1392
}
},
"kind": "struct",
"size": 140
},
"_VECTOR": {
"fields": {
"Begin": {
"type": {
"kind": "pointer",
"subtype": {
"kind": "struct",
"name": "_COMMAND"
}
},
"offset": 0
},
"End": {
"type": {
"kind": "pointer",
"subtype": {
"kind": "struct",
"name": "_COMMAND"
}
},
"offset": 8
},
"EndCapacity": {
"type": {
"kind": "pointer",
"subtype": {
"kind": "base",
"name": "unsigned long"
}
},
"offset": 16
}
},
"kind": "struct",
"size": 24
},
"_COMMAND": {
"fields": {
"Chars": {
"type": {
"count": 1,
"kind": "array",
"subtype": {
"kind": "base",
"name": "unsigned char"
}
},
"offset": 0
},
"Pointer": {
"type": {
"kind": "pointer",
"subtype": {
"kind": "base",
"name": "string"
}
},
"offset": 0
},
"Length": {
"type": {
"kind": "base",
"name": "unsigned int"
},
"offset": 16
},
"Allocated": {
"type": {
"kind": "base",
"name": "unsigned int"
},
"offset": 24
}
},
"kind": "struct",
"size": 32
},
"_CONSOLE_PROCESS_LIST": {
"fields": {
"ListEntry": {
"type": {
"kind": "struct",
"name": "nt_symbols!_LIST_ENTRY"
},
"offset": 0
},
"ConsoleProcess": {
"type": {
"kind": "pointer",
"subtype": {
"kind": "struct",
"name": "_CONSOLE_PROCESS"
}
},
"offset": 16
}
},
"kind": "struct",
"size": 24
},
"_CONSOLE_PROCESS_HANDLE": {
"fields": {
"ProcessHandle": {
"type": {
"kind": "base",
"name": "unsigned int"
},
"offset": 48
}
},
"kind": "struct",
"size": 52
},
"_CONSOLE_PROCESS": {
"fields": {
"ProcessId": {
"type": {
"kind": "base",
"name": "unsigned int"
},
"offset": 28
},
"ThreadId": {
"type": {
"kind": "base",
"name": "unsigned int"
},
"offset": 32
},
"ProcessHandle": {
"type": {
"kind": "base",
"name": "unsigned int"
},
"offset": 48
}
},
"kind": "struct",
"size": 24
},
"_COMMAND_HISTORY": {
"fields": {
"ListEntry": {
"type": {
"kind": "struct",
"name": "nt_symbols!_LIST_ENTRY"
},
"offset": 0
},
"CommandBucket": {
"type": {
"kind": "struct",
"name": "_VECTOR"
},
"offset": 16
},
"CommandCountMax": {
"type": {
"kind": "base",
"name": "unsigned short"
},
"offset": 40
},
"Application": {
"type": {
"kind": "struct",
"name": "_COMMAND"
},
"offset": 48
},
"ConsoleProcessHandle": {
"type": {
"kind": "pointer",
"subtype": {
"kind": "struct",
"name": "_CONSOLE_PROCESS_HANDLE"
}
},
"offset": 80
},
"Flags": {
"type": {
"kind": "base",
"name": "unsigned short"
},
"offset": 88
},
"LastDisplayed": {
"type": {
"kind": "base",
"name": "short"
},
"offset": 92
}
},
"kind": "struct",
"size": 96
},
"_SCREEN_INFORMATION": {
"fields": {
"TextBufferInfo": {
"type": {
"kind": "pointer",
"subtype": {
"kind": "struct",
"name": "_TEXT_BUFFER_INFO"
}
},
"offset": 32
},
"Next": {
"type": {
"kind": "pointer",
"subtype": {
"kind": "struct",
"name": "_SCREEN_INFORMATION"
}
},
"offset": 36
}
},
"kind": "struct",
"size": 72
},
"_ROW_POINTER": {
"fields": {
"Row": {
"type": {
"kind": "pointer",
"subtype": {
"kind": "struct",
"name": "_ROW"
}
},
"offset": 0
}
},
"kind": "struct",
"size": 8
},
"_ROWS_ARRAY": {
"fields": {
"Rows": {
"type": {
"count": 1,
"kind": "array",
"subtype": {
"kind": "struct",
"name": "_ROW_POINTER"
}
},
"offset": 0
}
},
"kind": "struct",
"size": 8
},
"_TEXT_BUFFER_INFO": {
"fields": {
"BufferDeque": {
"type": {
"kind": "pointer",
"subtype": {
"kind": "struct",
"name": "_DEQUE"
}
},
"offset": 0
},
"BufferRows": {
"type": {
"kind": "pointer",
"subtype": {
"kind": "struct",
"name": "_ROWS_ARRAY"
}
},
"offset": 8
},
"BufferCapacity": {
"type": {
"kind": "base",
"name": "short"
},
"offset": 32
},
"ThisBuffer": {
"type": {
"kind": "pointer",
"subtype": {
"kind": "struct",
"name": "_TEXT_BUFFER_INFO"
}
},
"offset": 48
},
"BufferEnd": {
"type": {
"kind": "base",
"name": "short"
},
"offset": 56
},
"BufferLastIndex": {
"type": {
"kind": "base",
"name": "short"
},
"offset": 58
},
"BufferStart": {
"type": {
"kind": "base",
"name": "short"
},
"offset": 112
}
},
"kind": "struct",
"size": 72
},
"_CHAR_ROW_CELL": {
"fields": {
"Text": {
"type": {
"kind": "struct",
"name": "nt_symbols!_UNICODE_STRING"
},
"offset": 0
},
"DbcsAttribute": {
"type": {
"kind": "base",
"name": "unsigned int"
},
"offset": 2
}
},
"kind": "struct",
"size": 3
},
"_CHAR_ROW_CELL_ARRAY": {
"fields": {
"Chars": {
"type": {
"count": 1,
"kind": "array",
"subtype": {
"kind": "struct",
"name": "_CHAR_ROW_CELL"
}
},
"offset": 0
}
},
"kind": "struct",
"size": 8
},
"_ROW": {
"fields": {
"CharRow": {
"type": {
"kind": "pointer",
"subtype": {
"kind": "struct",
"name": "_CHAR_ROW_CELL_ARRAY"
}
},
"offset": 8
},
"RowLength": {
"type": {
"kind": "base",
"name": "short"
},
"offset": 64
},
"Index": {
"type": {
"kind": "base",
"name": "short"
},
"offset": 72
},
"RowLength2": {
"type": {
"kind": "base",
"name": "short"
},
"offset": 80
},
"TextBuffer": {
"type": {
"kind": "pointer",
"subtype": {
"kind": "struct",
"name": "_TEXT_BUFFER_INFO"
}
},
"offset": 88
}
},
"kind": "struct",
"size": 96
},
"_DEQUE": {
"fields": {
"Blink": {
"type": {
"kind": "pointer",
"subtype": {
"kind": "base",
"name": "void"
}
},
"offset": 0
}
},
"kind": "struct",
"size": 8
},
"_EXE_ALIAS_LIST": {
"fields": {
"ListEntry": {
"type": {
"kind": "struct",
"name": "nt_symbols!_LIST_ENTRY"
},
"offset": 0
},
"ExeLength": {
"type": {
"kind": "base",
"name": "short"
},
"offset": 16
},
"ExeName": {
"type": {
"kind": "pointer",
"subtype": {
"kind": "base",
"name": "string"
}
},
"offset": 24
},
"AliasList": {
"type": {
"kind": "struct",
"name": "nt_symbols!_LIST_ENTRY"
},
"offset": 32
}
},
"kind": "struct",
"size": 48
},
"_ALIAS": {
"fields": {
"ListEntry": {
"type": {
"kind": "struct",
"name": "nt_symbols!_LIST_ENTRY"
},
"offset": 0
},
"Source": {
"type": {
"kind": "struct",
"name": "_COMMAND"
},
"offset": 16
},
"Target": {
"type": {
"kind": "struct",
"name": "_COMMAND"
},
"offset": 48
}
},
"kind": "struct",
"size": 32
}
},
"metadata": {
"producer": {
"version": "0.0.1",
"name": "Dave Lassalle",
"datetime": "2024-07-31T15:05:35-06:00"
},
"format": "4.1.0"
}
}
@@ -0,0 +1,649 @@
{
"symbols": {},
"enums": {},
"base_types": {
"unsigned long": {
"kind": "int",
"size": 4,
"signed": false,
"endian": "little"
},
"unsigned char": {
"kind": "char",
"size": 1,
"signed": false,
"endian": "little"
},
"pointer": {
"kind": "int",
"size": 8,
"signed": false,
"endian": "little"
},
"unsigned int": {
"kind": "int",
"size": 4,
"signed": false,
"endian": "little"
},
"unsigned short": {
"kind": "int",
"size": 2,
"signed": false,
"endian": "little"
},
"short": {
"kind": "int",
"size": 2,
"signed": true,
"endian": "little"
},
"long": {
"kind": "int",
"size": 4,
"signed": false,
"endian": "little"
}
},
"user_types": {
"_CONSOLE_INFORMATION": {
"fields": {
"ScreenX": {
"type": {
"kind": "base",
"name": "short"
},
"offset": 32
},
"ScreenY": {
"type": {
"kind": "base",
"name": "short"
},
"offset": 34
},
"CommandHistorySize": {
"type": {
"kind": "base",
"name": "unsigned short"
},
"offset": 144
},
"HistoryBufferMax": {
"type": {
"kind": "base",
"name": "unsigned short"
},
"offset": 148
},
"OriginalTitle": {
"type": {
"kind": "pointer",
"subtype": {
"kind": "base",
"name": "string"
}
},
"offset": 1744
},
"Title": {
"type": {
"kind": "pointer",
"subtype": {
"kind": "base",
"name": "string"
}
},
"offset": 1680
},
"GetScreenBuffer": {
"type": {
"kind": "pointer",
"subtype": {
"kind": "struct",
"name": "_SCREEN_INFORMATION"
}
},
"offset": 1808
},
"CurrentScreenBuffer": {
"type": {
"kind": "pointer",
"subtype": {
"kind": "struct",
"name": "_SCREEN_INFORMATION"
}
},
"offset": 1392
},
"ConsoleProcessList": {
"type": {
"kind": "pointer",
"subtype": {
"kind": "struct",
"name": "nt_symbols!_LIST_ENTRY"
}
},
"offset": 1368
},
"ProcessCount": {
"type": {
"kind": "base",
"name": "unsigned short"
},
"offset": 1376
},
"HistoryList": {
"type": {
"kind": "pointer",
"subtype": {
"kind": "struct",
"name": "nt_symbols!_LIST_ENTRY"
}
},
"offset": -768
},
"HistoryBufferCount": {
"type": {
"kind": "base",
"name": "unsigned short"
},
"offset": -760
},
"ExeAliasList": {
"type": {
"kind": "struct",
"name": "nt_symbols!_LIST_ENTRY"
},
"offset": -856
}
},
"kind": "struct",
"size": 140
},
"_VECTOR": {
"fields": {
"Begin": {
"type": {
"kind": "pointer",
"subtype": {
"kind": "struct",
"name": "_COMMAND"
}
},
"offset": 0
},
"End": {
"type": {
"kind": "pointer",
"subtype": {
"kind": "struct",
"name": "_COMMAND"
}
},
"offset": 8
},
"EndCapacity": {
"type": {
"kind": "pointer",
"subtype": {
"kind": "base",
"name": "unsigned long"
}
},
"offset": 16
}
},
"kind": "struct",
"size": 24
},
"_COMMAND": {
"fields": {
"Chars": {
"type": {
"count": 1,
"kind": "array",
"subtype": {
"kind": "base",
"name": "unsigned char"
}
},
"offset": 0
},
"Pointer": {
"type": {
"kind": "pointer",
"subtype": {
"kind": "base",
"name": "string"
}
},
"offset": 0
},
"Length": {
"type": {
"kind": "base",
"name": "unsigned int"
},
"offset": 16
},
"Allocated": {
"type": {
"kind": "base",
"name": "unsigned int"
},
"offset": 24
}
},
"kind": "struct",
"size": 32
},
"_CONSOLE_PROCESS_LIST": {
"fields": {
"ListEntry": {
"type": {
"kind": "struct",
"name": "nt_symbols!_LIST_ENTRY"
},
"offset": 0
},
"ConsoleProcess": {
"type": {
"kind": "pointer",
"subtype": {
"kind": "struct",
"name": "_CONSOLE_PROCESS"
}
},
"offset": 16
}
},
"kind": "struct",
"size": 24
},
"_CONSOLE_PROCESS_HANDLE": {
"fields": {
"ProcessHandle": {
"type": {
"kind": "base",
"name": "unsigned int"
},
"offset": 48
}
},
"kind": "struct",
"size": 52
},
"_CONSOLE_PROCESS": {
"fields": {
"ProcessId": {
"type": {
"kind": "base",
"name": "unsigned int"
},
"offset": 28
},
"ThreadId": {
"type": {
"kind": "base",
"name": "unsigned int"
},
"offset": 32
},
"ProcessHandle": {
"type": {
"kind": "base",
"name": "unsigned int"
},
"offset": 48
}
},
"kind": "struct",
"size": 24
},
"_COMMAND_HISTORY": {
"fields": {
"ListEntry": {
"type": {
"kind": "struct",
"name": "nt_symbols!_LIST_ENTRY"
},
"offset": 0
},
"CommandBucket": {
"type": {
"kind": "struct",
"name": "_VECTOR"
},
"offset": 16
},
"CommandCountMax": {
"type": {
"kind": "base",
"name": "unsigned short"
},
"offset": 40
},
"Application": {
"type": {
"kind": "struct",
"name": "_COMMAND"
},
"offset": 48
},
"ConsoleProcessHandle": {
"type": {
"kind": "pointer",
"subtype": {
"kind": "struct",
"name": "_CONSOLE_PROCESS_HANDLE"
}
},
"offset": 80
},
"Flags": {
"type": {
"kind": "base",
"name": "unsigned short"
},
"offset": 88
},
"LastDisplayed": {
"type": {
"kind": "base",
"name": "short"
},
"offset": 92
}
},
"kind": "struct",
"size": 96
},
"_SCREEN_INFORMATION": {
"fields": {
"TextBufferInfo": {
"type": {
"kind": "pointer",
"subtype": {
"kind": "struct",
"name": "_TEXT_BUFFER_INFO"
}
},
"offset": 56
},
"Next": {
"type": {
"kind": "pointer",
"subtype": {
"kind": "struct",
"name": "_SCREEN_INFORMATION"
}
},
"offset": 64
}
},
"kind": "struct",
"size": 72
},
"_ROW_POINTER": {
"fields": {
"Row": {
"type": {
"kind": "pointer",
"subtype": {
"kind": "struct",
"name": "_ROW"
}
},
"offset": 0
}
},
"kind": "struct",
"size": 8
},
"_ROWS_ARRAY": {
"fields": {
"Rows": {
"type": {
"count": 1,
"kind": "array",
"subtype": {
"kind": "struct",
"name": "_ROW_POINTER"
}
},
"offset": 0
}
},
"kind": "struct",
"size": 8
},
"_TEXT_BUFFER_INFO": {
"fields": {
"BufferDeque": {
"type": {
"kind": "pointer",
"subtype": {
"kind": "struct",
"name": "_DEQUE"
}
},
"offset": 0
},
"BufferRows": {
"type": {
"kind": "pointer",
"subtype": {
"kind": "struct",
"name": "_ROWS_ARRAY"
}
},
"offset": 8
},
"BufferCapacity": {
"type": {
"kind": "base",
"name": "short"
},
"offset": 32
},
"BufferEnd": {
"type": {
"kind": "base",
"name": "short"
},
"offset": 56
},
"BufferLastIndex": {
"type": {
"kind": "base",
"name": "short"
},
"offset": 58
},
"BufferStart": {
"type": {
"kind": "base",
"name": "short"
},
"offset": 88
}
},
"kind": "struct",
"size": 72
},
"_CHAR_ROW_CELL": {
"fields": {
"Text": {
"type": {
"kind": "struct",
"name": "nt_symbols!_UNICODE_STRING"
},
"offset": 0
},
"DbcsAttribute": {
"type": {
"kind": "base",
"name": "unsigned int"
},
"offset": 2
}
},
"kind": "struct",
"size": 3
},
"_CHAR_ROW_CELL_ARRAY": {
"fields": {
"Chars": {
"type": {
"count": 1,
"kind": "array",
"subtype": {
"kind": "struct",
"name": "_CHAR_ROW_CELL"
}
},
"offset": 0
}
},
"kind": "struct",
"size": 8
},
"_ROW": {
"fields": {
"CharRow": {
"type": {
"kind": "pointer",
"subtype": {
"kind": "struct",
"name": "_CHAR_ROW_CELL_ARRAY"
}
},
"offset": 8
},
"RowLength": {
"type": {
"kind": "base",
"name": "short"
},
"offset": 64
},
"Index": {
"type": {
"kind": "base",
"name": "short"
},
"offset": 72
},
"RowLength2": {
"type": {
"kind": "base",
"name": "short"
},
"offset": 80
},
"TextBuffer": {
"type": {
"kind": "pointer",
"subtype": {
"kind": "struct",
"name": "_TEXT_BUFFER_INFO"
}
},
"offset": 88
}
},
"kind": "struct",
"size": 96
},
"_DEQUE": {
"fields": {
"Blink": {
"type": {
"kind": "pointer",
"subtype": {
"kind": "base",
"name": "void"
}
},
"offset": 0
}
},
"kind": "struct",
"size": 8
},
"_EXE_ALIAS_LIST": {
"fields": {
"ListEntry": {
"type": {
"kind": "struct",
"name": "nt_symbols!_LIST_ENTRY"
},
"offset": 0
},
"ExeLength": {
"type": {
"kind": "base",
"name": "short"
},
"offset": 8
},
"ExeName": {
"type": {
"kind": "pointer",
"subtype": {
"kind": "base",
"name": "string"
}
},
"offset": 24
},
"AliasList": {
"type": {
"kind": "struct",
"name": "nt_symbols!_LIST_ENTRY"
},
"offset": 32
}
},
"kind": "struct",
"size": 48
},
"_ALIAS": {
"fields": {
"ListEntry": {
"type": {
"kind": "struct",
"name": "nt_symbols!_LIST_ENTRY"
},
"offset": 0
},
"Source": {
"type": {
"kind": "struct",
"name": "_COMMAND"
},
"offset": 16
},
"Target": {
"type": {
"kind": "struct",
"name": "_COMMAND"
},
"offset": 48
}
},
"kind": "struct",
"size": 32
}
},
"metadata": {
"producer": {
"version": "0.0.1",
"name": "Dave Lassalle",
"datetime": "2024-07-31T15:05:35-06:00"
},
"format": "4.1.0"
}
}
@@ -0,0 +1,649 @@
{
"symbols": {},
"enums": {},
"base_types": {
"unsigned long": {
"kind": "int",
"size": 4,
"signed": false,
"endian": "little"
},
"unsigned char": {
"kind": "char",
"size": 1,
"signed": false,
"endian": "little"
},
"pointer": {
"kind": "int",
"size": 8,
"signed": false,
"endian": "little"
},
"unsigned int": {
"kind": "int",
"size": 4,
"signed": false,
"endian": "little"
},
"unsigned short": {
"kind": "int",
"size": 2,
"signed": false,
"endian": "little"
},
"short": {
"kind": "int",
"size": 2,
"signed": true,
"endian": "little"
},
"long": {
"kind": "int",
"size": 4,
"signed": false,
"endian": "little"
}
},
"user_types": {
"_CONSOLE_INFORMATION": {
"fields": {
"ScreenX": {
"type": {
"kind": "base",
"name": "short"
},
"offset": 32
},
"ScreenY": {
"type": {
"kind": "base",
"name": "short"
},
"offset": 34
},
"CommandHistorySize": {
"type": {
"kind": "base",
"name": "unsigned short"
},
"offset": 144
},
"HistoryBufferMax": {
"type": {
"kind": "base",
"name": "unsigned short"
},
"offset": 148
},
"OriginalTitle": {
"type": {
"kind": "pointer",
"subtype": {
"kind": "base",
"name": "string"
}
},
"offset": 1752
},
"Title": {
"type": {
"kind": "pointer",
"subtype": {
"kind": "base",
"name": "string"
}
},
"offset": 1688
},
"GetScreenBuffer": {
"type": {
"kind": "pointer",
"subtype": {
"kind": "struct",
"name": "_SCREEN_INFORMATION"
}
},
"offset": 1816
},
"CurrentScreenBuffer": {
"type": {
"kind": "pointer",
"subtype": {
"kind": "struct",
"name": "_SCREEN_INFORMATION"
}
},
"offset": 1392
},
"ConsoleProcessList": {
"type": {
"kind": "pointer",
"subtype": {
"kind": "struct",
"name": "nt_symbols!_LIST_ENTRY"
}
},
"offset": 1368
},
"ProcessCount": {
"type": {
"kind": "base",
"name": "unsigned short"
},
"offset": 1376
},
"HistoryList": {
"type": {
"kind": "pointer",
"subtype": {
"kind": "struct",
"name": "nt_symbols!_LIST_ENTRY"
}
},
"offset": -352
},
"HistoryBufferCount": {
"type": {
"kind": "base",
"name": "unsigned short"
},
"offset": -344
},
"ExeAliasList": {
"type": {
"kind": "struct",
"name": "nt_symbols!_LIST_ENTRY"
},
"offset": 1436
}
},
"kind": "struct",
"size": 140
},
"_VECTOR": {
"fields": {
"Begin": {
"type": {
"kind": "pointer",
"subtype": {
"kind": "struct",
"name": "_COMMAND"
}
},
"offset": 0
},
"End": {
"type": {
"kind": "pointer",
"subtype": {
"kind": "struct",
"name": "_COMMAND"
}
},
"offset": 8
},
"EndCapacity": {
"type": {
"kind": "pointer",
"subtype": {
"kind": "base",
"name": "unsigned long"
}
},
"offset": 16
}
},
"kind": "struct",
"size": 24
},
"_COMMAND": {
"fields": {
"Chars": {
"type": {
"count": 1,
"kind": "array",
"subtype": {
"kind": "base",
"name": "unsigned char"
}
},
"offset": 0
},
"Pointer": {
"type": {
"kind": "pointer",
"subtype": {
"kind": "base",
"name": "string"
}
},
"offset": 0
},
"Length": {
"type": {
"kind": "base",
"name": "unsigned int"
},
"offset": 16
},
"Allocated": {
"type": {
"kind": "base",
"name": "unsigned int"
},
"offset": 24
}
},
"kind": "struct",
"size": 32
},
"_CONSOLE_PROCESS_LIST": {
"fields": {
"ListEntry": {
"type": {
"kind": "struct",
"name": "nt_symbols!_LIST_ENTRY"
},
"offset": 0
},
"ConsoleProcess": {
"type": {
"kind": "pointer",
"subtype": {
"kind": "struct",
"name": "_CONSOLE_PROCESS"
}
},
"offset": 16
}
},
"kind": "struct",
"size": 24
},
"_CONSOLE_PROCESS_HANDLE": {
"fields": {
"ProcessHandle": {
"type": {
"kind": "base",
"name": "unsigned int"
},
"offset": 48
}
},
"kind": "struct",
"size": 52
},
"_CONSOLE_PROCESS": {
"fields": {
"ProcessId": {
"type": {
"kind": "base",
"name": "unsigned int"
},
"offset": 28
},
"ThreadId": {
"type": {
"kind": "base",
"name": "unsigned int"
},
"offset": 32
},
"ProcessHandle": {
"type": {
"kind": "base",
"name": "unsigned int"
},
"offset": 48
}
},
"kind": "struct",
"size": 24
},
"_COMMAND_HISTORY": {
"fields": {
"ListEntry": {
"type": {
"kind": "struct",
"name": "nt_symbols!_LIST_ENTRY"
},
"offset": 0
},
"CommandBucket": {
"type": {
"kind": "struct",
"name": "_VECTOR"
},
"offset": 16
},
"CommandCountMax": {
"type": {
"kind": "base",
"name": "unsigned short"
},
"offset": 40
},
"Application": {
"type": {
"kind": "struct",
"name": "_COMMAND"
},
"offset": 48
},
"ConsoleProcessHandle": {
"type": {
"kind": "pointer",
"subtype": {
"kind": "struct",
"name": "_CONSOLE_PROCESS_HANDLE"
}
},
"offset": 80
},
"Flags": {
"type": {
"kind": "base",
"name": "unsigned short"
},
"offset": 88
},
"LastDisplayed": {
"type": {
"kind": "base",
"name": "short"
},
"offset": 92
}
},
"kind": "struct",
"size": 96
},
"_SCREEN_INFORMATION": {
"fields": {
"TextBufferInfo": {
"type": {
"kind": "pointer",
"subtype": {
"kind": "struct",
"name": "_TEXT_BUFFER_INFO"
}
},
"offset": 56
},
"Next": {
"type": {
"kind": "pointer",
"subtype": {
"kind": "struct",
"name": "_SCREEN_INFORMATION"
}
},
"offset": 64
}
},
"kind": "struct",
"size": 72
},
"_ROW_POINTER": {
"fields": {
"Row": {
"type": {
"kind": "pointer",
"subtype": {
"kind": "struct",
"name": "_ROW"
}
},
"offset": 0
}
},
"kind": "struct",
"size": 8
},
"_ROWS_ARRAY": {
"fields": {
"Rows": {
"type": {
"count": 1,
"kind": "array",
"subtype": {
"kind": "struct",
"name": "_ROW_POINTER"
}
},
"offset": 0
}
},
"kind": "struct",
"size": 8
},
"_TEXT_BUFFER_INFO": {
"fields": {
"BufferDeque": {
"type": {
"kind": "pointer",
"subtype": {
"kind": "struct",
"name": "_DEQUE"
}
},
"offset": 0
},
"BufferRows": {
"type": {
"kind": "pointer",
"subtype": {
"kind": "struct",
"name": "_ROWS_ARRAY"
}
},
"offset": 8
},
"BufferCapacity": {
"type": {
"kind": "base",
"name": "short"
},
"offset": 32
},
"BufferEnd": {
"type": {
"kind": "base",
"name": "short"
},
"offset": 56
},
"BufferLastIndex": {
"type": {
"kind": "base",
"name": "short"
},
"offset": 58
},
"BufferStart": {
"type": {
"kind": "base",
"name": "short"
},
"offset": 88
}
},
"kind": "struct",
"size": 72
},
"_CHAR_ROW_CELL": {
"fields": {
"Text": {
"type": {
"kind": "struct",
"name": "nt_symbols!_UNICODE_STRING"
},
"offset": 0
},
"DbcsAttribute": {
"type": {
"kind": "base",
"name": "unsigned int"
},
"offset": 2
}
},
"kind": "struct",
"size": 3
},
"_CHAR_ROW_CELL_ARRAY": {
"fields": {
"Chars": {
"type": {
"count": 1,
"kind": "array",
"subtype": {
"kind": "struct",
"name": "_CHAR_ROW_CELL"
}
},
"offset": 0
}
},
"kind": "struct",
"size": 8
},
"_ROW": {
"fields": {
"CharRow": {
"type": {
"kind": "pointer",
"subtype": {
"kind": "struct",
"name": "_CHAR_ROW_CELL_ARRAY"
}
},
"offset": 8
},
"RowLength": {
"type": {
"kind": "base",
"name": "short"
},
"offset": 64
},
"Index": {
"type": {
"kind": "base",
"name": "short"
},
"offset": 72
},
"RowLength2": {
"type": {
"kind": "base",
"name": "short"
},
"offset": 80
},
"TextBuffer": {
"type": {
"kind": "pointer",
"subtype": {
"kind": "struct",
"name": "_TEXT_BUFFER_INFO"
}
},
"offset": 88
}
},
"kind": "struct",
"size": 96
},
"_DEQUE": {
"fields": {
"Blink": {
"type": {
"kind": "pointer",
"subtype": {
"kind": "base",
"name": "void"
}
},
"offset": 0
}
},
"kind": "struct",
"size": 8
},
"_EXE_ALIAS_LIST": {
"fields": {
"ListEntry": {
"type": {
"kind": "struct",
"name": "nt_symbols!_LIST_ENTRY"
},
"offset": 0
},
"ExeLength": {
"type": {
"kind": "base",
"name": "short"
},
"offset": 8
},
"ExeName": {
"type": {
"kind": "pointer",
"subtype": {
"kind": "base",
"name": "string"
}
},
"offset": 24
},
"AliasList": {
"type": {
"kind": "struct",
"name": "nt_symbols!_LIST_ENTRY"
},
"offset": 32
}
},
"kind": "struct",
"size": 48
},
"_ALIAS": {
"fields": {
"ListEntry": {
"type": {
"kind": "struct",
"name": "nt_symbols!_LIST_ENTRY"
},
"offset": 0
},
"Source": {
"type": {
"kind": "struct",
"name": "_COMMAND"
},
"offset": 16
},
"Target": {
"type": {
"kind": "struct",
"name": "_COMMAND"
},
"offset": 48
}
},
"kind": "struct",
"size": 32
}
},
"metadata": {
"producer": {
"version": "0.0.1",
"name": "Dave Lassalle",
"datetime": "2024-07-31T15:05:35-06:00"
},
"format": "4.1.0"
}
}
@@ -0,0 +1,655 @@
{
"symbols": {},
"enums": {},
"base_types": {
"unsigned long": {
"kind": "int",
"size": 4,
"signed": false,
"endian": "little"
},
"unsigned char": {
"kind": "char",
"size": 1,
"signed": false,
"endian": "little"
},
"pointer": {
"kind": "int",
"size": 8,
"signed": false,
"endian": "little"
},
"unsigned int": {
"kind": "int",
"size": 4,
"signed": false,
"endian": "little"
},
"unsigned short": {
"kind": "int",
"size": 2,
"signed": false,
"endian": "little"
},
"short": {
"kind": "int",
"size": 2,
"signed": true,
"endian": "little"
},
"long": {
"kind": "int",
"size": 4,
"signed": false,
"endian": "little"
}
},
"user_types": {
"_CONSOLE_INFORMATION": {
"fields": {
"ScreenX": {
"type": {
"kind": "base",
"name": "short"
},
"offset": 24
},
"ScreenY": {
"type": {
"kind": "base",
"name": "short"
},
"offset": 26
},
"CommandHistorySize": {
"type": {
"kind": "base",
"name": "unsigned short"
},
"offset": 136
},
"HistoryBufferMax": {
"type": {
"kind": "base",
"name": "unsigned short"
},
"offset": 140
},
"OriginalTitle": {
"type": {
"kind": "pointer",
"subtype": {
"kind": "base",
"name": "string"
}
},
"offset": 1616
},
"Title": {
"type": {
"kind": "pointer",
"subtype": {
"kind": "base",
"name": "string"
}
},
"offset": 1552
},
"GetScreenBuffer": {
"type": {
"kind": "pointer",
"subtype": {
"kind": "struct",
"name": "_SCREEN_INFORMATION"
}
},
"offset": 1680
},
"CurrentScreenBuffer": {
"type": {
"kind": "pointer",
"subtype": {
"kind": "struct",
"name": "_SCREEN_INFORMATION"
}
},
"offset": 1296
},
"ConsoleProcessList": {
"type": {
"kind": "pointer",
"subtype": {
"kind": "struct",
"name": "nt_symbols!_LIST_ENTRY"
}
},
"offset": 1272
},
"ProcessCount": {
"type": {
"kind": "base",
"name": "unsigned short"
},
"offset": 1280
},
"HistoryList": {
"type": {
"kind": "pointer",
"subtype": {
"kind": "struct",
"name": "nt_symbols!_LIST_ENTRY"
}
},
"offset": 9320
},
"HistoryBufferCount": {
"type": {
"kind": "base",
"name": "unsigned short"
},
"offset": 9328
},
"ExeAliasList": {
"type": {
"kind": "pointer",
"subtype": {
"kind": "struct",
"name": "nt_symbols!_LIST_ENTRY"
}
},
"offset": 2410
}
},
"kind": "struct",
"size": 140
},
"_VECTOR": {
"fields": {
"Begin": {
"type": {
"kind": "pointer",
"subtype": {
"kind": "struct",
"name": "_COMMAND"
}
},
"offset": 0
},
"End": {
"type": {
"kind": "pointer",
"subtype": {
"kind": "struct",
"name": "_COMMAND"
}
},
"offset": 8
},
"EndCapacity": {
"type": {
"kind": "pointer",
"subtype": {
"kind": "base",
"name": "unsigned long"
}
},
"offset": 16
}
},
"kind": "struct",
"size": 24
},
"_COMMAND": {
"fields": {
"Chars": {
"type": {
"count": 1,
"kind": "array",
"subtype": {
"kind": "base",
"name": "unsigned char"
}
},
"offset": 0
},
"Pointer": {
"type": {
"kind": "pointer",
"subtype": {
"kind": "base",
"name": "string"
}
},
"offset": 0
},
"Length": {
"type": {
"kind": "base",
"name": "unsigned int"
},
"offset": 16
},
"Allocated": {
"type": {
"kind": "base",
"name": "unsigned int"
},
"offset": 24
}
},
"kind": "struct",
"size": 32
},
"_CONSOLE_PROCESS_LIST": {
"fields": {
"ListEntry": {
"type": {
"kind": "struct",
"name": "nt_symbols!_LIST_ENTRY"
},
"offset": 0
},
"ConsoleProcess": {
"type": {
"kind": "pointer",
"subtype": {
"kind": "struct",
"name": "_CONSOLE_PROCESS"
}
},
"offset": 16
}
},
"kind": "struct",
"size": 24
},
"_CONSOLE_PROCESS_HANDLE": {
"fields": {
"ProcessHandle": {
"type": {
"kind": "base",
"name": "unsigned int"
},
"offset": 48
}
},
"kind": "struct",
"size": 52
},
"_CONSOLE_PROCESS": {
"fields": {
"ProcessId": {
"type": {
"kind": "base",
"name": "unsigned int"
},
"offset": 28
},
"ThreadId": {
"type": {
"kind": "base",
"name": "unsigned int"
},
"offset": 32
},
"ProcessHandle": {
"type": {
"kind": "base",
"name": "unsigned int"
},
"offset": 48
}
},
"kind": "struct",
"size": 24
},
"_COMMAND_HISTORY": {
"fields": {
"ListEntry": {
"type": {
"kind": "struct",
"name": "nt_symbols!_LIST_ENTRY"
},
"offset": 0
},
"CommandBucket": {
"type": {
"kind": "struct",
"name": "_VECTOR"
},
"offset": 16
},
"CommandCountMax": {
"type": {
"kind": "base",
"name": "unsigned short"
},
"offset": 40
},
"Application": {
"type": {
"kind": "struct",
"name": "_COMMAND"
},
"offset": 48
},
"ConsoleProcessHandle": {
"type": {
"kind": "pointer",
"subtype": {
"kind": "struct",
"name": "_CONSOLE_PROCESS_HANDLE"
}
},
"offset": 80
},
"Flags": {
"type": {
"kind": "base",
"name": "unsigned short"
},
"offset": 88
},
"LastDisplayed": {
"type": {
"kind": "base",
"name": "short"
},
"offset": 92
}
},
"kind": "struct",
"size": 96
},
"_SCREEN_INFORMATION": {
"fields": {
"TextBufferInfo": {
"type": {
"kind": "pointer",
"subtype": {
"kind": "struct",
"name": "_TEXT_BUFFER_INFO"
}
},
"offset": 56
},
"Next": {
"type": {
"kind": "pointer",
"subtype": {
"kind": "struct",
"name": "_SCREEN_INFORMATION"
}
},
"offset": 64
}
},
"kind": "struct",
"size": 72
},
"_ROW_POINTER": {
"fields": {
"Row": {
"type": {
"kind": "pointer",
"subtype": {
"kind": "struct",
"name": "_ROW"
}
},
"offset": 0
}
},
"kind": "struct",
"size": 8
},
"_ROWS_ARRAY": {
"fields": {
"Rows": {
"type": {
"count": 1,
"kind": "array",
"subtype": {
"kind": "struct",
"name": "_ROW_POINTER"
}
},
"offset": 0
}
},
"kind": "struct",
"size": 8
},
"_TEXT_BUFFER_INFO": {
"fields": {
"BufferDeque": {
"type": {
"kind": "pointer",
"subtype": {
"kind": "struct",
"name": "_DEQUE"
}
},
"offset": 0
},
"BufferRows": {
"type": {
"kind": "pointer",
"subtype": {
"kind": "struct",
"name": "_ROWS_ARRAY"
}
},
"offset": 16
},
"BufferCapacity": {
"type": {
"kind": "base",
"name": "short"
},
"offset": 40
},
"ThisBuffer": {
"type": {
"kind": "pointer",
"subtype": {
"kind": "struct",
"name": "_TEXT_BUFFER_INFO"
}
},
"offset": 48
},
"BufferEnd": {
"type": {
"kind": "base",
"name": "short"
},
"offset": 54
},
"BufferLastIndex": {
"type": {
"kind": "base",
"name": "short"
},
"offset": 56
},
"BufferStart": {
"type": {
"kind": "base",
"name": "short"
},
"offset": 96
}
},
"kind": "struct",
"size": 72
},
"_CHAR_ROW_CELL": {
"fields": {
"Text": {
"type": {
"kind": "struct",
"name": "nt_symbols!_UNICODE_STRING"
},
"offset": 0
},
"DbcsAttribute": {
"type": {
"kind": "base",
"name": "unsigned int"
},
"offset": 2
}
},
"kind": "struct",
"size": 3
},
"_CHAR_ROW_CELL_ARRAY": {
"fields": {
"Chars": {
"type": {
"count": 1,
"kind": "array",
"subtype": {
"kind": "struct",
"name": "_CHAR_ROW_CELL"
}
},
"offset": 0
}
},
"kind": "struct",
"size": 8
},
"_ROW": {
"fields": {
"CharRow": {
"type": {
"kind": "pointer",
"subtype": {
"kind": "struct",
"name": "_CHAR_ROW_CELL_ARRAY"
}
},
"offset": 8
},
"RowLength": {
"type": {
"kind": "base",
"name": "short"
},
"offset": 64
},
"Index": {
"type": {
"kind": "base",
"name": "short"
},
"offset": 72
},
"RowLength2": {
"type": {
"kind": "base",
"name": "short"
},
"offset": 80
},
"TextBuffer": {
"type": {
"kind": "pointer",
"subtype": {
"kind": "struct",
"name": "_TEXT_BUFFER_INFO"
}
},
"offset": 88
}
},
"kind": "struct",
"size": 96
},
"_DEQUE": {
"fields": {
"Blink": {
"type": {
"kind": "pointer",
"subtype": {
"kind": "base",
"name": "void"
}
},
"offset": 0
}
},
"kind": "struct",
"size": 8
},
"_EXE_ALIAS_LIST": {
"fields": {
"ListEntry": {
"type": {
"kind": "struct",
"name": "nt_symbols!_LIST_ENTRY"
},
"offset": 0
},
"ExeName": {
"type": {
"kind": "struct",
"name": "_COMMAND"
},
"offset": 16
},
"AliasList": {
"type": {
"kind": "pointer",
"subtype": {
"kind": "struct",
"name": "nt_symbols!_LIST_ENTRY"
}
},
"offset": 56
}
},
"kind": "struct",
"size": 64
},
"_ALIAS": {
"fields": {
"ListEntry": {
"type": {
"kind": "struct",
"name": "nt_symbols!_LIST_ENTRY"
},
"offset": 0
},
"Source": {
"type": {
"kind": "struct",
"name": "_COMMAND"
},
"offset": 16
},
"Target": {
"type": {
"kind": "struct",
"name": "_COMMAND"
},
"offset": 48
}
},
"kind": "struct",
"size": 32
}
},
"metadata": {
"producer": {
"version": "0.0.1",
"name": "Dave Lassalle",
"datetime": "2024-07-31T15:05:35-06:00"
},
"format": "4.1.0"
}
}
@@ -0,0 +1,655 @@
{
"symbols": {},
"enums": {},
"base_types": {
"unsigned long": {
"kind": "int",
"size": 4,
"signed": false,
"endian": "little"
},
"unsigned char": {
"kind": "char",
"size": 1,
"signed": false,
"endian": "little"
},
"pointer": {
"kind": "int",
"size": 8,
"signed": false,
"endian": "little"
},
"unsigned int": {
"kind": "int",
"size": 4,
"signed": false,
"endian": "little"
},
"unsigned short": {
"kind": "int",
"size": 2,
"signed": false,
"endian": "little"
},
"short": {
"kind": "int",
"size": 2,
"signed": true,
"endian": "little"
},
"long": {
"kind": "int",
"size": 4,
"signed": false,
"endian": "little"
}
},
"user_types": {
"_CONSOLE_INFORMATION": {
"fields": {
"ScreenX": {
"type": {
"kind": "base",
"name": "short"
},
"offset": 24
},
"ScreenY": {
"type": {
"kind": "base",
"name": "short"
},
"offset": 26
},
"CommandHistorySize": {
"type": {
"kind": "base",
"name": "unsigned short"
},
"offset": 136
},
"HistoryBufferMax": {
"type": {
"kind": "base",
"name": "unsigned short"
},
"offset": 140
},
"OriginalTitle": {
"type": {
"kind": "pointer",
"subtype": {
"kind": "base",
"name": "string"
}
},
"offset": 1616
},
"Title": {
"type": {
"kind": "pointer",
"subtype": {
"kind": "base",
"name": "string"
}
},
"offset": 1552
},
"GetScreenBuffer": {
"type": {
"kind": "pointer",
"subtype": {
"kind": "struct",
"name": "_SCREEN_INFORMATION"
}
},
"offset": 1680
},
"CurrentScreenBuffer": {
"type": {
"kind": "pointer",
"subtype": {
"kind": "struct",
"name": "_SCREEN_INFORMATION"
}
},
"offset": 1296
},
"ConsoleProcessList": {
"type": {
"kind": "pointer",
"subtype": {
"kind": "struct",
"name": "nt_symbols!_LIST_ENTRY"
}
},
"offset": 1272
},
"ProcessCount": {
"type": {
"kind": "base",
"name": "unsigned short"
},
"offset": 1280
},
"HistoryList": {
"type": {
"kind": "pointer",
"subtype": {
"kind": "struct",
"name": "nt_symbols!_LIST_ENTRY"
}
},
"offset": 9176
},
"HistoryBufferCount": {
"type": {
"kind": "base",
"name": "unsigned short"
},
"offset": 9184
},
"ExeAliasList": {
"type": {
"kind": "pointer",
"subtype": {
"kind": "struct",
"name": "nt_symbols!_LIST_ENTRY"
}
},
"offset": 9232
}
},
"kind": "struct",
"size": 140
},
"_VECTOR": {
"fields": {
"Begin": {
"type": {
"kind": "pointer",
"subtype": {
"kind": "struct",
"name": "_COMMAND"
}
},
"offset": 0
},
"End": {
"type": {
"kind": "pointer",
"subtype": {
"kind": "struct",
"name": "_COMMAND"
}
},
"offset": 8
},
"EndCapacity": {
"type": {
"kind": "pointer",
"subtype": {
"kind": "base",
"name": "unsigned long"
}
},
"offset": 16
}
},
"kind": "struct",
"size": 24
},
"_COMMAND": {
"fields": {
"Chars": {
"type": {
"count": 1,
"kind": "array",
"subtype": {
"kind": "base",
"name": "unsigned char"
}
},
"offset": 0
},
"Pointer": {
"type": {
"kind": "pointer",
"subtype": {
"kind": "base",
"name": "string"
}
},
"offset": 0
},
"Length": {
"type": {
"kind": "base",
"name": "unsigned int"
},
"offset": 16
},
"Allocated": {
"type": {
"kind": "base",
"name": "unsigned int"
},
"offset": 24
}
},
"kind": "struct",
"size": 32
},
"_CONSOLE_PROCESS_LIST": {
"fields": {
"ListEntry": {
"type": {
"kind": "struct",
"name": "nt_symbols!_LIST_ENTRY"
},
"offset": 0
},
"ConsoleProcess": {
"type": {
"kind": "pointer",
"subtype": {
"kind": "struct",
"name": "_CONSOLE_PROCESS"
}
},
"offset": 16
}
},
"kind": "struct",
"size": 24
},
"_CONSOLE_PROCESS_HANDLE": {
"fields": {
"ProcessHandle": {
"type": {
"kind": "base",
"name": "unsigned int"
},
"offset": 48
}
},
"kind": "struct",
"size": 52
},
"_CONSOLE_PROCESS": {
"fields": {
"ProcessId": {
"type": {
"kind": "base",
"name": "unsigned int"
},
"offset": 28
},
"ThreadId": {
"type": {
"kind": "base",
"name": "unsigned int"
},
"offset": 32
},
"ProcessHandle": {
"type": {
"kind": "base",
"name": "unsigned int"
},
"offset": 48
}
},
"kind": "struct",
"size": 24
},
"_COMMAND_HISTORY": {
"fields": {
"ListEntry": {
"type": {
"kind": "struct",
"name": "nt_symbols!_LIST_ENTRY"
},
"offset": 0
},
"CommandBucket": {
"type": {
"kind": "struct",
"name": "_VECTOR"
},
"offset": 16
},
"CommandCountMax": {
"type": {
"kind": "base",
"name": "unsigned short"
},
"offset": 40
},
"Application": {
"type": {
"kind": "struct",
"name": "_COMMAND"
},
"offset": 48
},
"ConsoleProcessHandle": {
"type": {
"kind": "pointer",
"subtype": {
"kind": "struct",
"name": "_CONSOLE_PROCESS_HANDLE"
}
},
"offset": 80
},
"Flags": {
"type": {
"kind": "base",
"name": "unsigned short"
},
"offset": 88
},
"LastDisplayed": {
"type": {
"kind": "base",
"name": "short"
},
"offset": 92
}
},
"kind": "struct",
"size": 96
},
"_SCREEN_INFORMATION": {
"fields": {
"TextBufferInfo": {
"type": {
"kind": "pointer",
"subtype": {
"kind": "struct",
"name": "_TEXT_BUFFER_INFO"
}
},
"offset": 56
},
"Next": {
"type": {
"kind": "pointer",
"subtype": {
"kind": "struct",
"name": "_SCREEN_INFORMATION"
}
},
"offset": 64
}
},
"kind": "struct",
"size": 72
},
"_ROW_POINTER": {
"fields": {
"Row": {
"type": {
"kind": "pointer",
"subtype": {
"kind": "struct",
"name": "_ROW"
}
},
"offset": 0
}
},
"kind": "struct",
"size": 8
},
"_ROWS_ARRAY": {
"fields": {
"Rows": {
"type": {
"count": 1,
"kind": "array",
"subtype": {
"kind": "struct",
"name": "_ROW_POINTER"
}
},
"offset": 0
}
},
"kind": "struct",
"size": 8
},
"_TEXT_BUFFER_INFO": {
"fields": {
"BufferDeque": {
"type": {
"kind": "pointer",
"subtype": {
"kind": "struct",
"name": "_DEQUE"
}
},
"offset": 0
},
"BufferRows": {
"type": {
"kind": "pointer",
"subtype": {
"kind": "struct",
"name": "_ROWS_ARRAY"
}
},
"offset": 16
},
"BufferCapacity": {
"type": {
"kind": "base",
"name": "short"
},
"offset": 40
},
"ThisBuffer": {
"type": {
"kind": "pointer",
"subtype": {
"kind": "struct",
"name": "_TEXT_BUFFER_INFO"
}
},
"offset": 48
},
"BufferEnd": {
"type": {
"kind": "base",
"name": "short"
},
"offset": 54
},
"BufferLastIndex": {
"type": {
"kind": "base",
"name": "short"
},
"offset": 56
},
"BufferStart": {
"type": {
"kind": "base",
"name": "short"
},
"offset": 96
}
},
"kind": "struct",
"size": 72
},
"_CHAR_ROW_CELL": {
"fields": {
"Text": {
"type": {
"kind": "struct",
"name": "nt_symbols!_UNICODE_STRING"
},
"offset": 0
},
"DbcsAttribute": {
"type": {
"kind": "base",
"name": "unsigned int"
},
"offset": 2
}
},
"kind": "struct",
"size": 3
},
"_CHAR_ROW_CELL_ARRAY": {
"fields": {
"Chars": {
"type": {
"count": 1,
"kind": "array",
"subtype": {
"kind": "struct",
"name": "_CHAR_ROW_CELL"
}
},
"offset": 0
}
},
"kind": "struct",
"size": 8
},
"_ROW": {
"fields": {
"CharRow": {
"type": {
"kind": "pointer",
"subtype": {
"kind": "struct",
"name": "_CHAR_ROW_CELL_ARRAY"
}
},
"offset": 8
},
"RowLength": {
"type": {
"kind": "base",
"name": "short"
},
"offset": 64
},
"Index": {
"type": {
"kind": "base",
"name": "short"
},
"offset": 72
},
"RowLength2": {
"type": {
"kind": "base",
"name": "short"
},
"offset": 80
},
"TextBuffer": {
"type": {
"kind": "pointer",
"subtype": {
"kind": "struct",
"name": "_TEXT_BUFFER_INFO"
}
},
"offset": 88
}
},
"kind": "struct",
"size": 96
},
"_DEQUE": {
"fields": {
"Blink": {
"type": {
"kind": "pointer",
"subtype": {
"kind": "base",
"name": "void"
}
},
"offset": 0
}
},
"kind": "struct",
"size": 8
},
"_EXE_ALIAS_LIST": {
"fields": {
"ListEntry": {
"type": {
"kind": "struct",
"name": "nt_symbols!_LIST_ENTRY"
},
"offset": 0
},
"ExeName": {
"type": {
"kind": "struct",
"name": "_COMMAND"
},
"offset": 16
},
"AliasList": {
"type": {
"kind": "pointer",
"subtype": {
"kind": "struct",
"name": "nt_symbols!_LIST_ENTRY"
}
},
"offset": 56
}
},
"kind": "struct",
"size": 64
},
"_ALIAS": {
"fields": {
"ListEntry": {
"type": {
"kind": "struct",
"name": "nt_symbols!_LIST_ENTRY"
},
"offset": 0
},
"Source": {
"type": {
"kind": "struct",
"name": "_COMMAND"
},
"offset": 16
},
"Target": {
"type": {
"kind": "struct",
"name": "_COMMAND"
},
"offset": 48
}
},
"kind": "struct",
"size": 32
}
},
"metadata": {
"producer": {
"version": "0.0.1",
"name": "Dave Lassalle",
"datetime": "2024-07-31T15:05:35-06:00"
},
"format": "4.1.0"
}
}
@@ -0,0 +1,655 @@
{
"symbols": {},
"enums": {},
"base_types": {
"unsigned long": {
"kind": "int",
"size": 4,
"signed": false,
"endian": "little"
},
"unsigned char": {
"kind": "char",
"size": 1,
"signed": false,
"endian": "little"
},
"pointer": {
"kind": "int",
"size": 8,
"signed": false,
"endian": "little"
},
"unsigned int": {
"kind": "int",
"size": 4,
"signed": false,
"endian": "little"
},
"unsigned short": {
"kind": "int",
"size": 2,
"signed": false,
"endian": "little"
},
"short": {
"kind": "int",
"size": 2,
"signed": true,
"endian": "little"
},
"long": {
"kind": "int",
"size": 4,
"signed": false,
"endian": "little"
}
},
"user_types": {
"_CONSOLE_INFORMATION": {
"fields": {
"ScreenX": {
"type": {
"kind": "base",
"name": "short"
},
"offset": 24
},
"ScreenY": {
"type": {
"kind": "base",
"name": "short"
},
"offset": 26
},
"CommandHistorySize": {
"type": {
"kind": "base",
"name": "unsigned short"
},
"offset": 136
},
"HistoryBufferMax": {
"type": {
"kind": "base",
"name": "unsigned short"
},
"offset": 140
},
"OriginalTitle": {
"type": {
"kind": "pointer",
"subtype": {
"kind": "base",
"name": "string"
}
},
"offset": 1616
},
"Title": {
"type": {
"kind": "pointer",
"subtype": {
"kind": "base",
"name": "string"
}
},
"offset": 1552
},
"GetScreenBuffer": {
"type": {
"kind": "pointer",
"subtype": {
"kind": "struct",
"name": "_SCREEN_INFORMATION"
}
},
"offset": 1680
},
"CurrentScreenBuffer": {
"type": {
"kind": "pointer",
"subtype": {
"kind": "struct",
"name": "_SCREEN_INFORMATION"
}
},
"offset": 1296
},
"ConsoleProcessList": {
"type": {
"kind": "pointer",
"subtype": {
"kind": "struct",
"name": "nt_symbols!_LIST_ENTRY"
}
},
"offset": 1272
},
"ProcessCount": {
"type": {
"kind": "base",
"name": "unsigned short"
},
"offset": 1280
},
"HistoryList": {
"type": {
"kind": "pointer",
"subtype": {
"kind": "struct",
"name": "nt_symbols!_LIST_ENTRY"
}
},
"offset": -288
},
"HistoryBufferCount": {
"type": {
"kind": "base",
"name": "unsigned short"
},
"offset": -280
},
"ExeAliasList": {
"type": {
"kind": "pointer",
"subtype": {
"kind": "struct",
"name": "nt_symbols!_LIST_ENTRY"
}
},
"offset": -376
}
},
"kind": "struct",
"size": 140
},
"_VECTOR": {
"fields": {
"Begin": {
"type": {
"kind": "pointer",
"subtype": {
"kind": "struct",
"name": "_COMMAND"
}
},
"offset": 0
},
"End": {
"type": {
"kind": "pointer",
"subtype": {
"kind": "struct",
"name": "_COMMAND"
}
},
"offset": 8
},
"EndCapacity": {
"type": {
"kind": "pointer",
"subtype": {
"kind": "base",
"name": "unsigned long"
}
},
"offset": 16
}
},
"kind": "struct",
"size": 24
},
"_COMMAND": {
"fields": {
"Chars": {
"type": {
"count": 1,
"kind": "array",
"subtype": {
"kind": "base",
"name": "unsigned char"
}
},
"offset": 0
},
"Pointer": {
"type": {
"kind": "pointer",
"subtype": {
"kind": "base",
"name": "string"
}
},
"offset": 0
},
"Length": {
"type": {
"kind": "base",
"name": "unsigned int"
},
"offset": 16
},
"Allocated": {
"type": {
"kind": "base",
"name": "unsigned int"
},
"offset": 24
}
},
"kind": "struct",
"size": 32
},
"_CONSOLE_PROCESS_LIST": {
"fields": {
"ListEntry": {
"type": {
"kind": "struct",
"name": "nt_symbols!_LIST_ENTRY"
},
"offset": 0
},
"ConsoleProcess": {
"type": {
"kind": "pointer",
"subtype": {
"kind": "struct",
"name": "_CONSOLE_PROCESS"
}
},
"offset": 16
}
},
"kind": "struct",
"size": 24
},
"_CONSOLE_PROCESS_HANDLE": {
"fields": {
"ProcessHandle": {
"type": {
"kind": "base",
"name": "unsigned int"
},
"offset": 48
}
},
"kind": "struct",
"size": 52
},
"_CONSOLE_PROCESS": {
"fields": {
"ProcessId": {
"type": {
"kind": "base",
"name": "unsigned int"
},
"offset": 28
},
"ThreadId": {
"type": {
"kind": "base",
"name": "unsigned int"
},
"offset": 32
},
"ProcessHandle": {
"type": {
"kind": "base",
"name": "unsigned int"
},
"offset": 48
}
},
"kind": "struct",
"size": 24
},
"_COMMAND_HISTORY": {
"fields": {
"ListEntry": {
"type": {
"kind": "struct",
"name": "nt_symbols!_LIST_ENTRY"
},
"offset": 0
},
"CommandBucket": {
"type": {
"kind": "struct",
"name": "_VECTOR"
},
"offset": 16
},
"CommandCountMax": {
"type": {
"kind": "base",
"name": "unsigned short"
},
"offset": 40
},
"Application": {
"type": {
"kind": "struct",
"name": "_COMMAND"
},
"offset": 48
},
"ConsoleProcessHandle": {
"type": {
"kind": "pointer",
"subtype": {
"kind": "struct",
"name": "_CONSOLE_PROCESS_HANDLE"
}
},
"offset": 80
},
"Flags": {
"type": {
"kind": "base",
"name": "unsigned short"
},
"offset": 88
},
"LastDisplayed": {
"type": {
"kind": "base",
"name": "short"
},
"offset": 92
}
},
"kind": "struct",
"size": 96
},
"_SCREEN_INFORMATION": {
"fields": {
"TextBufferInfo": {
"type": {
"kind": "pointer",
"subtype": {
"kind": "struct",
"name": "_TEXT_BUFFER_INFO"
}
},
"offset": 56
},
"Next": {
"type": {
"kind": "pointer",
"subtype": {
"kind": "struct",
"name": "_SCREEN_INFORMATION"
}
},
"offset": 64
}
},
"kind": "struct",
"size": 72
},
"_ROW_POINTER": {
"fields": {
"Row": {
"type": {
"kind": "pointer",
"subtype": {
"kind": "struct",
"name": "_ROW"
}
},
"offset": 0
}
},
"kind": "struct",
"size": 8
},
"_ROWS_ARRAY": {
"fields": {
"Rows": {
"type": {
"count": 1,
"kind": "array",
"subtype": {
"kind": "struct",
"name": "_ROW_POINTER"
}
},
"offset": 0
}
},
"kind": "struct",
"size": 8
},
"_TEXT_BUFFER_INFO": {
"fields": {
"BufferDeque": {
"type": {
"kind": "pointer",
"subtype": {
"kind": "struct",
"name": "_DEQUE"
}
},
"offset": 0
},
"BufferRows": {
"type": {
"kind": "pointer",
"subtype": {
"kind": "struct",
"name": "_ROWS_ARRAY"
}
},
"offset": 16
},
"BufferCapacity": {
"type": {
"kind": "base",
"name": "short"
},
"offset": 40
},
"ThisBuffer": {
"type": {
"kind": "pointer",
"subtype": {
"kind": "struct",
"name": "_TEXT_BUFFER_INFO"
}
},
"offset": 48
},
"BufferEnd": {
"type": {
"kind": "base",
"name": "short"
},
"offset": 54
},
"BufferLastIndex": {
"type": {
"kind": "base",
"name": "short"
},
"offset": 56
},
"BufferStart": {
"type": {
"kind": "base",
"name": "short"
},
"offset": 96
}
},
"kind": "struct",
"size": 72
},
"_CHAR_ROW_CELL": {
"fields": {
"Text": {
"type": {
"kind": "struct",
"name": "nt_symbols!_UNICODE_STRING"
},
"offset": 0
},
"DbcsAttribute": {
"type": {
"kind": "base",
"name": "unsigned int"
},
"offset": 2
}
},
"kind": "struct",
"size": 3
},
"_CHAR_ROW_CELL_ARRAY": {
"fields": {
"Chars": {
"type": {
"count": 1,
"kind": "array",
"subtype": {
"kind": "struct",
"name": "_CHAR_ROW_CELL"
}
},
"offset": 0
}
},
"kind": "struct",
"size": 8
},
"_ROW": {
"fields": {
"CharRow": {
"type": {
"kind": "pointer",
"subtype": {
"kind": "struct",
"name": "_CHAR_ROW_CELL_ARRAY"
}
},
"offset": 8
},
"RowLength": {
"type": {
"kind": "base",
"name": "short"
},
"offset": 64
},
"Index": {
"type": {
"kind": "base",
"name": "short"
},
"offset": 72
},
"RowLength2": {
"type": {
"kind": "base",
"name": "short"
},
"offset": 80
},
"TextBuffer": {
"type": {
"kind": "pointer",
"subtype": {
"kind": "struct",
"name": "_TEXT_BUFFER_INFO"
}
},
"offset": 88
}
},
"kind": "struct",
"size": 96
},
"_DEQUE": {
"fields": {
"Blink": {
"type": {
"kind": "pointer",
"subtype": {
"kind": "base",
"name": "void"
}
},
"offset": 0
}
},
"kind": "struct",
"size": 8
},
"_EXE_ALIAS_LIST": {
"fields": {
"ListEntry": {
"type": {
"kind": "struct",
"name": "nt_symbols!_LIST_ENTRY"
},
"offset": 0
},
"ExeName": {
"type": {
"kind": "struct",
"name": "_COMMAND"
},
"offset": 16
},
"AliasList": {
"type": {
"kind": "pointer",
"subtype": {
"kind": "struct",
"name": "nt_symbols!_LIST_ENTRY"
}
},
"offset": 56
}
},
"kind": "struct",
"size": 64
},
"_ALIAS": {
"fields": {
"ListEntry": {
"type": {
"kind": "struct",
"name": "nt_symbols!_LIST_ENTRY"
},
"offset": 0
},
"Source": {
"type": {
"kind": "struct",
"name": "_COMMAND"
},
"offset": 16
},
"Target": {
"type": {
"kind": "struct",
"name": "_COMMAND"
},
"offset": 48
}
},
"kind": "struct",
"size": 32
}
},
"metadata": {
"producer": {
"version": "0.0.1",
"name": "Dave Lassalle",
"datetime": "2024-07-31T15:05:35-06:00"
},
"format": "4.1.0"
}
}
@@ -0,0 +1,681 @@
{
"symbols": {},
"enums": {},
"base_types": {
"unsigned long": {
"kind": "int",
"size": 4,
"signed": false,
"endian": "little"
},
"unsigned char": {
"kind": "char",
"size": 1,
"signed": false,
"endian": "little"
},
"pointer": {
"kind": "int",
"size": 8,
"signed": false,
"endian": "little"
},
"unsigned int": {
"kind": "int",
"size": 4,
"signed": false,
"endian": "little"
},
"unsigned short": {
"kind": "int",
"size": 2,
"signed": false,
"endian": "little"
},
"short": {
"kind": "int",
"size": 2,
"signed": true,
"endian": "little"
},
"long": {
"kind": "int",
"size": 4,
"signed": false,
"endian": "little"
}
},
"user_types": {
"_CONSOLE_INFORMATION": {
"fields": {
"ScreenX": {
"type": {
"kind": "base",
"name": "short"
},
"offset": 24
},
"ScreenY": {
"type": {
"kind": "base",
"name": "short"
},
"offset": 26
},
"CommandHistorySize": {
"type": {
"kind": "base",
"name": "unsigned short"
},
"offset": 136
},
"HistoryBufferMax": {
"type": {
"kind": "base",
"name": "unsigned short"
},
"offset": 140
},
"OriginalTitle": {
"type": {
"kind": "pointer",
"subtype": {
"kind": "base",
"name": "string"
}
},
"offset": 1648
},
"Title": {
"type": {
"kind": "pointer",
"subtype": {
"kind": "base",
"name": "string"
}
},
"offset": 1616
},
"GetScreenBuffer": {
"type": {
"kind": "pointer",
"subtype": {
"kind": "struct",
"name": "_SCREEN_INFORMATION"
}
},
"offset": 1664
},
"CurrentScreenBuffer": {
"type": {
"kind": "pointer",
"subtype": {
"kind": "struct",
"name": "_SCREEN_INFORMATION"
}
},
"offset": 1296
},
"ConsoleProcessList": {
"type": {
"kind": "pointer",
"subtype": {
"kind": "struct",
"name": "nt_symbols!_LIST_ENTRY"
}
},
"offset": 1272
},
"ProcessCount": {
"type": {
"kind": "base",
"name": "unsigned short"
},
"offset": 1280
},
"HistoryList": {
"type": {
"kind": "pointer",
"subtype": {
"kind": "struct",
"name": "nt_symbols!_LIST_ENTRY"
}
},
"offset": -920
},
"HistoryBufferCount": {
"type": {
"kind": "base",
"name": "unsigned short"
},
"offset": -912
},
"ExeAliasList": {
"type": {
"kind": "pointer",
"subtype": {
"kind": "struct",
"name": "nt_symbols!_LIST_ENTRY"
}
},
"offset": -1008
}
},
"kind": "struct",
"size": 140
},
"_VECTOR": {
"fields": {
"Begin": {
"type": {
"kind": "pointer",
"subtype": {
"kind": "struct",
"name": "_COMMAND"
}
},
"offset": 0
},
"End": {
"type": {
"kind": "pointer",
"subtype": {
"kind": "struct",
"name": "_COMMAND"
}
},
"offset": 8
},
"EndCapacity": {
"type": {
"kind": "pointer",
"subtype": {
"kind": "base",
"name": "unsigned long"
}
},
"offset": 16
}
},
"kind": "struct",
"size": 24
},
"_COMMAND": {
"fields": {
"Chars": {
"type": {
"count": 1,
"kind": "array",
"subtype": {
"kind": "base",
"name": "unsigned char"
}
},
"offset": 0
},
"Pointer": {
"type": {
"kind": "pointer",
"subtype": {
"kind": "base",
"name": "string"
}
},
"offset": 0
},
"Length": {
"type": {
"kind": "base",
"name": "unsigned int"
},
"offset": 16
},
"Allocated": {
"type": {
"kind": "base",
"name": "unsigned int"
},
"offset": 24
}
},
"kind": "struct",
"size": 32
},
"_CONSOLE_PROCESS_LIST": {
"fields": {
"ListEntry": {
"type": {
"kind": "struct",
"name": "nt_symbols!_LIST_ENTRY"
},
"offset": 0
},
"ConsoleProcess": {
"type": {
"kind": "pointer",
"subtype": {
"kind": "struct",
"name": "_CONSOLE_PROCESS"
}
},
"offset": 16
}
},
"kind": "struct",
"size": 24
},
"_CONSOLE_PROCESS_HANDLE": {
"fields": {
"ProcessHandle": {
"type": {
"kind": "base",
"name": "unsigned int"
},
"offset": 48
}
},
"kind": "struct",
"size": 52
},
"_CONSOLE_PROCESS": {
"fields": {
"ProcessId": {
"type": {
"kind": "base",
"name": "unsigned int"
},
"offset": 28
},
"ThreadId": {
"type": {
"kind": "base",
"name": "unsigned int"
},
"offset": 32
},
"ProcessHandle": {
"type": {
"kind": "base",
"name": "unsigned int"
},
"offset": 48
}
},
"kind": "struct",
"size": 24
},
"_COMMAND_HISTORY": {
"fields": {
"ListEntry": {
"type": {
"kind": "struct",
"name": "nt_symbols!_LIST_ENTRY"
},
"offset": 0
},
"CommandBucket": {
"type": {
"kind": "struct",
"name": "_VECTOR"
},
"offset": 16
},
"CommandCountMax": {
"type": {
"kind": "base",
"name": "unsigned short"
},
"offset": 40
},
"Application": {
"type": {
"kind": "struct",
"name": "_COMMAND"
},
"offset": 48
},
"ConsoleProcessHandle": {
"type": {
"kind": "pointer",
"subtype": {
"kind": "struct",
"name": "_CONSOLE_PROCESS_HANDLE"
}
},
"offset": 80
},
"Flags": {
"type": {
"kind": "base",
"name": "unsigned short"
},
"offset": 88
},
"LastDisplayed": {
"type": {
"kind": "base",
"name": "short"
},
"offset": 92
}
},
"kind": "struct",
"size": 96
},
"_SCREEN_INFORMATION": {
"fields": {
"TextBufferInfo": {
"type": {
"kind": "pointer",
"subtype": {
"kind": "struct",
"name": "_TEXT_BUFFER_INFO"
}
},
"offset": 56
},
"Next": {
"type": {
"kind": "pointer",
"subtype": {
"kind": "struct",
"name": "_SCREEN_INFORMATION"
}
},
"offset": 64
}
},
"kind": "struct",
"size": 72
},
"_ROW_POINTER": {
"fields": {
"Row": {
"type": {
"kind": "pointer",
"subtype": {
"kind": "struct",
"name": "_ROW"
}
},
"offset": 0
}
},
"kind": "struct",
"size": 8
},
"_ROWS_ARRAY": {
"fields": {
"Rows": {
"type": {
"count": 1,
"kind": "array",
"subtype": {
"kind": "struct",
"name": "_ROW"
}
},
"offset": 0
}
},
"kind": "struct",
"size": 8
},
"_TEXT_BUFFER_INFO": {
"fields": {
"ScreenX": {
"type": {
"kind": "base",
"name": "short"
},
"offset": 4
},
"ScreenY": {
"type": {
"kind": "base",
"name": "short"
},
"offset": 6
},
"BufferRows": {
"type": {
"kind": "pointer",
"subtype": {
"kind": "struct",
"name": "_ROWS_ARRAY"
}
},
"offset": 8
},
"BufferCapacity": {
"type": {
"kind": "base",
"name": "short"
},
"offset": 6
},
"ThisBuffer": {
"type": {
"kind": "pointer",
"subtype": {
"kind": "struct",
"name": "_TEXT_BUFFER_INFO"
}
},
"offset": 32
},
"FirstRow": {
"type": {
"kind": "pointer",
"subtype": {
"kind": "struct",
"name": "_ROW"
}
},
"offset": 8
},
"LastRow": {
"type": {
"kind": "pointer",
"subtype": {
"kind": "struct",
"name": "_ROW"
}
},
"offset": 16
},
"BufferStart": {
"type": {
"kind": "base",
"name": "short"
},
"offset": 80
}
},
"kind": "struct",
"size": 72
},
"_CHAR_ROW_CELL": {
"fields": {
"Text": {
"type": {
"kind": "struct",
"name": "nt_symbols!_UNICODE_STRING"
},
"offset": 0
},
"DbcsAttribute": {
"type": {
"kind": "base",
"name": "unsigned int"
},
"offset": 2
}
},
"kind": "struct",
"size": 3
},
"_CHAR_ROW_CELL_ARRAY": {
"fields": {
"Chars": {
"type": {
"count": 1,
"kind": "array",
"subtype": {
"kind": "struct",
"name": "_CHAR_ROW_CELL"
}
},
"offset": 0
}
},
"kind": "struct",
"size": 8
},
"_ROW": {
"fields": {
"Blink": {
"type": {
"kind": "pointer",
"subtype": {
"kind": "struct",
"name": "_ROW"
}
},
"offset": -88
},
"CharRow": {
"type": {
"kind": "pointer",
"subtype": {
"kind": "struct",
"name": "_CHAR_ROW_CELL_ARRAY"
}
},
"offset": 0
},
"RowLength": {
"type": {
"kind": "base",
"name": "short"
},
"offset": -18
},
"Index": {
"type": {
"kind": "base",
"name": "short"
},
"offset": -20
},
"RowLength2": {
"type": {
"kind": "base",
"name": "short"
},
"offset": 8
},
"Allocated": {
"type": {
"kind": "base",
"name": "short"
},
"offset": 16
},
"TextBuffer": {
"type": {
"kind": "pointer",
"subtype": {
"kind": "struct",
"name": "_TEXT_BUFFER_INFO"
}
},
"offset": -8
}
},
"kind": "struct",
"size": 480
},
"_DEQUE": {
"fields": {
"Blink": {
"type": {
"kind": "pointer",
"subtype": {
"kind": "base",
"name": "void"
}
},
"offset": 0
}
},
"kind": "struct",
"size": 8
},
"_EXE_ALIAS_LIST": {
"fields": {
"ListEntry": {
"type": {
"kind": "struct",
"name": "nt_symbols!_LIST_ENTRY"
},
"offset": 0
},
"ExeName": {
"type": {
"kind": "struct",
"name": "_COMMAND"
},
"offset": 16
},
"AliasList": {
"type": {
"kind": "pointer",
"subtype": {
"kind": "struct",
"name": "nt_symbols!_LIST_ENTRY"
}
},
"offset": 56
}
},
"kind": "struct",
"size": 64
},
"_ALIAS": {
"fields": {
"ListEntry": {
"type": {
"kind": "struct",
"name": "nt_symbols!_LIST_ENTRY"
},
"offset": 0
},
"Source": {
"type": {
"kind": "struct",
"name": "_COMMAND"
},
"offset": 16
},
"Target": {
"type": {
"kind": "struct",
"name": "_COMMAND"
},
"offset": 48
}
},
"kind": "struct",
"size": 32
}
},
"metadata": {
"producer": {
"version": "0.0.1",
"name": "Dave Lassalle",
"datetime": "2024-07-31T15:05:35-06:00"
},
"format": "4.1.0"
}
}
@@ -0,0 +1,681 @@
{
"symbols": {},
"enums": {},
"base_types": {
"unsigned long": {
"kind": "int",
"size": 4,
"signed": false,
"endian": "little"
},
"unsigned char": {
"kind": "char",
"size": 1,
"signed": false,
"endian": "little"
},
"pointer": {
"kind": "int",
"size": 8,
"signed": false,
"endian": "little"
},
"unsigned int": {
"kind": "int",
"size": 4,
"signed": false,
"endian": "little"
},
"unsigned short": {
"kind": "int",
"size": 2,
"signed": false,
"endian": "little"
},
"short": {
"kind": "int",
"size": 2,
"signed": true,
"endian": "little"
},
"long": {
"kind": "int",
"size": 4,
"signed": false,
"endian": "little"
}
},
"user_types": {
"_CONSOLE_INFORMATION": {
"fields": {
"ScreenX": {
"type": {
"kind": "base",
"name": "short"
},
"offset": 2400
},
"ScreenY": {
"type": {
"kind": "base",
"name": "short"
},
"offset": 2402
},
"CommandHistorySize": {
"type": {
"kind": "base",
"name": "unsigned short"
},
"offset": 2512
},
"HistoryBufferMax": {
"type": {
"kind": "base",
"name": "unsigned short"
},
"offset": 2516
},
"OriginalTitle": {
"type": {
"kind": "pointer",
"subtype": {
"kind": "base",
"name": "string"
}
},
"offset": 2944
},
"Title": {
"type": {
"kind": "pointer",
"subtype": {
"kind": "base",
"name": "string"
}
},
"offset": 2912
},
"GetScreenBuffer": {
"type": {
"kind": "pointer",
"subtype": {
"kind": "struct",
"name": "_SCREEN_INFORMATION"
}
},
"offset": 3008
},
"CurrentScreenBuffer": {
"type": {
"kind": "pointer",
"subtype": {
"kind": "struct",
"name": "_SCREEN_INFORMATION"
}
},
"offset": 2632
},
"ConsoleProcessList": {
"type": {
"kind": "pointer",
"subtype": {
"kind": "struct",
"name": "nt_symbols!_LIST_ENTRY"
}
},
"offset": 2608
},
"ProcessCount": {
"type": {
"kind": "base",
"name": "unsigned short"
},
"offset": 2616
},
"HistoryList": {
"type": {
"kind": "pointer",
"subtype": {
"kind": "struct",
"name": "nt_symbols!_LIST_ENTRY"
}
},
"offset": 10640
},
"HistoryBufferCount": {
"type": {
"kind": "base",
"name": "unsigned short"
},
"offset": 10648
},
"ExeAliasList": {
"type": {
"kind": "pointer",
"subtype": {
"kind": "struct",
"name": "nt_symbols!_LIST_ENTRY"
}
},
"offset": 10552
}
},
"kind": "struct",
"size": 140
},
"_VECTOR": {
"fields": {
"Begin": {
"type": {
"kind": "pointer",
"subtype": {
"kind": "struct",
"name": "_COMMAND"
}
},
"offset": 0
},
"End": {
"type": {
"kind": "pointer",
"subtype": {
"kind": "struct",
"name": "_COMMAND"
}
},
"offset": 8
},
"EndCapacity": {
"type": {
"kind": "pointer",
"subtype": {
"kind": "base",
"name": "unsigned long"
}
},
"offset": 16
}
},
"kind": "struct",
"size": 24
},
"_COMMAND": {
"fields": {
"Chars": {
"type": {
"count": 1,
"kind": "array",
"subtype": {
"kind": "base",
"name": "unsigned char"
}
},
"offset": 0
},
"Pointer": {
"type": {
"kind": "pointer",
"subtype": {
"kind": "base",
"name": "string"
}
},
"offset": 0
},
"Length": {
"type": {
"kind": "base",
"name": "unsigned int"
},
"offset": 16
},
"Allocated": {
"type": {
"kind": "base",
"name": "unsigned int"
},
"offset": 24
}
},
"kind": "struct",
"size": 32
},
"_CONSOLE_PROCESS_LIST": {
"fields": {
"ListEntry": {
"type": {
"kind": "struct",
"name": "nt_symbols!_LIST_ENTRY"
},
"offset": 0
},
"ConsoleProcess": {
"type": {
"kind": "pointer",
"subtype": {
"kind": "struct",
"name": "_CONSOLE_PROCESS"
}
},
"offset": 16
}
},
"kind": "struct",
"size": 24
},
"_CONSOLE_PROCESS_HANDLE": {
"fields": {
"ProcessHandle": {
"type": {
"kind": "base",
"name": "unsigned int"
},
"offset": 48
}
},
"kind": "struct",
"size": 52
},
"_CONSOLE_PROCESS": {
"fields": {
"ProcessId": {
"type": {
"kind": "base",
"name": "unsigned int"
},
"offset": 28
},
"ThreadId": {
"type": {
"kind": "base",
"name": "unsigned int"
},
"offset": 32
},
"ProcessHandle": {
"type": {
"kind": "base",
"name": "unsigned int"
},
"offset": 48
}
},
"kind": "struct",
"size": 24
},
"_COMMAND_HISTORY": {
"fields": {
"ListEntry": {
"type": {
"kind": "struct",
"name": "nt_symbols!_LIST_ENTRY"
},
"offset": 0
},
"CommandBucket": {
"type": {
"kind": "struct",
"name": "_VECTOR"
},
"offset": 16
},
"CommandCountMax": {
"type": {
"kind": "base",
"name": "unsigned short"
},
"offset": 40
},
"Application": {
"type": {
"kind": "struct",
"name": "_COMMAND"
},
"offset": 48
},
"ConsoleProcessHandle": {
"type": {
"kind": "pointer",
"subtype": {
"kind": "struct",
"name": "_CONSOLE_PROCESS_HANDLE"
}
},
"offset": 80
},
"Flags": {
"type": {
"kind": "base",
"name": "unsigned short"
},
"offset": 88
},
"LastDisplayed": {
"type": {
"kind": "base",
"name": "short"
},
"offset": 92
}
},
"kind": "struct",
"size": 96
},
"_SCREEN_INFORMATION": {
"fields": {
"TextBufferInfo": {
"type": {
"kind": "pointer",
"subtype": {
"kind": "struct",
"name": "_TEXT_BUFFER_INFO"
}
},
"offset": 56
},
"Next": {
"type": {
"kind": "pointer",
"subtype": {
"kind": "struct",
"name": "_SCREEN_INFORMATION"
}
},
"offset": 64
}
},
"kind": "struct",
"size": 72
},
"_ROW_POINTER": {
"fields": {
"Row": {
"type": {
"kind": "pointer",
"subtype": {
"kind": "struct",
"name": "_ROW"
}
},
"offset": 0
}
},
"kind": "struct",
"size": 8
},
"_ROWS_ARRAY": {
"fields": {
"Rows": {
"type": {
"count": 1,
"kind": "array",
"subtype": {
"kind": "struct",
"name": "_ROW"
}
},
"offset": 0
}
},
"kind": "struct",
"size": 8
},
"_TEXT_BUFFER_INFO": {
"fields": {
"ScreenX": {
"type": {
"kind": "base",
"name": "short"
},
"offset": 4
},
"ScreenY": {
"type": {
"kind": "base",
"name": "short"
},
"offset": 6
},
"BufferRows": {
"type": {
"kind": "pointer",
"subtype": {
"kind": "struct",
"name": "_ROWS_ARRAY"
}
},
"offset": 8
},
"BufferCapacity": {
"type": {
"kind": "base",
"name": "short"
},
"offset": 6
},
"ThisBuffer": {
"type": {
"kind": "pointer",
"subtype": {
"kind": "struct",
"name": "_TEXT_BUFFER_INFO"
}
},
"offset": 32
},
"FirstRow": {
"type": {
"kind": "pointer",
"subtype": {
"kind": "struct",
"name": "_ROW"
}
},
"offset": 8
},
"LastRow": {
"type": {
"kind": "pointer",
"subtype": {
"kind": "struct",
"name": "_ROW"
}
},
"offset": 16
},
"BufferStart": {
"type": {
"kind": "base",
"name": "short"
},
"offset": 72
}
},
"kind": "struct",
"size": 72
},
"_CHAR_ROW_CELL": {
"fields": {
"Text": {
"type": {
"kind": "struct",
"name": "nt_symbols!_UNICODE_STRING"
},
"offset": 0
},
"DbcsAttribute": {
"type": {
"kind": "base",
"name": "unsigned int"
},
"offset": 2
}
},
"kind": "struct",
"size": 3
},
"_CHAR_ROW_CELL_ARRAY": {
"fields": {
"Chars": {
"type": {
"count": 1,
"kind": "array",
"subtype": {
"kind": "struct",
"name": "_CHAR_ROW_CELL"
}
},
"offset": 0
}
},
"kind": "struct",
"size": 8
},
"_ROW": {
"fields": {
"Blink": {
"type": {
"kind": "pointer",
"subtype": {
"kind": "struct",
"name": "_ROW"
}
},
"offset": -80
},
"CharRow": {
"type": {
"kind": "pointer",
"subtype": {
"kind": "struct",
"name": "_CHAR_ROW_CELL_ARRAY"
}
},
"offset": 0
},
"RowLength": {
"type": {
"kind": "base",
"name": "short"
},
"offset": 8
},
"Index": {
"type": {
"kind": "base",
"name": "short"
},
"offset": -20
},
"RowLength2": {
"type": {
"kind": "base",
"name": "short"
},
"offset": 8
},
"Allocated": {
"type": {
"kind": "base",
"name": "short"
},
"offset": 16
},
"TextBuffer": {
"type": {
"kind": "pointer",
"subtype": {
"kind": "struct",
"name": "_TEXT_BUFFER_INFO"
}
},
"offset": -16
}
},
"kind": "struct",
"size": 472
},
"_DEQUE": {
"fields": {
"Blink": {
"type": {
"kind": "pointer",
"subtype": {
"kind": "base",
"name": "void"
}
},
"offset": 0
}
},
"kind": "struct",
"size": 8
},
"_EXE_ALIAS_LIST": {
"fields": {
"ListEntry": {
"type": {
"kind": "struct",
"name": "nt_symbols!_LIST_ENTRY"
},
"offset": 0
},
"ExeName": {
"type": {
"kind": "struct",
"name": "_COMMAND"
},
"offset": 16
},
"AliasList": {
"type": {
"kind": "pointer",
"subtype": {
"kind": "struct",
"name": "nt_symbols!_LIST_ENTRY"
}
},
"offset": 56
}
},
"kind": "struct",
"size": 64
},
"_ALIAS": {
"fields": {
"ListEntry": {
"type": {
"kind": "struct",
"name": "nt_symbols!_LIST_ENTRY"
},
"offset": 0
},
"Source": {
"type": {
"kind": "struct",
"name": "_COMMAND"
},
"offset": 16
},
"Target": {
"type": {
"kind": "struct",
"name": "_COMMAND"
},
"offset": 48
}
},
"kind": "struct",
"size": 32
}
},
"metadata": {
"producer": {
"version": "0.0.1",
"name": "Dave Lassalle",
"datetime": "2024-07-31T15:05:35-06:00"
},
"format": "4.1.0"
}
}
@@ -0,0 +1,681 @@
{
"symbols": {},
"enums": {},
"base_types": {
"unsigned long": {
"kind": "int",
"size": 4,
"signed": false,
"endian": "little"
},
"unsigned char": {
"kind": "char",
"size": 1,
"signed": false,
"endian": "little"
},
"pointer": {
"kind": "int",
"size": 8,
"signed": false,
"endian": "little"
},
"unsigned int": {
"kind": "int",
"size": 4,
"signed": false,
"endian": "little"
},
"unsigned short": {
"kind": "int",
"size": 2,
"signed": false,
"endian": "little"
},
"short": {
"kind": "int",
"size": 2,
"signed": true,
"endian": "little"
},
"long": {
"kind": "int",
"size": 4,
"signed": false,
"endian": "little"
}
},
"user_types": {
"_CONSOLE_INFORMATION": {
"fields": {
"ScreenX": {
"type": {
"kind": "base",
"name": "short"
},
"offset": 2400
},
"ScreenY": {
"type": {
"kind": "base",
"name": "short"
},
"offset": 2402
},
"CommandHistorySize": {
"type": {
"kind": "base",
"name": "unsigned short"
},
"offset": 2512
},
"HistoryBufferMax": {
"type": {
"kind": "base",
"name": "unsigned short"
},
"offset": 2516
},
"OriginalTitle": {
"type": {
"kind": "pointer",
"subtype": {
"kind": "base",
"name": "string"
}
},
"offset": 2944
},
"Title": {
"type": {
"kind": "pointer",
"subtype": {
"kind": "base",
"name": "string"
}
},
"offset": 2912
},
"GetScreenBuffer": {
"type": {
"kind": "pointer",
"subtype": {
"kind": "struct",
"name": "_SCREEN_INFORMATION"
}
},
"offset": 3008
},
"CurrentScreenBuffer": {
"type": {
"kind": "pointer",
"subtype": {
"kind": "struct",
"name": "_SCREEN_INFORMATION"
}
},
"offset": 2632
},
"ConsoleProcessList": {
"type": {
"kind": "pointer",
"subtype": {
"kind": "struct",
"name": "nt_symbols!_LIST_ENTRY"
}
},
"offset": 2608
},
"ProcessCount": {
"type": {
"kind": "base",
"name": "unsigned short"
},
"offset": 2616
},
"HistoryList": {
"type": {
"kind": "pointer",
"subtype": {
"kind": "struct",
"name": "nt_symbols!_LIST_ENTRY"
}
},
"offset": 10664
},
"HistoryBufferCount": {
"type": {
"kind": "base",
"name": "unsigned short"
},
"offset": 10672
},
"ExeAliasList": {
"type": {
"kind": "pointer",
"subtype": {
"kind": "struct",
"name": "nt_symbols!_LIST_ENTRY"
}
},
"offset": 10576
}
},
"kind": "struct",
"size": 140
},
"_VECTOR": {
"fields": {
"Begin": {
"type": {
"kind": "pointer",
"subtype": {
"kind": "struct",
"name": "_COMMAND"
}
},
"offset": 0
},
"End": {
"type": {
"kind": "pointer",
"subtype": {
"kind": "struct",
"name": "_COMMAND"
}
},
"offset": 8
},
"EndCapacity": {
"type": {
"kind": "pointer",
"subtype": {
"kind": "base",
"name": "unsigned long"
}
},
"offset": 16
}
},
"kind": "struct",
"size": 24
},
"_COMMAND": {
"fields": {
"Chars": {
"type": {
"count": 1,
"kind": "array",
"subtype": {
"kind": "base",
"name": "unsigned char"
}
},
"offset": 0
},
"Pointer": {
"type": {
"kind": "pointer",
"subtype": {
"kind": "base",
"name": "string"
}
},
"offset": 0
},
"Length": {
"type": {
"kind": "base",
"name": "unsigned int"
},
"offset": 16
},
"Allocated": {
"type": {
"kind": "base",
"name": "unsigned int"
},
"offset": 24
}
},
"kind": "struct",
"size": 32
},
"_CONSOLE_PROCESS_LIST": {
"fields": {
"ListEntry": {
"type": {
"kind": "struct",
"name": "nt_symbols!_LIST_ENTRY"
},
"offset": 0
},
"ConsoleProcess": {
"type": {
"kind": "pointer",
"subtype": {
"kind": "struct",
"name": "_CONSOLE_PROCESS"
}
},
"offset": 16
}
},
"kind": "struct",
"size": 24
},
"_CONSOLE_PROCESS_HANDLE": {
"fields": {
"ProcessHandle": {
"type": {
"kind": "base",
"name": "unsigned int"
},
"offset": 48
}
},
"kind": "struct",
"size": 52
},
"_CONSOLE_PROCESS": {
"fields": {
"ProcessId": {
"type": {
"kind": "base",
"name": "unsigned int"
},
"offset": 28
},
"ThreadId": {
"type": {
"kind": "base",
"name": "unsigned int"
},
"offset": 32
},
"ProcessHandle": {
"type": {
"kind": "base",
"name": "unsigned int"
},
"offset": 48
}
},
"kind": "struct",
"size": 24
},
"_COMMAND_HISTORY": {
"fields": {
"ListEntry": {
"type": {
"kind": "struct",
"name": "nt_symbols!_LIST_ENTRY"
},
"offset": 0
},
"CommandBucket": {
"type": {
"kind": "struct",
"name": "_VECTOR"
},
"offset": 16
},
"CommandCountMax": {
"type": {
"kind": "base",
"name": "unsigned short"
},
"offset": 40
},
"Application": {
"type": {
"kind": "struct",
"name": "_COMMAND"
},
"offset": 48
},
"ConsoleProcessHandle": {
"type": {
"kind": "pointer",
"subtype": {
"kind": "struct",
"name": "_CONSOLE_PROCESS_HANDLE"
}
},
"offset": 80
},
"Flags": {
"type": {
"kind": "base",
"name": "unsigned short"
},
"offset": 88
},
"LastDisplayed": {
"type": {
"kind": "base",
"name": "short"
},
"offset": 92
}
},
"kind": "struct",
"size": 96
},
"_SCREEN_INFORMATION": {
"fields": {
"TextBufferInfo": {
"type": {
"kind": "pointer",
"subtype": {
"kind": "struct",
"name": "_TEXT_BUFFER_INFO"
}
},
"offset": 56
},
"Next": {
"type": {
"kind": "pointer",
"subtype": {
"kind": "struct",
"name": "_SCREEN_INFORMATION"
}
},
"offset": 64
}
},
"kind": "struct",
"size": 72
},
"_ROW_POINTER": {
"fields": {
"Row": {
"type": {
"kind": "pointer",
"subtype": {
"kind": "struct",
"name": "_ROW"
}
},
"offset": 0
}
},
"kind": "struct",
"size": 8
},
"_ROWS_ARRAY": {
"fields": {
"Rows": {
"type": {
"count": 1,
"kind": "array",
"subtype": {
"kind": "struct",
"name": "_ROW"
}
},
"offset": 0
}
},
"kind": "struct",
"size": 8
},
"_TEXT_BUFFER_INFO": {
"fields": {
"ScreenX": {
"type": {
"kind": "base",
"name": "short"
},
"offset": 4
},
"ScreenY": {
"type": {
"kind": "base",
"name": "short"
},
"offset": 6
},
"BufferRows": {
"type": {
"kind": "pointer",
"subtype": {
"kind": "struct",
"name": "_ROWS_ARRAY"
}
},
"offset": 8
},
"BufferCapacity": {
"type": {
"kind": "base",
"name": "short"
},
"offset": 6
},
"ThisBuffer": {
"type": {
"kind": "pointer",
"subtype": {
"kind": "struct",
"name": "_TEXT_BUFFER_INFO"
}
},
"offset": 32
},
"FirstRow": {
"type": {
"kind": "pointer",
"subtype": {
"kind": "struct",
"name": "_ROW"
}
},
"offset": 8
},
"LastRow": {
"type": {
"kind": "pointer",
"subtype": {
"kind": "struct",
"name": "_ROW"
}
},
"offset": 16
},
"BufferStart": {
"type": {
"kind": "base",
"name": "short"
},
"offset": 72
}
},
"kind": "struct",
"size": 72
},
"_CHAR_ROW_CELL": {
"fields": {
"Text": {
"type": {
"kind": "struct",
"name": "nt_symbols!_UNICODE_STRING"
},
"offset": 0
},
"DbcsAttribute": {
"type": {
"kind": "base",
"name": "unsigned int"
},
"offset": 2
}
},
"kind": "struct",
"size": 3
},
"_CHAR_ROW_CELL_ARRAY": {
"fields": {
"Chars": {
"type": {
"count": 1,
"kind": "array",
"subtype": {
"kind": "struct",
"name": "_CHAR_ROW_CELL"
}
},
"offset": 0
}
},
"kind": "struct",
"size": 8
},
"_ROW": {
"fields": {
"Blink": {
"type": {
"kind": "pointer",
"subtype": {
"kind": "struct",
"name": "_ROW"
}
},
"offset": -96
},
"CharRow": {
"type": {
"kind": "pointer",
"subtype": {
"kind": "struct",
"name": "_CHAR_ROW_CELL_ARRAY"
}
},
"offset": 0
},
"RowLength": {
"type": {
"kind": "base",
"name": "short"
},
"offset": 8
},
"Index": {
"type": {
"kind": "base",
"name": "short"
},
"offset": -20
},
"RowLength2": {
"type": {
"kind": "base",
"name": "short"
},
"offset": 8
},
"Allocated": {
"type": {
"kind": "base",
"name": "short"
},
"offset": 16
},
"TextBuffer": {
"type": {
"kind": "pointer",
"subtype": {
"kind": "struct",
"name": "_TEXT_BUFFER_INFO"
}
},
"offset": -8
}
},
"kind": "struct",
"size": 480
},
"_DEQUE": {
"fields": {
"Blink": {
"type": {
"kind": "pointer",
"subtype": {
"kind": "base",
"name": "void"
}
},
"offset": 0
}
},
"kind": "struct",
"size": 8
},
"_EXE_ALIAS_LIST": {
"fields": {
"ListEntry": {
"type": {
"kind": "struct",
"name": "nt_symbols!_LIST_ENTRY"
},
"offset": 0
},
"ExeName": {
"type": {
"kind": "struct",
"name": "_COMMAND"
},
"offset": 16
},
"AliasList": {
"type": {
"kind": "pointer",
"subtype": {
"kind": "struct",
"name": "nt_symbols!_LIST_ENTRY"
}
},
"offset": 56
}
},
"kind": "struct",
"size": 64
},
"_ALIAS": {
"fields": {
"ListEntry": {
"type": {
"kind": "struct",
"name": "nt_symbols!_LIST_ENTRY"
},
"offset": 0
},
"Source": {
"type": {
"kind": "struct",
"name": "_COMMAND"
},
"offset": 16
},
"Target": {
"type": {
"kind": "struct",
"name": "_COMMAND"
},
"offset": 48
}
},
"kind": "struct",
"size": 32
}
},
"metadata": {
"producer": {
"version": "0.0.1",
"name": "Dave Lassalle",
"datetime": "2024-07-31T15:05:35-06:00"
},
"format": "4.1.0"
}
}
@@ -0,0 +1,682 @@
{
"symbols": {},
"enums": {},
"base_types": {
"unsigned long": {
"kind": "int",
"size": 4,
"signed": false,
"endian": "little"
},
"unsigned char": {
"kind": "char",
"size": 1,
"signed": false,
"endian": "little"
},
"pointer": {
"kind": "int",
"size": 8,
"signed": false,
"endian": "little"
},
"unsigned int": {
"kind": "int",
"size": 4,
"signed": false,
"endian": "little"
},
"unsigned short": {
"kind": "int",
"size": 2,
"signed": false,
"endian": "little"
},
"short": {
"kind": "int",
"size": 2,
"signed": true,
"endian": "little"
},
"long": {
"kind": "int",
"size": 4,
"signed": false,
"endian": "little"
}
},
"user_types": {
"_CONSOLE_INFORMATION": {
"fields": {
"ScreenX": {
"type": {
"kind": "base",
"name": "short"
},
"offset": 2592
},
"ScreenY": {
"type": {
"kind": "base",
"name": "short"
},
"offset": 2594
},
"CommandHistorySize": {
"type": {
"kind": "base",
"name": "unsigned short"
},
"offset": 2704
},
"HistoryBufferMax": {
"type": {
"kind": "base",
"name": "unsigned short"
},
"offset": 2708
},
"OriginalTitle": {
"type": {
"kind": "pointer",
"subtype": {
"kind": "base",
"name": "string"
}
},
"offset": 3056
},
"Title": {
"type": {
"kind": "pointer",
"subtype": {
"kind": "base",
"name": "string"
}
},
"offset": 3120
},
"GetScreenBuffer": {
"type": {
"kind": "pointer",
"subtype": {
"kind": "struct",
"name": "_SCREEN_INFORMATION"
}
},
"offset": 3216
},
"CurrentScreenBuffer": {
"type": {
"kind": "pointer",
"subtype": {
"kind": "struct",
"name": "_SCREEN_INFORMATION"
}
},
"offset": 2824
},
"ConsoleProcessList": {
"type": {
"kind": "pointer",
"subtype": {
"kind": "struct",
"name": "nt_symbols!_LIST_ENTRY"
}
},
"offset": 2800
},
"ProcessCount": {
"type": {
"kind": "base",
"name": "unsigned short"
},
"offset": 2808
},
"HistoryList": {
"type": {
"kind": "pointer",
"subtype": {
"kind": "struct",
"name": "nt_symbols!_LIST_ENTRY"
}
},
"offset": -360
},
"HistoryBufferCount": {
"type": {
"kind": "base",
"name": "unsigned short"
},
"offset": -352
},
"ExeAliasList": {
"type": {
"kind": "pointer",
"subtype": {
"kind": "struct",
"name": "nt_symbols!_LIST_ENTRY"
}
},
"offset": 3776
}
},
"kind": "struct",
"size": 140
},
"_VECTOR": {
"fields": {
"Begin": {
"type": {
"kind": "pointer",
"subtype": {
"kind": "struct",
"name": "_COMMAND"
}
},
"offset": 0
},
"End": {
"type": {
"kind": "pointer",
"subtype": {
"kind": "struct",
"name": "_COMMAND"
}
},
"offset": 8
},
"EndCapacity": {
"type": {
"kind": "pointer",
"subtype": {
"kind": "base",
"name": "unsigned long"
}
},
"offset": 16
}
},
"kind": "struct",
"size": 24
},
"_COMMAND": {
"fields": {
"Chars": {
"type": {
"count": 1,
"kind": "array",
"subtype": {
"kind": "base",
"name": "unsigned char"
}
},
"offset": 0
},
"Pointer": {
"type": {
"kind": "pointer",
"subtype": {
"kind": "base",
"name": "string"
}
},
"offset": 0
},
"Length": {
"type": {
"kind": "base",
"name": "unsigned int"
},
"offset": 16
},
"Allocated": {
"type": {
"kind": "base",
"name": "unsigned int"
},
"offset": 24
}
},
"kind": "struct",
"size": 32
},
"_CONSOLE_PROCESS_LIST": {
"fields": {
"ListEntry": {
"type": {
"kind": "struct",
"name": "nt_symbols!_LIST_ENTRY"
},
"offset": 0
},
"ConsoleProcess": {
"type": {
"kind": "pointer",
"subtype": {
"kind": "struct",
"name": "_CONSOLE_PROCESS"
}
},
"offset": 16
}
},
"kind": "struct",
"size": 24
},
"_CONSOLE_PROCESS_HANDLE": {
"fields": {
"ProcessHandle": {
"type": {
"kind": "base",
"name": "unsigned int"
},
"offset": 48
}
},
"kind": "struct",
"size": 52
},
"_CONSOLE_PROCESS": {
"fields": {
"ProcessId": {
"type": {
"kind": "base",
"name": "unsigned int"
},
"offset": 28
},
"ThreadId": {
"type": {
"kind": "base",
"name": "unsigned int"
},
"offset": 32
},
"ProcessHandle": {
"type": {
"kind": "base",
"name": "unsigned int"
},
"offset": 48
}
},
"kind": "struct",
"size": 24
},
"_COMMAND_HISTORY": {
"fields": {
"ListEntry": {
"type": {
"kind": "struct",
"name": "nt_symbols!_LIST_ENTRY"
},
"offset": 0
},
"CommandBucket": {
"type": {
"kind": "struct",
"name": "_VECTOR"
},
"offset": 16
},
"CommandCountMax": {
"type": {
"kind": "base",
"name": "unsigned short"
},
"offset": 40
},
"Application": {
"type": {
"kind": "struct",
"name": "_COMMAND"
},
"offset": 48
},
"ConsoleProcessHandle": {
"type": {
"kind": "pointer",
"subtype": {
"kind": "struct",
"name": "_CONSOLE_PROCESS_HANDLE"
}
},
"offset": 80
},
"Flags": {
"type": {
"kind": "base",
"name": "unsigned short"
},
"offset": 88
},
"LastDisplayed": {
"type": {
"kind": "base",
"name": "short"
},
"offset": 92
}
},
"kind": "struct",
"size": 96
},
"_SCREEN_INFORMATION": {
"fields": {
"TextBufferInfo": {
"type": {
"kind": "pointer",
"subtype": {
"kind": "struct",
"name": "_TEXT_BUFFER_INFO"
}
},
"offset": 56
},
"Next": {
"type": {
"kind": "pointer",
"subtype": {
"kind": "struct",
"name": "_SCREEN_INFORMATION"
}
},
"offset": 64
}
},
"kind": "struct",
"size": 72
},
"_ROW_POINTER": {
"fields": {
"Row": {
"type": {
"kind": "pointer",
"subtype": {
"kind": "struct",
"name": "_ROW"
}
},
"offset": 0
}
},
"kind": "struct",
"size": 8
},
"_ROWS_ARRAY": {
"fields": {
"Rows": {
"type": {
"count": 1,
"kind": "array",
"subtype": {
"kind": "struct",
"name": "_ROW_POINTER"
}
},
"offset": 0
}
},
"kind": "struct",
"size": 8
},
"_TEXT_BUFFER_INFO": {
"fields": {
"ScreenX": {
"type": {
"kind": "base",
"name": "short"
},
"offset": 8
},
"ScreenY": {
"type": {
"kind": "base",
"name": "short"
},
"offset": 10
},
"BufferRows": {
"type": {
"kind": "pointer",
"subtype": {
"kind": "struct",
"name": "_ROWS_ARRAY"
}
},
"offset": 16
},
"BufferCapacity": {
"type": {
"kind": "base",
"name": "short"
},
"offset": 10
},
"ThisBuffer": {
"type": {
"kind": "pointer",
"subtype": {
"kind": "struct",
"name": "_TEXT_BUFFER_INFO"
}
},
"offset": 40
},
"FirstRow": {
"type": {
"kind": "pointer",
"subtype": {
"kind": "struct",
"name": "_ROW"
}
},
"offset": 16
},
"LastRow": {
"type": {
"kind": "pointer",
"subtype": {
"kind": "struct",
"name": "_ROW"
}
},
"offset": 24
},
"BufferStart": {
"type": {
"kind": "base",
"name": "short"
},
"offset": 88
}
},
"kind": "struct",
"size": 72
},
"_CHAR_ROW_CELL": {
"fields": {
"Text": {
"type": {
"kind": "struct",
"name": "nt_symbols!_UNICODE_STRING"
},
"offset": 0
},
"DbcsAttribute": {
"type": {
"kind": "base",
"name": "unsigned int"
},
"offset": 2
}
},
"kind": "struct",
"size": 3
},
"_CHAR_ROW_CELL_ARRAY": {
"fields": {
"Chars": {
"type": {
"count": 1,
"kind": "array",
"subtype": {
"kind": "struct",
"name": "_CHAR_ROW_CELL"
}
},
"offset": 0
}
},
"kind": "struct",
"size": 8
},
"_ROW": {
"fields": {
"Blink": {
"type": {
"kind": "pointer",
"subtype": {
"kind": "struct",
"name": "_ROW"
}
},
"offset": -88
},
"CharRow": {
"type": {
"kind": "pointer",
"subtype": {
"kind": "struct",
"name": "_CHAR_ROW_CELL_ARRAY"
}
},
"offset": 0
},
"RowLength": {
"type": {
"kind": "base",
"name": "short"
},
"offset": 8
},
"Index": {
"type": {
"kind": "base",
"name": "short"
},
"offset": -20
},
"RowLength2": {
"type": {
"kind": "base",
"name": "short"
},
"offset": 8
},
"Allocated": {
"type": {
"kind": "base",
"name": "short"
},
"offset": 16
},
"TextBuffer": {
"type": {
"kind": "pointer",
"subtype": {
"kind": "struct",
"name": "_TEXT_BUFFER_INFO"
}
},
"offset": -8
}
},
"kind": "struct",
"size": 464
},
"_DEQUE": {
"fields": {
"Blink": {
"type": {
"kind": "pointer",
"subtype": {
"kind": "base",
"name": "void"
}
},
"offset": 0
}
},
"kind": "struct",
"size": 8
},
"_EXE_ALIAS_LIST": {
"fields": {
"ListEntry": {
"type": {
"kind": "struct",
"name": "nt_symbols!_LIST_ENTRY"
},
"offset": 0
},
"ExeName": {
"type": {
"kind": "struct",
"name": "_COMMAND"
},
"offset": 16
},
"AliasList": {
"type": {
"kind": "pointer",
"subtype": {
"kind": "struct",
"name": "nt_symbols!_LIST_ENTRY"
}
},
"offset": 56
}
},
"kind": "struct",
"size": 64
},
"_ALIAS": {
"fields": {
"ListEntry": {
"type": {
"kind": "struct",
"name": "nt_symbols!_LIST_ENTRY"
},
"offset": 0
},
"Source": {
"type": {
"kind": "struct",
"name": "_COMMAND"
},
"offset": 16
},
"Target": {
"type": {
"kind": "struct",
"name": "_COMMAND"
},
"offset": 48
}
},
"kind": "struct",
"size": 32
}
},
"metadata": {
"producer": {
"version": "0.0.1",
"name": "Dave Lassalle",
"datetime": "2024-07-31T15:05:35-06:00"
},
"format": "4.1.0"
}
}
@@ -1247,6 +1247,15 @@ class CONTROL_AREA(objects.StructType):
is_64bit = symbols.symbol_table_is_64bit(self._context, symbol_table_name)
is_pae = self._context.layers[self.vol.layer_name].metadata.get("pae", False)
# the sector_size is used as a multiplier to the StartingSector
# within each _SUBSECTION. ImageSectionObjects use a multiplier
# of 0x200 corresponding to sector alignment on disk,
# while DataSectionObjects use a multiplier of 0x1000 corresponding
# to the size of a page
sector_size = 0x200
if self.u.Flags.Image != 1:
sector_size = 0x1000
# This is a null-terminated single-linked list.
while subsection != 0:
try:
@@ -1257,7 +1266,7 @@ class CONTROL_AREA(objects.StructType):
# The offset into the file is stored implicitly based on the PTE location within the Subsection.
starting_sector = subsection.StartingSector
subsection_offset = starting_sector * 0x200
subsection_offset = starting_sector * sector_size
# Similar to the check in is_valid(), make sure the SubsectionBase is not page aligned.
# if subsection.SubsectionBase & self.PAGE_MASK == 0:
@@ -1,4 +1,5 @@
import logging
from typing import Dict
from volatility3.framework import exceptions, objects
from volatility3.framework.symbols.windows.extensions import pool
@@ -24,12 +25,8 @@ class _SHUTDOWN_PACKET(objects.StructType, pool.ExecutiveObject):
and self.Entry.Blink.is_readable()
and self.DeviceObject.is_readable()
):
return False
device = self.DeviceObject
if not device or not (device.DriverObject.DriverStart % 0x1000 == 0):
vollog.debug(
f"callback obj 0x{self.vol.offset:x} invalid due to invalid device object"
f"Callback obj 0x{self.vol.offset:x} invalid due to unreadable structure members"
)
return False
@@ -39,12 +36,43 @@ class _SHUTDOWN_PACKET(objects.StructType, pool.ExecutiveObject):
)
return False
return True
def is_parseable(self, type_map: Dict[int, str]) -> bool:
"""
Determines whether or not this `_SHUTDOWN_PACKET` callback can be reliably parsed.
Requires a `type_map` that maps NT executive object type indices to string representations.
This type map can be acquired via the `handles.Handles.get_type_map` classmethod.
"""
if not self.is_valid():
return False
try:
device = self.DeviceObject
if not device or not (device.DriverObject.DriverStart % 0x1000 == 0):
vollog.debug(
f"callback obj 0x{self.vol.offset:x} invalid due to invalid device object"
)
return False
header = device.get_object_header()
valid = header.NameInfo.Name == "Device"
return valid
object_type = header.get_object_type(type_map)
is_valid = object_type == "Device"
if not is_valid:
vollog.debug(
f"Callback obj 0x{self.vol.offset:x} invalid due to invalid device type: wanted 'Device', found '{object_type}'"
)
return is_valid
except exceptions.InvalidAddressException:
vollog.debug(
f"callback obj 0x{self.vol.offset:x} invalid due to invalid address access"
)
return False
except ValueError:
vollog.debug(f"Could not get NameInfo for object at 0x{self.vol.offset:x}")
vollog.debug(
f"Could not get object type for object at 0x{self.vol.offset:x}"
)
return False
@@ -0,0 +1,415 @@
# This file is Copyright 2024 Volatility Foundation and licensed under the Volatility Software License 1.0
# which is available at https://www.volatilityfoundation.org/license/vsl-v1.0
#
import logging
from typing import Generator, List, Union, Tuple
from volatility3.framework import objects, interfaces
from volatility3.framework import constants
vollog = logging.getLogger(__name__)
class ROW(objects.StructType):
"""A Row Structure."""
def _valid_dbcs(self, dbcs_attr: int, text_attr_msb: int) -> bool:
# TODO this need more research and testing
# https://github.com/search?q=repo%3Amicrosoft%2Fterminal+DbcsAttr&type=code
valid = text_attr_msb == 0 and dbcs_attr in (
0x0,
0x1,
0x2,
0x8,
0x10,
0x18,
0x20,
0x28,
0x30,
0x48,
0x50,
0x58,
0x60,
0x68,
0x70,
0x78,
0x80,
0x88,
0xA8,
0xB8,
0xC0,
0xC8,
0x98,
0xD8,
0xE0,
0xE8,
0xF8,
0xF0,
0xA0,
)
if text_attr_msb == 0 and not valid:
vollog.debug(f"Bad Dbcs Attribute {dbcs_attr:#x}")
return valid
def get_text(self, truncate: bool = True) -> str:
"""A convenience method to extract the text from the _ROW. The _ROW
contains a pointer CharRow to an array of CharRowCell objects. Each
CharRowCell contains the wide character and an attribute. Enumerating
self.CharRow.Chars and casting each character to unicode takes too long,
so this reads the whole row into a buffer, then extracts the text characters."""
layer = self._context.layers[self.vol.layer_name]
offset = self.CharRow.Chars.vol.offset
length = self.RowLength * 3
char_row = layer.read(offset, length)
line = ""
try:
if char_row:
line = "".join(
(
char_row[i : i + 2].decode("utf-16le", errors="replace")
if self._valid_dbcs(char_row[i + 2], char_row[i + 1])
else ""
)
for i in range(0, len(char_row), 3)
)
except Exception as e:
line = ""
if truncate:
return line.rstrip()
else:
return line
class ALIAS(objects.StructType):
"""An Alias Structure"""
def get_source(self) -> Union[str, None]:
return self.Source.get_command_string()
def get_target(self) -> Union[str, None]:
return self.Target.get_command_string()
class EXE_ALIAS_LIST(objects.StructType):
"""An Exe Alias List Structure"""
def get_exename(self) -> Union[str, None]:
exe_name = self.ExeName
# Windows 10 22000 and Server 20348 removed the Pointer
if isinstance(exe_name, objects.Pointer):
exe_name = exe_name.dereference()
return exe_name.get_string()
return exe_name.get_command_string()
def get_aliases(self) -> Generator[interfaces.objects.ObjectInterface, None, None]:
"""Generator for the individual aliases for a
particular executable."""
for alias in self.AliasList.to_list(
f"{self.get_symbol_table_name()}{constants.BANG}_ALIAS",
"ListEntry",
):
yield alias
class SCREEN_INFORMATION(objects.StructType):
"""A Screen Information Structure."""
@property
def ScreenX(self) -> int:
# 22000 change from an array of pointers to _ROW to an array of _ROW
row = self.TextBufferInfo.BufferRows.Rows[0]
if hasattr(row, "Row"):
return row.Row.RowLength2
else:
return row.RowLength2
@property
def ScreenY(self) -> int:
return self.TextBufferInfo.BufferCapacity
def _truncate_rows(self, rows: List[str]) -> List[str]:
"""To truncate empty rows at the end, walk the list
backwards and get the last non-empty row. Use that
row index to splice. Rows are created based on the
length given in the ROW structure, so empty rows will
be ''."""
non_empty_index = 0
rows_traversed = False
for index, row in enumerate(reversed(rows)):
# the string was created based on the length in the ROW structure so it shouldn't have any bad data
if len(row.rstrip()) > 0:
non_empty_index = index
break
rows_traversed = True
if non_empty_index == 0 and rows_traversed:
rows = []
else:
rows = rows[0 : len(rows) - non_empty_index]
return rows
def get_buffer(
self, truncate_rows: bool = True, truncate_lines: bool = True
) -> List[str]:
"""Get the screen buffer.
The screen buffer is comprised of the screen's Y
coordinate which tells us the number of rows and
the X coordinate which tells us the width of each
row in characters. Windows 10 17763 changed from
a large text buffer to a grid of cells, with each
cell containing a single wide character in that
cell, stored in a CharRowCell object.
@param truncate: True if the empty rows at the
end (i.e. bottom) of the screen buffer should be
supressed.
"""
rows = []
capacity = self.TextBufferInfo.BufferCapacity
start = self.TextBufferInfo.BufferStart
buffer_rows = self.TextBufferInfo.BufferRows
buffer_rows.Rows.count = self.TextBufferInfo.BufferCapacity
for i in range(capacity):
index = (start + i) % capacity
row = buffer_rows.Rows[index]
if hasattr(row, "Row"):
row = row.Row
try:
text = row.get_text(truncate_lines)
rows.append(text)
except Exception:
break
if truncate_rows:
rows = self._truncate_rows(rows)
return rows
class CONSOLE_INFORMATION(objects.StructType):
"""A Console Information Structure."""
@property
def ScreenBuffer(self) -> interfaces.objects.ObjectInterface:
return self.GetScreenBuffer
def is_valid(self, max_buffers: int = 4) -> bool:
"""Determine if the structure is valid."""
# Last displayed must be between -1 and max
if self.HistoryBufferCount < 1 or self.HistoryBufferCount > max_buffers:
return False
if not self.get_title() and not self.get_original_title():
return False
return True
def get_screens(self) -> Generator[interfaces.objects.ObjectInterface, None, None]:
"""Generator for screens in the console.
A console can have multiple screen buffers at a time,
but only the current/active one is displayed.
Multiple screens are tracked using the singly-linked
list _SCREEN_INFORMATION.Next.
See CreateConsoleScreenBuffer
"""
screens = [self.CurrentScreenBuffer]
if self.ScreenBuffer not in screens:
screens.append(self.ScreenBuffer)
seen = set()
for screen in screens:
cur = screen
while cur and cur.vol.offset != 0 and cur.vol.offset not in seen:
cur.TextBufferInfo.BufferRows.Rows.count = (
cur.TextBufferInfo.BufferCapacity
)
yield cur
seen.add(cur.vol.offset)
cur = cur.Next
def get_histories(
self,
) -> Generator[interfaces.objects.ObjectInterface, None, None]:
for cmd_hist in self.HistoryList.to_list(
f"{self.get_symbol_table_name()}{constants.BANG}_COMMAND_HISTORY",
"ListEntry",
):
yield cmd_hist
def get_exe_aliases(
self,
) -> Generator[interfaces.objects.ObjectInterface, None, None]:
exe_alias_list = self.ExeAliasList
# Windows 10 22000 and Server 20348 made this a Pointer
if isinstance(exe_alias_list, objects.Pointer):
exe_alias_list = exe_alias_list.dereference()
for exe_alias_list_item in exe_alias_list.to_list(
f"{self.get_symbol_table_name()}{constants.BANG}_EXE_ALIAS_LIST",
"ListEntry",
):
yield exe_alias_list_item
def get_processes(
self,
) -> Generator[interfaces.objects.ObjectInterface, None, None]:
for proc in self.ConsoleProcessList.to_list(
f"{self.get_symbol_table_name()}{constants.BANG}_CONSOLE_PROCESS_LIST",
"ListEntry",
):
yield proc
def get_title(self) -> Union[str, None]:
try:
return self.Title.dereference().cast(
"string", encoding="utf-16", errors="replace", max_length=512
)
except Exception:
return ""
def get_original_title(self) -> Union[str, None]:
try:
return self.OriginalTitle.dereference().cast(
"string", encoding="utf-16", errors="replace", max_length=512
)
except Exception:
return ""
class COMMAND(objects.StructType):
"""A Command Structure"""
def is_valid(self) -> bool:
if (
self.Length < 1
or self.Allocated < 1
or self.Length > 1024
or self.Allocated > 1024
):
return False
return True
def get_command_string(self) -> Union[str, None]:
if self.Length < 8:
return self.Chars.cast(
"string",
encoding="utf-16",
errors="replace",
max_length=self.Length * 2,
)
elif self.Length < 1024:
return self.Pointer.dereference().cast(
"string",
encoding="utf-16",
errors="replace",
max_length=self.Length * 2,
)
return None
class COMMAND_HISTORY(objects.StructType):
"""A Command History Structure."""
@property
def CommandCount(self) -> int:
command_type = self.get_symbol_table_name() + constants.BANG + "_COMMAND"
command_size = self._context.symbol_space.get_type(command_type).size
return int((self.CommandBucket.End - self.CommandBucket.Begin) / command_size)
@property
def ProcessHandle(self) -> int:
"""Allow ProcessHandle to be referenced regardless of OS version"""
return self.ConsoleProcessHandle.ProcessHandle
def is_valid(self, max_history: int = 50) -> bool:
# The count must be between zero and max
if self.CommandCount < 0 or self.CommandCount > max_history:
return False
# Last displayed must be between -1 and max
if self.LastDisplayed < -1 or self.LastDisplayed > max_history:
return False
# Process handle must be a valid pid
if (
self.ProcessHandle <= 0
or self.ProcessHandle > 0xFFFF
or self.ProcessHandle % 4 != 0
):
return False
return True
def get_application(self) -> Union[str, None]:
return self.Application.get_command_string()
def scan_command_bucket(
self, end: Union[int, None] = None
) -> Generator[Tuple[int, interfaces.objects.ObjectInterface], None, None]:
"""Brute force print all strings pointed to by the CommandBucket entries by
going to greater of EndCapacity or CommandCountMax*sizeof(_COMMAND)"""
command_type = self.get_symbol_table_name() + constants.BANG + "_COMMAND"
command_history_size = self._context.symbol_space.get_type(
self.vol.type_name
).size
command_size = self._context.symbol_space.get_type(command_type).size
if end is None:
end = max(
self.CommandBucket.EndCapacity,
self.CommandBucket.Begin + command_history_size * self.CommandCountMax,
)
for i, pointer in enumerate(range(self.CommandBucket.Begin, end, command_size)):
cmd = self._context.object(command_type, self.vol.layer_name, pointer)
if cmd.is_valid():
yield i, cmd
def get_commands(
self,
) -> Generator[Tuple[int, interfaces.objects.ObjectInterface], None, None]:
"""Generator for commands in the history buffer.
The CommandBucket is an array of pointers to _COMMAND
structures. The array size is CommandCount. Once CommandCount
is reached, the oldest commands are cycled out and the
rest are coalesced.
"""
for i, cmd in self.scan_command_bucket(self.CommandBucket.End):
yield i, cmd
win10_x64_class_types = {
"_EXE_ALIAS_LIST": EXE_ALIAS_LIST,
"_ALIAS": ALIAS,
"_ROW": ROW,
"_SCREEN_INFORMATION": SCREEN_INFORMATION,
"_CONSOLE_INFORMATION": CONSOLE_INFORMATION,
"_COMMAND_HISTORY": COMMAND_HISTORY,
"_COMMAND": COMMAND,
}
class_types = {
"_ROW": ROW,
"_SCREEN_INFORMATION": SCREEN_INFORMATION,
"_CONSOLE_INFORMATION": CONSOLE_INFORMATION,
"_COMMAND_HISTORY": COMMAND_HISTORY,
"_COMMAND": COMMAND,
}
@@ -2,6 +2,8 @@
# which is available at https://www.volatilityfoundation.org/license/vsl-v1.0
#
from typing import Optional
from volatility3.framework import objects, constants, exceptions
@@ -26,7 +28,15 @@ class MFTFileName(objects.StructType):
class MFTAttribute(objects.StructType):
"""This represents an MFT ATTRIBUTE"""
def get_resident_filename(self) -> str:
def get_resident_filename(self) -> Optional[str]:
# 4MB chosen as cutoff instead of 4KB to allow for recovery from format /L created file systems
# Length as 512 as its 256*2, which is the maximum size for an entire file path, so this is even generous
if (
self.Attr_Header.ContentOffset > 0x400000
or self.Attr_Header.NameLength > 512
):
return None
# To get the resident name, we jump to relative name offset and read name length * 2 bytes of data
try:
name = self._context.object(
@@ -41,7 +51,15 @@ class MFTAttribute(objects.StructType):
except exceptions.InvalidAddressException:
return None
def get_resident_filecontent(self) -> bytes:
def get_resident_filecontent(self) -> Optional[bytes]:
# smear observed in mass testing of samples
# 4MB chosen as cutoff instead of 4KB to allow for recovery from format /L created file systems
if (
self.Attr_Header.ContentOffset > 0x400000
or self.Attr_Header.ContentLength > 0x400000
):
return None
# To get the resident content, we jump to relative content offset and read name length * 2 bytes of data
try:
bytesobj = self._context.object(
@@ -5,7 +5,7 @@ import contextlib
import enum
import logging
import struct
from typing import Iterable, Optional, Union
from typing import Iterator, Optional, Union, cast
from volatility3.framework import constants, exceptions, interfaces, objects
from volatility3.framework.layers.registry import (
@@ -39,6 +39,29 @@ class RegValueTypes(enum.Enum):
return cls(RegValueTypes.REG_UNKNOWN)
INTEGER_TYPES = [
RegValueTypes.REG_DWORD,
RegValueTypes.REG_QWORD,
RegValueTypes.REG_DWORD_BIG_ENDIAN,
RegValueTypes.REG_DWORD_BIG_ENDIAN,
]
STRING_TYPES = [
RegValueTypes.REG_SZ,
RegValueTypes.REG_MULTI_SZ,
RegValueTypes.REG_EXPAND_SZ,
RegValueTypes.REG_LINK,
]
BINARY_TYPES = [
RegValueTypes.REG_RESOURCE_LIST,
RegValueTypes.REG_BINARY,
RegValueTypes.REG_FULL_RESOURCE_DESCRIPTOR,
RegValueTypes.REG_RESOURCE_REQUIREMENTS_LIST,
RegValueTypes.REG_NONE,
]
class RegKeyFlags(enum.IntEnum):
KEY_IS_VOLATILE = 0x01
KEY_HIVE_EXIT = 0x02
@@ -142,7 +165,7 @@ class CM_KEY_NODE(objects.StructType):
)
return bool(self.vol.offset & 0x80000000)
def get_subkeys(self) -> Iterable[interfaces.objects.ObjectInterface]:
def get_subkeys(self) -> Iterator["CM_KEY_NODE"]:
"""Returns a list of the key nodes."""
hive = self._context.layers[self.vol.layer_name]
if not isinstance(hive, RegistryHive):
@@ -154,7 +177,7 @@ class CM_KEY_NODE(objects.StructType):
def _get_subkeys_recursive(
self, hive: RegistryHive, node: interfaces.objects.ObjectInterface
) -> Iterable[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
# We can either double the list and only use the even items, or
@@ -170,7 +193,7 @@ class CM_KEY_NODE(objects.StructType):
elif signature == "lh" or signature == "lf":
listjump = 2
elif node.vol.type_name.endswith(constants.BANG + "_CM_KEY_NODE"):
yield node
yield cast("CM_KEY_NODE", node)
else:
vollog.debug(
"Unexpected node type encountered when traversing subkeys: {}, signature: {}".format(
@@ -200,7 +223,7 @@ class CM_KEY_NODE(objects.StructType):
continue
yield from self._get_subkeys_recursive(hive, subnode)
def get_values(self) -> Iterable[interfaces.objects.ObjectInterface]:
def get_values(self) -> Iterator["CM_KEY_VALUE"]:
"""Returns a list of the Value nodes for a key."""
hive = self._context.layers[self.vol.layer_name]
if not isinstance(hive, RegistryHive):
@@ -216,8 +239,9 @@ class CM_KEY_NODE(objects.StructType):
except (RegistryInvalidIndex, RegistryFormatException) as excp:
vollog.debug(f"Invalid address {excp}")
continue
if node.vol.type_name.endswith(constants.BANG + "_CM_KEY_VALUE"):
if isinstance(node, CM_KEY_VALUE):
yield node
except (exceptions.InvalidAddressException, RegistryFormatException) as excp:
vollog.debug(f"Invalid address in get_values iteration: {excp}")
return None
@@ -249,6 +273,10 @@ class CM_KEY_VALUE(objects.StructType):
self.Name.count = namelength
return self.Name.cast("string", max_length=namelength, encoding="latin-1")
def get_type(self) -> RegValueTypes:
"""Get the type of the registry value"""
return RegValueTypes(self.Type)
def decode_data(self) -> Union[int, bytes]:
"""Properly decodes the data associated with the value node"""
# Determine if the data is stored inline
@@ -293,29 +321,28 @@ class CM_KEY_VALUE(objects.StructType):
# but the length at the start could be negative so just adding 4 to jump past it
data = layer.read(self.Data + 4, datalen)
self_type = RegValueTypes(self.Type)
if self_type == RegValueTypes.REG_DWORD:
if self.get_type() == RegValueTypes.REG_DWORD:
if len(data) != struct.calcsize("<L"):
raise ValueError(
f"Size of data does not match the type of registry value {self.get_name()}"
)
(res,) = struct.unpack("<L", data)
return res
if self_type == RegValueTypes.REG_DWORD_BIG_ENDIAN:
if self.get_type() == RegValueTypes.REG_DWORD_BIG_ENDIAN:
if len(data) != struct.calcsize(">L"):
raise ValueError(
f"Size of data does not match the type of registry value {self.get_name()}"
)
(res,) = struct.unpack(">L", data)
return res
if self_type == RegValueTypes.REG_QWORD:
if self.get_type() == RegValueTypes.REG_QWORD:
if len(data) != struct.calcsize("<Q"):
raise ValueError(
f"Size of data does not match the type of registry value {self.get_name()}"
)
(res,) = struct.unpack("<Q", data)
return res
if self_type in [
if self.get_type() in [
RegValueTypes.REG_SZ,
RegValueTypes.REG_EXPAND_SZ,
RegValueTypes.REG_LINK,
@@ -326,7 +353,7 @@ class CM_KEY_VALUE(objects.StructType):
RegValueTypes.REG_RESOURCE_REQUIREMENTS_LIST,
]:
return data
if self_type == RegValueTypes.REG_NONE:
if self.get_type() == RegValueTypes.REG_NONE:
return b""
# Fall back if it's something weird

Some files were not shown because too many files have changed in this diff Show More