Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

volatility3: add FreeBSD support #1182

Open
wants to merge 2 commits into
base: develop
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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
151 changes: 151 additions & 0 deletions volatility3/framework/automagic/freebsd.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,151 @@
# 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
kernload_offset = 0
kernload = table.get_symbol("kernload").address
for interp in layer.scan(context = context, scanner = scanners.BytesScanner(b"/red/herring\x00\x00\x00\x00"), progress_callback = progress_callback):
kernload_from_interp = interp & 0xfffffffffffff800
# Verify 2MB alignment
if kernload_from_interp & 0x1fffff == 0:
kernload_offset = kernload_from_interp - kernload
break
kernbase = table.get_symbol("kernbase").address
kpml4phys_ptr = table.get_symbol("KPML4phys").address
kpml4phys_str = layer.read(kpml4phys_ptr - kernbase + kernload_offset, 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