0%

tcpdump 用法

tcpdump 是一个命令行的网络流量分析工具,功能非常强大,一般我们用来抓 TCP 的包。

非常重要:如果 tcpdump 没有输出,尝试加上 -i any 选项。

tcpdump 核心参数图解

tcpdump 参数的组成部分: 1. option 可选参数 2. proto 类过滤器:根据协议进行过滤,可识别的关键词有:tcp, udp 等 3. type 类过滤器:可识别的关键词有:host, net, port, portrange 4. direction 类过滤器:根据数据流向进行过滤,可识别的关键词有:src, dst,你可以使用逻辑运算符进行组合,如 src or dst

理解 tcpdump 的输出

输出内容结构

1
21:26:49.013621 IP 172.20.20.1.15605 > 172.20.20.2.5920: Flags [P.], seq 49:97, ack 106048, win 4723, length 48
  • 第一列:时分秒毫秒 21:26:49.013621
  • 第二列:网络协议 IP
  • 第三列:发送方的 ip地址+端口号,其中 172.20.20.1 是 ip,而 15605 是端口号
  • 第四列:箭头 >,表示数据流向
  • 第五列:接收方的 ip地址+端口号,其中 172.20.20.2 是 ip,而 5920 是端口号
  • 第六列:冒号
  • 第七列: 数据包内容,包括 Flags 标识符,seq 号,ack 号,win 窗口,数据长度,其中 [P.] 表示 PUSH 标志位为 1

Flags 标识符

  • [S]:SYN(开始连接)
  • [P]:PSH(推送数据)
  • [F]:FIN(结束连接)
  • [R]:RST(重置连接)
  • [.]:没有 Flag(意思是除上面四种类型外的其他情况,有可能是 ACK 也有可能是 URG)

常规过滤规则

基于 IP 地址过滤:host

使用 host 就可以指定 host ip 进行过滤:

1
tcpdump host 192.168.10.100

数据包的 ip 可以再细分为源 ip 和目标 ip 两种:

1
2
3
4
# 根据源 ip 进行过滤
tcpdump -i eth2 src 192.168.10.100
# 根据目标 ip 进行过滤
tdpcump -i eth2 dst 192.168.10.200

基于网段进行过滤:net

若你的 ip 范围是一个网段,可以直接这样指定:

1
tcpdump net 192.168.10.0/24

网段统一可以再细分为源网段和目标网段:

1
2
3
4
# 根据源网段进行过滤
tcpdump src net 192.168
# 根据目标网段进行过滤
tcpdump dst net 192.168

基于端口进行过滤:port

使用 port 就可以指定特定端口进行过滤:

1
tcpdump port 8888

端口同样可以再细分为源端口,目标端口:

1
2
3
4
# 根据源端口进行过滤
tcpdump src port 8888
# 根据目标端口进行过滤
tcpdump dst port 8888

如果你想同时指定两个端口你可以这样写:

1
tcpdump port 80 or port 8888

但也可以简写成这样:

1
tcpdump port 80 or 8888

如果你想抓取的不再是一两个端口,而是一个范围,一个一个指定就非常麻烦了,此时你可以这样指定一个端口段:

1
2
3
tcpdump portrange 8000-8080
tcpdump src portrange 8000-8080
tcpdump dst portrange 8000-8080

对于一些常见协议的默认端口,我们还可以直接使用协议名,而不用具体的端口号:

比如 http == 80, https == 443 等。

1
tcpdump tcp port http

基于协议进行过滤:proto

常见的网络协议有 tcp,udp,icmp,http,ip,ipv6 等。

若你只想查看 icmp 的包,可以直接这样写:

1
tcpdump icmp

protocol 可选值:ip,ip6,arp,rarp,atalk,aarp,decnet,sca,lat,mopdl,moprc,iso,stp,ipx,or netbeui

基于IP协议版本进行过滤

当你想查看 tcp 的包,你也许会这样子写:

1
tcpdump tcp

这样子写也没问题,就是不够精准,为什么这么说呢?

