Skip to content

feature: added iptables (legacy) and iptables-nft plugins - #1974

Open
Tasty-Murder wants to merge 3 commits into
volatilityfoundation:developfrom
Tasty-Murder:feature/iptables-plugin
Open

feature: added iptables (legacy) and iptables-nft plugins#1974
Tasty-Murder wants to merge 3 commits into
volatilityfoundation:developfrom
Tasty-Murder:feature/iptables-plugin

Conversation

@Tasty-Murder

Copy link
Copy Markdown
  • linux.iptables — Extracts active firewall rules from Linux memory images by walking the ipt_table / ip6t_table kernel structures via the ISF symbol table. Outputs rules for both IPv4 and IPv6 across all tables (filter, nat, mangle, raw) with their match expressions and targets, equivalent to iptables-save / ip6tables-save without requiring a live shell.

  • linux.iptables_nft — Extracts firewall rules from the nf_tables subsystem, covering both native nftables rules and rules loaded through the iptables-nft compatibility layer. Decodes native nft expressions (meta, payload, cmp, nat, log, limit, range) as well as xt_compat match extensions (conntrack, addrtype, multiport, etc.), producing human-readable output similar to nft list ruleset.

Both plugins output: network namespace, address family, table, chain, default policy, rule index, decoded match string, and target. They support Docker-generated rulesets, interface negation, IPv6 addresses, and --ctstate conntrack matching.

@ikelos

ikelos commented Apr 30, 2026

Copy link
Copy Markdown
Member

Wow, thanks for submitting this! It is huge though, so it may take us a while to go through it... I've not seen anyone go off to the internet to pull down source code to figure out how to parse data out of the image though, so I'll need to do some thinking on that (and if it seems solid it might be something we add as support in the library itself). I've asked @atcuno and @Abyss-W4tcher, as our resident linux gurus to look through this. 5:)

# ---------------------------------------------------------------------------

_NF_DROP = 0
_NF_ACCEPT = 1
# ---------------------------------------------------------------------------

# NFT_PAYLOAD_* base constants
_NFT_PAYLOAD_LL = 0 # link-layer header
_NFT_PAYLOAD_BASE_OFF = 0 # byte 0
_NFT_PAYLOAD_OFFSET_OFF = 1 # byte 1
_NFT_PAYLOAD_LEN_OFF = 2 # byte 2
_NFT_PAYLOAD_DREG_OFF = 3 # byte 3
# +18 u8 op:8 (1B) — OR at +20 as full int
# We try reading op from +16 first (fits in range 0–5), then +18, then +20.
_NFT_CMP_DATA_OFF = 0 # nft_data starts here
_NFT_CMP_SREG_OFF = 16
# We try reading op from +16 first (fits in range 0–5), then +18, then +20.
_NFT_CMP_DATA_OFF = 0 # nft_data starts here
_NFT_CMP_SREG_OFF = 16
_NFT_CMP_LEN_OFF = 17
nf_t = vmlinux.get_type("netns_nf")
if nf_t.has_member("nft"):
return nf_off + nf_t.members["nft"][0]
except Exception:
hook_arrays = []
try:
hook_arrays.append((2, "ip", net.nf.hooks_ipv4))
except Exception:
pass
try:
hook_arrays.append((10, "ip6", net.nf.hooks_ipv6))
except Exception:
layout.tbl_name_off, refined.tbl_name_off,
)
layout = refined
except Exception:
flags = layer.read(chain_addr + layout.chain_flags_off, 1)[0]
if flags & _NFT_CHAIN_BASE_FLAG:
continue
except Exception:
@ikelos

ikelos commented Apr 30, 2026

Copy link
Copy Markdown
Member

Github's security is a little strict, but it's generally quite helpful. So is ruff, sadly, it helps make sure all our formatting is consistent even if no one actually likes every change it makes.

…out and decoder infrastructure

Splits the single iptables plugin into linux.iptables_legacy.IPTables
(physical-scan based, renamed from iptables.py) and
linux.iptables_nft.IPTablesNFT (nftables backend), backed by shared
helpers in symbols/linux/utilities/ instead of one plugin reaching into
the other's internals:

