cscg ist super
This commit is contained in:
97
2026/cscg/misc/table-madness/parse.py
Normal file
97
2026/cscg/misc/table-madness/parse.py
Normal file
@@ -0,0 +1,97 @@
|
||||
import sys
|
||||
import re
|
||||
from z3 import *
|
||||
|
||||
IPTABLES_FILE = "./service-target/iptables.save"
|
||||
|
||||
|
||||
def solve_circuit(iptables_path: str) -> list[int]:
|
||||
rules_raw = []
|
||||
with open(iptables_path) as f:
|
||||
for line in f:
|
||||
line = line.strip()
|
||||
if line.startswith("-A INPUT"):
|
||||
rules_raw.append(line)
|
||||
|
||||
input_knock: dict[str, int] = {} # ipset_bit -> source_port
|
||||
eval_del: list[str] = [] # bits cleared unconditionally on sport=1338
|
||||
eval_gate: list[str] = [] # conditional gate rules on sport=1338
|
||||
|
||||
for rule in rules_raw:
|
||||
# Input knock: --sport 2XXXX without any match-set condition
|
||||
m = re.search(r"--sport (2\d+) -j SET --add-set (i\d+) src", rule)
|
||||
if m and "match-set" not in rule:
|
||||
input_knock[m.group(2)] = int(m.group(1))
|
||||
continue
|
||||
# Eval del: unconditional clear on sport=1338
|
||||
if "--sport 1338" in rule and "--del-set" in rule and "match-set" not in rule:
|
||||
m = re.search(r"--del-set (i\d+)", rule)
|
||||
if m:
|
||||
eval_del.append(m.group(1))
|
||||
continue
|
||||
# Gate rule: conditional add on sport=1338
|
||||
if "--sport 1338" in rule and "--add-set" in rule:
|
||||
eval_gate.append(rule)
|
||||
|
||||
print(f"[*] Circuit: {len(input_knock)} inputs, {len(eval_gate)} gates, {len(eval_del)} resets")
|
||||
|
||||
# Build symbolic Z3 state
|
||||
bits: dict[str, object] = {}
|
||||
|
||||
# Input bits = free boolean variables
|
||||
for bit in input_knock:
|
||||
bits[bit] = Bool(bit)
|
||||
|
||||
# Cleared bits start as False (reset each eval cycle)
|
||||
for bit in eval_del:
|
||||
bits[bit] = BoolVal(False)
|
||||
|
||||
def get_bit(b):
|
||||
return bits.get(b, BoolVal(False))
|
||||
|
||||
# Process gate rules in file order (sequential iptables evaluation)
|
||||
for rule in eval_gate:
|
||||
# Double-condition: AND gate
|
||||
m2 = re.search(
|
||||
r"-m set (!?) ?--match-set (\S+) src -m set (!?) ?--match-set (\S+) src -j SET --add-set (\S+) src",
|
||||
rule,
|
||||
)
|
||||
if m2:
|
||||
neg1, a, neg2, b, out = m2.group(1) == "!", m2.group(2), m2.group(3) == "!", m2.group(4), m2.group(5)
|
||||
expr_a = Not(get_bit(a)) if neg1 else get_bit(a)
|
||||
expr_b = Not(get_bit(b)) if neg2 else get_bit(b)
|
||||
bits[out] = Or(get_bit(out), And(expr_a, expr_b))
|
||||
continue
|
||||
# Single-condition: OR feed
|
||||
m1 = re.search(
|
||||
r"-m set (!?) ?--match-set (\S+) src -j SET --add-set (\S+) src",
|
||||
rule,
|
||||
)
|
||||
if m1:
|
||||
neg, a, out = m1.group(1) == "!", m1.group(2), m1.group(3)
|
||||
expr_a = Not(get_bit(a)) if neg else get_bit(a)
|
||||
bits[out] = Or(get_bit(out), expr_a)
|
||||
|
||||
# Solve: i00002 must be True (the ACCEPT gate)
|
||||
print("[*] Running Z3 solver...")
|
||||
s = Solver()
|
||||
s.add(get_bit("i00002") == True)
|
||||
|
||||
if s.check() != sat:
|
||||
print("[!] UNSAT — circuit has no solution")
|
||||
sys.exit(1)
|
||||
|
||||
model = s.model()
|
||||
knock_ports = []
|
||||
for bit, port in input_knock.items():
|
||||
if is_true(model.eval(Bool(bit))):
|
||||
knock_ports.append(port)
|
||||
|
||||
knock_ports.sort()
|
||||
print(f"[+] Solution found: {len(knock_ports)} ports to knock")
|
||||
return knock_ports
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
knock_ports = solve_circuit(IPTABLES_FILE)
|
||||
print(f"[*] Knock ports: {knock_ports}\n")
|
||||
Reference in New Issue
Block a user