ip 根据版本的不同,可以再细分为 IPv4 和 IPv6 两种,如果你只指定了 tcp,这两种其实都会包含在内。

那有什么办法,能够将 IPv4 和 IPv6 区分开来呢?

很简单,如果是 IPv4 的 tcp 包,就这样写(数字 6 表示的是 tcp 在 ip 报文中的编号。)

1
2
3
4
tcpdump 'ip proto tcp'
tcpdump ip proto 6
tcpdump 'ip protochain tcp'
tcpdump ip protochain 6

而如果是 IPv6 的 tcp 包,就这样写:

1
2
3
4
tcpdump 'ip6 proto tcp'
tcpdump ip6 proto 6
tcpdump 'ip6 protochain tcp'
tcpdump ip6 prorochain 6

关于上面这几个命令示例,有两点需要注意:

  1. 跟在 proto 和 protochain 后面的如果是 tcp, udp, icmp ,那么过滤器需要用引号包含,这是因为 tcp,udp, icmp 是 tcpdump 的关键字。
  2. 跟在ip 和 ip6 关键字后面的 proto 和 protochain 是两个新面孔,看起来用法类似,它们是否等价,又有什么区别呢?

proto 后面跟的 的关键词是固定的,只能是 ip, ip6, arp, rarp, atalk, aarp, decnet, sca, lat, mopdl, moprc, iso, stp, ipx, or netbeui 这里面的其中一个。

而 protochain 后面跟的 protocol 要求就没有那么严格,它可以是任意词,只要 tcpdump 的 IP 报文头部里的 protocol 字段为 就能匹配上。

理论上来讲,下面两种写法效果是一样的

1
2
tcpdump 'ip && tcp'
tcpdump 'ip proto tcp'

同样的,这两种写法也是一样的

1
2
tcpdump 'ip6 && tcp'
tcpdump 'ip6 proto tcp'

可选参数解析

设置不解析域名提升速度

  • -n 不把 ip 转化成域名,直接显示 ip,避免执行 DNS lookups 的过程,速度会快很多。
  • -nn 不把协议和端口号转化成名字,速度也会快很多
  • -N 不打印出 host 的域名部分。比如,如果设置了此选项,tcpdump 将会打印 'nic' 而不是 'nic.ddn.mil'

过滤结果输出到文件

使用 tcpdump 工具抓到包后,往往需要再借助其他的工具进行分析,比如常见的 wireshark。

而要使用 wireshark,我们得将 tcpdump 抓到的包数据生成到文件中,最后再使用 wireshark 打开它即可。

使用 -w 参数后接一个以 .pcap 后缀命名的文件名,就可以将 tcpdump 抓到的数据保存到文件中。(可以使用 wireshark 分析这个文件,也可以使用 tcpdump 读取)

1
tcpdump icmp -w icmp.pcap

从文件中读取包数据

使用 -w 是写入数据到文件,而使用 -r 是从文件中读取数据。

读取后,我们照样可以使用上述的过滤器语法进行过滤分析。

1
tcpdump -r all.pcap

读取之后过滤:

1
tcpdump -r all.pcap src host 192.168.2.11

控制详细内容的输出

  • -v 产生详细的输出。比如包的 TTL,id 标识,数据包长度,以及 IP 包的一些选项。同时它还会打开一些附加的包完整性检测,比如对 IP 和 ICMP 包头部的校验和。
  • -vv 产生比-v更详细的输出. 比如NFS回应包中的附加域将会被打印, SMB数据包也会被完全解码。
  • -vvv 产生比-vv更详细的输出。比如 telent 时所使用的SB, SE 选项将会被打印, 如果telnet同时使用的是图形界面,其相应的图形选项将会以16进制的方式打印出来

控制时间的显示

  • -t 在每行的输出中不输出时间
  • -tt 在每行的输出中会输出时间戳
  • -ttt 输出每两行打印的时间间隔(以毫秒为单位)
  • -tttt 在每行打印的时间戳之前添加日期的打印(此种选项,输出的时间最为直观)

