mirror of
https://github.com/volatilityfoundation/volatility3.git
synced 2026-10-01 22:14:53 +02:00
Merge pull request #1555 from volatilityfoundation/release/v2.11.0
Release/v2.11.0
This commit is contained in:
@@ -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 }}
|
||||
|
||||
@@ -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
|
||||
|
||||
|
||||
@@ -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
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
|
||||
@@ -4,5 +4,6 @@ sphinx_autodoc_typehints>=1.4.0
|
||||
sphinx-rtd-theme>=0.4.3
|
||||
|
||||
yara-python
|
||||
yara-x
|
||||
pycryptodome
|
||||
pefile
|
||||
|
||||
+1
-1
@@ -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
@@ -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
@@ -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
|
||||
|
||||
@@ -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)
|
||||
@@ -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
|
||||
|
||||
@@ -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(
|
||||
|
||||
@@ -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 = ""
|
||||
|
||||
@@ -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:
|
||||
|
||||
@@ -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:
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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)"
|
||||
|
||||
@@ -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]
|
||||
|
||||
@@ -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
|
||||
|
||||
|
||||
@@ -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,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
|
||||
|
||||
@@ -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"
|
||||
)
|
||||
|
||||
@@ -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[
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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(),
|
||||
)
|
||||
@@ -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
|
||||
|
||||
@@ -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))
|
||||
@@ -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,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(
|
||||
|
||||
@@ -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",
|
||||
|
||||
@@ -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]:
|
||||
|
||||
@@ -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
@@ -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
Reference in New Issue
Block a user