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
用来填充首部, 保证首部长度时 32bit 的整数倍.
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.
链路层封装
链路层封装, 通过抓包方式分析 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, 'tcp[12:1] & 0xf0 >> 4' 得到的结果是 1000, 对应 TCP 首部中的 Data offset 4bit 信息, 即可以得到 TCP 首部长度, 4位信息是表示 32 bit 的数目, 换成字节的话应该是 1000 << 2, 所以'tcp[12:1] & 0xf0) >> 4' 等同 TCP 报文中数据的起始位置, 最后要抓取 HTTP GET请求的规则应该是:
# tcpdump -i eth1 'port 80 and tcp[((tcp[12:1] & 0xf0) >> 4):4] = 0x47455420'
UDP 首部
UDP 过滤规则相对简单:
udp[0:2] source port udp[2:2] destination port udp[4:2] datagram length udp[6:2] UDP checksum
更多规则可参考 http://www.packetlevel.ch/html/tcpdumpf.html
参考
http://tools.ietf.org/html/rfc791#section-3.1
http://tools.ietf.org/html/rfc768
http://tools.ietf.org/html/rfc793
http://tools.ietf.org/html/rfc3540
https://wiki.wireshark.org/CaptureFilters
http://www.packetlevel.ch/html/tcpdumpf.html
http://www.wains.be/pub/networking/tcpdump_advanced_filters.txt