
Strumento per risolvere i filtri BPF e creare pacchetti basati su questi.
Toolkit Python per il reverse engineering del bytecode Berkeley Packet Filter (BPF). Date istruzioni BPF grezze — quelle che potresti estrarre da un campione di malware, un filtro di cattura di pacchetti o un'appliance di rete — filterforge le disassembla in forma leggibile, utilizza Z3 per risolvere il set di vincoli del pacchetto che soddisfano un percorso di accettazione, e poi sfrutta Scapy per forgiare un pacchetto di rete concreto che passerebbe il filtro.
In breve: fornisci un programma BPF e ti restituirà un pacchetto completamente formato (Ethernet, IPv4/IPv6, TCP/UDP/SCTP e oltre) che il filtro accetta — pronto per essere ispezionato, salvato in PCAP o inviato sulla rete.
Si consiglia di installare uv per eseguire questo strumento.
Fai riferimento agli esempi seguenti per l'utilizzo dello strumento; è probabile che nel tempo verranno aggiunte ulteriori funzionalità.
Per ottenere rapidamente risultati per un dato BPF, il modo più semplice è utilizzare la riga di comando. A causa di come alcuni strumenti di reverse engineering restituiscono i dati copiati, puoi fornire una stringa esadecimale grezza o un array di valori esadecimali, utilizzando il flag -d per mostrare semplicemente il disassemblaggio del BPF fornito;
# hex-string
uv run ff -d -b "280000000c00000015002900060800001500000bdd860000300000001400000015000027110000002800000038000000150024003500000015002300e91400001500220043000000150021009c690000150020006c07000015001f0089000000280000003600000015001d0fe91400001500001d0008000030000000170000001500000f11000000280000001400000045001900ff1f0000b10000000e0000004800000010000000150015003500000015001400e91400001500130043000000150012009c690000150011006c0700001500100089000000480000000e00000015000e00e914000015000d009c69000015000c006c07000015000b0c890000001500000b06000000280000001400000045000900ff1f0000b10000000e000000500000001a000000740000000400000024000000040000000c000000000000000700000000000000400000000e000000150000012054454706000000000004000600000000000000"
# array of hex values
uv run ff -d -b "[0x28,0x0,0x0,0xc,0x15,0x0,0x1b,0x800,0x30,0x0,0x0,0x17,0x15,0x0,0x5,0x11,0x28,0x0,0x0,0x14,0x45,0x17,0x0,0x1fff,0xb1,0x0,0x0,0xe,0x48,0x0,0x0,0x16,0x15,0x13,0x14,0x7255,0x15,0x0,0x7,0x1,0x28,0x0,0x0,0x14,0x45,0x11,0x0,0x1fff,0xb1,0x0,0x0,0xe,0x48,0x0,0x0,0x16,0x15,0x0,0xe,0x7255,0x50,0x0,0x0,0xe,0x15,0xb,0xc,0x8,0x15,0x0,0xb,0x6,0x28,0x0,0x0,0x14,0x45,0x9,0x0,0x1fff,0xb1,0x0,0x0,0xe,0x50,0x0,0x0,0x1a,0x54,0x0,0x0,0xf0,0x74,0x0,0x0,0x2,0xc,0x0,0x0,0x0,0x7,0x0,0x0,0x0,0x48,0x0,0x0,0xe,0x15,0x0,0x1,0x5293,0x6,0x0,0x0,0xffff,0x6,0x0,0x0,0x0]"
Per avere una panoramica di come sarebbe un pacchetto di rete se fosse creato dal bytecode BPF fornito, puoi usare il flag -c:
uv run ff -c -b "280000000c0000001500000fdd8600003000000014000000150002008400000015000100060000001500002611000000280000003600000015002300fad5000015002200f6e500001500210012e90000150020005fd6000015001f0083e2000015001e0042fb000015001d006db2000015001c00fbf60000280000003800000015001a13fad500001500001a000800003000000017000000150002008400000015000100060000001500001611000000280000001400000045001400ff1f0000b10000000e000000480000000e00000015001000fad5000015000f00f6e5000015000e0012e9000015000d005fd6000015000c0083e2000015000b0042fb000015000a006db2000015000900fbf60000480000001000000015000700fad5000015000600f6e500001500050012e90000150004005fd600001500030083e200001500020042fb0000150001006db2000015000001fbf6000006000000000000"
2026-02-09 12:06:23,342 - INFO - Loaded 45 instructions
2026-02-09 12:06:23,342 - DEBUG - Found 50 accepting path(s)
2026-02-09 12:06:23,346 - DEBUG - Path 1 is satisfiable
2026-02-09 12:06:23,347 - INFO - Hexdump:
0000 00 00 00 00 00 00 00 00 00 00 00 00 86 DD 60 00 ..............`.
0010 00 00 00 00 84 00 00 00 00 00 00 00 00 00 00 00 ................
0020 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ................
0030 00 00 00 00 00 00 D5 FA 00 00 00 00 00 00 00 00 ................
0040 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ................
0050 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ................
0060 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ................
0070 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ................
2026-02-09 12:06:23,347 - INFO - Packet summary:
###[ Ethernet ]###
dst = 00:00:00:00:00:00
src = 00:00:00:00:00:00
type = IPv6
###[ IPv6 ]###
version = 6
tc = 0
fl = 0
plen = 0
nh = SCTP
hlim = 0
src = ::
dst = ::
###[ SCTP ]###
sport = 54778
dport = 0
tag = 0x0
chksum = 0x0
###[ Raw ]###
load = b'\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00'
Alcuni BPF potrebbero utilizzare il livello IP grezzo; puoi specificare il link-type -l raw per questo scopo (il default è ethernet);
uv run ff -c -b 300000000000000054000000f00000001500001e40000000300000000000000054000000f00000001500000660000000300000000600000015000900110000003000000006000000150000022c00000030000000280000001500050011000000300000000000000054000000f0000000150000124000000030000000090000001500001011000000280000000600000045000e00ff1f000000000000080000000200000000000000b10000000000000060000000000000000c0000000000000007000000000000004800000000000000020000000100000000000000557200000200000002000000610000000200000060000000010000001c000000000000001500c20000000000300000000000000054000000f00000001500002d40000000300000000000000054000000f00000001500002a4000000030000000090000001500002801000000280000000600000045002600ff1f000000000000080000000200000002000000b10000000000000060000000020000000c0000000000000007000000000000004800000000000000020000000300000000000000557200000200000004000000610000000400000060000000030000001c000000000000001500001800000000300000000000000054000000f00000001500001540000000300000000000000054000000f0000000150000124000000030000000090000001500001001000000280000000600000045000e00ff1f000000000000000000000200000004000000b10000000000000060000000040000000c0000000000000007000000000000005000000000000000020000000500000000000000080000000200000006000000610000000600000060000000050000001c000000000000001500920000000000300000000000000054000000f00000001500004340000000300000000000000054000000f00000001500004040000000300000000000000054000000f00000001500000660000000300000000600000015000900060000003000000006000000150000022c00000030000000280000001500050006000000300000000000000054000000f0000000150000344000000030000000090000001500003206000000280000000600000045003000ff1f0000300000000000000054000000f00000001500000660000000300000000600000015000900060000003000000006000000150000022c00000030000000280000001500050006000000300000000000000054000000f0000000150000244000000030000000090000001500002206000000280000000600000045002000ff1f0000000000000c0000000200000006000000b10000000000000060000000060000000c0000000000000007000000000000005000000000000000020000000700000000000000f00000000200000008000000610000000800000060000000070000005c00000000000000020000000800000000000000020000000200000009000000610000000900000060000000080000007c000000000000000200000009000000b10000000000000060000000090000000c0000000000000007000000000000004800000000000000020000000a0000000000000093520000020000000b000000610000000b000000600000000a0000001c0000000000000015004c0000000000300000000000000054000000f00000001500004a40000000300000000000000054000000f00000001500004740000000300000000000000054000000f00000001500000660000000300000000600000015000900060000003000000006000000150000022c00000030000000280000001500050006000000300000000000000054000000f00000001500003b4000000030000000090000001500003906000000280000000600000045003700ff1f0000300000000000000054000000f00000001500000660000000300000000600000015000900060000003000000006000000150000022c00000030000000280000001500050006000000300000000000000054000000f00000001500002b4000000030000000090000001500002906000000280000000600000045002700ff1f0000000000000c000000020000000b000000b100000000000000600000000b0000000c0000000000000007000000000000005000000000000000020000000c00000000000000f0000000020000000d000000610000000d000000600000000c0000005c00000000000000020000000d0000000000000002000000020000000e000000610000000e000000600000000d0000007c00000000000000020000000e000000000000001a000000020000000f000000610000000f000000600000000e0000000c00000000000000020000000f000000b100000000000000600000000f0000000c0000000000000007000000000000004000000000000000020000000000000000000000393939390200000001000000610000000100000060000000000000001c00000000000000150000010000000006000000ffff00000600000000000000 -l raw
2026-02-09 12:06:46,064 - INFO - Loaded 229 instructions
2026-02-09 12:06:46,717 - DEBUG - Found 50 accepting path(s)
2026-02-09 12:06:46,725 - DEBUG - Path 1 unsatisfiable
2026-02-09 12:06:46,730 - DEBUG - Path 2 is satisfiable
2026-02-09 12:06:46,731 - INFO - Hexdump:
0000 45 00 00 00 00 00 00 00 00 11 00 00 00 00 00 00 E...............
0010 00 00 00 00 00 00 00 00 00 00 00 00 72 55 00 00 ............rU..
0020 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ................
0030 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ................
0040 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ................
0050 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ................
0060 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ................
0070 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ................
2026-02-09 12:06:46,731 - INFO - Packet summary:
###[ IP ]###
version = 4
ihl = 5
tos = 0x0
len = 0
id = 0
flags =
frag = 0
ttl = 0
proto = udp
chksum = 0x0
src = 0.0.0.0
dst = 0.0.0.0
\options \
###[ UDP ]###
sport = 0
dport = 0
len = 0
chksum = 0x0
###[ Raw ]###
load = b'rU\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00'
Per utilizzare lo strumento in modo più programmatico abbiamo creato un'API relativamente facile da usare che può essere implementata in molti modi diversi; fai riferimento ad alcuni degli esempi seguenti per trarre ispirazione:
from ff import BPFSolver
# Create a solver with BPF bytecode
solver = BPFSolver("280000000c00000015001b00...")
# Solve and craft a packet
result = solver.solve()
if result:
result.hexdump()
result.show()
print(result.summary())
from ff import BPFSolver
solver = BPFSolver("280000000c00000015001b00...")
# Get human-readable disassembly
print(solver.disassemble())
# Access parsed instructions
print(f"Instruction count: {solver.instruction_count}")
for insn in solver.instructions:
print(insn)
from ff import BPFSolver, PacketOverrides
solver = BPFSolver("280000000c00000015001b00...")
# Override specific packet fields
overrides = PacketOverrides(
src_ip="192.168.1.100",
dst_ip="10.0.0.1",
src_port=12345,
dst_port=80,
src_mac="00:11:22:33:44:55",
dst_mac="aa:bb:cc:dd:ee:ff"
)
result = solver.solve(overrides=overrides)
if result:
result.show()
from ff import BPFSolver
solver = BPFSolver("280000000c00000015001b00...")
result = solver.solve()
if result:
# Save to PCAP file
solver.to_pcap("output.pcap")
# Or save a specific result
solver.to_pcap("output.pcap", result=result)
from ff import BPFSolver
# Use link_type="raw" for DLT_RAW (no Ethernet header)
solver = BPFSolver("280000000c00000015001b00...", link_type="raw")
result = solver.solve()
from ff import BPFSolver
solver = BPFSolver("280000000c00000015001b00...")
# Find all paths that lead to packet acceptance
paths = solver.find_paths()
print(f"Found {len(paths)} accepting path(s)")
for i, path in enumerate(paths):
print(f"Path {i+1}: {path}")
from ff import BPFSolver
solver = BPFSolver("280000000c00000015001b00...")
result = solver.solve()
if result:
# Raw bytes
raw_bytes = result.raw
# Scapy packet object for advanced manipulation
scapy_pkt = result.scapy_packet
scapy_pkt[0].dst = "192.168.1.1" # Modify directly
# Execution path taken
print(f"Path: {result.path}")
print(f"Link type: {result.link_type}")
from ff import BPFSolver
solver = BPFSolver("280000000c00000015001b00...")
result = solver.solve()
if result:
# Send packet (requires root/sudo)
solver.send(iface="eth0", count=1)
# Or send a specific result
solver.send(result=result, iface="eth0")
A volte il BPF non ha le 2 istruzioni ret che ci aspettiamo (1 per accettare un pacchetto, 1 per rifiutare un pacchetto). Prendi ad esempio questo esempio Symbiote (dcfbd5054bb6ea61b8f5a352a482e0cf7e8c5545bd88915d3e67f7ba01c2b3d4):
uv run ff -d -b "280000000c0000001500000fdd8600003000000014000000150002008400000015000100060000001500002611000000280000003600000015002300fad5000015002200f6e500001500210012e90000150020005fd6000015001f0083e2000015001e0042fb000015001d006db2000015001c00fbf60000280000003800000015001a13fad500001500001a000800003000000017000000150002008400000015000100060000001500001611000000280000001400000045001400ff1f0000b10000000e000000480000000e00000015001000fad5000015000f00f6e5000015000e0012e9000015000d005fd6000015000c0083e2000015000b0042fb000015000a006db2000015000900fbf60000480000001000000015000700fad5000015000600f6e500001500050012e90000150004005fd600001500030083e200001500020042fb0000150001006db2000015000001fbf600000600000000000000"
2026-02-09 12:08:32,009 - INFO - Loaded 44 instructions
(000) ldh [12]
(001) jeq #0x86dd, jt 2, jf 17
(002) ldb [20]
<SNIPPED>
(026) jeq #0xd5fa, jt 43, jf 27
(027) jeq #0xe5f6, jt 43, jf 28
(028) jeq #0xe912, jt 43, jf 29
(029) jeq #0xd65f, jt 43, jf 30
(030) jeq #0xe283, jt 43, jf 31
(031) jeq #0xfb42, jt 43, jf 32
(032) jeq #0xb26d, jt 43, jf 33
(033) jeq #0xf6fb, jt 43, jf 34
(034) ldh [x + 16]
(035) jeq #0xd5fa, jt 43, jf 36
(036) jeq #0xe5f6, jt 43, jf 37
(037) jeq #0xe912, jt 43, jf 38
(038) jeq #0xd65f, jt 43, jf 39
(039) jeq #0xe283, jt 43, jf 40
(040) jeq #0xfb42, jt 43, jf 41
(041) jeq #0xb26d, jt 43, jf 42
(042) jeq #0xf6fb, jt 43, jf 44
(043) ret #0 (DROP)
Possiamo vedere qui che abbiamo solo 1 delle istruzioni ret previste, causando il fallimento del nostro risolutore Z3;
2025-12-16 12:09:31,123 - ERROR - No satisfiable paths found, failed to craft packet
Possiamo aggiungere la nostra istruzione qui con un salto riuscito guardando cosa sta facendo il BPF. Nel frammento sopra possiamo vedere che ci sono molte condizioni relative ai numeri di porta, ognuna delle quali controlla la condizione successiva oppure salta all'istruzione 43, che da noi viene indicata come DROP a causa di come stiamo disassemblando il BPF. In questo caso non si tratta di un vero DROP, ma di un'istruzione di ritorno valida. Ciò significa che se aggiungiamo la nostra istruzione ret fittizia di successo, verrà analizzata correttamente, ad esempio:
ret corrente: 06 00 00 00 00 00 00 0006 00 00 00 ff ff 00 00 06 00 00 00 00 00 00 00Risultato:
uv run ff -d -b "280000000c0000001500000fdd8600003000000014000000150002008400000015000100060000001500002611000000280000003600000015002300fad5000015002200f6e500001500210012e90000150020005fd6000015001f0083e2000015001e0042fb000015001d006db2000015001c00fbf60000280000003800000015001a13fad500001500001a000800003000000017000000150002008400000015000100060000001500001611000000280000001400000045001400ff1f0000b10000000e000000480000000e00000015001000fad5000015000f00f6e5000015000e0012e9000015000d005fd6000015000c0083e2000015000b0042fb000015000a006db2000015000900fbf60000480000001000000015000700fad5000015000600f6e500001500050012e90000150004005fd600001500030083e200001500020042fb0000150001006db2000015000001fbf6000006000000ffff00000600000000000000"
2025-12-16 12:12:04,346 - INFO - Loaded 45 instructions
(000) ldh [12]
(001) jeq #0x86dd, jt 2, jf 17
(002) ldb [20]
<SNIPPED>
(026) jeq #0xd5fa, jt 43, jf 27
(027) jeq #0xe5f6, jt 43, jf 28
(028) jeq #0xe912, jt 43, jf 29
(029) jeq #0xd65f, jt 43, jf 30
(030) jeq #0xe283, jt 43, jf 31
(031) jeq #0xfb42, jt 43, jf 32
(032) jeq #0xb26d, jt 43, jf 33
(033) jeq #0xf6fb, jt 43, jf 34
(034) ldh [x + 16]
(035) jeq #0xd5fa, jt 43, jf 36
(036) jeq #0xe5f6, jt 43, jf 37
(037) jeq #0xe912, jt 43, jf 38
(038) jeq #0xd65f, jt 43, jf 39
(039) jeq #0xe283, jt 43, jf 40
(040) jeq #0xfb42, jt 43, jf 41
(041) jeq #0xb26d, jt 43, jf 42
(042) jeq #0xf6fb, jt 43, jf 44
(043) ret #0xffff (ACCEPT)
(044) ret #0 (DROP)
Se ora eseguiamo il nostro -c per creare un pacchetto, verremo accolti dal nostro nuovo pacchetto magico:
uv run ff -c -b "280000000c0000001500000fdd8600003000000014000000150002008400000015000100060000001500002611000000280000003600000015002300fad5000015002200f6e500001500210012e90000150020005fd6000015001f0083e2000015001e0042fb000015001d006db2000015001c00fbf60000280000003800000015001a13fad500001500001a000800003000000017000000150002008400000015000100060000001500001611000000280000001400000045001400ff1f0000b10000000e000000480000000e00000015001000fad5000015000f00f6e5000015000e0012e9000015000d005fd6000015000c0083e2000015000b0042fb000015000a006db2000015000900fbf60000480000001000000015000700fad5000015000600f6e500001500050012e90000150004005fd600001500030083e200001500020042fb0000150001006db2000015000001fbf6000006000000ffff00000600000000000000"
2026-02-09 12:07:47,247 - INFO - Loaded 45 instructions
2026-02-09 12:07:47,247 - DEBUG - Found 50 accepting path(s)
2026-02-09 12:07:47,251 - DEBUG - Path 1 is satisfiable
2026-02-09 12:07:47,251 - INFO - Hexdump:
0000 00 00 00 00 00 00 00 00 00 00 00 00 86 DD 60 00 ..............`.
0010 00 00 00 00 84 00 00 00 00 00 00 00 00 00 00 00 ................
0020 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ................
0030 00 00 00 00 00 00 D5 FA 00 00 00 00 00 00 00 00 ................
0040 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ................
0050 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ................
0060 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ................
0070 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ................
2026-02-09 12:07:47,252 - INFO - Packet summary:
###[ Ethernet ]###
dst = 00:00:00:00:00:00
src = 00:00:00:00:00:00
type = IPv6
###[ IPv6 ]###
version = 6
tc = 0
fl = 0
plen = 0
nh = SCTP
hlim = 0
src = ::
dst = ::
###[ SCTP ]###
sport = 54778
dport = 0
tag = 0x0
chksum = 0x0
###[ Raw ]###
load = b'\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00'
Per eseguire i test in locale:
uv run --extra dev pytest tests/ -v