Skip to content

Commit

Permalink
Merge pull request #1599 from gcmoreira/linux_kallsyms
Browse files Browse the repository at this point in the history
Linux - Introduce the Linux kallsyms API and related plugins
  • Loading branch information
ikelos authored Feb 2, 2025
2 parents 9a01f83 + cef87e0 commit b3760ce
Show file tree
Hide file tree
Showing 8 changed files with 2,379 additions and 14 deletions.
41 changes: 41 additions & 0 deletions test/test_volatility.py
Original file line number Diff line number Diff line change
Expand Up @@ -842,6 +842,47 @@ def test_linux_hidden_modules(image, volatility, python):
assert out.count(b"\n") >= 4


def test_linux_kallsyms(image, volatility, python):
rc, out, _err = runvol_plugin(
"linux.kallsyms.Kallsyms",
image,
volatility,
python,
pluginargs=["--modules"],
)
# linux-sample-1.bin has no hidden modules.
# This validates that plugin requirements are met and exceptions are not raised.
assert rc == 0
assert out.count(b"\n") > 1000

# Addr Type Size Exported SubSystem ModuleName SymbolName Description
# 0xffffa009eba9 t 28 False module usbcore usb_mon_register Symbol is in the text (code) section
assert re.search(
rb"0xffffa009eba9\s+t\s+28\s+False\s+module\s+usbcore\s+usb_mon_register\s+Symbol is in the text \(code\) section",
out,
)


def test_linux_pscallstack(image, volatility, python):
rc, out, _err = runvol_plugin(
"linux.pscallstack.PsCallStack",
image,
volatility,
python,
pluginargs=["--pid", "1"],
)

assert rc == 0
assert out.count(b"\n") > 30

# TID Comm Position Address Value Name Type Module
# 1 init 39 0x88001f999a40 0xffff81109039 do_select T kernel
assert re.search(
rb"1\s+init\s+39\s+0x88001f999a40.*?0xffff81109039\s+do_select\s+T\s+kernel",
out,
)


# MAC


Expand Down
2 changes: 1 addition & 1 deletion volatility3/framework/constants/_version.py
Original file line number Diff line number Diff line change
@@ -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 = 19 # Number of changes that only add to the interface
VERSION_MINOR = 20 # Number of changes that only add to the interface
VERSION_PATCH = 0 # Number of changes that do not change the interface
VERSION_SUFFIX = ""

Expand Down
21 changes: 21 additions & 0 deletions volatility3/framework/constants/linux/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -356,6 +356,27 @@ def flags(self) -> str:

# Kallsyms
KSYM_NAME_LEN = 512
NM_TYPES_DESC = {
"a": "Symbol is absolute and doesn't change during linking",
"b": "Symbol in the BSS section, typically holding zero-initialized or uninitialized data",
"c": "Symbol is common, typically holding uninitialized data",
"d": "Symbol is in the initialized data section",
"g": "Symbol is in an initialized data section for small objects",
"i": "Symbol is an indirect reference to another symbol",
"N": "Symbol is a debugging symbol",
"n": "Symbol is in a non-data, non-code, non-debug read-only section",
"p": "Symbol is in a stack unwind section",
"r": "Symbol is in a read only data section",
"s": "Symbol is in an uninitialized or zero-initialized data section for small objects",
"t": "Symbol is in the text (code) section",
"U": "Symbol is undefined",
"u": "Symbol is a unique global symbol",
"V": "Symbol is a weak object, with a default value",
"v": "Symbol is a weak object",
"W": "Symbol is a weak symbol but not marked as a weak object symbol, with a default value",
"w": "Symbol is a weak symbol but not marked as a weak object symbol",
"?": "Symbol type is unknown",
}

# VMCOREINFO
VMCOREINFO_MAGIC = b"VMCOREINFO\x00"
Expand Down
138 changes: 138 additions & 0 deletions volatility3/framework/plugins/linux/kallsyms.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,138 @@
# This file is Copyright 2025 Volatility Foundation and licensed under the Volatility Software License 1.0
# which is available at https://www.volatilityfoundation.org/license/vsl-v1.0
#
import logging
from typing import List, Union

