标准bpf过滤报文 1

本文将以问题驱动,算是一篇trouble shooting,以解决问题为导向,故不会所有方面都提及。主要讲解我对问题的了解、思考和在实际场景中的工程化解决方法。更深层次的bpf语句使用可能会在下一篇(如果我能研究透彻的话~)

0x00 问题背景

​ 去年在suricata中实现了报文的过滤,主要的方式就是在驱动层获取报文后利用libpcap中的几个接口,以标准bpf的形式对报文进行匹配过滤,类似于很多防火墙提供的功能。

​ 今年收到反馈的问题,在某客户环境下无法完成过滤,我远程操作后发现手动在引擎播放攻击流量可以完成过滤,但是在外网实时进行模拟攻击却无法过滤。

​ 产品bpf过滤功能有两种模式,一是简易模式,用户可以配置源目的IP、port,tcp、udp、any协议,vlan id;二是专业模式,完全由用户编写bpf语句。

下面的报文均为测试构造的报文

0x01 标准bpf的使用

标准bpf语法,标准bpf语法和wireshark的filter栏中语法不同,前者是后者的子集。wireshark算是将协议变量都解析出来再提供比标准bpf更丰富的搜索,标准bpf更多还是通过偏移来完成过滤。

常用的关键字(原语)

1
2
3
4
type限定符:host、net、port、vlan等。 例如:host 1.1.1.1、net 10.0.0.0/8、port 8080、vlan 1000
dir限定符:src、dst。 例如:src host 2.2.2.2、dst port 443
proto限定符:ip、icmp、tcp、udp等。 例如:ip proto 17、tcp port 21
逻辑运算符:and、or、!(非)。例如:(src host 1.2.3.4 and dst net 192.168.0.0/16) or (!port 80)

BPF Syntax

0x02 基本报文过滤

​ 下面我们主要用tcpdump和对应的报文来完成bpf的测试。-r读取报文,同时要判断bpf解析对不对我们会用到-d选项,来查看字节码。

先给个示例介绍基本分析方式,测试报文:test_bpf.pcap

过滤源IP:10.51.15.205,目的IP:222.186.160.66

  • tcpdump -r test_bpf.pcap "ip src host 10.51.15.205 and ip dst host 222.186.160.66"

能看到使用这条bpf语句能过滤出330帧报文,下面我们用-d选项查看字节码

  • tcpdump -d "ip src host 10.51.15.205 and ip dst host 222.186.160.66"
1
2
3
4
5
6
7
8
(000) ldh      [12]
(001) jeq #0x800 jt 2 jf 7
(002) ld [26]
(003) jeq #0xa330fcd jt 4 jf 7
(004) ld [30]
(005) jeq #0xdebaa042 jt 6 jf 7
(006) ret #262144
(007) ret #0

​ 通过字节码能够看见bpf经过解释后生成的逻辑语句,帮助我们进一步判断自己的bpf是否编写正确。

​ 偏移默认从ether协议开始。ldh [12]是从ether offset 12读取两个字节,jeq0x800比较(报文是否为IP协议),相同则跳转到2步骤,不同则跳转到7步骤。如果是IP协议,进入2步骤ld [26],在ether offset 26读取一个字(IP src host)同10.51.15.205的16进制数比较,其后同理。可看到我们的bpf是合理的,所以能够过滤对应的报文。

字节码中的部分关键字含义 [所有关键字资料]

1
2
3
4
5
6
7
ldb(load byte)读取1字节
ldh(load half word)读取2字节
ld(load)读取一个字,即4字节
jeq(judge equal)判断是否相等
jt(jump true)真则跳转到
jf(jump false)假则跳转到
ret(return)返回

0x03 还原问题情景

​ 由于在引擎上对报文重放能够过滤,而在外网对客户模拟渗透无法达到过滤效果,就决定在客户现场进行实时抓包,拿到报文之后再进行报文分析。

0x31 vlan作祟

测试报文:single_vlan.pcap(模拟用,非用户现场报文)

​ 拿到报文之后就发现了报文中存在vlan,这在用户环境很常见。我们也支持vlan配置,不过每次都需要配置vlan id,这种必须要用户对自己环境下的流量进行分析后才能编写bpf语句。

​ 分析完用户报文之后,在web页面配置上vlan id下发bpf语句到引擎,每条独立的bpf语句用or来进行连接,保证过滤逻辑。生成的bpf语句如下:

((vlan 2000) and (ip src host 10.216.143.250)) or ((vlan 2000) and (ip src host 101.35.124.69))

再来看一下我们测试报文,一共15个分组

尝试过滤

发现仅过滤出13个报文,抓到了所有250->69的包,但缺少了两个69->250的包,可以判断在现场也是出现了这样的问题,下面分析字节码:

tcpdump -d "((vlan 2000) and (ip src host 10.216.143.250)) or ((vlan 2000) and (ip src host 101.35.124.69))"

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
(000) ldb      [-4048]
(001) jeq #0x1 jt 2 jf 8 // 判断vlan
(002) ldb [-4052]
(003) jeq #0x7d0 jt 4 jf 8 // 判断vlan id是否为2000
(004) ldh [12]
(005) jeq #0x800 jt 6 jf 8 // 判断是否为IP协议
(006) ld [26]
(007) jeq #0xad88ffa jt 18 jf 19 // host判断完直接结束??!!
(008) ldh [12]
(009) jeq #0x8100 jt 11 jf 10 // 判断vlan
(010) jeq #0x9100 jt 11 jf 19 // 判断vlan
(011) ldh [14]
(012) and #0xfff
(013) jeq #0x7d0 jt 14 jf 19 // 判断vlan id是否为2000
(014) ldh [16]
(015) jeq #0x800 jt 16 jf 19 // IP协议
(016) ld [30]
(017) jeq #0x65237c45 jt 18 jf 19 // src host判断
(018) ret #262144
(019) ret #0

从字节码中可以看到,在第一段对于src host的判断中在最终的IP判断后直接跳转结束。这样我们第二段的bpf永远也走不到。经过尝试,调换一二段bpf顺序,靠前的bpf语句段会被过滤。但为什么会这样?

0x32 括号怎么不起作用

​ 从逻辑上看,我们下发的bpf语句使用or来连接,是准确无误的,在各种编程语言中都能正确地表达含义,但这里最终效果却表明bpf的语义分析似乎不同。于是我的怀疑点转到了括号的优先级上。

下面的bpf测试用例用于判断括号是否无效

  • tcpdump -d "ip src host 1.1.1.1 and ip dst host 2.2.2.2 or ip dst host 3.3.3.3"
1
2
3
4
5
6
7
8
9
10
(000) ldh      [12]
(001) jeq #0x800 jt 2 jf 9
(002) ld [26]
(003) jeq #0x1010101 jt 4 jf 6
(004) ld [30]
(005) jeq #0x2020202 jt 8 jf 6
(006) ld [30]
(007) jeq #0x3030303 jt 8 jf 9
(008) ret #262144
(009) ret #0
  • tcpdump -d "ip src host 1.1.1.1 and (ip dst host 2.2.2.2 or ip src host 3.3.3.3)"
1
2
3
4
5
6
7
8
9
(000) ldh      [12]
(001) jeq #0x800 jt 2 jf 8
(002) ld [26]
(003) jeq #0x1010101 jt 4 jf 8
(004) ld [30]
(005) jeq #0x2020202 jt 7 jf 6
(006) jeq #0x3030303 jt 7 jf 8
(007) ret #262144
(008) ret #0
  • tcpdump -d "ip src host 1.1.1.1 and ip dst 2.2.2.2 or ip src host 1.1.1.1 and ip dst host 3.3.3.3"
1
2
3
4
5
6
7
8
9
(000) ldh      [12]
(001) jeq #0x800 jt 2 jf 8
(002) ld [26]
(003) jeq #0x1010101 jt 4 jf 8 // 1.1.1.1被合并了
(004) ld [30]
(005) jeq #0x2020202 jt 8 jf 6
(006) jeq #0x3030303 jt 7 jf 8
(007) ret #262144
(008) ret #0

从上面的测试用例看,普通情况下括号并没有失效,而且bpf语义分析后会对相同部分进行简单的合并

0x33 深入探索

这时候我们减去部分多余括号,带入vlan关键字再次进行测试

  • tcpdump -d "(vlan and ip src host 10.216.143.250) or (vlan and ip src host 101.35.124.69)"
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
(000) ldb      [-4048]
(001) jeq #0x1 jt 2 jf 6
(002) ldh [12]
(003) jeq #0x800 jt 4 jf 6
(004) ld [26]
(005) jeq #0xad88ffa jt 13 jf 14
(006) ldh [12]
(007) jeq #0x8100 jt 9 jf 8
(008) jeq #0x9100 jt 9 jf 14
(009) ldh [16]
(010) jeq #0x800 jt 11 jf 14
(011) ld [30]
(012) jeq #0x65237c45 jt 13 jf 14
(013) ret #262144
(014) ret #0

