From 833d2adac5b24ee40efe81e19c808c6ff69d6b30 Mon Sep 17 00:00:00 2001 From: Gustavo Moreira Date: Wed, 2 Jan 2019 23:15:29 -0500 Subject: [PATCH 1/7] Netfilter plugin improvements: * Added LKM lookup, showing the kernel module name to which the hook belongs to. * If the module is part of the kernel text, it also resolves the symbol to that specific address. It is showed between square brackets, ie: [selinux_ipv4_forward] * All kernel exisiting protocols were added (unless until kernel v4.20). It now allows to identify for instance IPv6, ARP, BRIDGE (ebtables), etc. * Fixed wrong number of hooks and protos. Removed hardcoded sizes. * Added function to the Linux common API to find LKM module addresses, similar to the one in mac implementation but it also resolve kernel symbols. --- volatility/plugins/linux/common.py | 27 ++++++++++++++------ volatility/plugins/linux/netfilter.py | 36 ++++++++++++--------------- 2 files changed, 36 insertions(+), 27 deletions(-) diff --git a/volatility/plugins/linux/common.py b/volatility/plugins/linux/common.py index efe68cb20..cfd8736ef 100644 --- a/volatility/plugins/linux/common.py +++ b/volatility/plugins/linux/common.py @@ -76,20 +76,33 @@ def register_options(config): config.add_option("VIRTUAL_SHIFT", type = 'int', default = 0, help = "Linux kernel virtual shift address") def is_known_address(self, addr, modules): + return self.is_known_address_name(addr, modules)[0] + + def is_known_address_name(self, addr, modules): addr = int(addr) text = self.profile.get_symbol("_text") etext = self.profile.get_symbol("_etext") - return (self.addr_space.address_compare(addr, text) != -1 and self.addr_space.address_compare(addr, etext) == -1) or self.address_in_module(addr, modules) + found = True + + if self.address_in_range(addr, text, etext): + module = "[%s]" % (self.profile.get_symbol_by_address("kernel", addr) or "kernel") + else: + module = self.address_in_module(addr, modules) + if not module: + found = False + module = "" + + return (found, module) def address_in_module(self, addr, modules): - - for (_, start, end) in modules: - if self.addr_space.address_compare(addr, start) != -1 and self.addr_space.address_compare(addr, end) == -1: - return True - - return False + for (module, start, end) in modules: + if self.address_in_range(addr, start, end): + return module + + def address_in_range(self, addr, start, end): + return self.addr_space.address_compare(addr, start) != -1 and self.addr_space.address_compare(addr, end) == -1 def verify_ops(self, ops, op_members, modules): ops_addr = ops.v() diff --git a/volatility/plugins/linux/netfilter.py b/volatility/plugins/linux/netfilter.py index e019ea58e..4e744993d 100644 --- a/volatility/plugins/linux/netfilter.py +++ b/volatility/plugins/linux/netfilter.py @@ -39,12 +39,9 @@ def calculate(self): linux_common.set_plugin_members(self) hook_names = ["PRE_ROUTING", "LOCAL_IN", "FORWARD", "LOCAL_OUT", "POST_ROUTING"] - proto_names = ["", "", "IPV4", "", "", "", "", "", "", "", "" , "", "", ""] - # struct list_head nf_hooks[NFPROTO_NUMPROTO][NF_MAX_HOOKS] - # NFPROTO_NUMPROTO = 12 - # NF_MAX_HOOKS = 7 - + proto_names = ["UNSPEC", "INET", "IPV4", "ARP", "", "NETDEV", "", "BRIDGE", "", "", "IPV6" , "", "DECNET"] + nf_hooks_addr = self.addr_space.profile.get_symbol("nf_hooks") if nf_hooks_addr == None: @@ -54,34 +51,33 @@ def calculate(self): list_head_size = self.addr_space.profile.get_obj_size("list_head") - for outer in range(13): - arr = nf_hooks_addr + (outer * (list_head_size * 8)) + for proto_idx, proto_name in enumerate(proto_names): + arr = nf_hooks_addr + (proto_idx * (list_head_size * 8)) - for inner in range(7): - list_head = obj.Object("list_head", offset = arr + (inner * list_head_size), vm = self.addr_space) + for hook_idx, hook_name in enumerate(hook_names): + list_head = obj.Object("list_head", offset = arr + (hook_idx * list_head_size), vm = self.addr_space) for hook_ops in list_head.list_of_type("nf_hook_ops", "list"): - if self.is_known_address(hook_ops.hook.v(), modules): - hooked = "False" - else: - hooked = "True" + found, module = self.is_known_address_name(hook_ops.hook.v(), modules) or "" + hooked = "False" if found else "True" - yield proto_names[outer], hook_names[inner], hook_ops.hook.v(), hooked + yield proto_name, hook_name, hook_ops.hook.v(), hooked, module def unified_output(self, data): return TreeGrid([("Proto", str), ("Hook", str), ("Handler", Address), - ("IsHooked", str)], + ("IsHooked", str), + ("Module", str)], self.generator(data)) def generator(self, data): - for outer, inner, hook_addr, hooked in data: - yield (0, [str(outer), str(inner), Address(hook_addr), str(hooked)]) + for proto_name, hook_name, hook_addr, hooked, module in data: + yield (0, [str(proto_name), str(hook_name), Address(hook_addr), str(hooked), str(module)]) def render_text(self, outfd, data): - self.table_header(outfd, [("Proto", "5"), ("Hook", "16"), ("Handler", "[addrpad]"), ("Is Hooked", "5")]) + self.table_header(outfd, [("Proto", "10"), ("Hook", "16"), ("Handler", "[addrpad]"), ("Is Hooked", "5"), ("Module", "30")]) - for outer, inner, hook_addr, hooked in data: - self.table_row(outfd, outer, inner, hook_addr, hooked) + for proto_name, hook_name, hook_addr, hooked, module in data: + self.table_row(outfd, proto_name, hook_name, hook_addr, hooked, module) From 49d547c412bb53a6935b0d0f9adb95b58b6d68cc Mon Sep 17 00:00:00 2001 From: Gustavo Moreira Date: Mon, 7 Jan 2019 00:04:47 -0500 Subject: [PATCH 2/7] Named hardcoded value and other cosmetics changes --- volatility/plugins/linux/netfilter.py | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/volatility/plugins/linux/netfilter.py b/volatility/plugins/linux/netfilter.py index 4e744993d..bc751e52e 100644 --- a/volatility/plugins/linux/netfilter.py +++ b/volatility/plugins/linux/netfilter.py @@ -39,20 +39,19 @@ def calculate(self): linux_common.set_plugin_members(self) hook_names = ["PRE_ROUTING", "LOCAL_IN", "FORWARD", "LOCAL_OUT", "POST_ROUTING"] - proto_names = ["UNSPEC", "INET", "IPV4", "ARP", "", "NETDEV", "", "BRIDGE", "", "", "IPV6" , "", "DECNET"] + NF_MAX_HOOKS = 8 nf_hooks_addr = self.addr_space.profile.get_symbol("nf_hooks") - if nf_hooks_addr == None: debug.error("Unable to analyze NetFilter. It is either disabled or compiled as a module.") - modules = linux_lsmod.linux_lsmod(self._config).get_modules() + modules = linux_lsmod.linux_lsmod(self._config).get_modules() list_head_size = self.addr_space.profile.get_obj_size("list_head") for proto_idx, proto_name in enumerate(proto_names): - arr = nf_hooks_addr + (proto_idx * (list_head_size * 8)) + arr = nf_hooks_addr + (proto_idx * (list_head_size * NF_MAX_HOOKS)) for hook_idx, hook_name in enumerate(hook_names): list_head = obj.Object("list_head", offset = arr + (hook_idx * list_head_size), vm = self.addr_space) From 607d25ff33ee5b0a0c599a00906c4d7c336b2012 Mon Sep 17 00:00:00 2001 From: Gustavo Moreira Date: Sat, 19 Jan 2019 02:04:31 +1100 Subject: [PATCH 3/7] Netfilter plugin improvements - Added support for every single Netfilter implementation in every single kernel version so far (v2.6ish to v5.0-rc2). - It gathers Netfilter hook information for all existing protocol families: IPV4, IPv6, ARP, BRIDGE, DECNET and INGRESS hooks. - Two new columns were added to the output report, the network namespace id and the module name or symbol name to which the hook address belongs to, allowing us to easily identify suspicious kernel modules using network hooks. - If a module is part of the kernel text, it also resolves the symbol to that specific address which is showed between square brackets, ie: [selinux_ipv4_forward] - Added function to the Linux common API to find LKM module addresses, similar to the one in mac implementation but it also resolve kernel symbols. --- volatility/plugins/linux/netfilter.py | 542 ++++++++++++++++++++++++-- 1 file changed, 503 insertions(+), 39 deletions(-) diff --git a/volatility/plugins/linux/netfilter.py b/volatility/plugins/linux/netfilter.py index bc751e52e..b62389b16 100644 --- a/volatility/plugins/linux/netfilter.py +++ b/volatility/plugins/linux/netfilter.py @@ -19,64 +19,528 @@ # """ -@author: Andrew Case +@author: Andrew Case and Gustavo Moreira @license: GNU General Public License 2.0 -@contact: atcuno@gmail.com -@organization: +@contact: atcuno@gmail.com,gmoreira@gmail.com +@organization: """ - +import re +from collections import namedtuple +from packaging import version import volatility.obj as obj import volatility.debug as debug import volatility.plugins.linux.common as linux_common import volatility.plugins.linux.lsmod as linux_lsmod +import volatility.plugins.linux.banner as linux_banner +import volatility.registry as registry from volatility.renderers import TreeGrid from volatility.renderers.basic import Address -class linux_netfilter(linux_common.AbstractLinuxCommand): - """Lists Netfilter hooks""" +class KernelProtocolDisabledException(Exception): pass - def calculate(self): - linux_common.set_plugin_members(self) +KERNEL_LATEST = "99" # Just a big version number to mean "until the latest version" +KERNEL_NONE = "" # For classes that do not need a version number,ie: Base classes. + +Proto = namedtuple('Proto', ['name', 'hooks']) +PROTO_NOT_IMPLEMENTED = Proto(name="UNSPEC", hooks=()) +class AbstractNetfilter(object): + """Base Netfilter class to handle all the details of the different Netfilter implementations, + providing also constants, helpers and common routines.""" + + PROTO_HOOKS = ( + PROTO_NOT_IMPLEMENTED, + Proto(name="INET", + hooks=("PRE_ROUTING", + "LOCAL_IN", + "FORWARD", + "LOCAL_OUT", + "POST_ROUTING")), + Proto(name="IPV4", + hooks=("PRE_ROUTING", + "LOCAL_IN", + "FORWARD", + "LOCAL_OUT", + "POST_ROUTING")), + Proto(name="ARP", + hooks=("IN", + "OUT", + "FORWARD")), + PROTO_NOT_IMPLEMENTED, + Proto(name="NETDEV", + hooks=("INGRESS",)), + PROTO_NOT_IMPLEMENTED, + Proto(name="BRIDGE", + hooks=("PRE_ROUTING", + "LOCAL_IN", + "FORWARD", + "LOCAL_OUT", + "POST_ROUTING", + "BROUTING")), + PROTO_NOT_IMPLEMENTED, + PROTO_NOT_IMPLEMENTED, + Proto(name="IPV6", + hooks=("PRE_ROUTING", + "LOCAL_IN", + "FORWARD", + "LOCAL_OUT", + "POST_ROUTING")), + PROTO_NOT_IMPLEMENTED, + Proto(name="DECNET", + hooks=("PRE_ROUTING", + "LOCAL_IN", + "FORWARD", + "LOCAL_OUT", + "POST_ROUTING", + "HELLO", + "ROUTE")), + ) + NF_MAX_HOOKS = 8 + + def __init__(self, kernel_version, volinst): + self.volinst = volinst + self.kernel_version = version.parse(kernel_version) + self._set_data_sizes() + self.modules = linux_lsmod.linux_lsmod(volinst._config).get_modules() + + def _set_data_sizes(self): + self.ptr_size = self.volinst.addr_space.profile.get_obj_size("address") + self.list_head_size = self.volinst.addr_space.profile.get_obj_size("list_head") + + @classmethod + def run_all(cls, kernel_version, volinst): + """It executes the appropriate classes for this specific kernel version. It returns an + iterable, actually a generator, ready to be returned at the same time by calculate(). + """ + kernel_version_cur = version.parse(kernel_version) + subclass = None + for subclass in registry._get_subclasses(cls): + if subclass != cls: + kv_min = version.parse(subclass.KERNEL_MIN) + kv_max = version.parse(subclass.KERNEL_MAX) + if kv_min <= kernel_version_cur <= kv_max: + nfimp_inst = subclass(kernel_version, volinst) + # More than one class could be executed for an specific kernel + # version. + # Certain aspects were not implemented at the same time, and + # also they need a different treatment. Netfilter Ingress hooks + # is the best example and the main reason of this. + for data in cls._execute(nfimp_inst): + yield data + + if subclass is None: + debug.error("Unsupported netfilter kernel implementation for %s"%(kernel_version)) + + @classmethod + def _proto_hook_loop(cls, nfimp_inst): + """It flattens the protocol families and hooks""" + for proto_idx, proto in enumerate(AbstractNetfilter.PROTO_HOOKS): + if proto.name in (PROTO_NOT_IMPLEMENTED.name, "INET"): + # There is no such Netfilter hook implementation for INET protocol in the kernel + # AFAIU this is used like NFPROTO_INET = NFPROTO_IPV4 || NFPROTO_IPV6 + continue + if proto.name not in nfimp_inst.subscribed_protocols(): + # This protocol is not managed in this object + continue + for hook_idx, hook_name in enumerate(proto.hooks): + yield proto_idx, proto.name, len(proto.hooks), hook_idx, hook_name + + @classmethod + def _execute(cls, nfimp_inst): + for netns, net in nfimp_inst.get_net_namespaces(): + for proto_idx, proto_name, hooks_count, hook_idx, hook_name in cls._proto_hook_loop(nfimp_inst): + try: + hooks_container = nfimp_inst.get_hooks_container_by_protocol(net, proto_name) + except KernelProtocolDisabledException: + continue + if not hooks_container: + continue + for hook_container in hooks_container: + for hook_ops in nfimp_inst.get_hook_ops(hook_container, proto_idx, hooks_count, hook_idx): + if not hook_ops: + continue + hook_ops_addr = hook_ops.hook.v() + found, module = nfimp_inst.volinst.is_known_address_name(hook_ops_addr, nfimp_inst.modules) + hooked = "False" if found else "True" + + yield netns, proto_name, hook_name, hook_ops_addr, hooked, module + + # Helpers + def build_nf_hook_ops_array(self, nf_hook_entries): + """Function helper to build the array of arrays of nf_hook_ops give a nf_hook_entries""" + # nf_hook_ops array is not part of the struct nf_hook_entries definition, so we need to + # craft it. + nf_hook_entry_count = nf_hook_entries.num_hook_entries + + nf_hook_entries_hook_addr = nf_hook_entries.hooks.obj_offset + nf_hook_entry_arr = obj.Object("Array", + targetType="nf_hook_entry", + offset=nf_hook_entries_hook_addr, + count=nf_hook_entry_count, + vm=self.volinst.addr_space) + + nf_hook_ops_addr = nf_hook_entries_hook_addr + nf_hook_entry_arr.size() + nf_hook_ops_ptr_arr = obj.Object("Array", + targetType="Pointer", + offset=nf_hook_ops_addr, + count=nf_hook_entry_count, + vm=self.volinst.addr_space) + + return nf_hook_ops_ptr_arr + + # Common functions to many of the implementations + def subscribed_protocols(self): + """Most of the implementation handlers respond to these protocols, except the ingress hook + implemention which handles an specific protocol called "NETDEV". + """ + return ("IPV4", "ARP", "BRIDGE", "IPV6", "DECNET") + + def get_net_namespaces(self): + """Common function to retrieve the different namespaces. + From 4.3 on, all the implementations use network namespaces.""" + nslist_addr = self.volinst.addr_space.profile.get_symbol("net_namespace_list") + + nethead = obj.Object("list_head", offset=nslist_addr, vm=self.volinst.addr_space) + for net_idx, net in enumerate(nethead.list_of_type("net", "list")): + yield net_idx, net - hook_names = ["PRE_ROUTING", "LOCAL_IN", "FORWARD", "LOCAL_OUT", "POST_ROUTING"] - proto_names = ["UNSPEC", "INET", "IPV4", "ARP", "", "NETDEV", "", "BRIDGE", "", "", "IPV6" , "", "DECNET"] - NF_MAX_HOOKS = 8 + def get_hooks_container_by_protocol(self, net, proto_name): + """Except for kernels < 4.3, all the implementations use network namespaces. + Also the data structure which contains the hooks, even though it changes its implementation + and/or data type, it is always in this location. + """ + yield net.nf.hooks.obj_offset - nf_hooks_addr = self.addr_space.profile.get_symbol("nf_hooks") - if nf_hooks_addr == None: + # Interface + def get_hook_ops(self, nf_hooks_addr, proto_idx, hooks_count, hook_idx): + """This is the most variable/unstable part of all Netfilter hook designs, it changes almost + in every single implementation. + """ + raise NotImplementedError("You must implement this method") + + +class NetfilterImp_to_4_2_8(AbstractNetfilter): + """At this point, Netfilter hooks were implemented as a linked list of 'struct nf_hook_ops' + type. One linked list per protocol per hook type. It was like that until 4.2.8. + + struct list_head nf_hooks[NFPROTO_NUMPROTO][NF_MAX_HOOKS]; + """ + KERNEL_MIN = "0" + KERNEL_MAX = "4.2.8" + + def get_net_namespaces(self): + # In kernels <= 4.2.8 netfilter hooks are not implemented per namespaces + netns, net = "-", None + yield netns, net + + def get_hooks_container_by_protocol(self, net, proto_name): + nf_hooks_addr = self.volinst.addr_space.profile.get_symbol("nf_hooks") + if nf_hooks_addr is None: debug.error("Unable to analyze NetFilter. It is either disabled or compiled as a module.") + yield nf_hooks_addr + + def get_hook_ops(self, nf_hooks_addr, proto_idx, hooks_count, hook_idx): + # It seems the API doesn't deal with array of arrays very well. + # So, doing it the old-school way + arr = nf_hooks_addr + (proto_idx * (self.list_head_size * AbstractNetfilter.NF_MAX_HOOKS)) + list_head_addr = arr + (hook_idx * self.list_head_size) + list_head = obj.Object("list_head", offset=list_head_addr, vm=self.volinst.addr_space) + + return list_head.list_of_type("nf_hook_ops", "list") + + +class NetfilterImp_4_3_to_4_8_17(AbstractNetfilter): + """Netfilter hooks were added to network namepaces in 4.3. + It is still implemented as a linked list of 'struct nf_hook_ops' type but inside a network + namespace. One linked list per protocol per hook type. + + struct net { ... struct netns_nf nf; ... } + struct netns_nf { ... + struct list_head hooks[NFPROTO_NUMPROTO][NF_MAX_HOOKS]; ... } + """ + KERNEL_MIN = "4.3" + KERNEL_MAX = "4.8.17" + + def get_hook_ops(self, nf_hooks_addr, proto_idx, hooks_count, hook_idx): + # It seems the API doesn't deal with array of arrays very well. + # So, doing it the old-school way + arr = nf_hooks_addr + (proto_idx * (self.list_head_size * AbstractNetfilter.NF_MAX_HOOKS)) + list_head_addr = arr + (hook_idx * self.list_head_size) + list_head = obj.Object("list_head", offset=list_head_addr, vm=self.volinst.addr_space) + + return list_head.list_of_type("nf_hook_ops", "list") + + +class NetfilterImp_4_9_to_4_13_16(AbstractNetfilter): + """In this range of kernel versions, the doubly-linked lists of netfilter hooks were replaced + by an array of arrays of nf_hook_entry pointers in a singly-linked lists. + struct net { ... struct netns_nf nf; ... } + struct netns_nf { .. + struct nf_hook_entry __rcu *hooks[NFPROTO_NUMPROTO][NF_MAX_HOOKS]; ... } + + Also in v4.10 the struct nf_hook_entry changed, a hook function pointer was added to it. + However, for simplicity of this design, we will still take the hook address from the + nf_hook_ops. As per v5.0-rc2, the hook address is duplicated in both sides. + - v4.9: + struct nf_hook_entry { + struct nf_hook_entry *next; + struct nf_hook_ops ops; + const struct nf_hook_ops *orig_ops; }; + - v4.10: + struct nf_hook_entry { + struct nf_hook_entry *next; + nf_hookfn *hook; + void *priv; + const struct nf_hook_ops *orig_ops; }; + (*) Even though the hook address is in the struct nf_hook_entry, we use the original + nf_hook_ops hook address value, the one which was filled by the user, to make it uniform to all + the implementations. + """ + KERNEL_MIN = "4.9" + KERNEL_MAX = "4.13.16" + + def get_hook_ops(self, nf_hooks_addr, proto_idx, hooks_count, hook_idx): + # It seems the API doesn't deal with array of arrays very well. + # So doing it the old-school way + arr = nf_hooks_addr + (proto_idx * (self.ptr_size * AbstractNetfilter.NF_MAX_HOOKS)) + nf_hook_entry_addr = arr + (hook_idx * self.ptr_size) + if not nf_hook_entry_addr: + yield None + + nf_hook_entry_ptr = obj.Object("Pointer", + offset=nf_hook_entry_addr, + vm=self.volinst.addr_space) + + nf_hook_entry_list = nf_hook_entry_ptr.dereference_as("nf_hook_entry") + for nf_hook_entry in linux_common.walk_internal_list("nf_hook_entry", "next", nf_hook_entry_list): + nf_hook_ops = nf_hook_entry.orig_ops.dereference_as("nf_hook_ops") + yield nf_hook_ops + - modules = linux_lsmod.linux_lsmod(self._config).get_modules() - - list_head_size = self.addr_space.profile.get_obj_size("list_head") - - for proto_idx, proto_name in enumerate(proto_names): - arr = nf_hooks_addr + (proto_idx * (list_head_size * NF_MAX_HOOKS)) - - for hook_idx, hook_name in enumerate(hook_names): - list_head = obj.Object("list_head", offset = arr + (hook_idx * list_head_size), vm = self.addr_space) - - for hook_ops in list_head.list_of_type("nf_hook_ops", "list"): - found, module = self.is_known_address_name(hook_ops.hook.v(), modules) or "" - hooked = "False" if found else "True" - - yield proto_name, hook_name, hook_ops.hook.v(), hooked, module +class NetfilterImp_4_14_to_4_15_18(AbstractNetfilter): + """nf_hook_ops was removed from struct nf_hook_entry. Instead, it was stored adjacent in memory + to the nf_hook_entry array, in the new struct 'nf_hook_entries'. + However, this nf_hooks_ops array 'orig_ops' is not part of the nf_hook_entries struct + definition. So, we have to craft it by hand. + + struct net { ... struct netns_nf nf; ... } + struct netns_nf { + struct nf_hook_entries *hooks[NFPROTO_NUMPROTO][NF_MAX_HOOKS]; ... } + struct nf_hook_entries { + u16 num_hook_entries; /* plus padding */ + struct nf_hook_entry hooks[]; + //const struct nf_hook_ops *orig_ops[]; } + struct nf_hook_entry { + nf_hookfn *hook; + void *priv; } + + (*) Even though the hook address is in the struct nf_hook_entry, we use the original + nf_hook_ops hook address value, the one which was filled by the user, to make it uniform to all + the implementations. + """ + KERNEL_MIN = "4.14" + KERNEL_MAX = "4.15.18" + + def get_nf_hook_entries_ptr(self, nf_hooks_addr, proto_idx, hook_idx, hooks_count): + """This allows to support different hook array implementations from this version on. + For instance, in kernels >= 4.16 this multi-dimensional array is split in one-dimensional + array of pointers to nf_hooks_entries per each protocol.""" + arr = nf_hooks_addr + (proto_idx * (self.ptr_size * AbstractNetfilter.NF_MAX_HOOKS)) + nf_hook_entries_addr = arr + (hook_idx * self.ptr_size) + nf_hook_entries_ptr = obj.Object("Pointer", + offset=nf_hook_entries_addr, + vm=self.volinst.addr_space) + return nf_hook_entries_ptr + + def get_hook_ops(self, nf_hooks_addr, proto_idx, hooks_count, hook_idx): + nf_hook_entries_ptr = self.get_nf_hook_entries_ptr(nf_hooks_addr, proto_idx, hook_idx, hooks_count) + if not nf_hook_entries_ptr: + yield None + + nf_hook_entries = nf_hook_entries_ptr.dereference_as("nf_hook_entries") + nf_hook_ops_ptr_arr = self.build_nf_hook_ops_array(nf_hook_entries) + for nf_hook_ops_ptr in nf_hook_ops_ptr_arr: + nf_hook_ops = nf_hook_ops_ptr.dereference_as("nf_hook_ops") + yield nf_hook_ops + + +class NetfilterImp_4_16_to_latest(NetfilterImp_4_14_to_4_15_18): + """The multidimensional array of nf_hook_entries was split in a one-dimensional array per each + protocol. + + struct net { + struct netns_nf nf; ... } + struct netns_nf { + struct nf_hook_entries * hooks_ipv4[NF_INET_NUMHOOKS]; + struct nf_hook_entries * hooks_ipv6[NF_INET_NUMHOOKS]; + struct nf_hook_entries * hooks_arp[NF_ARP_NUMHOOKS]; + struct nf_hook_entries * hooks_bridge[NF_INET_NUMHOOKS]; + struct nf_hook_entries * hooks_decnet[NF_DN_NUMHOOKS]; ... } + struct nf_hook_entries { + u16 num_hook_entries; /* plus padding */ + struct nf_hook_entry hooks[]; + //const struct nf_hook_ops *orig_ops[]; } + struct nf_hook_entry { + nf_hookfn *hook; + void *priv; } + + (*) Even though the hook address is in the struct nf_hook_entry, we use the original + nf_hook_ops hook address value, the one which was filled by the user, to make it uniform to all + the implementations. + """ + KERNEL_MIN = "4.16" + KERNEL_MAX = KERNEL_LATEST + + def get_hooks_container_by_protocol(self, net, proto_name): + try: + if proto_name == "IPV4": + net_nf_hooks = net.nf.hooks_ipv4 + elif proto_name == "ARP": + net_nf_hooks = net.nf.hooks_arp + elif proto_name == "BRIDGE": + net_nf_hooks = net.nf.hooks_bridge + elif proto_name == "IPV6": + net_nf_hooks = net.nf.hooks_ipv6 + elif proto_name == "DECNET": + net_nf_hooks = net.nf.hooks_decnet + else: + debug.error("Weird, we didn't subscribe to this protocol %s"% (proto_name)) + except AttributeError: + # Protocol family disabled at kernel compilation + # CONFIG_NETFILTER_FAMILY_ARP=n || + # CONFIG_NETFILTER_FAMILY_BRIDGE=n || + # CONFIG_DECNET=n + raise KernelProtocolDisabledException() + yield net_nf_hooks.obj_offset + + def get_nf_hook_entries_ptr(self, nf_hooks_addr, proto_idx, hook_idx, hooks_count): + nf_hook_entries_ptr_arr = obj.Object("Array", + targetType="Pointer", + offset=nf_hooks_addr, + count=hooks_count, + vm=self.volinst.addr_space) + nf_hook_entries_ptr = nf_hook_entries_ptr_arr[hook_idx] + return nf_hook_entries_ptr + + +class AbstractNetfilterIngress(AbstractNetfilter): + """Base class to handle the Netfilter Ingress hooks. + It won't be executed. It has some common functions to all Netfilter Ingress hook implementions. + + Netfilter Ingress hooks are set per network device which belongs to a network namespace. + """ + KERNEL_MIN = KERNEL_NONE + KERNEL_MAX = KERNEL_NONE + + def subscribed_protocols(self): + return ("NETDEV",) + + def get_hooks_container_by_protocol(self, net, proto_name): + if proto_name != "NETDEV": + debug.error("Weird, we didn't subscribe to this protocol %s"% (proto_name)) + + for net_device in net.dev_base_head.list_of_type("net_device", "dev_list"): + try: + nf_hooks_ingress = net_device.nf_hooks_ingress + except AttributeError: + # CONFIG_NETFILTER_INGRESS=n + raise KernelProtocolDisabledException() + yield nf_hooks_ingress + + +class NetfilterIngressImp_4_2_to_4_8_17(AbstractNetfilterIngress): + """This is the first implementation of Netfilter Ingress hooks which was implemented using a + doubly-linked list of nf_hook_ops. + struct list_head nf_hooks_ingress; + """ + + KERNEL_MIN = "4.2" + KERNEL_MAX = "4.8.17" + + def get_hook_ops(self, nf_hooks_ingress, proto_idx, hooks_count, hook_idx): + return nf_hooks_ingress.list_of_type("nf_hook_ops", "list") + + +class NetfilterIngressImp_4_9_to_4_13_16(AbstractNetfilterIngress): + """In 4.9 it was changed to a simple singly-linked list. + struct nf_hook_entry * nf_hooks_ingress; + """ + KERNEL_MIN = "4.9" + KERNEL_MAX = "4.13.16" + + def get_hook_ops(self, nf_hooks_ingress, proto_idx, hooks_count, hook_idx): + if not nf_hooks_ingress: + yield None + + nf_hook_entry_list = nf_hooks_ingress.dereference_as("nf_hook_entry") + for nf_hook_entry in linux_common.walk_internal_list("nf_hook_entry", "next", nf_hook_entry_list): + nf_hook_ops = nf_hook_entry.orig_ops.dereference_as("nf_hook_ops") + yield nf_hook_ops + + +class NetfilterIngressImp_4_14_to_latest(AbstractNetfilterIngress): + """In 4.14 the hook list was converted to an array of pointers inside the struct + nf_hook_entries. + struct nf_hook_entries * nf_hooks_ingress; + struct nf_hook_entries { + u16 num_hook_entries; // padding + struct nf_hook_entry hooks[]; + } + """ + KERNEL_MIN = "4.14" + KERNEL_MAX = KERNEL_LATEST + + def get_hook_ops(self, nf_hook_entries_ptr, proto_idx, hooks_count, hook_idx): + if not nf_hook_entries_ptr: + yield None + + nf_hook_entries = nf_hook_entries_ptr.dereference_as("nf_hook_entries") + nf_hook_ops_ptr_arr = self.build_nf_hook_ops_array(nf_hook_entries) + for nf_hook_ops_ptr in nf_hook_ops_ptr_arr: + nf_hook_ops = nf_hook_ops_ptr.dereference_as("nf_hook_ops") + yield nf_hook_ops + + +class linux_netfilter(linux_common.AbstractLinuxCommand): + """Lists Netfilter hooks.""" + + def _get_kernel_version(self): + banner = linux_banner.linux_banner(self._config).calculate().next() + match = re.match(r"^Linux version (\d+\.\d+\.\d+)", banner) + if not match: + debug.error("Unable to get kernel version") + + return match.group(1) + + def calculate(self): + linux_common.set_plugin_members(self) + kernel_version = self._get_kernel_version() + return AbstractNetfilter.run_all(kernel_version, volinst=self) def unified_output(self, data): - return TreeGrid([("Proto", str), - ("Hook", str), - ("Handler", Address), - ("IsHooked", str), - ("Module", str)], + return TreeGrid([("NS", str), + ("Proto", str), + ("Hook", str), + ("Handler", Address), + ("IsHooked", str), + ("Module", str)], self.generator(data)) def generator(self, data): - for proto_name, hook_name, hook_addr, hooked, module in data: - yield (0, [str(proto_name), str(hook_name), Address(hook_addr), str(hooked), str(module)]) + for namespace, proto_name, hook_name, hook_addr, hooked, module in data: + yield (0, [str(namespace), + str(proto_name), + str(hook_name), + Address(hook_addr), + str(hooked), + str(module)]) def render_text(self, outfd, data): - self.table_header(outfd, [("Proto", "10"), ("Hook", "16"), ("Handler", "[addrpad]"), ("Is Hooked", "5"), ("Module", "30")]) - - for proto_name, hook_name, hook_addr, hooked, module in data: - self.table_row(outfd, proto_name, hook_name, hook_addr, hooked, module) + self.table_header(outfd, [("NS", "2"), + ("Proto", "10"), + ("Hook", "16"), + ("Handler", "[addrpad]"), + ("Is Hooked", "5"), + ("Module", "30")]) + for namespace, proto_name, hook_name, hook_addr, hooked, module in data: + self.table_row(outfd, namespace, proto_name, hook_name, hook_addr, hooked, module) From b1a5ced772d918da28f3b24b30ea7daec82069ee Mon Sep 17 00:00:00 2001 From: Gustavo Moreira Date: Mon, 21 Jan 2019 15:04:42 +1100 Subject: [PATCH 4/7] Improved documentation and some design issues. - Converted unnecesary class methods to instance methods. - Improved docstrings documentation for some of the class methods plus an explanation how the callbacks work. --- volatility/plugins/linux/netfilter.py | 55 +++++++++++++++++++-------- 1 file changed, 40 insertions(+), 15 deletions(-) diff --git a/volatility/plugins/linux/netfilter.py b/volatility/plugins/linux/netfilter.py index b62389b16..c19280cf6 100644 --- a/volatility/plugins/linux/netfilter.py +++ b/volatility/plugins/linux/netfilter.py @@ -130,36 +130,51 @@ def run_all(cls, kernel_version, volinst): if subclass is None: debug.error("Unsupported netfilter kernel implementation for %s"%(kernel_version)) - @classmethod - def _proto_hook_loop(cls, nfimp_inst): + def _proto_hook_loop(self): """It flattens the protocol families and hooks""" for proto_idx, proto in enumerate(AbstractNetfilter.PROTO_HOOKS): - if proto.name in (PROTO_NOT_IMPLEMENTED.name, "INET"): - # There is no such Netfilter hook implementation for INET protocol in the kernel - # AFAIU this is used like NFPROTO_INET = NFPROTO_IPV4 || NFPROTO_IPV6 + if proto == PROTO_NOT_IMPLEMENTED: continue - if proto.name not in nfimp_inst.subscribed_protocols(): + if proto.name not in self.subscribed_protocols(): # This protocol is not managed in this object continue for hook_idx, hook_name in enumerate(proto.hooks): yield proto_idx, proto.name, len(proto.hooks), hook_idx, hook_name - @classmethod - def _execute(cls, nfimp_inst): - for netns, net in nfimp_inst.get_net_namespaces(): - for proto_idx, proto_name, hooks_count, hook_idx, hook_name in cls._proto_hook_loop(nfimp_inst): + def _execute(self): + """It does all the iteration over the namespaces and protocols, executing the different + callbacks allowing to customize the code to the specific data structure used in a + specific kernel version. + + get_hooks_container_by_protocol(net, proto_name) + It returns the data structure used in a specific kernel implementation to store + the hooks for a respective namespace and protocol, basically: + For Ingress hooks: + network_namespace[] -> net_device[] -> nf_hooks_ingress[] + For all the other Netfilter hooks: + <= 4.2.8 + nf_hooks[] + >= 4.3 + network_namespace[] -> nf.hooks[] + + get_hook_ops(hook_container, proto_idx, hooks_count, hook_idx) + Give the hook_container got in get_hooks_container_by_protocol(), it returns an + iterable of nf_hook_ops elements for a respective protocol and hook type. + """ + for netns, net in self.get_net_namespaces(): + for proto_idx, proto_name, hooks_count, hook_idx, hook_name in self._proto_hook_loop(): try: - hooks_container = nfimp_inst.get_hooks_container_by_protocol(net, proto_name) + hooks_container = self.get_hooks_container_by_protocol(net, proto_name) except KernelProtocolDisabledException: continue if not hooks_container: continue for hook_container in hooks_container: - for hook_ops in nfimp_inst.get_hook_ops(hook_container, proto_idx, hooks_count, hook_idx): + for hook_ops in self.get_hook_ops(hook_container, proto_idx, hooks_count, hook_idx): if not hook_ops: continue hook_ops_addr = hook_ops.hook.v() - found, module = nfimp_inst.volinst.is_known_address_name(hook_ops_addr, nfimp_inst.modules) + found, module = self.volinst.is_known_address_name(hook_ops_addr, self.modules) hooked = "False" if found else "True" yield netns, proto_name, hook_name, hook_ops_addr, hooked, module @@ -191,6 +206,9 @@ def build_nf_hook_ops_array(self, nf_hook_entries): def subscribed_protocols(self): """Most of the implementation handlers respond to these protocols, except the ingress hook implemention which handles an specific protocol called "NETDEV". + On the other hand, there is no such Netfilter hook implementation for INET protocol in the + kernel. AFAIU, this is used like "NFPROTO_INET = NFPROTO_IPV4 || NFPROTO_IPV6" in other + parts of the kernel source code. """ return ("IPV4", "ARP", "BRIDGE", "IPV6", "DECNET") @@ -204,7 +222,10 @@ def get_net_namespaces(self): yield net_idx, net def get_hooks_container_by_protocol(self, net, proto_name): - """Except for kernels < 4.3, all the implementations use network namespaces. + """It returns the data structure used in a specific kernel implementation to store + the hooks for a respective namespace and protocol. + + Except for kernels < 4.3, all the implementations use network namespaces. Also the data structure which contains the hooks, even though it changes its implementation and/or data type, it is always in this location. """ @@ -212,7 +233,11 @@ def get_hooks_container_by_protocol(self, net, proto_name): # Interface def get_hook_ops(self, nf_hooks_addr, proto_idx, hooks_count, hook_idx): - """This is the most variable/unstable part of all Netfilter hook designs, it changes almost + """Give the hook_container got in get_hooks_container_by_protocol(), it returns an + iterable of nf_hook_ops elements for a respective protocol and hook type. + + + This is the most variable/unstable part of all Netfilter hook designs, it changes almost in every single implementation. """ raise NotImplementedError("You must implement this method") From ca293c704254ba424861c4d0c5aa129def472936 Mon Sep 17 00:00:00 2001 From: Gustavo Moreira Date: Mon, 21 Jan 2019 15:25:02 +1100 Subject: [PATCH 5/7] - Removed hook_count from all the callbacks, it's only needed once and it could be calculated at that point using the protocol index. - Using the same argument name for "hook_container" in get_hook_ops method. It's reassigned to a more meanful variable name in every callback. --- volatility/plugins/linux/netfilter.py | 39 ++++++++++++++++----------- 1 file changed, 23 insertions(+), 16 deletions(-) diff --git a/volatility/plugins/linux/netfilter.py b/volatility/plugins/linux/netfilter.py index c19280cf6..8ea6e4d5a 100644 --- a/volatility/plugins/linux/netfilter.py +++ b/volatility/plugins/linux/netfilter.py @@ -139,7 +139,7 @@ def _proto_hook_loop(self): # This protocol is not managed in this object continue for hook_idx, hook_name in enumerate(proto.hooks): - yield proto_idx, proto.name, len(proto.hooks), hook_idx, hook_name + yield proto_idx, proto.name, hook_idx, hook_name def _execute(self): """It does all the iteration over the namespaces and protocols, executing the different @@ -157,12 +157,12 @@ def _execute(self): >= 4.3 network_namespace[] -> nf.hooks[] - get_hook_ops(hook_container, proto_idx, hooks_count, hook_idx) + get_hook_ops(hook_container, proto_idx, hook_idx) Give the hook_container got in get_hooks_container_by_protocol(), it returns an iterable of nf_hook_ops elements for a respective protocol and hook type. """ for netns, net in self.get_net_namespaces(): - for proto_idx, proto_name, hooks_count, hook_idx, hook_name in self._proto_hook_loop(): + for proto_idx, proto_name, hook_idx, hook_name in self._proto_hook_loop(): try: hooks_container = self.get_hooks_container_by_protocol(net, proto_name) except KernelProtocolDisabledException: @@ -170,7 +170,7 @@ def _execute(self): if not hooks_container: continue for hook_container in hooks_container: - for hook_ops in self.get_hook_ops(hook_container, proto_idx, hooks_count, hook_idx): + for hook_ops in self.get_hook_ops(hook_container, proto_idx, hook_idx): if not hook_ops: continue hook_ops_addr = hook_ops.hook.v() @@ -232,11 +232,10 @@ def get_hooks_container_by_protocol(self, net, proto_name): yield net.nf.hooks.obj_offset # Interface - def get_hook_ops(self, nf_hooks_addr, proto_idx, hooks_count, hook_idx): + def get_hook_ops(self, hook_container, proto_idx, hook_idx): """Give the hook_container got in get_hooks_container_by_protocol(), it returns an iterable of nf_hook_ops elements for a respective protocol and hook type. - This is the most variable/unstable part of all Netfilter hook designs, it changes almost in every single implementation. """ @@ -263,9 +262,10 @@ def get_hooks_container_by_protocol(self, net, proto_name): debug.error("Unable to analyze NetFilter. It is either disabled or compiled as a module.") yield nf_hooks_addr - def get_hook_ops(self, nf_hooks_addr, proto_idx, hooks_count, hook_idx): + def get_hook_ops(self, hook_container, proto_idx, hook_idx): # It seems the API doesn't deal with array of arrays very well. # So, doing it the old-school way + nf_hooks_addr = hook_container arr = nf_hooks_addr + (proto_idx * (self.list_head_size * AbstractNetfilter.NF_MAX_HOOKS)) list_head_addr = arr + (hook_idx * self.list_head_size) list_head = obj.Object("list_head", offset=list_head_addr, vm=self.volinst.addr_space) @@ -285,9 +285,10 @@ class NetfilterImp_4_3_to_4_8_17(AbstractNetfilter): KERNEL_MIN = "4.3" KERNEL_MAX = "4.8.17" - def get_hook_ops(self, nf_hooks_addr, proto_idx, hooks_count, hook_idx): + def get_hook_ops(self, hook_container, proto_idx, hook_idx): # It seems the API doesn't deal with array of arrays very well. # So, doing it the old-school way + nf_hooks_addr = hook_container arr = nf_hooks_addr + (proto_idx * (self.list_head_size * AbstractNetfilter.NF_MAX_HOOKS)) list_head_addr = arr + (hook_idx * self.list_head_size) list_head = obj.Object("list_head", offset=list_head_addr, vm=self.volinst.addr_space) @@ -323,9 +324,10 @@ class NetfilterImp_4_9_to_4_13_16(AbstractNetfilter): KERNEL_MIN = "4.9" KERNEL_MAX = "4.13.16" - def get_hook_ops(self, nf_hooks_addr, proto_idx, hooks_count, hook_idx): + def get_hook_ops(self, hook_container, proto_idx, hook_idx): # It seems the API doesn't deal with array of arrays very well. # So doing it the old-school way + nf_hooks_addr = hook_container arr = nf_hooks_addr + (proto_idx * (self.ptr_size * AbstractNetfilter.NF_MAX_HOOKS)) nf_hook_entry_addr = arr + (hook_idx * self.ptr_size) if not nf_hook_entry_addr: @@ -365,7 +367,7 @@ class NetfilterImp_4_14_to_4_15_18(AbstractNetfilter): KERNEL_MIN = "4.14" KERNEL_MAX = "4.15.18" - def get_nf_hook_entries_ptr(self, nf_hooks_addr, proto_idx, hook_idx, hooks_count): + def get_nf_hook_entries_ptr(self, nf_hooks_addr, proto_idx, hook_idx): """This allows to support different hook array implementations from this version on. For instance, in kernels >= 4.16 this multi-dimensional array is split in one-dimensional array of pointers to nf_hooks_entries per each protocol.""" @@ -376,8 +378,9 @@ def get_nf_hook_entries_ptr(self, nf_hooks_addr, proto_idx, hook_idx, hooks_coun vm=self.volinst.addr_space) return nf_hook_entries_ptr - def get_hook_ops(self, nf_hooks_addr, proto_idx, hooks_count, hook_idx): - nf_hook_entries_ptr = self.get_nf_hook_entries_ptr(nf_hooks_addr, proto_idx, hook_idx, hooks_count) + def get_hook_ops(self, hook_container, proto_idx, hook_idx): + nf_hooks_addr = hook_container + nf_hook_entries_ptr = self.get_nf_hook_entries_ptr(nf_hooks_addr, proto_idx, hook_idx) if not nf_hook_entries_ptr: yield None @@ -437,7 +440,8 @@ def get_hooks_container_by_protocol(self, net, proto_name): raise KernelProtocolDisabledException() yield net_nf_hooks.obj_offset - def get_nf_hook_entries_ptr(self, nf_hooks_addr, proto_idx, hook_idx, hooks_count): + def get_nf_hook_entries_ptr(self, nf_hooks_addr, proto_idx, hook_idx): + hooks_count = len(self.PROTO_HOOKS[proto_idx].hooks) nf_hook_entries_ptr_arr = obj.Object("Array", targetType="Pointer", offset=nf_hooks_addr, @@ -481,7 +485,8 @@ class NetfilterIngressImp_4_2_to_4_8_17(AbstractNetfilterIngress): KERNEL_MIN = "4.2" KERNEL_MAX = "4.8.17" - def get_hook_ops(self, nf_hooks_ingress, proto_idx, hooks_count, hook_idx): + def get_hook_ops(self, hook_container, proto_idx, hook_idx): + nf_hooks_ingress = hook_container return nf_hooks_ingress.list_of_type("nf_hook_ops", "list") @@ -492,7 +497,8 @@ class NetfilterIngressImp_4_9_to_4_13_16(AbstractNetfilterIngress): KERNEL_MIN = "4.9" KERNEL_MAX = "4.13.16" - def get_hook_ops(self, nf_hooks_ingress, proto_idx, hooks_count, hook_idx): + def get_hook_ops(self, hook_container, proto_idx, hook_idx): + nf_hooks_ingress = hook_container if not nf_hooks_ingress: yield None @@ -514,7 +520,8 @@ class NetfilterIngressImp_4_14_to_latest(AbstractNetfilterIngress): KERNEL_MIN = "4.14" KERNEL_MAX = KERNEL_LATEST - def get_hook_ops(self, nf_hook_entries_ptr, proto_idx, hooks_count, hook_idx): + def get_hook_ops(self, hook_container, proto_idx, hook_idx): + nf_hook_entries_ptr = hook_container if not nf_hook_entries_ptr: yield None From 251f50f5efe0e5d66c4282a2ce536da031dd6a08 Mon Sep 17 00:00:00 2001 From: Gustavo Moreira Date: Mon, 21 Jan 2019 15:44:06 +1100 Subject: [PATCH 6/7] Removing unnecessary array creation. Instead, the specific pointer to nf_hook_entries is calculated. --- volatility/plugins/linux/netfilter.py | 11 ++++------- 1 file changed, 4 insertions(+), 7 deletions(-) diff --git a/volatility/plugins/linux/netfilter.py b/volatility/plugins/linux/netfilter.py index 8ea6e4d5a..8c75ec2fb 100644 --- a/volatility/plugins/linux/netfilter.py +++ b/volatility/plugins/linux/netfilter.py @@ -441,13 +441,10 @@ def get_hooks_container_by_protocol(self, net, proto_name): yield net_nf_hooks.obj_offset def get_nf_hook_entries_ptr(self, nf_hooks_addr, proto_idx, hook_idx): - hooks_count = len(self.PROTO_HOOKS[proto_idx].hooks) - nf_hook_entries_ptr_arr = obj.Object("Array", - targetType="Pointer", - offset=nf_hooks_addr, - count=hooks_count, - vm=self.volinst.addr_space) - nf_hook_entries_ptr = nf_hook_entries_ptr_arr[hook_idx] + nf_hook_entries_addr = nf_hooks_addr + (hook_idx * self.ptr_size) + nf_hook_entries_ptr = obj.Object("Pointer", + offset=nf_hook_entries_addr, + vm=self.volinst.addr_space) return nf_hook_entries_ptr From 2a6c87a4264bb8a899d64196934109e85176af2e Mon Sep 17 00:00:00 2001 From: Gustavo Moreira Date: Mon, 21 Jan 2019 16:27:35 +1100 Subject: [PATCH 7/7] Some cosmetic changes like renaming variables. --- volatility/plugins/linux/netfilter.py | 28 +++++++++++++++------------ 1 file changed, 16 insertions(+), 12 deletions(-) diff --git a/volatility/plugins/linux/netfilter.py b/volatility/plugins/linux/netfilter.py index 8c75ec2fb..33b539bc4 100644 --- a/volatility/plugins/linux/netfilter.py +++ b/volatility/plugins/linux/netfilter.py @@ -181,19 +181,23 @@ def _execute(self): # Helpers def build_nf_hook_ops_array(self, nf_hook_entries): - """Function helper to build the array of arrays of nf_hook_ops give a nf_hook_entries""" - # nf_hook_ops array is not part of the struct nf_hook_entries definition, so we need to - # craft it. + """Function helper to build the nf_hook_ops array when it is not part of the struct + nf_hook_entries definition. + + nf_hook_ops was stored adjacent in memory to the nf_hook_entry array, in the new struct + 'nf_hook_entries'. However, this nf_hooks_ops array 'orig_ops' is not part of the + nf_hook_entries struct. So, we need to calculate the offset. + + struct nf_hook_entries { + u16 num_hook_entries; /* plus padding */ + struct nf_hook_entry hooks[]; + //const struct nf_hook_ops *orig_ops[]; + } + """ + nf_hook_entry_size = self.volinst.addr_space.profile.get_obj_size("nf_hook_entry") nf_hook_entry_count = nf_hook_entries.num_hook_entries - - nf_hook_entries_hook_addr = nf_hook_entries.hooks.obj_offset - nf_hook_entry_arr = obj.Object("Array", - targetType="nf_hook_entry", - offset=nf_hook_entries_hook_addr, - count=nf_hook_entry_count, - vm=self.volinst.addr_space) - - nf_hook_ops_addr = nf_hook_entries_hook_addr + nf_hook_entry_arr.size() + nf_hook_entry_arr_size = nf_hook_entry_count * nf_hook_entry_size + nf_hook_ops_addr = nf_hook_entries.hooks.obj_offset + nf_hook_entry_arr_size nf_hook_ops_ptr_arr = obj.Object("Array", targetType="Pointer", offset=nf_hook_ops_addr,