Skip to content

Commit

Permalink
volatility3: add FreeBSD support
Browse files Browse the repository at this point in the history
This includes several plugins: creds, envars, lskld, lsmod, lsof,
mount, proc_maps, psaux, pslist, pstree.

This work was sponsored by OWN https://www.own.security/
  • Loading branch information
ant1 committed Jun 25, 2024
1 parent ac5769c commit f5c4302
Show file tree
Hide file tree
Showing 25 changed files with 1,373 additions and 10 deletions.
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ workspace.xml
# Manually generated files
.mypy_cache
stubs
volatility3/symbols/freebsd*
volatility3/symbols/linux*
volatility3/symbols/windows*
volatility3/symbols/mac*
Expand Down
5 changes: 4 additions & 1 deletion volatility3/cli/volshell/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@
import volatility3.plugins
import volatility3.symbols
from volatility3 import cli, framework
from volatility3.cli.volshell import generic, linux, mac, windows
from volatility3.cli.volshell import freebsd, generic, linux, mac, windows
from volatility3.framework import (
automagic,
constants,
Expand Down Expand Up @@ -167,6 +167,7 @@ def run(self):
action="store_true",
help="Run a Windows volshell",
)
os_specific.add_argument("--freebsd", default = False, action = "store_true", help = "Run a Freebsd volshell")
os_specific.add_argument(
"-l",
"--linux",
Expand Down Expand Up @@ -285,6 +286,8 @@ def run(self):
plugin = generic.Volshell
if args.windows:
plugin = windows.Volshell
if args.freebsd:
plugin = freebsd.Volshell
if args.linux:
plugin = linux.Volshell
if args.mac:
Expand Down
74 changes: 74 additions & 0 deletions volatility3/cli/volshell/freebsd.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,74 @@
# 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
#

from typing import Any, List, Tuple, Union

from volatility3.cli.volshell import generic
from volatility3.framework import constants, interfaces
from volatility3.framework.configuration import requirements
from volatility3.plugins.freebsd import pslist


class Volshell(generic.Volshell):
"""Shell environment to directly interact with a freebsd memory image."""

@classmethod
def get_requirements(cls):
return [
requirements.ModuleRequirement(name = "kernel", description = "Kernel module for the OS"),
requirements.PluginRequirement(name = "pslist", plugin = pslist.PsList, version = (1, 0, 0)),
requirements.IntRequirement(name = "pid", description = "Process ID", optional = True),
]

def change_task(self, pid = None):
"""Change the current process and layer, based on a process ID"""
tasks = self.list_tasks()
for task in tasks:
if task.p_pid == pid:
process_layer = task.add_process_layer()
if process_layer is not None:
self.change_layer(process_layer)
return None
print(f"Layer for task ID {pid} could not be constructed")
return None
print(f"No task with task ID {pid} found")

def list_tasks(self):
"""Returns a list of task objects from the primary layer"""
# We always use the main kernel memory and associated symbols
return list(pslist.PsList.list_tasks(self.context, self.current_kernel_name))

def construct_locals(self) -> List[Tuple[List[str], Any]]:
result = super().construct_locals()
result += [
(["ct", "change_task", "cp"], self.change_task),
(["lt", "list_tasks", "ps"], self.list_tasks),
(["symbols"], self.context.symbol_space[self.current_symbol_table]),
]
if self.config.get("pid", None) is not None:
self.change_task(self.config["pid"])
return result

def display_type(
self,
object: Union[str, interfaces.objects.ObjectInterface, interfaces.objects.Template],
offset: int = None,
):
"""Display Type describes the members of a particular object in alphabetical order"""
if isinstance(object, str):
if constants.BANG not in object:
object = self.current_symbol_table + constants.BANG + object
return super().display_type(object, offset)

def display_symbols(self, symbol_table: str = None):
"""Prints an alphabetical list of symbols for a symbol table"""
if symbol_table is None:
symbol_table = self.current_symbol_table
return super().display_symbols(symbol_table)

@property
def current_layer(self):
if self.__current_layer is None:
self.__current_layer = self.kernel.layer_name
return self.__current_layer
143 changes: 143 additions & 0 deletions volatility3/framework/automagic/freebsd.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,143 @@
# 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 logging
import os
import struct
from typing import Optional, Type

from volatility3.framework import constants, interfaces
from volatility3.framework.automagic import symbol_cache, symbol_finder
from volatility3.framework.configuration import requirements
from volatility3.framework.layers import intel, scanners
from volatility3.framework.symbols import freebsd

vollog = logging.getLogger(__name__)


class FreebsdIntelStacker(interfaces.automagic.StackerLayerInterface):
stack_order = 35
exclusion_list = ["linux", "mac", "windows"]

@classmethod
def stack(
cls,
context: interfaces.context.ContextInterface,
layer_name: str,
progress_callback: constants.ProgressCallback = None,
) -> Optional[interfaces.layers.DataLayerInterface]:
"""Attempts to identify freebsd within this layer."""
# Version check the SQlite cache
required = (1, 0, 0)
if not requirements.VersionRequirement.matches_required(required, symbol_cache.SqliteCache.version):
vollog.info(
f"SQLiteCache version not suitable: required {required} found {symbol_cache.SqliteCache.version}")
return None

# Bail out by default unless we can stack properly
layer = context.layers[layer_name]
join = interfaces.configuration.path_join

# Never stack on top of an intel layer
# FIXME: Find a way to improve this check
if isinstance(layer, intel.Intel):
return None

identifiers_path = os.path.join(constants.CACHE_PATH, constants.IDENTIFIERS_FILENAME)
freebsd_banners = symbol_cache.SqliteCache(identifiers_path).get_identifier_dictionary(
operating_system = "freebsd")
# If we have no banners, don't bother scanning
if not freebsd_banners:
vollog.info(
"No Freebsd banners found - if this is a freebsd plugin, please check your symbol files location")
return None

mss = scanners.MultiStringScanner([x for x in freebsd_banners if x is not None])
for _, banner in layer.scan(context = context, scanner = mss, progress_callback = progress_callback):
dtb = None
vollog.debug(f"Identified banner: {repr(banner)}")

isf_path = freebsd_banners.get(banner, None)
if isf_path:
table_name = context.symbol_space.free_table_name("FreebsdIntelStacker")
table = freebsd.FreebsdKernelIntermedSymbols(
context,
"temporary." + table_name,
name = table_name,
isf_url = isf_path,
)
context.symbol_space.append(table)

layer_class: Type = intel.Intel
# Freebsd amd64
if "KPML4phys" in table.symbols:
layer_class = intel.Intel32e
kernbase = table.get_symbol("kernbase").address
kpml4phys_ptr = table.get_symbol("KPML4phys").address
kpml4phys_str = layer.read(kpml4phys_ptr - kernbase, 8)
dtb = struct.unpack("<Q", kpml4phys_str)[0]
# Freebsd i386
elif "IdlePTD" in table.symbols:
layer_class = intel.Intel
if "tramp_idleptd" in table.symbols:
kernbase = 0
else:
kernbase = table.get_symbol("kernbase").address
idleptd_ptr = table.get_symbol("IdlePTD").address
idleptd_str = layer.read(idleptd_ptr - kernbase, 4)
dtb = struct.unpack("<I", idleptd_str)[0]
# Freebsd i386 after merge of PAE and non-PAE pmaps into same kernel
elif "IdlePTD_nopae" in table.symbols:
pae_mode_addr = table.get_symbol("pae_mode").address
pae_mode = layer.read(pae_mode_addr, 4)
if pae_mode == b'\x01\x00\x00\x00':
layer_class = intel.IntelPAE
idlepdpt_ptr = table.get_symbol("IdlePDPT").address
idlepdpt_str = layer.read(idlepdpt_ptr, 4)
dtb = struct.unpack("<I", idlepdpt_str)[0]
elif pae_mode == b'\x00\x00\x00\x00':
layer_class = intel.Intel
idleptd_ptr = table.get_symbol("IdlePTD_nopae").address
idleptd_str = layer.read(idleptd_ptr, 4)
dtb = struct.unpack("<I", idleptd_str)[0]
# Freebsd i386 with PAE
elif "IdlePDPT" in table.symbols:
layer_class = intel.IntelPAE
if "tramp_idleptd" in table.symbols:
kernbase = 0
else:
kernbase = table.get_symbol("kernbase").address
idlepdpt_ptr = table.get_symbol("IdlePDPT").address
idlepdpt_str = layer.read(idlepdpt_ptr - kernbase, 4)
dtb = struct.unpack('<I', idlepdpt_str)[0]

# Build the new layer
new_layer_name = context.layers.free_layer_name("IntelLayer")
config_path = join("IntelHelper", new_layer_name)
context.config[join(config_path, "memory_layer")] = layer_name
context.config[join(config_path, "page_map_offset")] = dtb
context.config[join(config_path, FreebsdSymbolFinder.banner_config_key)] = str(banner, "latin-1")

layer = layer_class(
context,
config_path = config_path,
name = new_layer_name,
metadata = {"os": "freebsd"},
)
layer.config["kernel_virtual_offset"] = 0

if layer and dtb:
vollog.debug(f"DTB was found at: 0x{dtb:0x}")
return layer
vollog.debug("No suitable freebsd banner could be matched")
return None


class FreebsdSymbolFinder(symbol_finder.SymbolFinder):
"""Freebsd symbol loader based on uname signature strings."""

banner_config_key = "kernel_banner"
operating_system = "freebsd"
symbol_class = "volatility3.framework.symbols.freebsd.FreebsdKernelIntermedSymbols"
exclusion_list = ["linux", "mac", "windows"]
4 changes: 2 additions & 2 deletions volatility3/framework/automagic/linux.py
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@

class LinuxIntelStacker(interfaces.automagic.StackerLayerInterface):
stack_order = 35
exclusion_list = ["mac", "windows"]
exclusion_list = ["freebsd", "mac", "windows"]

@classmethod
def stack(
Expand Down Expand Up @@ -200,4 +200,4 @@ class LinuxSymbolFinder(symbol_finder.SymbolFinder):
operating_system = "linux"
symbol_class = "volatility3.framework.symbols.linux.LinuxKernelIntermedSymbols"
find_aslr = lambda cls, *args: LinuxIntelStacker.find_aslr(*args)[1]
exclusion_list = ["mac", "windows"]
exclusion_list = ["freebsd", "mac", "windows"]
4 changes: 2 additions & 2 deletions volatility3/framework/automagic/mac.py
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@

class MacIntelStacker(interfaces.automagic.StackerLayerInterface):
stack_order = 35
exclusion_list = ["windows", "linux"]
exclusion_list = ["freebsd", "linux", "windows"]

@classmethod
def stack(
Expand Down Expand Up @@ -267,4 +267,4 @@ class MacSymbolFinder(symbol_finder.SymbolFinder):
operating_system = "mac"
find_aslr = MacIntelStacker.find_aslr
symbol_class = "volatility3.framework.symbols.mac.MacKernelIntermedSymbols"
exclusion_list = ["windows", "linux"]
exclusion_list = ["freebsd", "linux", "windows"]
2 changes: 1 addition & 1 deletion volatility3/framework/automagic/pdbscan.py
Original file line number Diff line number Diff line change
Expand Up @@ -48,7 +48,7 @@ class KernelPDBScanner(interfaces.automagic.AutomagicInterface):

priority = 30
max_pdb_size = 0x400000
exclusion_list = ["linux", "mac"]
exclusion_list = ["freebsd", "linux", "mac"]

def find_virtual_layers_from_req(
self,
Expand Down
15 changes: 15 additions & 0 deletions volatility3/framework/automagic/symbol_cache.py
Original file line number Diff line number Diff line change
Expand Up @@ -75,6 +75,8 @@ def get_identifier(cls, json) -> Optional[bytes]:
mac_banner = (
json.get("symbols", {}).get("version", {}).get("constant_data", None)
)
if mac_banner and json.get("symbols", {}).get("kfreebsd_brand_info", {}):
return None
if mac_banner:
return base64.b64decode(mac_banner)
return None
Expand All @@ -93,6 +95,19 @@ def get_identifier(cls, json) -> Optional[bytes]:
return None


class FreebsdIdentifier(IdentifierProcessor):
operating_system = "freebsd"

@classmethod
def get_identifier(cls, json) -> Optional[bytes]:
freebsd_banner = (json.get("symbols", {}).get("version", {}).get("constant_data", None))
if freebsd_banner and not json.get("symbols", {}).get("kfreebsd_brand_info", {}):
return None
if freebsd_banner:
return base64.b64decode(freebsd_banner)
return None


### CacheManagers


Expand Down
4 changes: 2 additions & 2 deletions volatility3/framework/automagic/windows.py
Original file line number Diff line number Diff line change
Expand Up @@ -192,7 +192,7 @@ def __call__(

class WindowsIntelStacker(interfaces.automagic.StackerLayerInterface):
stack_order = 40
exclusion_list = ["mac", "linux"]
exclusion_list = ["freebsd", "linux", "mac"]

# Group these by region so we only run over the data once
test_sets = [
Expand Down Expand Up @@ -357,7 +357,7 @@ class WinSwapLayers(interfaces.automagic.AutomagicInterface):
"""Class to read swap_layers filenames from single-swap-layers, create the
layers and populate the single-layers swap_layers."""

exclusion_list = ["linux", "mac"]
exclusion_list = ["freebsd", "linux", "mac"]

def __call__(
self,
Expand Down
2 changes: 1 addition & 1 deletion volatility3/framework/constants/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -95,7 +95,7 @@
ProgressCallback = Optional[Callable[[float, str], None]]
"""Type information for ProgressCallback objects"""

OS_CATEGORIES = ["windows", "mac", "linux"]
OS_CATEGORIES = ["windows", "mac", "linux", "freebsd"]


class Parallelism(enum.IntEnum):
Expand Down
2 changes: 1 addition & 1 deletion volatility3/framework/plugins/banners.py
Original file line number Diff line number Diff line change
Expand Up @@ -41,7 +41,7 @@ def locate_banners(
for offset in layer.scan(
context=context,
scanner=scanners.RegExScanner(
rb"(Linux version|Darwin Kernel Version) [0-9]+\.[0-9]+\.[0-9]+"
rb"((Linux version|Darwin Kernel Version) [0-9]+\.[0-9]+\.[0-9]+|FreeBSD [0-9]+\.[0-9]+)"
),
):
data = layer.read(offset, 0xFFF)
Expand Down
8 changes: 8 additions & 0 deletions volatility3/framework/plugins/freebsd/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
# 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
#
"""All core freebsd plugins.
These modules should only be imported from volatility3.plugins NOT
volatility3.framework.plugins
"""
Loading

0 comments on commit f5c4302

Please sign in to comment.