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

[2024] AArch64 support #1088

Draft
wants to merge 91 commits into
base: develop
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from 20 commits
Commits
Show all changes
91 commits
Select commit Hold shift + click to select a range
e5f4071
arm layer integrating aarch64 support
Abyss-W4tcher Jan 20, 2024
1fd3708
adapt linux_stacker for aarch64 support
Abyss-W4tcher Jan 20, 2024
490b593
remove irrelevant va_bits comparison
Abyss-W4tcher Jan 20, 2024
c9e7a3b
add volatility license
Abyss-W4tcher Jan 21, 2024
584d5f1
correct return type hinting
Abyss-W4tcher Jan 21, 2024
156eb51
python3.7 compatibility on Literal type
Abyss-W4tcher Jan 21, 2024
f63a6f4
add explicit return None
Abyss-W4tcher Jan 21, 2024
f54c2d1
correct descriptors choices and order
Abyss-W4tcher Jan 21, 2024
1833dbd
debug requirements and descriptions
Abyss-W4tcher Jan 22, 2024
fee620c
parameterize debug
Abyss-W4tcher Jan 22, 2024
720aa60
layer docstring enhancement
Abyss-W4tcher Jan 22, 2024
001e473
set default mappings in cls context
Abyss-W4tcher Jan 22, 2024
8e56198
more granularity for context dependent options
Abyss-W4tcher Jan 22, 2024
f46d2a6
keep only space context values for more clarity
Abyss-W4tcher Jan 22, 2024
8c0c6f6
wrap utilities into functions
Abyss-W4tcher Jan 22, 2024
19e65ea
set correct visibility for (de)canonicalize
Abyss-W4tcher Jan 22, 2024
254a3e7
simplify page size choice
Abyss-W4tcher Jan 22, 2024
bc1712f
be explicit about arm registers and page sizes
Abyss-W4tcher Jan 22, 2024
a2145be
typo
Abyss-W4tcher Jan 22, 2024
edee718
do a quick bruteforce on page size if necessary
Abyss-W4tcher Jan 23, 2024
7b50a34
Merge branch 'volatilityfoundation:develop' into aarch64-support
Abyss-W4tcher Jan 29, 2024
6d60a08
black 24.1.1
Abyss-W4tcher Jan 29, 2024
0e23aee
minor optimizations
Abyss-W4tcher Feb 23, 2024
c10ab69
pass endianness as config
Abyss-W4tcher Mar 5, 2024
6a821e7
add seen banners check
Abyss-W4tcher Mar 5, 2024
37512f9
exception management is less restrictive
Abyss-W4tcher Mar 5, 2024
5355126
add kernel endianness automagic
Abyss-W4tcher Mar 5, 2024
eb872e6
change linux source comment position
Abyss-W4tcher Mar 5, 2024
f994d28
remove initial va_bits calculation method
Abyss-W4tcher Mar 5, 2024
f9f5c0f
calculate va_bits by elimination if no info available
Abyss-W4tcher Mar 5, 2024
66e989b
destroy invalid layers
Abyss-W4tcher Mar 7, 2024
7040645
black formatting
Abyss-W4tcher Mar 7, 2024
65de635
explicit comments + revert and improve va_bits calculation
Abyss-W4tcher Mar 8, 2024
7ad5bb1
Merge branch 'volatilityfoundation:develop' into aarch64-support
Abyss-W4tcher Apr 3, 2024
bc47e76
explain ttb_granule and page_size similarity
Abyss-W4tcher Apr 4, 2024
8978b38
add page related getters
Abyss-W4tcher Apr 4, 2024
6cafcc5
tidy up incremental level variable
Abyss-W4tcher Apr 4, 2024
dc559fc
more generic kernel_banner comment
Abyss-W4tcher Apr 4, 2024
25f94df
handle null va_bits in symbols
Abyss-W4tcher Apr 16, 2024
7aae9c8
decrease banner error log level
Abyss-W4tcher Apr 16, 2024
680d0e4
bits_per_register getter
Abyss-W4tcher Apr 16, 2024
ff0bfc3
move generic getters to the the end
Abyss-W4tcher Apr 16, 2024
b4ab6ee
Merge branch 'volatilityfoundation:develop' into aarch64-support
Abyss-W4tcher Jun 22, 2024
94cb067
Merge branch 'volatilityfoundation:develop' into aarch64-support
Abyss-W4tcher Aug 5, 2024
f55a1cd
add cpu_registers requirement
Abyss-W4tcher Aug 7, 2024
69ee431
add cpu registers attributes mappings
Abyss-W4tcher Aug 7, 2024
cf51013
doc
Abyss-W4tcher Aug 7, 2024
26d92c7
calculate 52 bits mappings once
Abyss-W4tcher Aug 7, 2024
5c63b13
wrapper to read a CPU register field
Abyss-W4tcher Aug 7, 2024
3449de2
hw dirty state management
Abyss-W4tcher Aug 7, 2024
62526b5
linuxaarch64 layer mixin
Abyss-W4tcher Aug 7, 2024
953eec0
imports and minor cleanups
Abyss-W4tcher Aug 7, 2024
f9a54e4
put arch substackers in classes, unify linux kaslr calculations, extr…
Abyss-W4tcher Aug 7, 2024
2e86c11
unify dtb debug statement
Abyss-W4tcher Aug 7, 2024
e6b68e7
use Dict instead of dict for type hinting
Abyss-W4tcher Aug 7, 2024
40f78d6
handle explicit keyerror exception
Abyss-W4tcher Aug 7, 2024
287f029
remove hardcoded register attribute
Abyss-W4tcher Aug 10, 2024
a8a641b
prefer the use of constants to reference technical cpu registers
Abyss-W4tcher Aug 10, 2024
0defdd6
jsonerror raise fix
Abyss-W4tcher Aug 10, 2024
9d05c2f
fix misplaced variables
Abyss-W4tcher Aug 14, 2024
96404f7
precalculate getters
Abyss-W4tcher Aug 14, 2024
07c416c
move _mapping comment up
Abyss-W4tcher Aug 14, 2024
06688a1
switch to a single cpu_registers requirements
Abyss-W4tcher Aug 16, 2024
ec5758a
enhance registers mappings
Abyss-W4tcher Aug 16, 2024
927365b
add register manipulation function
Abyss-W4tcher Aug 16, 2024
20fcc68
switch to a single cpu_registers requirements
Abyss-W4tcher Aug 16, 2024
5798394
arm comments, and typos
Abyss-W4tcher Aug 18, 2024
2bb84d6
wrong page_mask calc (negative int)
Abyss-W4tcher Aug 18, 2024
f7614e3
initial windowsaarch64mixin and windowsaarch64
Abyss-W4tcher Aug 18, 2024
47f8a91
interrupt on missing cpu_registers config
Abyss-W4tcher Aug 18, 2024
7dced24
wrong windows parent class
Abyss-W4tcher Aug 20, 2024
9f609f1
switch kernel_endianness req to struct format
Abyss-W4tcher Aug 21, 2024
2e8a467
add bogus descriptor tables detection
Abyss-W4tcher Aug 21, 2024
b3279f1
switch kernel_endianness req to struct format
Abyss-W4tcher Aug 21, 2024
d706bd7
use is instead of == None
Abyss-W4tcher Aug 22, 2024
c5afe39
cache find_aslr()
Abyss-W4tcher Aug 30, 2024
9676330
add subclass granularity to logging
Abyss-W4tcher Aug 30, 2024
c4766c2
use TTB1 terminology instead of DTB for aarch64
Abyss-W4tcher Aug 30, 2024
cb69983
fix cpu_registers description
Abyss-W4tcher Aug 30, 2024
537b404
add ttbr comments
Abyss-W4tcher Aug 30, 2024
9f17302
use explicit cpu requirements, to prevent confusion
Abyss-W4tcher Sep 5, 2024
d18496f
switch to explicit cpu requirements
Abyss-W4tcher Sep 5, 2024
e1ffb7e
remove json import
Abyss-W4tcher Sep 5, 2024
74b30cc
remove unused imports
Abyss-W4tcher Sep 9, 2024
b682f49
remove aslr_shift from va calculations
Abyss-W4tcher Sep 10, 2024
1f3ab8e
Merge branch 'volatilityfoundation:develop' into aarch64-support
Abyss-W4tcher Sep 12, 2024
59b58a5
handle no kaslr_shift
Abyss-W4tcher Sep 12, 2024
046c806
introduce an explicit class constant for win page size
Abyss-W4tcher Sep 25, 2024
d1e26c9
correct page_mask calculation
Abyss-W4tcher Sep 25, 2024
b302885
enhance dirty state management references
Abyss-W4tcher Oct 8, 2024
5ee05ce
fix canonicalization
Abyss-W4tcher Oct 20, 2024
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
279 changes: 234 additions & 45 deletions volatility3/framework/automagic/linux.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,20 +4,21 @@