显示数据包的头部

  • -x 以 16 进制的形式打印每个包的头部数据(但不包括数据链路层的头部)
  • -xx 以 16 进制的形式打印每个包的头部数据(包括数据链路层的头部)
  • -X 以 16 进制和 ASCII 码形式打印出每个包的数据(但不包括连接层的头部),这在分析一些新协议的数据包很方便。
  • -XX 以 16 进制和 ASCII 码形式打印出每个包的数据(包括连接层的头部),这在分析一些新协议的数据包很方便

过滤指定网卡的数据包

  • -i 指定要过滤的网卡接口,如果要查看所有网卡,可以 -i any

过滤特定流向的数据包

  • -Q 选择是入方向还是出方向的数据包,可选项有:in,out,inout。也可以使用 --direction=[direction] 这种写法

其他常用的一些参数

  • -A 以 ASCII 码方式显示每一个数据包(不显示链路层头部信息)。在抓取包含网页数据的数据包时,可方便查看数据。
  • -l 基于行的输出,便于你保存查看,或者交给其他工具分析
  • -q 简洁地打印输出。即打印很少的协议相关的信息,从而输出行都比较简短
  • -c 捕获 count 个包 tcpdump 就退出
  • -s tcpdump 默认只会截取前 96 字节的内容,要想截取所有的报文内容,可以使用 -s numbernumber 就是你要截取的报文子节数,如果是 0 的话,表示截取报文全部内容。
  • -S 使用绝对序列号,而不是相对序列号
  • -C file-size,tcpdump 在把原始数据包直接保存到文件中之前,检查此文件大小是否超过 file-size。如果超过了,将关闭文件,另创一个文件继续用于原始数据包的记录。新创建的文件名与 -w 选项指定的文件名一致,但文件名后多了一个* 数字。该数字会从 1 开始随着新创建文件的增多而增加。file-size 的单位是百万子节(nt:这里指1,000,000个字节,并非1,048,576个字节, 后者是以1024字节为1k, 1024k字节为1M计算所得, 即1M=1024 * 1024 = 1,048,576)
  • -F 使用 file 文件作为过滤条件表达式的输入,此时命令行上的输入将被忽略。

对输出内容进行控制的参数

  • -D 显示所有可用的网络接口的列表(跟 netstat -i 类似)
  • -e 每行的打印输出中将包括数据包的数据链路层头部信息
  • -E 揭秘 IPSEC 数据
  • -L 列出指定网络接口所支持的数据链路层的类型后退出
  • -Z 后接用户名,在抓包时会受到权限的控制。如果以 root 用户启动 tcpdump,tcpdump 将会有超级用户权限
  • -d 打印出易读的包匹配码
  • -dd 以 C 语言的形式打印出包匹配码
  • -ddd 以十进制的形式打印出包匹配码

过滤规则组合

有编程基础的同学,对于下面三个逻辑运算符应该不陌生了吧。

  • and 所有的条件都要满足,也可以表示为 &&
  • or 只要有一个条件满足就可以,也可以表示为 ||
  • not 取反,也可以使用 |

举个例子,我想需要抓取一个来自 10.5.2.3,发往任意主机的 3389 端口的包。

1
tcpdump src 10.5.2.3 and dst port 3389

当你在使用多个过滤器进行组合时,有可能需要用到括号,而括号在 shell 中是特殊符号,因此你需要使用引号将其包含。例子如下:

1
tcpdump 'src 10.0.2.4 and (dst port 3389 or 22)'

而在单个过滤器里,常常会判断一条件是否成立,这时候,就要使用下面两个符号:

  • = 判断二者相等
  • == 判断二者相等
  • != 判断二者不相等

当你使用这两个符号时,tcpdump 还提供了一些关键字的接口来方便我们进行判断,比如:

  • if:表示网卡接口名
  • proc:表示进程名
  • pid:表示进程 id
  • svc:表示 service class
  • dir:表示方向,in 和 out
  • eproc:表示 effictive processes name
  • epid:表示 effictive process ID

