
Ferramenta para resolver filtros BPF e criar pacotes baseados neles.
Ferramenta Python para engenharia reversa de bytecode BPF (Berkeley Packet Filter). Dadas instruções BPF brutas — como as que podem ser extraídas de uma amostra de malware, um filtro de captura de pacotes ou um appliance de rede — o filterforge desmonta-as para uma forma legível, utiliza Z3 para resolver o conjunto de restrições do pacote que satisfaz um caminho de aceitação e, em seguida, emprega Scapy para forjar um pacote de rede concreto que passará no filtro.
Em resumo: forneça um programa BPF e ele devolverá um pacote completamente formado (Ethernet, IPv4/IPv6, TCP/UDP/SCTP e além) que o filtro aceita — pronto para inspecionar, salvar em PCAP ou enviar pela rede.
Recomenda-se instalar o uv para executar esta ferramenta.
Consulte os exemplos abaixo para o uso da ferramenta; é provável que mais funcionalidades sejam adicionadas ao longo do tempo.
Para obter resultados rapidamente para um determinado BPF, é mais fácil usar a linha de comando. Devido à forma como algumas ferramentas de engenharia reversa fornecem os dados copiados, pode-se dar uma string hexadecimal bruta ou um array de valores hexadecimais, utilizando a flag -d para apenas mostrar a desmontagem do BPF fornecido:
# string hexadecimal
uv run ff -d -b "280000000c00000015002900060800001500000bdd860000300000001400000015000027110000002800000038000000150024003500000015002300e91400001500220043000000150021009c690000150020006c07000015001f0089000000280000003600000015001d0fe91400001500001d0008000030000000170000001500000f11000000280000001400000045001900ff1f0000b10000000e0000004800000010000000150015003500000015001400e91400001500130043000000150012009c690000150011006c0700001500100089000000480000000e00000015000e00e914000015000d009c69000015000c006c07000015000b0c890000001500000b06000000280000001400000045000900ff1f0000b10000000e000000500000001a000000740000000400000024000000040000000c000000000000000700000000000000400000000e000000150000012054454706000000000004000600000000000000"
# array de valores hexadecimais
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]"
Para obter uma visão geral de como seria um pacote de rede se fosse criado a partir do bytecode BPF fornecido, pode usar a flag -c:
uv run ff -c -b "280000000c0000001500000fdd8600003000000014000000150002008400000015000100060000001500002611000000280000003600000015002300fad5000015002200f6e500001500210012e90000150020005fd6000015001f0083e2000015001e0042fb000015001d006db2000015001c00fbf60000280000003800000015001a13fad500001500001a000800003000000017000000150002008400000015000100060000001500001611000000280000001400000045001400ff1f0000b10000000e000000480000000e00000015001000fad5000015000f00f6e5000015000e0012e9000015000d005fd6000015000c0083e2000015000b0042fb000015000a006db2000015000900fbf60000480000001000000015000700fad5000015000600f6e500001500050012e90000150004005fd600001500030083e200001500020042fb0000150001006db2000015000001fbf6000006000000ffff00000600000000000000"
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'
Alguns BPF podem usar a camada IP bruta; pode-se especificar o tipo de enlace -l raw para este propósito (o padrão é 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'
Para utilizar a ferramenta de forma mais programática, criamos uma API relativamente fácil de usar que pode ser implementada de várias formas; consulte alguns dos exemplos abaixo para inspiração:
from ff import BPFSolver
# Criar um solver com bytecode BPF
solver = BPFSolver("280000000c00000015001b00...")
# Resolver e forjar um pacote
result = solver.solve()
if result:
result.hexdump()
result.show()
print(result.summary())
from ff import BPFSolver
solver = BPFSolver("280000000c00000015001b00...")
# Obter desmontagem legível
print(solver.disassemble())
# Aceder às instruções analisadas
print(f"Número de instruções: {solver.instruction_count}")
for insn in solver.instructions:
print(insn)
from ff import BPFSolver, PacketOverrides
solver = BPFSolver("280000000c00000015001b00...")
# Substituir campos específicos do pacote
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:
# Guardar em ficheiro PCAP
solver.to_pcap("output.pcap")
# Ou guardar um resultado específico
solver.to_pcap("output.pcap", result=result)
from ff import BPFSolver
# Usar link_type="raw" para DLT_RAW (sem cabeçalho Ethernet)
solver = BPFSolver("280000000c00000015001b00...", link_type="raw")
result = solver.solve()
from ff import BPFSolver
solver = BPFSolver("280000000c00000015001b00...")
# Encontrar todos os caminhos que levam à aceitação do pacote
paths = solver.find_paths()
print(f"Encontrados {len(paths)} caminho(s) de aceitação")
for i, path in enumerate(paths):
print(f"Caminho {i+1}: {path}")
from ff import BPFSolver
solver = BPFSolver("280000000c00000015001b00...")
result = solver.solve()
if result:
# Bytes brutos
raw_bytes = result.raw
# Objeto de pacote Scapy para manipulação avançada
scapy_pkt = result.scapy_packet
scapy_pkt[0].dst = "192.168.1.1" # Modificar diretamente
# Caminho de execução percorrido
print(f"Caminho: {result.path}")
print(f"Tipo de enlace: {result.link_type}")
from ff import BPFSolver
solver = BPFSolver("280000000c00000015001b00...")
result = solver.solve()
if result:
# Enviar pacote (requer root/sudo)
solver.send(iface="eth0", count=1)
# Ou enviar um resultado específico
solver.send(result=result, iface="eth0")
Por vezes o BPF não tem as 2 instruções ret que esperamos (1 para aceitar um pacote, 1 para rejeitar um pacote). Veja por exemplo este exemplo do 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)
Podemos ver aqui que temos apenas 1 das instruções ret esperadas, fazendo com que o nosso solver Z3 falhe;
2025-12-16 12:09:31,123 - ERROR - No satisfiable paths found, failed to craft packet
Podemos adicionar a nossa própria instrução aqui com um salto bem-sucedido, observando o que o BPF está a fazer. No trecho acima, vemos que há muitas condições relacionadas com números de porta, cada uma verificando a condição seguinte ou saltando para a instrução 43, que designamos como DROP devido à forma como estamos a desmontar o BPF. Neste caso, não se trata exatamente de um DROP, mas sim de uma instrução de retorno válida. Isto significa que, se adicionarmos a nossa própria instrução ret de sucesso fictícia, ela será analisada corretamente, por exemplo:
ret atual: 06 00 00 00 00 00 00 0006 00 00 00 ff ff 00 00 06 00 00 00 00 00 00 00Resultando em:
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 executarmos agora o -c para forjar um pacote, seremos recebidos com o nosso novo pacote mágico:
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'
Para executar os testes localmente:
uv run --extra dev pytest tests/ -v