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

Linux netfilter hooks plugin #1080

Open
wants to merge 4 commits into
base: develop
Choose a base branch
from

Conversation

gcmoreira
Copy link
Contributor

It supports the following protocols: INET, IPv4, IPv6, ARP, NETDEV (ingress and egress hooks), BRIDGE and DECNET.

Supported Netfilter hooks implementations:

  • kernels < 4.3 (Tested on kernel 3.11.0-26)
  • 4.3 <= kernels < 4.9 (Tested on kernel 4.4.0-210)
  • 4.9 <= kernels < 4.14 (Tested on kernel 4.13.0-46)
  • 4.14 <= kernels < 4.16 (Tested on kernel 4.15.0-169)
  • kernels >= 4.16 (Tested on kernel 4.18.0-10 and 5.19.0-50)

Supported NetDev ingress hooks implementation

  • 4.2 <= kernels < 4.9 (Tested on kernel 4.4.0-210)
  • 4.9 <= kernels < 4.14 (Tested on kernel 4.13.0-46)
  • kernels >= 4.14 (Tested on kernel 4.18.0-10)

Supported NetDev egress hooks implementation

  • kernels >= 5.16 (Tested on kernel 5.19.0-50)

Example:

$ python3 ./vol.py -r pretty \
  -f ram-5.19.0-50 \
  linux.netfilter.Netfilter
Volatility 3 Framework 2.5.2
Formatting...0.00               Stacking attempts finished                 
  |     Net NS |  Proto |         Hook |   Priority |        Handler |                  Module | Is Hooked
* | 4026531840 |   IPV4 |  PRE_ROUTING |       -400 | 0xffffc05b5160 |          nf_defrag_ipv4 |      True
* | 4026531840 |   IPV4 |  PRE_ROUTING |       -200 | 0xffffc062a510 |            nf_conntrack |      True
* | 4026531840 |   IPV4 |     LOCAL_IN |          0 | 0xffffc05e7c60 |               nf_tables |      True
* | 4026531840 |   IPV4 |     LOCAL_IN |          0 | 0xffffc05e74a0 |               nf_tables |      True
* | 4026531840 |   IPV4 |     LOCAL_IN | 2147483647 | 0xffffc062b040 |            nf_conntrack |      True
* | 4026531840 |   IPV4 |      FORWARD |          0 | 0xffffc05e7c60 |               nf_tables |      True
* | 4026531840 |   IPV4 |      FORWARD |          0 | 0xffffc05e74a0 |               nf_tables |      True
* | 4026531840 |   IPV4 |    LOCAL_OUT |       -400 | 0xffffc05b5160 |          nf_defrag_ipv4 |      True
* | 4026531840 |   IPV4 |    LOCAL_OUT |       -200 | 0xffffc062ae60 |            nf_conntrack |      True
* | 4026531840 |   IPV4 |    LOCAL_OUT |          0 | 0xffffc05e7c60 |               nf_tables |      True
* | 4026531840 |   IPV4 |    LOCAL_OUT |          0 | 0xffffc05e74a0 |               nf_tables |      True
* | 4026531840 |   IPV4 | POST_ROUTING |       -225 | 0xffffa681c9c0 | [apparmor_ip_postroute] |      True
* | 4026531840 |   IPV4 | POST_ROUTING | 2147483647 | 0xffffc062b040 |            nf_conntrack |      True
* | 4026531840 |   IPV6 |  PRE_ROUTING |       -400 | 0xffffc061b160 |          nf_defrag_ipv6 |      True
* | 4026531840 |   IPV6 |  PRE_ROUTING |       -200 | 0xffffc062aa40 |            nf_conntrack |      True
* | 4026531840 |   IPV6 |     LOCAL_IN |          0 | 0xffffc05e7cf0 |               nf_tables |      True
* | 4026531840 |   IPV6 |     LOCAL_IN |          0 | 0xffffc05e74a0 |               nf_tables |      True
* | 4026531840 |   IPV6 |     LOCAL_IN | 2147483646 | 0xffffc062b130 |            nf_conntrack |      True
* | 4026531840 |   IPV6 |      FORWARD |          0 | 0xffffc05e7cf0 |               nf_tables |      True
* | 4026531840 |   IPV6 |      FORWARD |          0 | 0xffffc05e74a0 |               nf_tables |      True
* | 4026531840 |   IPV6 |    LOCAL_OUT |       -400 | 0xffffc061b160 |          nf_defrag_ipv6 |      True
* | 4026531840 |   IPV6 |    LOCAL_OUT |       -200 | 0xffffc062aa20 |            nf_conntrack |      True
* | 4026531840 |   IPV6 |    LOCAL_OUT |          0 | 0xffffc05e7cf0 |               nf_tables |      True
* | 4026531840 |   IPV6 |    LOCAL_OUT |          0 | 0xffffc05e74a0 |               nf_tables |      True
* | 4026531840 |   IPV6 | POST_ROUTING |       -225 | 0xffffa681c9c0 | [apparmor_ip_postroute] |      True
* | 4026531840 |   IPV6 | POST_ROUTING | 2147483647 | 0xffffc062b130 |            nf_conntrack |      True
* | 4026531840 | NETDEV |      INGRESS |          0 | 0xffffc05e7a60 |               nf_tables |      True
* | 4026531840 | NETDEV |       EGRESS |          0 | 0xffffc05e7a60 |               nf_tables |      True

