Python 工具包,用于逆向工程 Berkeley Packet Filter (BPF) 字节码。给定原始的 BPF 指令——这些指令可能来自恶意软件样本、数据包捕获过滤器或网络设备——filterforge 将其反汇编为人类可读的形式,使用 Z3 求解满足接受路径的数据包约束,然后利用 Scapy 构建一个具体的网络数据包,使其能够通过该过滤器。
简而言之:向其提供一个 BPF 程序,它将返回一个完整的数据包(Ethernet、IPv4/IPv6、TCP/UDP/SCTP 等),该数据包可被过滤器接受——随时可以检查、保存为 PCAP 或在网络上发送。
建议安装 uv 来运行此工具。
请参考下面的示例来了解该工具的用法,随着时间的推移可能会增加更多功能。
要快速获取给定 BPF 的结果,最简单的方法是使用命令行。由于某些逆向工程工具返回数据的方式不同,您可以提供原始十六进制字符串或十六进制值数组,使用 -d 标志仅显示给定 BPF 的反汇编:
# 十六进制字符串
uv run ff -d -b "280000000c00000015002900060800001500000bdd860000300000001400000015000027110000002800000038000000150024003500000015002300e91400001500220043000000150021009c690000150020006c07000015001f0089000000280000003600000015001d0fe91400001500001d0008000030000000170000001500000f11000000280000001400000045001900ff1f0000b10000000e0000004800000010000000150015003500000015001400e91400001500130043000000150012009c690000150011006c0700001500100089000000480000000e00000015000e00e914000015000d009c69000015000c006c07000015000b0c890000001500000b06000000280000001400000045000900ff1f0000b10000000e000000500000001a000000740000000400000024000000040000000c000000000000000700000000000000400000000e000000150000012054454706000000000004000600000000000000"
# 十六进制值数组
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]"
要了解根据给定的 BPF 字节码构建的网络数据包的大致外观,可以使用 -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'
某些 BPF 可能使用原始 IP 层,为此你可以指定链路类型 -l raw(默认为 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'
为了以更可编程的方式使用该工具,我们创建了一个相对易于使用的 API,可以以多种不同方式实现,参考以下示例以获取灵感:
from ff import BPFSolver
# 使用 BPF 字节码创建求解器
solver = BPFSolver("280000000c00000015001b00...")
# 求解并构建数据包
result = solver.solve()
if result:
result.hexdump()
result.show()
print(result.summary())
from ff import BPFSolver
solver = BPFSolver("280000000c00000015001b00...")
# 获取人类可读的反汇编
print(solver.disassemble())
# 访问解析后的指令
print(f"指令数量: {solver.instruction_count}")
for insn in solver.instructions:
print(insn)
from ff import BPFSolver, PacketOverrides
solver = BPFSolver("280000000c00000015001b00...")
# 覆盖特定的数据包字段
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:
# 保存为 PCAP 文件
solver.to_pcap("output.pcap")
# 或保存特定结果
solver.to_pcap("output.pcap", result=result)
from ff import BPFSolver
# 使用 link_type="raw" 用于 DLT_RAW(无 Ethernet 头部)
solver = BPFSolver("280000000c00000015001b00...", link_type="raw")
result = solver.solve()
from ff import BPFSolver
solver = BPFSolver("280000000c00000015001b00...")
# 查找所有导致数据包被接受的路径
paths = solver.find_paths()
print(f"找到 {len(paths)} 条接受路径")
for i, path in enumerate(paths):
print(f"路径 {i+1}: {path}")
from ff import BPFSolver
solver = BPFSolver("280000000c00000015001b00...")
result = solver.solve()
if result:
# 原始字节
raw_bytes = result.raw
# Scapy 数据包对象,用于高级操作
scapy_pkt = result.scapy_packet
scapy_pkt[0].dst = "192.168.1.1" # 直接修改
# 执行路径
print(f"路径: {result.path}")
print(f"链路类型: {result.link_type}")
from ff import BPFSolver
solver = BPFSolver("280000000c00000015001b00...")
result = solver.solve()
if result:
# 发送数据包(需要 root/sudo 权限)
solver.send(iface="eth0", count=1)
# 或发送特定结果
solver.send(result=result, iface="eth0")
有时 BPF 没有我们期望的 2 条 ret 指令(1 条用于接受数据包,1 条用于丢弃数据包)。以下面的 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)
我们可以看到这里只有 1 条预期的 ret 指令,导致我们的 Z3 求解器失败;
2025-12-16 12:09:31,123 - ERROR - No satisfiable paths found, failed to craft packet
我们可以通过观察 BPF 正在做什么来添加自己的指令以实现成功跳转。在上面的片段中,我们可以看到有很多与端口号相关的条件,每个条件要么检查下一个条件,要么跳转到指令 43,由于我们反汇编 BPF 的方式,这个指令被我们标记为 DROP。在这种情况下,这不是一个确切的 DROP,而是一个有效的返回语句。这意味着如果我们添加自己的虚拟成功 ret 指令,它将正确解析,例如:
ret:06 00 00 00 00 00 00 0006 00 00 00 ff ff 00 00 06 00 00 00 00 00 00 00结果如下:
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)
如果我们现在运行 -c 来构建数据包,将会得到一个全新的 magic 数据包:
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'
要在本地运行测试:
uv run --extra dev pytest tests/ -v