比如我现在要过滤来自进程名为 nc 发出的流经 en0 网卡的数据包,或者不流经 en0 的入方向数据包,可以这样子写。

1
tcpdump "(if=en0 and proc=nc) || (if != en0 and dir = in)"

特殊过滤规则

根据 tcpflags 进行过滤

tcpdump 支持我们根据数据包的标志位进行过滤:

1
proto [ expr:size ]
  • proto 可以是熟知的协议之一(如ip,arp,udp,icmp,ipv6)
  • expr 可以是数值,也可以是一个表达式,表示与指定的协议头开始处的子节偏移量。
  • size 是可选的,表示从子节偏移量开始取的子节数量
  1. tcpflags 可以理解为一个别名常量,相当于 13,它代表着与指定的协议头开头相关的子节偏移量,也就是标志位,所以 tcp[tcpflags] 等价于 tcp[13]就是 tcp 报文的第 13 个字节。
  2. tcp-fin, tcp-syn, tcp-rst, tcp-push, tcp-ack, tcp-urg 这些同样可以理解为别名常量,分别代表 1,2,4,8,16,32,64。(就是 000001,000010 ... 这些二进制数)

由于数字不好记忆,所以一般使用这样的 “别名常量” 表示。

因此当下面这个表达式成立时,就代表这个包是一个 syn 包。

1
tcp[tcpflags] == tcp-syn

要抓取特定数据包,方法有很多种。

下面以最常见的 syn 包为例,演示一下如何用 tcpdump 抓取到 syn 包,而其他的类型的包也是同样的道理。

据我总结,主要有三种写法:

  1. 使用数字表示偏移量:
1
tcpdump -i eth0 "tcp[13] & 2 != 0"
  1. 使用别名常量表示偏移量:
1
tcpdump -i eth0 "tcp[tcpflags] & tcp-syn != 0"
  1. 使用混合写法
1
2
tcpdump -i eth0 "tcp[tcpflags] & 2 != 0"
tcpdump -i eth0 "tcp[13] & tcp-syn != 0"

如果我想同时捕获多种类型的包呢,比如 syn 或 ack 包

  1. 第一种写法
1
tcpdump -i eth0 'tcp[13] == 2 or tcp[13] == 16'
  1. 第二种写法
1
tcpdump -i eth0 'tcp[tcpflags] == tcp-syn or tcp[tcpflags] == tcp-ack'
  1. 第三种写法

捕获的是 tcp and ack

1
tcpdump -i eth0 "tcp[tcpflags] & (tcp-syn|tcp-ack) != 0"
  1. 第四种写法:注意这里是单个等号,而不是像上面一样两个等号,18 (syn+ack) = 2 (syn) + 16(ack)
1
2
tcpdump -i eth0 'tcp[13] = 18'
tcpdump -i eth0 'tcp[tcpflags] = 18'

tcp 中有类似 tcp-syn 的别名常量,其他协议也是有的,比如 icmp 协议,可以使用的别名常量有:

1
2
3
4
5
icmp-echoreply, icmp-unreach, icmp-sourcequench, 
icmp-redirect, icmp-echo, icmp-routeradvert,
icmp-routersolicit, icmp-timx-ceed, icmp-paramprob,
icmp-tstamp, icmp-tstampreply,icmp-ireq,
icmp-ireqreply, icmp-maskreq, icmp-maskreply

基于包大小进行过滤

若你想查看指定大小的数据包,也是可以的:

1
2
3
tcpdump less 32
tcpdump greater 64
tcpdump <= 128

根据 mac 地址进行过滤

例子如下,其中 ehost 是记录在 /etc/ethers 里的 name

1
2
3
tcpdump ether host [ehost]
tcpdump ether dst [ehost]
tcpdump ether src [ehost]

过滤通过指定网关的数据包

1
tcpdump gateway [host]

过滤广播/多播数据包

1
2
3
4
5
6
7
tcpdump ether broadcast
tcpdump ether multicast

tcpdump ip broadcast
tcpdump ip multicast

tcpdump ip6 multicast

抓包实战应用例子

提取 HTTP 的 UserAgent