See the full test case suite output:
vol3_linux_netfilter_output.txt

@gcmoreira
Copy link
Contributor Author

gcmoreira commented Mar 13, 2024

Hey @ikelos/@atcuno this is still awaiting a review 🙏

@atcuno
Copy link
Contributor

atcuno commented May 8, 2024

@ikelos this one has been tested quite a bit and is good from my end. It is a powerful plugin for rootkit detection.

Copy link
Member

@ikelos ikelos left a comment

Choose a reason for hiding this comment

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

Yep, really nice plugin. A couple of minor niggles, some bits that make me uneasy but otherwise really good. And those docstrings!!! Keep those coming in, they're awesome!!! 5:D

):
self._context = context
self._config = config
symbol_table = self._config["kernel"]
Copy link
Member

Choose a reason for hiding this comment

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

symbol_table is a bit of an odd name, but not a show stopper. 5;P

def run_all(
cls,
context: interfaces.context.ContextInterface,
config: interfaces.configuration.HierarchicalDict,
Copy link
Member

Choose a reason for hiding this comment

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

It looks like config['kernel'] is the only time this is ever used? I'd be much happier passing through a module_name than a whole hierarchical dict? Was the expectation this might need extending or is there another good reason to pass config through?


from typing import Iterator, List, Tuple
from volatility3.framework import (
class_subclasses,
Copy link
Member

Choose a reason for hiding this comment

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

This is a function, not a module. It would be better (and is somewhat the coding standard in volatility) to import volatility.framework and then call framework.class_subclasses to avoid people accidentally thinking it's defined here and then importing it from here.

self.layer_name = self.vmlinux.layer_name

modules = lsmod.Lsmod.list_modules(context, symbol_table)
self.handlers = linux.LinuxUtilities.generate_kernel_handler_info(
Copy link
Member

Choose a reason for hiding this comment

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

I believe LinuxUtilities is also versioned, so should be one of the plugin's requirements, a VersionRequirement please (just to make sure the API won't change out from under you).

self._set_data_sizes()

def _set_data_sizes(self):
self.ptr_size = self.vmlinux.get_type("pointer").size
Copy link
Member

Choose a reason for hiding this comment

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

This isn't a show stopper because this gets called as part of the __init__ but in general all class variables should be defined only and directly in the initializer, to avoid accesses before they've been set.

vollog.error("Unsupported Netfilter kernel implementation")

def _run(self) -> Iterator[Tuple[int, str, str, int, int, str, bool]]:
"""Iterates over namespaces and protocols, executing various callbacks that
Copy link
Member

Choose a reason for hiding this comment

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

Really good docstrings! Thanks! 5:D

# the INET protocol in the kernel. AFAIU, this is used as
# 'NFPROTO_INET = NFPROTO_IPV4 || NFPROTO_IPV6'
# in other parts of the kernel source code.
return ("IPV4", "ARP", "BRIDGE", "IPV6", "DECNET")
Copy link
Member

Choose a reason for hiding this comment

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

Feels like this should be defined as a constant at the top of the module? Any reason this list is different?

"""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.
Copy link
Member

Choose a reason for hiding this comment

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

Did I mention how awesome your docstrings are! 5:D

return self.vmlinux.symbol_table_name + constants.BANG + symbol_basename

@staticmethod
def get_member_type(
Copy link
Member

Choose a reason for hiding this comment

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

This feels a little black magicy... You're definitely playing around with volatility internals here. My concern is if there's a change and we add another object with a subtype or something. I can see it gets used a lot and looks useful, but I worry about how robust it is. If you're happy with it, I'll go for it, it's just got my spidey sense tingling a little... 5;)

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

None yet

3 participants