from volatility3.framework import interfaces, renderers
from volatility3.framework.interfaces import plugins
from volatility3.framework.configuration import requirements
from volatility3.framework.renderers import format_hints
from volatility3.framework.constants import architectures
from volatility3.framework.symbols.linux import kallsyms


vollog = logging.getLogger(__name__)


class Kallsyms(plugins.PluginInterface):
"""Kallsyms symbols enumeration plugin.
If no arguments are provided, all symbols are included: core, modules, ftrace, and BPF.
Alternatively, you can use any combination of --core, --modules, --ftrace, and --bpf
to customize the output.
"""

_required_framework_version = (2, 19, 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="Kallsyms", component=kallsyms.Kallsyms, version=(1, 0, 0)
),
requirements.BooleanRequirement(
name="core",
description="Include core symbols",
default=False,
optional=True,
),
requirements.BooleanRequirement(
name="modules",
description="Include module symbols",
default=False,
optional=True,
),
requirements.BooleanRequirement(
name="ftrace",
description="Include ftrace symbols",
default=False,
optional=True,
),
requirements.BooleanRequirement(
name="bpf",
description="Include BPF symbols",
default=False,
optional=True,
),
]

def _get_symbol_size(
self, kassymbol: kallsyms.KASSymbol
) -> Union[int, interfaces.renderers.BaseAbsentValue]:
# Symbol sizes are calculated using the address of the next non-aliased
# symbol or the end of the kernel text area _end/_etext. However, some kernel
# symbols live beyond that area. For these symbols, the size will be negative,
# resulting in incorrect values. Unfortunately, there isn't much that can be done
# in such cases.
# See comments on .init.scratch in arch/x86/kernel/vmlinux.lds.S for details
return kassymbol.size if kassymbol.size >= 0 else renderers.NotAvailableValue()

def _generator(self):
module_name = self.config["kernel"]
vmlinux = self.context.modules[module_name]

kas = kallsyms.Kallsyms(
context=self.context,
layer_name=vmlinux.layer_name,
module_name=module_name,
)

include_core = self.config.get("core", False)
include_modules = self.config.get("modules", False)
include_ftrace = self.config.get("ftrace", False)
include_bpf = self.config.get("bpf", False)

symbols_flags = (include_core, include_modules, include_ftrace, include_bpf)
if not any(symbols_flags):
include_core = include_modules = include_ftrace = include_bpf = True

symbol_generators = []
if include_core:
symbol_generators.append(kas.get_core_symbols())
if include_modules:
symbol_generators.append(kas.get_modules_symbols())
if include_ftrace:
symbol_generators.append(kas.get_ftrace_symbols())
if include_bpf:
symbol_generators.append(kas.get_bpf_symbols())

for symbols_generator in symbol_generators:
for kassymbol in symbols_generator:
# Symbol sizes are calculated using the address of the next non-aliased
# symbol or the end of the kernel text area _end/_etext. However, some kernel
# symbols are located beyond that area, which causes this method to fail for
# the last symbol, resulting in a negative size.
# See comments on .init.scratch in arch/x86/kernel/vmlinux.lds.S for details
symbol_size = self._get_symbol_size(kassymbol)
fields = (
format_hints.Hex(kassymbol.address),
kassymbol.type,
symbol_size,
kassymbol.exported,
kassymbol.subsystem,
kassymbol.module_name,
kassymbol.name,
kassymbol.type_description or renderers.NotAvailableValue(),
)
yield 0, fields

def run(self):
headers = [
("Addr", format_hints.Hex),
("Type", str),
("Size", int),
("Exported", bool),
("SubSystem", str),
("ModuleName", str),
("SymbolName", str),
("Description", str),
]
return renderers.TreeGrid(headers, self._generator())
Loading

0 comments on commit b3760ce

Please sign in to comment.