import logging
import os
from typing import Optional, Tuple, Type
from typing import Optional, Tuple, Type, Union

from volatility3.framework import constants, interfaces
from volatility3.framework import constants, interfaces, exceptions
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.layers import intel, scanners, arm
from volatility3.framework.symbols import linux

vollog = logging.getLogger(__name__)


class LinuxIntelStacker(interfaces.automagic.StackerLayerInterface):
class LinuxStacker(interfaces.automagic.StackerLayerInterface):
stack_order = 35
exclusion_list = ["mac", "windows"]
join = interfaces.configuration.path_join

@classmethod
def stack(
Expand All @@ -39,11 +40,10 @@ def stack(

# 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
# Never stack on top of a linux layer
Copy link
Contributor Author

@Abyss-W4tcher Abyss-W4tcher Jan 30, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

To improve this, why not add an explicit layer property, like _is_top_layer inside volatility3/framework/layers/intel.py#Intel and volatility3/framework/layers/arm.py#AArch64 and check with following :

        if getattr(layer, "_is_top_layer") and layer._is_top_layer:
            return None

If we keep the current implementation, we have to change the Linux, Windows and Mac stacker for each new architecture layer.

Copy link
Member

@ikelos ikelos Jan 30, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Part of the reason is virtualization, it's possible to have an arm layer inside an intel layer (and it's certainly possible to have an intel layer on top an intel layer). You're right, it's not really a scalable solution (hence the FIXME right beneath this line), but it's also not a trivial attribute...

Copy link
Contributor Author

@Abyss-W4tcher Abyss-W4tcher Jan 30, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Which stacker is supposed to do this (LinuxStacker, WindowsIntelStacker and MacIntelStacker blocks it), for example if a VM managed by qemu-system-aarch64 on an Intel host was running when the memory was dumped ?

Will a "VM (qemu) layer" be available from the globals context.layers variable too :

  • FileLayer
    • LimeLayer
      • IntelLayer
        • Memory Layer
        • QemuLayer (not sure about this one)
          • IntelLayer
            • Memory Layer

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't think there is anything automatic in core that would do that, but it would be nice to have.

There has been this issue from a while ago that talks about that kind of thing.
#464

Copy link
Contributor Author

@Abyss-W4tcher Abyss-W4tcher Jan 30, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks for pointing it out :) So in the current state, as it is not implemented, we will continue to strictly refuse stacking on top of Linux and AArch64. Adding an explicit flag on those two might be a temporary and more scalable solution ?