从 HTTP 请求头提取 HTTP 的User-Agent:

1
tcpdump -nn -A -s1500 -l | grep "User-Agent:"

通过 egrep 可以同时提取 User-Agent 和 主机名(或其他 HTTP 头):

1
tcpdump -nn -A -s1500 -l | egrep "User-Agent:|Host:"

抓取 HTTP GET 和 POST 请求

抓取 HTTP GET 请求包:

1
2
3
$ tcpdump -s 0 -A -vv 'tcp[((tcp[12:1] & 0xf0) >> 2):4] = 0x47455420'
# or
$ tcpdump -vvAls0 | grep 'GET'

抓取 HTTP POST 请求包:

1
2
3
$ tcpdump -s 0 -A -vv 'tcp[((tcp[12:1] & 0xf0) >> 2):4] = 0x504f5354'
# or
$ tcpdump -vvAls0 | grep 'POST'

注意:该方法不能保证抓取到 HTTP POST 有效数据流量,因为一个 POST 请求会被分割为多个 TCP 数据包。

找出发包数量最多的 IP

找出一段时间内发包最多的 IP,或者从一堆报文中找出发包最多的 IP,可以使用下面的命令:

1
tcpdump -nnn -t -c 200 | cut -f 1,2,3,4 -d '.' | sort | uniq -c | sort -nr | head -n 20
  • cut -f 1,2,3,4 -d '.': 以 . 为分隔符,打印出每行的前四列。即 IP 地址。
  • sort | uniq -c: 排序并计数
  • sort -nr: 按照数值大小逆向排序

抓取 DNS 请求和响应

DNS 的默认端口是 53,因此可以通过端口进行过滤:

1
tcpdump -i any -s0 port 53

切割 pcap 文件

当抓取大量数据并写入文件时,可以自动切割为多个大小相同的文件。例如,下面的命令表示每 3600 秒创建一个新文件 capture-(hour).pcap,每个文件大小不超过 200*1000000 字节:

1
tcpdump  -w /tmp/capture-%H.pcap -G 3600 -C 200

这些文件的命名为 capture-{1-24}.pcap,24 小时之后,之前的文件就会被覆盖。

提取 HTTP POST 请求中的密码

从 HTTP POST 请求中提取密码和主机名:

1
tcpdump -s 0 -A -n -l | egrep -i "POST /|pwd=|passwd=|password=|Host:"

提取 HTTP 请求的 URL

提取 HTTP 请求的主机名和路径:

1
tcpdump -s 0 -v -n -l | egrep -i "POST /|GET /|Host:"

抓取 HTTP 有效数据包

抓取 80 端口的 HTTP 有效数据包,排除 TCP 连接建立过程的数据包(SYN / FIN / ACK):

1
tcpdump 'tcp port 80 and (((ip[2:2] - ((ip[0]&0xf)<<2)) - ((tcp[12]&0xf0)>>2)) != 0)'

结合 Wireshark 进行分析

通常 Wireshark(或 tshark)比 tcpdump 更容易分析应用层协议。一般的做法是在远程服务器上先使用 tcpdump 抓取数据并写入文件,然后再将文件拷贝到本地工作站上用 Wireshark 分析。

还有一种更高效的方法,可以通过 ssh 连接将抓取到的数据实时发送给 Wireshark 进行分析。以 MacOS 系统为例,可以通过 brew cask install wireshark 来安装,然后通过下面的命令来分析:

1
ssh root@remotesystem 'tcpdump -s0 -c 1000 -nn -w - not port 22' | /Applications/Wireshark.app/Contents/MacOS/Wireshark -k -i -

例如,如果想分析 DNS 协议,可以使用下面的命令:

1
ssh root@remotesystem 'tcpdump -s0 -c 1000 -nn -w - port 53' | /Applications/Wireshark.app/Contents/MacOS/Wireshark -k -i -

-c 选项用来限制抓取数据的大小。如果不限制大小,就只能通过 ctrl-c 来停止抓取,这样一来不仅关闭了 tcpdump,也关闭了 wireshark。