mirror of
https://github.com/volatilityfoundation/volatility3.git
synced 2026-09-02 23:28:43 +02:00
Merge pull request #1101 from gcmoreira/linux_library_list_plugin
Linux: Add linux.library_list.LibraryList plugin
This commit is contained in:
@@ -6,6 +6,7 @@
|
||||
#
|
||||
|
||||
import os
|
||||
import re
|
||||
import subprocess
|
||||
import sys
|
||||
import shutil
|
||||
@@ -331,6 +332,32 @@ def test_linux_tty_check(image, volatility, python):
|
||||
assert rc == 0
|
||||
|
||||
|
||||
def test_linux_library_list(image, volatility, python):
|
||||
rc, out, err = runvol_plugin(
|
||||
"linux.library_list.LibraryList", image, volatility, python
|
||||
)
|
||||
|
||||
assert re.search(
|
||||
rb"NetworkManager\s2363\s0x7f52cdda0000\s/lib/x86_64-linux-gnu/libnss_files.so.2",
|
||||
out,
|
||||
)
|
||||
assert re.search(
|
||||
rb"gnome-settings-\s3807\s0x7f7e660b5000\s/lib/x86_64-linux-gnu/libbz2.so.1.0",
|
||||
out,
|
||||
)
|
||||
assert re.search(
|
||||
rb"gdu-notificatio\s3878\s0x7f25ce33e000\s/usr/lib/x86_64-linux-gnu/libXau.so.6",
|
||||
out,
|
||||
)
|
||||
assert re.search(
|
||||
rb"bash\s8600\s0x7fe78a85f000\s/lib/x86_64-linux-gnu/libnss_files.so.2",
|
||||
out,
|
||||
)
|
||||
|
||||
assert out.count(b"\n") >= 2677
|
||||
assert rc == 0
|
||||
|
||||
|
||||
# MAC
|
||||
|
||||
|
||||
|
||||
@@ -5,11 +5,10 @@
|
||||
|
||||
Linux-specific values that aren't found in debug symbols
|
||||
"""
|
||||
from enum import IntEnum
|
||||
|
||||
KERNEL_NAME = "__kernel__"
|
||||
|
||||
# arch/x86/include/asm/page_types.h
|
||||
PAGE_SHIFT = 12
|
||||
"""The value hard coded from the Linux Kernel (hence not extracted from the layer itself)"""
|
||||
|
||||
# include/linux/sched.h
|
||||
@@ -281,3 +280,25 @@ CAPABILITIES = (
|
||||
)
|
||||
|
||||
ELF_MAX_EXTRACTION_SIZE = 1024 * 1024 * 1024 * 4 - 1
|
||||
|
||||
|
||||
class ELF_IDENT(IntEnum):
|
||||
"""ELF header e_ident indexes"""
|
||||
|
||||
EI_MAG0 = 0
|
||||
EI_MAG1 = 1
|
||||
EI_MAG2 = 2
|
||||
EI_MAG3 = 3
|
||||
EI_CLASS = 4
|
||||
EI_DATA = 5
|
||||
EI_VERSION = 6
|
||||
EI_OSABI = 7
|
||||
EI_PAD = 8
|
||||
|
||||
|
||||
class ELF_CLASS(IntEnum):
|
||||
"""ELF header class types"""
|
||||
|
||||
ELFCLASSNONE = 0
|
||||
ELFCLASS32 = 1
|
||||
ELFCLASS64 = 2
|
||||
|
||||
@@ -6,9 +6,11 @@ import struct
|
||||
from typing import Optional
|
||||
|
||||
from volatility3.framework import exceptions, interfaces, constants
|
||||
from volatility3.framework.constants.linux import ELF_CLASS
|
||||
from volatility3.framework.layers import segmented
|
||||
from volatility3.framework.symbols import intermed
|
||||
|
||||
|
||||
vollog = logging.getLogger(__name__)
|
||||
|
||||
|
||||
@@ -21,7 +23,7 @@ class Elf64Layer(segmented.SegmentedLayer):
|
||||
|
||||
_header_struct = struct.Struct("<IBBB")
|
||||
MAGIC = 0x464C457F # "\x7fELF"
|
||||
ELF_CLASS = 2
|
||||
ELF_CLASS = ELF_CLASS.ELFCLASS64
|
||||
|
||||
def __init__(
|
||||
self, context: interfaces.context.ContextInterface, config_path: str, name: str
|
||||
@@ -50,8 +52,17 @@ class Elf64Layer(segmented.SegmentedLayer):
|
||||
offset=ehdr.e_phoff + (pindex * ehdr.e_phentsize),
|
||||
)
|
||||
# We only want PT_TYPES with valid sizes
|
||||
try:
|
||||
ptype = phdr.p_type.description
|
||||
except ValueError:
|
||||
vollog.log(
|
||||
constants.LOGLEVEL_VVVV,
|
||||
f"Skipping unknown ELF program header type: {phdr.p_type}",
|
||||
)
|
||||
continue
|
||||
|
||||
if (
|
||||
phdr.p_type.lookup() == "PT_LOAD"
|
||||
ptype == "PT_LOAD"
|
||||
and phdr.p_filesz == phdr.p_memsz
|
||||
and phdr.p_filesz > 0
|
||||
):
|
||||
|
||||
@@ -67,6 +67,12 @@ class Intel(linear.LinearlyMappedLayer):
|
||||
math.ceil(math.log2(struct.calcsize(self._entry_format)))
|
||||
)
|
||||
|
||||
@classproperty
|
||||
@functools.lru_cache()
|
||||
def page_shift(cls) -> int:
|
||||
"""Page shift for the intel memory layers."""
|
||||
return cls._page_size_in_bits
|
||||
|
||||
@classproperty
|
||||
@functools.lru_cache()
|
||||
def page_size(cls) -> int:
|
||||
@@ -76,6 +82,12 @@ class Intel(linear.LinearlyMappedLayer):
|
||||
"""
|
||||
return 1 << cls._page_size_in_bits
|
||||
|
||||
@classproperty
|
||||
@functools.lru_cache()
|
||||
def page_mask(cls) -> int:
|
||||
"""Page mask for the intel memory layers."""
|
||||
return ~(cls.page_size - 1)
|
||||
|
||||
@classproperty
|
||||
@functools.lru_cache()
|
||||
def bits_per_register(cls) -> int:
|
||||
|
||||
@@ -5,6 +5,7 @@ from typing import Optional
|
||||
from volatility3.framework import constants, interfaces, exceptions
|
||||
from volatility3.framework.layers import elf
|
||||
from volatility3.framework.symbols import intermed
|
||||
from volatility3.framework.constants.linux import ELF_CLASS
|
||||
|
||||
vollog = logging.getLogger(__name__)
|
||||
|
||||
@@ -14,7 +15,7 @@ class XenCoreDumpLayer(elf.Elf64Layer):
|
||||
|
||||
_header_struct = struct.Struct("<IBBB")
|
||||
MAGIC = 0x464C457F # "\x7fELF"
|
||||
ELF_CLASS = 2
|
||||
ELF_CLASS = ELF_CLASS.ELFCLASS64
|
||||
|
||||
def __init__(
|
||||
self, context: interfaces.context.ContextInterface, config_path: str, name: str
|
||||
@@ -115,12 +116,10 @@ class XenCoreDumpLayer(elf.Elf64Layer):
|
||||
)
|
||||
)
|
||||
elif p2m_data and pfn_data:
|
||||
raise elf.ElfFormatException(
|
||||
self.name, f"Both P2M and PFN in Xen Core Dump"
|
||||
)
|
||||
raise elf.ElfFormatException(self.name, "Both P2M and PFN in Xen Core Dump")
|
||||
else:
|
||||
raise elf.ElfFormatException(
|
||||
self.name, f"Neither P2M nor PFN in Xen Core Dump"
|
||||
self.name, "Neither P2M nor PFN in Xen Core Dump"
|
||||
)
|
||||
|
||||
if len(segments) == 0:
|
||||
|
||||
@@ -14,8 +14,10 @@ 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.framework.constants.linux import ELF_MAX_EXTRACTION_SIZE
|
||||
from volatility3.plugins.linux import pslist
|
||||
|
||||
|
||||
vollog = logging.getLogger(__name__)
|
||||
|
||||
|
||||
@@ -23,7 +25,7 @@ class Elfs(plugins.PluginInterface):
|
||||
"""Lists all memory mapped ELF files for all processes."""
|
||||
|
||||
_required_framework_version = (2, 0, 0)
|
||||
_version = (2, 0, 0)
|
||||
_version = (2, 0, 1)
|
||||
|
||||
@classmethod
|
||||
def get_requirements(cls) -> List[interfaces.configuration.RequirementInterface]:
|
||||
@@ -87,7 +89,14 @@ class Elfs(plugins.PluginInterface):
|
||||
sections = {}
|
||||
# TODO: Apply more effort to reconstruct ELF, e.g.: https://github.com/enbarberis/core2ELF64 ?
|
||||
for phdr in elf_object.get_program_headers():
|
||||
if phdr.p_type != 1: # PT_LOAD = 1
|
||||
try:
|
||||
if phdr.p_type.description != "PT_LOAD":
|
||||
continue
|
||||
except ValueError:
|
||||
vollog.log(
|
||||
constants.LOGLEVEL_VVVV,
|
||||
f"Skipping unknown ELF program header type: {phdr.p_type}",
|
||||
)
|
||||
continue
|
||||
|
||||
start = phdr.p_vaddr
|
||||
@@ -95,18 +104,18 @@ class Elfs(plugins.PluginInterface):
|
||||
end = start + size
|
||||
|
||||
# Use complete memory pages for dumping
|
||||
# If start isn't a multiple of 4096, stick to the highest multiple < start
|
||||
# If end isn't a multiple of 4096, stick to the lowest multiple > end
|
||||
if start % 4096:
|
||||
start = start & ~0xFFF
|
||||
# If start isn't a multiple of a page, stick to the highest multiple < start
|
||||
# If end isn't a multiple of a page, stick to the lowest multiple > end
|
||||
if start % proc_layer.page_size:
|
||||
start = start & proc_layer.page_mask
|
||||
|
||||
if end % 4096:
|
||||
end = (end & ~0xFFF) + 4096
|
||||
if end % proc_layer.page_size:
|
||||
end = (end & proc_layer.page_mask) + proc_layer.page_size
|
||||
|
||||
real_size = end - start
|
||||
|
||||
# Check if ELF has a legitimate size
|
||||
if real_size < 0 or real_size > constants.linux.ELF_MAX_EXTRACTION_SIZE:
|
||||
if real_size < 0 or real_size > ELF_MAX_EXTRACTION_SIZE:
|
||||
raise ValueError(f"The claimed size of the ELF is invalid: {real_size}")
|
||||
|
||||
sections[start] = real_size
|
||||
@@ -140,12 +149,7 @@ class Elfs(plugins.PluginInterface):
|
||||
|
||||
for vma in task.mm.get_vma_iter():
|
||||
hdr = proc_layer.read(vma.vm_start, 4, pad=True)
|
||||
if not (
|
||||
hdr[0] == 0x7F
|
||||
and hdr[1] == 0x45
|
||||
and hdr[2] == 0x4C
|
||||
and hdr[3] == 0x46
|
||||
):
|
||||
if hdr != b"\x7fELF":
|
||||
continue
|
||||
|
||||
path = vma.get_name(self.context, task)
|
||||
|
||||
@@ -0,0 +1,169 @@
|
||||
# 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 Iterable, Tuple
|
||||
|
||||
from volatility3.framework import interfaces, renderers, constants, exceptions
|
||||
from volatility3.framework.configuration import requirements
|
||||
from volatility3.framework.renderers import format_hints
|
||||
from volatility3.framework.objects import utility
|
||||
from volatility3.framework.symbols import intermed
|
||||
from volatility3.framework.symbols.linux.extensions import elf
|
||||
from volatility3.plugins.linux import pslist
|
||||
|
||||
|
||||
vollog = logging.getLogger(__name__)
|
||||
|
||||
|
||||
class LibraryList(interfaces.plugins.PluginInterface):
|
||||
"""Enumerate libraries loaded into processes"""
|
||||
|
||||
_required_framework_version = (2, 0, 0)
|
||||
|
||||
_version = (1, 0, 0)
|
||||
|
||||
@classmethod
|
||||
def get_requirements(cls):
|
||||
return [
|
||||
requirements.ModuleRequirement(
|
||||
name="kernel",
|
||||
description="Linux kernel",
|
||||
architectures=["Intel32", "Intel64"],
|
||||
),
|
||||
requirements.PluginRequirement(
|
||||
name="pslist", plugin=pslist.PsList, version=(2, 2, 0)
|
||||
),
|
||||
requirements.ListRequirement(
|
||||
name="pids",
|
||||
description="Filter on specific process IDs",
|
||||
element_type=int,
|
||||
optional=True,
|
||||
),
|
||||
]
|
||||
|
||||
def _get_libdl_libraries(
|
||||
self, proc_layer_name: str, vma_start: int
|
||||
) -> interfaces.objects.ObjectInterface:
|
||||
"""Get the ELF link map objects for the given VMA address
|
||||
|
||||
Args:
|
||||
proc_layer_name (str): Name of the process layer
|
||||
vma_start (int): VMA start address
|
||||
|
||||
Yields:
|
||||
ELF link map objects for the given VMA address
|
||||
"""
|
||||
elf_table_name = intermed.IntermediateSymbolTable.create(
|
||||
self.context,
|
||||
self.config_path,
|
||||
"linux",
|
||||
"elf",
|
||||
class_types=elf.class_types,
|
||||
)
|
||||
elf_object = self.context.object(
|
||||
elf_table_name + constants.BANG + "Elf",
|
||||
offset=vma_start,
|
||||
layer_name=proc_layer_name,
|
||||
)
|
||||
|
||||
if not elf_object or not elf_object.is_valid():
|
||||
return None
|
||||
|
||||
kernel = self.context.modules[self.config["kernel"]]
|
||||
|
||||
try:
|
||||
for link_map in elf_object.get_link_maps(kernel.symbol_table_name):
|
||||
if link_map.l_addr and link_map.l_name:
|
||||
yield link_map
|
||||
except exceptions.InvalidAddressException:
|
||||
# Protection against memory smear in this VMA
|
||||
pass
|
||||
|
||||
def _get_libdl_maps(
|
||||
self, task: interfaces.objects.ObjectInterface, proc_layer_name: str
|
||||
) -> interfaces.objects.ObjectInterface:
|
||||
"""Get the ELF link maps objects for a task
|
||||
|
||||
Args:
|
||||
task (task_struct): A reference task
|
||||
proc_layer_name (str): Name of the process layer
|
||||
|
||||
Yields:
|
||||
ELF link map objects
|
||||
"""
|
||||
|
||||
link_map_seen = set()
|
||||
for vma in task.mm.get_vma_iter():
|
||||
for link_map in self._get_libdl_libraries(proc_layer_name, vma.vm_start):
|
||||
if link_map.l_addr in link_map_seen:
|
||||
continue
|
||||
|
||||
yield link_map
|
||||
link_map_seen.add(link_map.l_addr)
|
||||
|
||||
def _get_task_libraries(
|
||||
self, task: interfaces.objects.ObjectInterface
|
||||
) -> Tuple[int, str]:
|
||||
"""Get the task libraries from the ELF headers found within the memory maps
|
||||
|
||||
Args:
|
||||
task (task_struct): The reference task
|
||||
|
||||
Yields:
|
||||
Tuples with a ELF link map address and name
|
||||
"""
|
||||
proc_layer_name = task.add_process_layer()
|
||||
if not proc_layer_name:
|
||||
return
|
||||
|
||||
for elf_link_map in self._get_libdl_maps(task, proc_layer_name):
|
||||
name = elf_link_map.get_name()
|
||||
if not name:
|
||||
continue
|
||||
yield elf_link_map.l_addr, name
|
||||
|
||||
def _get_tasks_libraries(
|
||||
self,
|
||||
tasks: Iterable[interfaces.objects.ObjectInterface],
|
||||
) -> Iterable[Tuple[str, int, int, str]]:
|
||||
"""Get the task libraries from the ELF headers found within the memory maps for
|
||||
all the tasks.
|
||||
|
||||
Args:
|
||||
tasks: An iterable of tasks
|
||||
|
||||
Yields:
|
||||
Tuples with a task name, task tgid, an ELF link map address and name
|
||||
"""
|
||||
for task in tasks:
|
||||
task_name = utility.array_to_string(task.comm)
|
||||
for linkmap_addr, linkmap_name in self._get_task_libraries(task):
|
||||
yield task_name, task.tgid, linkmap_addr, linkmap_name
|
||||
|
||||
def _format_fields(self, fields):
|
||||
task_name, task_pid, addr, name = fields
|
||||
return task_name, task_pid, format_hints.Hex(addr), name
|
||||
|
||||
def _generator(
|
||||
self, tasks: Iterable[interfaces.objects.ObjectInterface]
|
||||
) -> Iterable[Tuple[int, Tuple]]:
|
||||
for fields in self._get_tasks_libraries(tasks):
|
||||
yield 0, self._format_fields(fields)
|
||||
|
||||
def run(self):
|
||||
pids = self.config.get("pids")
|
||||
pid_filter = pslist.PsList.create_pid_filter(pids)
|
||||
tasks = pslist.PsList.list_tasks(
|
||||
self.context, self.config["kernel"], filter_func=pid_filter
|
||||
)
|
||||
|
||||
headers = [
|
||||
("Name", str),
|
||||
("Pid", int),
|
||||
("LoadAddress", format_hints.Hex),
|
||||
("Path", str),
|
||||
]
|
||||
|
||||
return renderers.TreeGrid(headers, self._generator(tasks))
|
||||
@@ -270,8 +270,8 @@
|
||||
"d_tag": {
|
||||
"offset": 0,
|
||||
"type": {
|
||||
"kind": "base",
|
||||
"name": "long long"
|
||||
"kind": "enum",
|
||||
"name": "DtypeEnum64"
|
||||
}
|
||||
},
|
||||
"d_ptr": {
|
||||
@@ -699,8 +699,8 @@
|
||||
"d_tag": {
|
||||
"offset": 0,
|
||||
"type": {
|
||||
"kind": "base",
|
||||
"name": "long"
|
||||
"kind": "enum",
|
||||
"name": "DtypeEnum32"
|
||||
}
|
||||
},
|
||||
"d_ptr": {
|
||||
@@ -905,11 +905,139 @@
|
||||
"PT_PHDR": 6,
|
||||
"PT_TLS": 7,
|
||||
"PT_LOOS": 1610612736,
|
||||
"PT_GNU_EH_FRAME": 1685382480,
|
||||
"PT_GNU_STACK": 1685382481,
|
||||
"PT_GNU_RELRO": 1685382482,
|
||||
"PT_GNU_PROPERTY": 1685382483,
|
||||
"PT_HIOS": 1879048191,
|
||||
"PT_LOWPROC": 1879048192,
|
||||
"PT_HIPROC": 2147483647
|
||||
},
|
||||
"size": 4
|
||||
},
|
||||
"DtypeEnum32": {
|
||||
"base": "long",
|
||||
"constants": {
|
||||
"DT_NULL": 0,
|
||||
"DT_NEEDED": 1,
|
||||
"DT_PLTRELSZ": 2,
|
||||
"DT_PLTGOT": 3,
|
||||
"DT_HASH": 4,
|
||||
"DT_STRTAB": 5,
|
||||
"DT_SYMTAB": 6,
|
||||
"DT_RELA": 7,
|
||||
"DT_RELASZ": 8,
|
||||
"DT_RELAENT": 9,
|
||||
"DT_STRSZ": 10,
|
||||
"DT_SYMENT": 11,
|
||||
"DT_INIT": 12,
|
||||
"DT_FINI": 13,
|
||||
"DT_SONAME": 14,
|
||||
"DT_RPATH": 15,
|
||||
"DT_SYMBOLIC": 16,
|
||||
"DT_REL": 17,
|
||||
"DT_RELSZ": 18,
|
||||
"DT_RELENT": 19,
|
||||
"DT_PLTREL": 20,
|
||||
"DT_DEBUG": 21,
|
||||
"DT_TEXTREL": 22,
|
||||
"DT_JMPREL": 23,
|
||||
"DT_BIND_NOW": 24,
|
||||
"DT_INIT_ARRAY": 25,
|
||||
"DT_FINI_ARRAY": 26,
|
||||
"DT_INIT_ARRAYSZ": 27,
|
||||
"DT_FINI_ARRAYSZ": 28,
|
||||
"DT_RUNPATH": 29,
|
||||
"DT_FLAGS": 30,
|
||||
"DT_ENCODING": 32,
|
||||
"DT_PREINIT_ARRAYSZ": 33,
|
||||
"DT_SYMTAB_SHNDX": 34,
|
||||
"DT_RELRSZ": 35,
|
||||
"DT_RELR": 36,
|
||||
"DT_RELRENT": 37,
|
||||
"DT_NUM": 38,
|
||||
"OLD_DT_LOOS": 1610612736,
|
||||
"DT_LOOS": 1610612749,
|
||||
"DT_HIOS": 1879044096,
|
||||
"DT_VALRNGLO": 1879047424,
|
||||
"DT_VALRNGHI": 1879047679,
|
||||
"DT_ADDRRNGLO": 1879047680,
|
||||
"DT_GNU_HASH": 1879047925,
|
||||
"DT_ADDRRNGHI": 1879047935,
|
||||
"DT_VERSYM": 1879048176,
|
||||
"DT_RELACOUNT": 1879048185,
|
||||
"DT_RELCOUNT": 1879048186,
|
||||
"DT_FLAGS_1": 1879048187,
|
||||
"DT_VERDEF": 1879048188,
|
||||
"DT_VERDEFNUM": 1879048189,
|
||||
"DT_VERNEED": 1879048190,
|
||||
"DT_VERNEEDNUM": 1879048191,
|
||||
"DT_LOPROC": 1879048192,
|
||||
"DT_HIPROC": 2147483647
|
||||
},
|
||||
"size": 4
|
||||
},
|
||||
"DtypeEnum64": {
|
||||
"base": "long long",
|
||||
"constants": {
|
||||
"DT_NULL": 0,
|
||||
"DT_NEEDED": 1,
|
||||
"DT_PLTRELSZ": 2,
|
||||
"DT_PLTGOT": 3,
|
||||
"DT_HASH": 4,
|
||||
"DT_STRTAB": 5,
|
||||
"DT_SYMTAB": 6,
|
||||
"DT_RELA": 7,
|
||||
"DT_RELASZ": 8,
|
||||
"DT_RELAENT": 9,
|
||||
"DT_STRSZ": 10,
|
||||
"DT_SYMENT": 11,
|
||||
"DT_INIT": 12,
|
||||
"DT_FINI": 13,
|
||||
"DT_SONAME": 14,
|
||||
"DT_RPATH": 15,
|
||||
"DT_SYMBOLIC": 16,
|
||||
"DT_REL": 17,
|
||||
"DT_RELSZ": 18,
|
||||
"DT_RELENT": 19,
|
||||
"DT_PLTREL": 20,
|
||||
"DT_DEBUG": 21,
|
||||
"DT_TEXTREL": 22,
|
||||
"DT_JMPREL": 23,
|
||||
"DT_BIND_NOW": 24,
|
||||
"DT_INIT_ARRAY": 25,
|
||||
"DT_FINI_ARRAY": 26,
|
||||
"DT_INIT_ARRAYSZ": 27,
|
||||
"DT_FINI_ARRAYSZ": 28,
|
||||
"DT_RUNPATH": 29,
|
||||
"DT_FLAGS": 30,
|
||||
"DT_ENCODING": 32,
|
||||
"DT_PREINIT_ARRAYSZ": 33,
|
||||
"DT_SYMTAB_SHNDX": 34,
|
||||
"DT_RELRSZ": 35,
|
||||
"DT_RELR": 36,
|
||||
"DT_RELRENT": 37,
|
||||
"DT_NUM": 38,
|
||||
"OLD_DT_LOOS": 1610612736,
|
||||
"DT_LOOS": 1610612749,
|
||||
"DT_HIOS": 1879044096,
|
||||
"DT_VALRNGLO": 1879047424,
|
||||
"DT_VALRNGHI": 1879047679,
|
||||
"DT_ADDRRNGLO": 1879047680,
|
||||
"DT_GNU_HASH": 1879047925,
|
||||
"DT_ADDRRNGHI": 1879047935,
|
||||
"DT_VERSYM": 1879048176,
|
||||
"DT_RELACOUNT": 1879048185,
|
||||
"DT_RELCOUNT": 1879048186,
|
||||
"DT_FLAGS_1": 1879048187,
|
||||
"DT_VERDEF": 1879048188,
|
||||
"DT_VERDEFNUM": 1879048189,
|
||||
"DT_VERNEED": 1879048190,
|
||||
"DT_VERNEEDNUM": 1879048191,
|
||||
"DT_LOPROC": 1879048192,
|
||||
"DT_HIPROC": 2147483647
|
||||
},
|
||||
"size": 8
|
||||
}
|
||||
},
|
||||
"base_types": {
|
||||
@@ -958,9 +1086,9 @@
|
||||
},
|
||||
"metadata": {
|
||||
"producer": {
|
||||
"version": "0.0.1",
|
||||
"name": "ikelos-by-hand",
|
||||
"datetime": "2019-10-21T22:52:00"
|
||||
"version": "0.0.2",
|
||||
"name": "gcmoreira-by-hand",
|
||||
"datetime": "2024-02-19T14:37:00"
|
||||
},
|
||||
"format": "6.1.0"
|
||||
}
|
||||
|
||||
@@ -7,14 +7,13 @@ import logging
|
||||
import socket as socket_module
|
||||
from typing import Generator, Iterable, Iterator, Optional, Tuple, List
|
||||
|
||||
from volatility3.framework import constants
|
||||
from volatility3.framework import constants, exceptions, objects, interfaces, symbols
|
||||
from volatility3.framework.constants.linux import SOCK_TYPES, SOCK_FAMILY
|
||||
from volatility3.framework.constants.linux import IP_PROTOCOLS, IPV6_PROTOCOLS
|
||||
from volatility3.framework.constants.linux import TCP_STATES, NETLINK_PROTOCOLS
|
||||
from volatility3.framework.constants.linux import ETH_PROTOCOLS, BLUETOOTH_STATES
|
||||
from volatility3.framework.constants.linux import BLUETOOTH_PROTOCOLS, SOCKET_STATES
|
||||
from volatility3.framework.constants.linux import CAPABILITIES
|
||||
from volatility3.framework import exceptions, objects, interfaces, symbols
|
||||
from volatility3.framework.layers import linear
|
||||
from volatility3.framework.objects import utility
|
||||
from volatility3.framework.symbols import generic, linux, intermed
|
||||
@@ -707,7 +706,8 @@ class vm_area_struct(objects.StructType):
|
||||
def get_page_offset(self) -> int:
|
||||
if self.vm_file == 0:
|
||||
return 0
|
||||
return self.vm_pgoff << constants.linux.PAGE_SHIFT
|
||||
parent_layer = self._context.layers[self.vol.layer_name]
|
||||
return self.vm_pgoff << parent_layer.page_shift
|
||||
|
||||
def get_name(self, context, task):
|
||||
if self.vm_file != 0:
|
||||
@@ -736,7 +736,7 @@ class vm_area_struct(objects.StructType):
|
||||
elif flags_str == "r-x" and self.vm_file.dereference().vol.offset == 0:
|
||||
ret = True
|
||||
elif proclayer and "x" in flags_str:
|
||||
for i in range(self.vm_start, self.vm_end, 1 << constants.linux.PAGE_SHIFT):
|
||||
for i in range(self.vm_start, self.vm_end, proclayer.page_size):
|
||||
try:
|
||||
if proclayer.is_dirty(i):
|
||||
vollog.warning(
|
||||
|
||||
@@ -6,6 +6,10 @@ from typing import Dict, Tuple
|
||||
import logging
|
||||
|
||||
from volatility3.framework import constants
|
||||
from volatility3.framework.constants.linux import (
|
||||
ELF_IDENT,
|
||||
ELF_CLASS,
|
||||
)
|
||||
from volatility3.framework import objects, interfaces, exceptions
|
||||
|
||||
vollog = logging.getLogger(__name__)
|
||||
@@ -59,13 +63,15 @@ class elf(objects.StructType):
|
||||
ei_class = self._context.object(
|
||||
symbol_table_name + constants.BANG + "unsigned char",
|
||||
layer_name=layer_name,
|
||||
offset=object_info.offset + 0x4,
|
||||
offset=object_info.offset + ELF_IDENT.EI_CLASS,
|
||||
)
|
||||
|
||||
if ei_class == 1:
|
||||
if ei_class == ELF_CLASS.ELFCLASS32:
|
||||
self._type_prefix = "Elf32_"
|
||||
elif ei_class == 2:
|
||||
self._ei_class_size = 32
|
||||
elif ei_class == ELF_CLASS.ELFCLASS64:
|
||||
self._type_prefix = "Elf64_"
|
||||
self._ei_class_size = 64
|
||||
else:
|
||||
raise ValueError(f"Unsupported ei_class value {ei_class}")
|
||||
|
||||
@@ -140,36 +146,137 @@ class elf(objects.StructType):
|
||||
)
|
||||
return section_headers
|
||||
|
||||
def get_link_maps(self, kernel_symbol_table_name):
|
||||
"""Get the ELF link map objects for the given VMA address
|
||||
|
||||
Args:
|
||||
kernel_symbol_table_name (str): Kernel symbol table name
|
||||
|
||||
Yields:
|
||||
The ELF link map objects
|
||||
"""
|
||||
got_entry_size = self._ei_class_size // 8
|
||||
|
||||
elf_symbol_table = self.get_symbol_table_name()
|
||||
|
||||
link_maps_seen = set()
|
||||
for phdr in self.get_program_headers():
|
||||
try:
|
||||
if phdr.p_type.description != "PT_DYNAMIC":
|
||||
continue
|
||||
except ValueError:
|
||||
vollog.log(
|
||||
constants.LOGLEVEL_VVVV,
|
||||
f"Skipping unknown ELF program header type: {phdr.p_type}",
|
||||
)
|
||||
continue
|
||||
|
||||
for dsec in phdr.dynamic_sections():
|
||||
try:
|
||||
if dsec.d_tag.description != "DT_PLTGOT":
|
||||
continue
|
||||
except ValueError:
|
||||
vollog.log(
|
||||
constants.LOGLEVEL_VVVV,
|
||||
f"Skipping unknown ELF dynamic section type: {dsec.d_tag}",
|
||||
)
|
||||
continue
|
||||
|
||||
got_start = dsec.d_ptr
|
||||
|
||||
# link_map is stored at the second GOT entry
|
||||
link_map_addr = got_start + got_entry_size
|
||||
|
||||
# It needs the kernel symbol table to create a pointer
|
||||
link_map_ptr = self._context.object(
|
||||
kernel_symbol_table_name + constants.BANG + "pointer",
|
||||
offset=link_map_addr,
|
||||
layer_name=self.vol.layer_name,
|
||||
)
|
||||
if not link_map_ptr:
|
||||
vollog.log(
|
||||
constants.LOGLEVEL_VVVV,
|
||||
f"Invalid ELF link map pointer at 0x{link_map_addr:x}",
|
||||
)
|
||||
continue
|
||||
|
||||
linkmap_symname = (
|
||||
elf_symbol_table + constants.BANG + self._type_prefix + "LinkMap"
|
||||
)
|
||||
try:
|
||||
link_map = self._context.object(
|
||||
object_type=linkmap_symname,
|
||||
offset=link_map_ptr,
|
||||
layer_name=self.vol.layer_name,
|
||||
)
|
||||
except exceptions.InvalidAddressException:
|
||||
vollog.log(
|
||||
constants.LOGLEVEL_VVVV,
|
||||
f"Invalid ELF link map address at 0x{link_map_ptr:x}",
|
||||
)
|
||||
continue
|
||||
|
||||
while link_map and link_map.vol.offset != 0:
|
||||
if link_map.vol.offset in link_maps_seen:
|
||||
break
|
||||
link_maps_seen.add(link_map.vol.offset)
|
||||
|
||||
yield link_map
|
||||
|
||||
try:
|
||||
link_map = self._context.object(
|
||||
object_type=linkmap_symname,
|
||||
offset=link_map.l_next,
|
||||
layer_name=self.vol.layer_name,
|
||||
)
|
||||
except exceptions.InvalidAddressException:
|
||||
vollog.log(
|
||||
constants.LOGLEVEL_VVVV,
|
||||
f"ELF link map linked list is corrupt at 0x{self.vol.offset:x}",
|
||||
)
|
||||
break
|
||||
|
||||
def _find_symbols(self):
|
||||
dt_strtab = None
|
||||
dt_symtab = None
|
||||
dt_strent = None
|
||||
|
||||
for phdr in self.get_program_headers():
|
||||
# Find PT_DYNAMIC segment
|
||||
try:
|
||||
# Find PT_DYNAMIC segment
|
||||
if str(phdr.p_type.description) != "PT_DYNAMIC":
|
||||
if phdr.p_type.description != "PT_DYNAMIC":
|
||||
continue
|
||||
except ValueError:
|
||||
# If the p_type value is outside the ones declared in the enumeration, an
|
||||
# exception is raised
|
||||
return None
|
||||
vollog.log(
|
||||
constants.LOGLEVEL_VVVV,
|
||||
f"Skipping unknown ELF program header type: {phdr.p_type}",
|
||||
)
|
||||
continue
|
||||
|
||||
# This section contains pointers to the strtab, symtab, and strent sections
|
||||
for dsec in phdr.dynamic_sections():
|
||||
if dsec.d_tag == 5:
|
||||
try:
|
||||
dtag = dsec.d_tag.description
|
||||
except ValueError:
|
||||
vollog.log(
|
||||
constants.LOGLEVEL_VVVV,
|
||||
f"Skipping unknown ELF dynamic section type: {dsec.d_tag}",
|
||||
)
|
||||
continue
|
||||
|
||||
if dtag == "DT_STRTAB":
|
||||
dt_strtab = dsec.d_ptr
|
||||
|
||||
elif dsec.d_tag == 6:
|
||||
elif dtag == "DT_SYMTAB":
|
||||
dt_symtab = dsec.d_ptr
|
||||
|
||||
elif dsec.d_tag == 11:
|
||||
elif dtag == "DT_SYMENT":
|
||||
# Size of the symtab symbol entry
|
||||
dt_strent = dsec.d_ptr
|
||||
|
||||
break
|
||||
|
||||
if dt_strtab is None or dt_symtab is None or dt_strent is None:
|
||||
if not (dt_strtab and dt_symtab and dt_strent):
|
||||
return None
|
||||
|
||||
self._cached_symtab = dt_symtab
|
||||
@@ -274,19 +381,31 @@ class elf_phdr(objects.StructType):
|
||||
def get_vaddr(self):
|
||||
offset = self.__getattr__("p_vaddr")
|
||||
|
||||
if self._parent_e_type == 3: # ET_DYN
|
||||
offset = self._parent_offset + offset
|
||||
try:
|
||||
if self._parent_e_type.description == "ET_DYN":
|
||||
offset = self._parent_offset + offset
|
||||
except ValueError:
|
||||
# Unknown ELF object file type. Anyway, if the ELF object file type is not a
|
||||
# shared object (ET_DYN), the virtual address is 'p_vaddr'.
|
||||
vollog.log(
|
||||
constants.LOGLEVEL_VVVV,
|
||||
f"Skipping unknown ELF object type: {self._parent_e_type}",
|
||||
)
|
||||
|
||||
return offset
|
||||
|
||||
def dynamic_sections(self):
|
||||
# sanity check
|
||||
try:
|
||||
if str(self.p_type.description) != "PT_DYNAMIC":
|
||||
if self.p_type.description != "PT_DYNAMIC":
|
||||
return None
|
||||
except ValueError:
|
||||
# If the value is outside the ones declared in the enumeration, an
|
||||
# exception is raised
|
||||
vollog.log(
|
||||
constants.LOGLEVEL_VVVV,
|
||||
f"Skipping unknown ELF program header type: {self.p_type}",
|
||||
)
|
||||
return None
|
||||
|
||||
# the buffer of array starts at elf_base + our virtual address ( offset )
|
||||
@@ -314,10 +433,30 @@ class elf_phdr(objects.StructType):
|
||||
break
|
||||
|
||||
|
||||
class elf_linkmap(objects.StructType):
|
||||
def get_name(self):
|
||||
try:
|
||||
buf = self._context.layers.read(self.vol.layer_name, self.l_name, 256)
|
||||
except exceptions.PagedInvalidAddressException:
|
||||
# Protection against memory smear
|
||||
vollog.log(
|
||||
constants.LOGLEVEL_VVVV,
|
||||
f"Invalid l_name address for ELF link map at 0x{self.vol.offset:x}",
|
||||
)
|
||||
return None
|
||||
|
||||
idx = buf.find(b"\x00")
|
||||
if idx != -1:
|
||||
buf = buf[:idx]
|
||||
return buf.decode()
|
||||
|
||||
|
||||
class_types = {
|
||||
"Elf": elf,
|
||||
"Elf64_Phdr": elf_phdr,
|
||||
"Elf32_Phdr": elf_phdr,
|
||||
"Elf32_Sym": elf_sym,
|
||||
"Elf64_Sym": elf_sym,
|
||||
"Elf32_LinkMap": elf_linkmap,
|
||||
"Elf64_LinkMap": elf_linkmap,
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user