- xtables_layout.py resolves struct offsets in three tiers: BTF parsed
  directly from the memory image (exact for that build, no network),
  an opt-in fetch of the matching kernel source tag from GitHub (off
  by default), and a hardcoded fallback table keyed by kernel version
  for when neither of the above is available.
- bpf_btf.py scans a memory image for BTF blobs and parses split-BTF
  string tables to resolve module struct layouts.
- xtables.py holds shared raw-read helpers, banner/version detection,
  and counter resolution used by both plugins.
- xtables_decoders.py holds the full match/target decoder engine (all
  match/target decoders, their dispatch tables, and xt_entry header
  parsing) that both plugins call into, rather than iptables_nft
  importing private functions out of iptables_legacy directly.

Decoder fixes found by testing every supported rule type against real
captured rulesets: multiport, icmp6, hashlimit, recent, statistic,
CONNMARK, SET, SYNPROXY, TPROXY, and CT/NOTRACK matches and targets;
SNAT/DNAT/MASQUERADE/REDIRECT decoding for the modern nf_nat_range2
struct; ctstate bit decoding disambiguated by revision/privdata size
instead of a heuristic guess; native nft expression decoding (payload,
cmp, meta, immediate/verdict, bitwise) fixed across several kernel
versions and struct variants; chain-name decoding rejects non-printable
garbage instead of displaying it; nftables per-netns tables resolved via
net_generic()/nf_tables_net_id using raw ELF symbol-table reads instead
of volatility3's object model, which was hitting an address-masking bug
on this path.

Two further decoding bugs fixed in linux.iptables_legacy, found by
testing custom (user-defined) chains with real jump topologies:
JUMP targets resolved to the wrong blob offset (the ERROR header's own
offset instead of where control actually transfers), producing
unresolved JUMP@+N targets instead of chain names; and every
user-defined chain's compiler-inserted implicit RETURN terminator was
decoded as a second, bogus rule whenever the chain's real last rule was
also an explicit RETURN.

Also hardens linux.iptables_legacy's hook-layout matching against a
subclass whose constructor fails for reasons other than
PluginRequirementException (e.g. a missing ISF symbol), which
previously crashed the whole plugin run instead of falling back to
another subclass.

Also: removed dead code, modernized type hints, fixed ruff/format lint
gates, and trimmed comments down to explain only the reasoning behind
non-obvious decisions.
…etfilter hook walk

xtables_layout.py:
- Add a kernel <4.0 xt_table fallback layout (previously defaulted to the
  >=4.15 layout, silently wrong).
- Add XtTableInfoLayout.entries_indirect: kernel <4.2's xt_table_info.entries
  is an array of per-CPU pointers to the rule blob, not an embedded byte
  array like >=4.2. Reading it as an embedded blob on <4.2 produced zero
  parseable entries even with correct offsets. Derived from the actual
  struct declaration when fetched from source, and set for the <4.2
  hardcoded fallback range.

iptables_legacy.py:
- _read_entries_blob(): dereference entries[0] when entries_indirect is
  set, but validate it's a real kernel pointer first and fall back to
  treating entries_off as the blob start directly otherwise -- some vendor
  kernels backport the >=4.2 embedded-array layout without their reported
  version implying it.
- _iter_hook_table_privs(): construct AbstractNetfilter with
  resolve_hook_owners=False. The default constructor scans every loaded
  module to attribute hooks to owners, which this function never reads and
  which dominates runtime.

netfilter.py:
- AbstractNetfilter.__init__: add resolve_hook_owners parameter (default
  True, preserving existing behavior) to make the module-attribution scan
  optional for callers that only need hook enumeration.

xtables_decoders.py:
- read_xt_name(): when the kernel-pointer dereference strategy is
  unavailable, fall back to only the genuinely preserved header bytes
  instead of reading into bytes that hold a raw pointer value -- this
  previously produced corrupted output for match/target names longer than
  5 characters.
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.

5 participants