I leave this as a side note, for a potentiel reader in the future interested in AArch64 hypervisor execution mode (and what it might imply, if treating a layer from the hypervisor point of view) : https://developer.arm.com/documentation/102412/0103/Privilege-and-Exception-levels/Exception-levels

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We do have a vmscan plugin, and in the future will likely generate a config file that will allow virtual machine guests to have volatility plugins run on them. This likely either by Intel on Intel, or IntelVM (doesn't exist yet) on Intel. The point is, it is a capability we'd like to leave open for the future, so planning for it now is useful.

# FIXME: Find a way to improve this check
if isinstance(layer, intel.Intel):
if isinstance(layer, intel.Intel) or isinstance(layer, arm.AArch64):
return None

identifiers_path = os.path.join(
Expand All @@ -63,66 +63,252 @@ def stack(
for _, banner in layer.scan(
context=context, scanner=mss, progress_callback=progress_callback
):
dtb = None
vollog.debug(f"Identified banner: {repr(banner)}")

isf_path = linux_banners.get(banner, None)
if isf_path:
table_name = context.symbol_space.free_table_name("LintelStacker")
table_name = context.symbol_space.free_table_name("LinuxStacker")
table = linux.LinuxKernelIntermedSymbols(
context,
"temporary." + table_name,
name=table_name,
isf_url=isf_path,
)
context.symbol_space.append(table)
kaslr_shift, aslr_shift = cls.find_aslr(
context, table_name, layer_name, progress_callback=progress_callback
)
new_layer_name = context.layers.free_layer_name("LinuxLayer")
config_path = cls.join("LinuxHelper", new_layer_name)
context.config[cls.join(config_path, "memory_layer")] = layer_name
context.config[
cls.join(config_path, LinuxSymbolFinder.banner_config_key)
] = str(banner, "latin-1")

layer_class: Type = intel.Intel
if "init_top_pgt" in table.symbols:
layer_class = intel.Intel32e
dtb_symbol_name = "init_top_pgt"
elif "init_level4_pgt" in table.symbols:
layer_class = intel.Intel32e
dtb_symbol_name = "init_level4_pgt"
else:
dtb_symbol_name = "swapper_pg_dir"
linux_arch_stackers = [cls.intel_stacker, cls.aarch64_stacker]

dtb = cls.virtual_to_physical_address(
table.get_symbol(dtb_symbol_name).address + kaslr_shift
)
for linux_arch_stacker in linux_arch_stackers:
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Here, the stacking is operated blindly. However, we could also rely on symbols to determine the architecture to stack on.

I identified idmap_pg_dir to be arm64 exclusive, and present since the first 3.7 release. Relying on a symbol, avoids "forced exception rounds", which shouts about missing symbols when an AArch64 image is first passed to the Intel stacker.

try:
layer = linux_arch_stacker(
context=context,
layer_name=layer_name,
table=table,
table_name=table_name,
config_path=config_path,
new_layer_name=new_layer_name,
banner=banner,
progress_callback=progress_callback,
)
if layer:
return layer
except Exception as e:
vollog.log(
constants.LOGLEVEL_VVVV,
f"{linux_arch_stacker.__name__} exception: {e}",
)

# 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, LinuxSymbolFinder.banner_config_key)
] = str(banner, "latin-1")
vollog.debug("No suitable linux banner could be matched")
return None

layer = layer_class(
context,
config_path=config_path,
name=new_layer_name,
metadata={"os": "Linux"},
@classmethod
def intel_stacker(
cls,
context: interfaces.context.ContextInterface,
layer_name: str,
table: linux.LinuxKernelIntermedSymbols,
table_name: str,
config_path: str,
new_layer_name: str,
banner: str,
progress_callback: constants.ProgressCallback = None,
) -> Union[intel.Intel, intel.Intel32e, None]:
Fixed Show fixed Hide fixed
layer_class: Type = intel.Intel
if "init_top_pgt" in table.symbols:
layer_class = intel.Intel32e
dtb_symbol_name = "init_top_pgt"
elif "init_level4_pgt" in table.symbols:
layer_class = intel.Intel32e
dtb_symbol_name = "init_level4_pgt"
else:
dtb_symbol_name = "swapper_pg_dir"

kaslr_shift, aslr_shift = cls.find_aslr(
context,
table_name,
layer_name,
layer_class,
progress_callback=progress_callback,
)

dtb = cls.virtual_to_physical_address(
table.get_symbol(dtb_symbol_name).address + kaslr_shift
)

# Build the new layer
context.config[cls.join(config_path, "page_map_offset")] = dtb

layer = layer_class(
context,
config_path=config_path,
name=new_layer_name,
metadata={"os": "Linux"},
)
layer.config["kernel_virtual_offset"] = aslr_shift
linux_banner_address = table.get_symbol("linux_banner").address + aslr_shift
test_banner_equality = cls.verify_translation_by_banner(
context=context,
layer=layer,
layer_name=layer_name,
linux_banner_address=linux_banner_address,
target_banner=banner,
)

if layer and dtb and test_banner_equality:
vollog.debug(f"DTB was found at: 0x{dtb:0x}")
vollog.debug("Intel image found")
return layer

return None

@classmethod
def aarch64_stacker(
cls,
context: interfaces.context.ContextInterface,
layer_name: str,
table: linux.LinuxKernelIntermedSymbols,
table_name: str,
config_path: str,
new_layer_name: str,
banner: bytes,
progress_callback: constants.ProgressCallback = None,
) -> Optional[arm.AArch64]:
Fixed Show fixed Hide fixed
layer_class = arm.AArch64
kaslr_shift, aslr_shift = cls.find_aslr(
context,
table_name,
layer_name,
layer_class,
progress_callback=progress_callback,
)
dtb = table.get_symbol("swapper_pg_dir").address + kaslr_shift
context.config[cls.join(config_path, "page_map_offset")] = dtb
context.config[cls.join(config_path, "page_map_offset_kernel")] = dtb

# CREDIT : https://github.com/crash-utility/crash/blob/28891d1127542dbb2d5ba16c575e14e741ed73ef/arm64.c#L941
kernel_flags = 0
if "_kernel_flags_le" in table.symbols:
kernel_flags = table.get_symbol("_kernel_flags_le").address
if "_kernel_flags_le_hi32" in table.symbols:
kernel_flags |= table.get_symbol("_kernel_flags_le_hi32").address << 32
if "_kernel_flags_le_lo32" in table.symbols:
kernel_flags |= table.get_symbol("_kernel_flags_le_lo32").address

# https://www.kernel.org/doc/Documentation/arm64/booting.txt
page_size_kernel_space_bit = (kernel_flags >> 1) & 3
linux_banner_address = table.get_symbol("linux_banner").address + aslr_shift

# v6.7/source/arch/arm64/include/asm/memory.h#L186 - v5.7/source/arch/arm64/include/asm/memory.h#L160
if "vabits_actual" in table.symbols:
vabits_actual_phys_addr = (
table.get_symbol("vabits_actual").address + kaslr_shift
)
va_bits = int.from_bytes(
context.layers[layer_name].read(vabits_actual_phys_addr, 8),
"little",
)
else:
# TODO: If KASAN space is large enough, it *might* push kernel addresses higher and generate inaccurate results ?
# We count the number of high bits equal to 1, which gives us the kernel space address mask and ultimately TCR_EL1.T1SZ.
va_bits = (linux_banner_address ^ (2**64 - 1)).bit_length() + 1

tcr_el1_t1sz = 64 - va_bits
context.config[cls.join(config_path, "tcr_el1_t1sz")] = tcr_el1_t1sz
context.config[cls.join(config_path, "tcr_el1_t0sz")] = tcr_el1_t1sz

# If "_kernel_flags_le*" aren't in the symbols, we can still do a quick bruteforce on [4,16,64] page sizes
# False positives cannot happen, as translation indexes will be off on a wrong page size
page_size_kernel_space_candidates = (
[4**page_size_kernel_space_bit]
if 1 <= page_size_kernel_space_bit <= 3
else [4, 16, 64]
)

for i, page_size_kernel_space in enumerate(page_size_kernel_space_candidates):
# Kernel space page size is considered equal to the user space page size
# T1SZ is considered equal to T0SZ
context.config[
cls.join(config_path, "page_size_kernel_space")
] = page_size_kernel_space
context.config[
cls.join(config_path, "page_size_user_space")
] = page_size_kernel_space

# Build layer
layer = layer_class(
context,
config_path=config_path,
name=new_layer_name,
metadata={"os": "Linux"},
)
layer.config["kernel_virtual_offset"] = aslr_shift

try:
test_banner_equality = cls.verify_translation_by_banner(
context=context,
layer=layer,
layer_name=layer_name,
linux_banner_address=linux_banner_address,
target_banner=banner,
)
layer.config["kernel_virtual_offset"] = aslr_shift
except Exception as e:
# Only raise the banner translation error if there are no more candidates
if i < len(page_size_kernel_space_candidates) - 1:
continue
else:
raise e

if layer and dtb:
vollog.debug(f"DTB was found at: 0x{dtb:0x}")
if layer and dtb and test_banner_equality:
vollog.debug(f"Kernel DTB was found at: 0x{dtb:0x}")
vollog.debug("AArch64 image found")
return layer
vollog.debug("No suitable linux banner could be matched")

return None

@classmethod
def verify_translation_by_banner(
cls,
context: interfaces.context.ContextInterface,
layer,
layer_name: str,
linux_banner_address: int,
target_banner: bytes,
) -> bool:
"""Determine if a stacked layer is correct or a false positive, by calling the underlying
_translate method against the linux_banner symbol virtual address. Then, compare it with
the detected banner to verify the correct translation.
This will directly raise an exception, as any failed attempt indicates a wrong layer selection.
"""

test_banner_equality = True
try:
banner_phys_address = layer._translate(linux_banner_address)[0]
banner_value = context.layers[layer_name].read(
banner_phys_address, len(target_banner)
)
except exceptions.InvalidAddressException:
raise Exception('Cannot translate "linux_banner" virtual address')

if not banner_value == target_banner:
raise Exception(
f'Translated "linux_banner" virtual address mismatches detected banner : \n{banner_value}\n!=\n{target_banner}'
)

return test_banner_equality

@classmethod
def find_aslr(
cls,
context: interfaces.context.ContextInterface,
symbol_table: str,
layer_name: str,
layer_class,
progress_callback: constants.ProgressCallback = None,
) -> Tuple[int, int]:
"""Determines the offset of the actual DTB in physical space and its
Expand Down Expand Up @@ -162,9 +348,12 @@ def find_aslr(
init_task.files.cast("long unsigned int")
- module.get_symbol("init_files").address
)
kaslr_shift = init_task_address - cls.virtual_to_physical_address(
init_task_json_address
)
if layer_class == arm.AArch64:
Abyss-W4tcher marked this conversation as resolved.
Show resolved Hide resolved
kaslr_shift = init_task_address - init_task_json_address
else:
kaslr_shift = init_task_address - cls.virtual_to_physical_address(
init_task_json_address
)
if address_mask:
aslr_shift = aslr_shift & address_mask

Expand Down Expand Up @@ -196,5 +385,5 @@ class LinuxSymbolFinder(symbol_finder.SymbolFinder):
banner_config_key = "kernel_banner"
operating_system = "linux"
symbol_class = "volatility3.framework.symbols.linux.LinuxKernelIntermedSymbols"
find_aslr = lambda cls, *args: LinuxIntelStacker.find_aslr(*args)[1]
find_aslr = lambda cls, *args: LinuxStacker.find_aslr(*args)[1]
exclusion_list = ["mac", "windows"]
Loading