​ 结果还是没变,这种情况下依然没有达到想要的效果,碰到vlan关键字我们的bpf语句都不对劲起来。又经过一番长时间的资料搜索,发现了两篇很有帮助的文章

[1] Mixed VLAN tags and BPF syntax

[2] VLAN BPF filter

​ 尤其第二篇,和我碰到的问题几乎相同,它里面给出的解法是自行编写bpf以vlan头来过滤,(ether[12:2]==0x8100) and (ether[14:2]&4095==7 or ether[14:2]&4095==10)。但这样给用户的操作难度太大,对bpf没有研究基本写不出来而且很容易写错。

0x34 再次尝试

考虑到前面所有的因素,发现一个规律,当vlan存在时,在一旦bpf过滤开始进入一个and段中并匹配上第一个关键字即vlan,那么就会不断匹配下去而不能进入后续的and段,其中有哪一特征不相同就会判断为过滤失败。此时bpf不会进行语义合并操作。

1
(vlan and B and D) or (vlan and A and C)

只要报文匹配上了vlan那么后面只会匹配B再匹配D,第二段不会再进入。

那么我们面对vlan这种情况,想到的办法就是用户操作时在下发至引擎的BPF语句直接合并,即:

1
(vlan and B and D) or (vlan and A and C)

合并为

1
(vlan) and ((B and D) or (A and C))
  • tcpdump -d "(vlan 2000) and ((ip src host 10.216.143.250) or (ip src host 101.35.124.69))"
1
2
3
4
5
6
7
8
9
10
11
(000) ldb      [-4048]
(001) jeq #0x1 jt 2 jf 10
(002) ldb [-4052]
(003) jeq #0x7d0 jt 4 jf 10
(004) ldh [12]
(005) jeq #0x800 jt 6 jf 10
(006) ld [26]
(007) jeq #0xad88ffa jt 9 jf 8
(008) jeq #0x65237c45 jt 9 jf 10
(009) ret #262144
(010) ret #0

用此方法最终解决了我们当前的问题

0x04 解决方法

​ 当前问题虽然解决了,但是如果给用户配置vlan id的机会,那么我们在后端合并操作的工作量就会变得大了而且没有必要。这时候考虑到开发量bpf学习成本以及客户可用性,我们将简易模式中的vlan id修改为下拉框的形式,只能选择0、1、2三层vlan,向用户隐藏vlan id,仅提供层数选择。这样既方便后端按照0、1、2三层vlan对bpf语句进行合并,对用户来说也减小了学习难度提升了易用性。

1
2
3
(ip src host 1.1.1.1) or // 0层
((vlan) and (ip src host 2.2.2.2)) or // 1层
((vlan and vlan) and (ip src host 3.3.3.3)) // 2层

0x05 总结

  1. bpf是单包过滤,想要对流进行过滤就只能使用ip host x.x.x.x的形式处理,不能使用srcdst两个关键字
  2. 字节码里怎么区分src和dst?按照偏移量来区分
  3. vlan这块有些和常理不符,编写带有vlan的bpf时需要经过分析和测试
  4. bpf这块的知识很深,时间紧任务重的情况下,我没有选择阅读源码,但也花费大量时间了解相关资料,才解决了问题,朋友们有空可以阅读这块的源码
  5. 提升产品可用性,但保留专业性

0x06 参考资料

[1] BPF and tcpdump(useful)

[2] EtherType

[3] BPF内部原理

[4] tell tcpdump to filter mixed tagged and untagged VLAN (IEEE 802.1Q) traffic

[5] bpf-the-forgotten-bytecode

[6] 透過 eBPF 觀察作業系統行為

[7] 深入理解 BPF:一个阅读清单

[8] Dive into BPF: a list of reading material

[9] BPF for IP or VLAN Traffic

[10] Understanding Tcpdump’s -d Option, Part 2

[11] BPF Internals - I


本博客所有文章除特别声明外,均采用 CC BY-SA 4.0 协议 ,转载请注明出处!