TCPDUMP 高级规则使用
概述
在了解 tcpdump 的高级规则之前, 需要对 IP, TCP 和 UDP 的报文首部有大致的了解, 实际上很多网络工具的使用都是基于报文首部的结构做相应的操作. 在了解报文结构后, 也可以按需实现私有的功能, 比如抓取匹配的请求, 再做相应的处理, Snapper 就是根据 TCP 首部信息实现的一个简单的 DoS 防御工具, 详见 https://github.com/vr000m/Snapper, 依次推论, 可以实现更细致的功能, 比如 HTTP 请求过滤, SQL 白名单等; 后续部分则参考一些链接对一些规则进行详细说明.
协议首部
IP 首部
IP header: http://tools.ietf.org/html/rfc791#section-3.1
0 1 2 3
0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
|Version| IHL |Type of Service| Total Length |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
| Identification |Flags| Fragment Offset |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
| Time to Live | Protocol | Header Checksum |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
| Source Address |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
| Destination Address |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
| Options | Padding |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
Example Internet Datagram Header
ip 首部中每行 4 个字节的 32 bit 值以大端(big endian)字节序传输, 先是 0~7 bit, 其次 8~15 最后 24~31 bit, TCP/IP 首部中所有的二进制整数在网络中都以这种次序传输, 所以 big endian 又称网络字节序, 一些机器采用小端(little endian)传输, 则在网络传输前需要把首部换成网络字节序.
IHL: 4bit
IP 首部长度(Internet Header Length), 表示 32 bit 字的数目, 4 bit 最大为 15(1111), 所有首部长度最长为 15*32/8 = 60 byte, 值得注意的是 IHL 的最小值应该是 5 byte(出去选项和数据).
Type of service: 8 bit
服务类型指定了服务质量需要的一些参数; 这些参数用来指示服务在网络传输中需要的一些实际参数, 比如一些服务需要需要更稳定的传输, 一些服务不能有较大的延迟等,都可以通过这些参数预先声明.
Total Length: 16 bits
数据报的总长度. 它包含了首部长度(IHL)和数据长度, 单位为字节. 16 bit 可以允许最大传输 65535 字节.
Identification: 16 bits
标识字段唯一的标识主机发送的每一分数据报, 通常每发送一个报文其值就加 1.
Flags: 3 bits
用于各种控制的标记符号, Bit 0 是保留的, 必须为 0, Bit 1 标识是否有碎片(DF), Bit 2 标识是否为最后一个碎片(MF).
Fragment Offset: 13 bits
碎片的偏移位置. 单位为 8 字节(64 bits), 第一个碎片的偏移为 0.
Time to Live: 8 bits
生存时间(TTL), 设置了数据报可以经过的最多路由器数, 源主机初始设置通常为 32 或 64, 由内核参数 net.ipv4.ip_default_ttl 指定. 每经过一个路由器处理就减去 1, 当该字段的值为 0时,数据报就被丢弃,并发送 ICMP 报文通知源主机.
Protocol: 8 bits
标识数据报中数据部分的协议, 比如TCP, UDP ICMP 等.
Header Checksum: 16 bits
IP首部的校验和, 一些字段的内容可能会改变(TTL等), 校验和用来重新计算并验证网络传输过程中的每端的 header. 校验和的算法是: 首先将校验和的字段置为 0, 再对首部中每个 16 bit (16 位为一组) 进行二进制反码求和, 结果存在该字段中.
Source Address: 32 bits
来源 ip 地址.
Destination Address: 32 bits
目的 ip 地址
Options: variable
可选项.数据报中的可变长可选信息, 比如可以定义安全, 记录路径, 网络时间戳等.
Padding: variable
用来填充首部, 保证首部长度是 32 bit 的整数倍.
UDP 首部
http://tools.ietf.org/html/rfc768
udp 数据报封装成 ip 数据报格式:
+------------+------------+-----------+
| IP header | UDP header | UDP data |
+------------+------------+-----------+
20 bytes 8 bytes
udp header 格式:
0 7 8 15 16 23 24 31
+--------+--------+--------+--------+
| Source | Destination |
| Port | Port |
+--------+--------+--------+--------+
| | |
| Length | Checksum |
+--------+--------+--------+--------+
|
| data octets ...
+---------------- ...
User Datagram Header Format
Source Port: 16 bits
Destination Port: 16 bits
源端口是可选的, 可以用来表示发送进程, 没有源端口就用 0 表示; 目的端口表示接收进程, TCP 端口和 UDP 端口是相互独立的.
Length: 16 bits
表示 UDP 首部和 UDP 数据的字节长度, 最小值为 8 字节(首部是 8 bytes). 可以为奇数字节.
Checksum: 16 bits
校验和覆盖 UDP 伪首部(pseudo), UDP 首部和 UDP 数据. 为了计算校验和, UDP 和 TCP 都包含 12 字节的伪首部, 伪首部包含 IP 首部的一些字段, 目的是为了保证数据的正确传输. 校验方法和 IP 首部类似, 不过由于 UDP 长度可以为奇数字节, 必要的时候在最后增加填充字节. 校验和包含的结构如下:
0 7 8 15 16 23 24 31
+--------+--------+--------+--------+ ------
| source address |
+--------+--------+--------+--------+
| destination address | pseudo Header
+--------+--------+--------+--------+
| zero |protocol| UDP length |
+--------+--------+--------+--------+ ------
| Source | Destination |
| Port | Port |
+--------+--------+--------+--------+ UDP Header
| | |
| Length | Checksum |
+--------+--------+--------+--------+ ------
|
| data octets ... (padding)
+---------------- ...
TCP 首部
http://tools.ietf.org/html/rfc793 http://tools.ietf.org/html/rfc3540
tcp 数据报封装成 ip 数据报格式:
+---------------+-------------+------------+
| IP header | TCP header | TCP data |
+---------------+-------------+------------+
20 bytes 20 bytes
tcp 首部格式:
0 1 2 3
0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
| Source Port | Destination Port |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
| Sequence Number |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
| Acknowledgment Number |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
| Data | |N|C|E|U|A|P|R|S|F| |
| Offset| Rsvd|S|W|C|R|C|S|S|Y|I| Window |
| | | |R|E|G|K|H|T|N|N| |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
| Checksum | Urgent Pointer |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
| Options | Padding |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
| data |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
TCP Header Format
Source Port: 16 bits
Destination Port: 16 bits
每个 tcp 数据报都包含来源和目的端口号, 用来寻找发送和接收的应用进程, 这两个信息和 IP 首部中的来源和目的 IP 唯一确定一个 TCP 连接.
Sequence Number: 32 bits
序号用来表示 TCP 发端向手段松松的数据字节流, 它用来表示该 TCP 报文中的第一个数据字节. 在 SYN 为 1 的情况下序号为 ISN + 1, ISN为连接的初始序号. 序号到达 2^32 - 1 后从 0 开始.
Acknowledgment Number: 32 bits
当 ACK 控制位为 1 时, 确认序号包含发送确认的一段所期望收到的下一个序号.
Data Offset: 4 bits
表示首部中 32 bit 的数量, 需要这个字段是因为 Options 字段是可变的.
Reserved: 3 bits
早期版本是 6 bit, 保留位以待将来使用, 必须都置为 0.
Control Bits: 6 bits (from left to right)
ECN: Explicit Congestion Notification. 3 bits. (NS, CWR, ECE)
URG: Urgent Pointer field significant
ACK: Acknowledgment field significant
PSH: Push Function
RST: Reset the connection
SYN: Synchronize sequence numbers
FIN: No more data from sender
Window: 16 bits
通过声明窗口大小(单位字节)来进行 TCP 流量控制.
Checksum: 16 bits
类似 UDP, 校验和覆盖了 IP 伪首部, TCP 首部和 TCP 数据.由发端计算并存储, 由接收端进行验证, 伪首部结构如下:
+--------+--------+--------+--------+
| Source Address |
+--------+--------+--------+--------+
| Destination Address |
+--------+--------+--------+--------+
| zero | PTCL | TCP Length |
+--------+--------+--------+--------+
Urgent Pointer: 16 bits
当 URG 标志为 1 的时候该字段才有效. 紧急指针是一个正的偏移量, 和序号字段中的值相加表示紧急数据最后一个字节的序号.
Options: variable
可选项, 最常见的可选字段是最长报文大小(MSS, Maximum Segment Size).指明本端所能接收的最大长度报文段. 常见的包含:
Kind Length Meaning
---- ------ -------
0 - End of option list.
1 - No-Operation.
2 4 Maximum Segment Size.
Padding: variable
padding 用于对 tcp 首部进行填充0以保证首部大小是 32 bit 的整数倍.
链路层
链路层封装, 通过抓包方式分析 IP/TCP 数据的时候, 可以跳过链路层的 14 字节, 直接分析 IP 数据报文.
+--------+---------+-------------+---------------+--------+
| DST | SRC | Ether type | Data | CRC |
+--------+---------+-------------+---------------+--------+
6bytes 6bytes 2bytes (46~1500)bytes 4bytes
DST: 目标 MAC 地址; SRC: 来源 MAC 地址; Ether type: 定义后续数据的类型; CRC: 帧内后续数据的循环冗余校验;
TCPDUMP 使用
基础语法
了解上面的结构后, 看看 tcpdump 的高级用法:
https://wiki.wireshark.org/CaptureFilters http://www.packetlevel.ch/html/tcpdumpf.html http://www.wains.be/pub/networking/tcpdump_advanced_filters.txt
基本使用可参考上述链接的示例;
tcpdump 支持的表达式:
否 : ! 或 not
与 : && 或 and
或 : || 或 or
多个条件可以使用括号包起来, 比如:
# tcpdump -i eth1 '(dst host 192.168.1.1 and port 80)'
协议 header 过滤规则:
proto[x:y] : 从第 x 个位置开始取 y 字节, ip[2:2] 表示取 IP 首部中的第 3,4 字节(x 从 0 开始计算);
proto[x:y] & z = 0 : proto[x:y] 的结果和 z 进行 与 运算, 匹配最终结果为 0的报文;
操作符包括: >, <, >=, <=, =, !=
IP 规则
结合 IP 首部的信息看看下面的示例, ip[0] 即表示 IP 首部第一个字节, 一共 8 bit, Version 为 0100 即表示 IPv4, IHL 最小的长度应该是 IP 首部的长度 20 字节, 也就是 5 * 32 bits, 所以 IHL 最小值应该是 0101, 01000101 的十进制就是 69. 如果我们要抓取 IP 首部中有 Options 的数据报, 只需要使用一次规则就可以:
# tcpdump -i eth1 'ip[0] > 69'
上面还可以用位操作符的方式进行抓取, 与操作:
0100 0101
0000 1111 (十六进制 0xf, 十进制 15)
==========
0000 0101 (十进制 5)
所以使用以下规则也能满足条件:
# tcpdump -i eth1 'ip[0] & 15 > 5'
# tcpdump -i eth1 'ip[0] & 0xf > 5'
0xf – 0000 1111 0xf0 – 1111 0000
如果想知道数据报中是否有碎片, 可以抓取 IP 首部中的 Flags 字段, bit 0 必须为 0, bit 1 为 0 表示可能有碎片, bit 2 为 0 表示最后一个碎片. 所以如果我们可以使用规则匹配:
# tcpdump -i eth1 'ip[6] = 64' # 没有碎片
# tcpdump -i eth1 'ip[6] = 32' # 有碎片, 但不匹配最后的碎片
# tcpdump -i eth1 '((ip[6:2] > 0) and (not ip[6] = 64))' # 匹配最后的碎片
另外想测试碎片信息可以使用 ping 命令检测:
# ping -M want -s 3000 192.168.1.1
ip[2:2] 对应 IP 首部的 total length, 比如以下可以抓取大于 600 字节的数据报:
# tcpdump -i eth1 'ip[2:2] > 600'
ip[8] 对应 IP 首部的 TTL, 如果我们机器的网络访问在 5 跳以内, 可以通过以下规则抓取本网内的数据:
# tcpdump -i eth1 'ip[8] < 5'
TCP 规则
同理, 在 TCP header 中, tcp[0:2] 即表示来源端口, tcp[2:2] 表示目的端口, 如果要抓取一个范围内的端口, 可以使用以下规则:
# tcpdump -i eth1 '(tcp[0:2] > 1500 and tcp[0:2] < 1550)'
上述等同 tcpdump -i eth1 ‘tcp portrange 1501-1549’ tcp 三次握手的过程中:
1. 发端发送 SYN;
2. 接收端回应 SYN, ACK;
3. 发送端发送 ACK;
如果只抓取 SYN 报文, 则对应控制位 00000010, 对应十进制 2, 想抓取 SYN + ACK, 则对应 00010010, 对应十进制 18, 如果要匹配 SYN 或 SYN + ACK 报文, 以按位运算应该是:
0001 0010
0000 0010
=========
0000 0010
整个匹配规则可以写成:
# tcpdump -i eth1 'tcp[13] = 2'
# tcpdump -i eth1 'tcp[13] = 18'
# tcpdump -i eth1 'tcp[13] & 2 = 2'
类似的, 要抓取 FIN 报文, 使用规则 ‘tcp[13] & 1 = 1’, 抓取 RST 复位报文, 使用规则 ‘tcp[13] & 4 = 4’, tcpdump 本身也支持标记过滤, ‘tcp[tcpflags] == tcp-ack’.
如果要抓取数据相关的报文, 比如 HTTP 的 “GET “ 请求, 则需要在 TCP 首部中找到数据部分, 再匹配 ‘G’, ‘E’, ‘T’ 和 ‘ ‘, 分别对应十六进制 0x47455420, 以如下的数据报文为例说明:
Internet Protocol Version 4, Src: 10.0.21.5 (10.0.21.5), Dst: 10.0.21.17 (10.0.21.17)
Transmission Control Protocol, Src Port: 62293 (62293), Dst Port: 80 (80), Seq: 1, Ack: 1, Len: 181
Source Port: 62293 (62293)
Destination Port: 80 (80)
[Stream index: 0]
[TCP Segment Len: 181]
Sequence number: 1 (relative sequence number)
[Next sequence number: 182 (relative sequence number)]
Acknowledgment number: 1 (relative ack number)
Header Length: 32 bytes
.... 0000 0001 1000 = Flags: 0x018 (PSH, ACK)
000. .... .... = Reserved: Not set
....
Hypertext Transfer Protocol
GET /rh6/x86_64/ HTTP/1.1\r\n
[Expert Info (Chat/Sequence): GET /rh6/x86_64/ HTTP/1.1\r\n]
Request Method: GET
Request URI: /rh6/x86_64/
Request Version: HTTP/1.1
...
[HTTP request 1/1]
[Response in frame: 5]
0000 f3 55 00 50 98 8d d6 b7 0a d8 0e a6 80 18 00 3a
0010 e7 79 00 00 01 01 08 0a 4a ce 26 a6 ea 24 8f f1
‘tcp[12:1]’ 即为第13个字节对应报文里的80, ‘tcp[12:1] & 0xf0 » 4’ 得到的结果是 1000, 对应 TCP 首部中的 Data offset 4bit 信息, 即可以得到 TCP 首部长度, 4位信息是表示 32 bit(4个字节) 的数目, 换成字节的话应该是 1000 « 2, 所以’tcp[12:1] & 0xf0) » 2’ 等同 TCP 报文中数据的起始位置, 最后要抓取 HTTP GET请求的规则应该是:
# tcpdump -i eth1 'port 80 and tcp[((tcp[12:1] & 0xf0) >> 2):4] = 0x47455420'
UDP 首部
UDP 过滤规则相对简单:
udp[0:2] source port
udp[2:2] destination port
udp[4:2] datagram length
udp[6:2] UDP checksum
ipv6 使用
tcpdump 默认支持 ipv6 报文的抓取, 可以通过 wireshark 分析抓取后的结果:
# 抓取所有目的端口 6380 的报文, 包含 ipv4 和 ipv6
tcpdump -nn -s 0 -S port 6380
# 抓取指定网卡上所有ipv6 的 tcp 报文
tcpdump -nn -s 0 -S -i em2 ip6 proto 6
# 仅抓取 ipv6 报文
tcpdump -nn -s 0 -S ip6 and not ip proto 41
# 抓取指定目的 ipv6 和端口的报文
tcpdump -S -s 0 -nn -i any dst fe80::1a03:73ff:fef5:f0 and port 6380 -w icmp.pcap -U
高级规则使用需要单独计算报文头部的信息, 不过使用具体协议则可以忽略这些计算, ipv4 下使用 ip, tcp, udp 规则进行计算, ipv6 下使用 ip6 进行计算, 不能使用 tcp, udp 规则, 比如下面
# 抓取所有 Next Header 为 tcp 的 ipv6 报文
tcpdump -nn -s 0 -i any '(ip6[6]&0x0f = 6)'
# 下面的条件永远为假, tcp 规则仅用于 ipv4
tcpdump -nn -s 0 -i any '((ip6[6]&0x0f = 6) and (tcp[2:2] = 6380))'
更多规则可参考 tcpdumpf
参考
rfc791#section-3.1
rfc768
frc793
rfc3540
CaptureFilters
tcpdumpf
tcpdump_advanced_filters