feature: added iptables (legacy) and iptables-nft plugins - #1974
Open
Tasty-Murder wants to merge 3 commits into
Open
feature: added iptables (legacy) and iptables-nft plugins#1974Tasty-Murder wants to merge 3 commits into
Tasty-Murder wants to merge 3 commits into
Conversation
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: |
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.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
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.