参数笔记
-n
一个n
表示不反向解析地址,两个n
表示不反向解析端口。-S
表示查看绝对序号,如客户端中的ack
为1,这是相对的,要查看绝对序号需要加上该参数。-v
表示查看详细的报文数据。-r
该参数接收一个pcap文件的路径,不抓取数据包,而是去读取指定的pcap文件,并格式化输出。-i
指定抓取的网卡,any
表示监听全部。-e
每行的打印输出中将包括数据包的数据链路层头部信息,我经常用它来看truck口的vlan id。
详情请参考: http://linux.51yip.com/search/tcpdump
语法编写
所有经过目的或原地址是192.168.1.1的数据包
1 | $ tcpdump host 192.168.1.1 |
源端口或者是目的端口为80的数据包
1 | $ tcpdump port 80 |
所有目的地址为192.168.1.1的数据包
1 | $ tcpdump dst host 192.168.1.1 |
相同的,所有目的端口为80的数据包为dst port
所有的源地址为192.168.1.1的数据包
1 | $ tcpdump src host 192.168.1.1 |
相同的,所有源端口为80的数据包为src port
协议过滤
1 | tcpdump arp |
常用的逻辑表达式
- 非:
!
或者是:not
- 且:
&&
或者是:and
- 或:
||
或者是:or
详情可以参考:https://linuxwiki.github.io/NetTools/tcpdump.html
抓取http数据包
抓取TCP说明
在讲如何抓取HTTP包之前应该先讲一下TCP的包头,不然等会直接去看肯定会懵逼。
注:上图中的位表示1bit
- 源端口、目的端口号没什么好说的,2的16次方也就是65536。
- 32为序列号,sequence number,保证网络传输数据的顺序性。
- 32位确认号,acknowledgment number,用来确认确实有收到相关封包,内容表示期望收到下一个报文的序列号,用来解决丢包的问题。
- 头部大小,4位,偏移量:最大值为0x0F,即15,单位为32位(bit),也就是4字节,所以TCP的头部最大为15*4=60字节。
- Reserved 4位 ,预留字段,都为0
- TCP,标识段位
- CWR:Congestion window reduced,拥塞窗口减少。拥塞窗口减少标志被发送主机设置,用来表明它接收到了设置ECE标志的TCP包。拥塞窗口是被TCP维护的一个内部变量,用来管理发送窗口大小。
- ECN-Echo:显式拥塞提醒回应。当一个IP包的ECN域被路由器设置为11时,接收端而非发送端被通知路径上发生了拥塞。ECN使用TCP头部来告知发送端网络正在经历拥塞,并且告知接收端发送段已经受到了接收端发来的拥塞通告,已经降低了发送速率。
- URG:为1时,紧急指针(urgent pointer)有效,配合紧急指针使用
- ACK:为1时,确认号有效
- PSH: 为1时,接收方应该尽快将这个报文段交给应用层
- RST:为1时,释放连接,重连。
- SYN:为1时,发起一个连接。
- FIN:为1时,关闭一个连接。
- 16位窗口大小:占16bit。此字段用来进行流量控制,主要用于解决流控拥塞的问题。单位为字节数,这个值是本机期望一次接收的字节数。
- 16位校验值: 占16bit。对整个TCP报文段,即TCP头部和TCP数据进行校验和计算,并由目标端进行验证。
- 16位紧急指针:占16bit。它是一个偏移量,和序号字段中的值相加表示紧急数据最后一个字节的序号。
- 32位Tcp选项:一般包含在三次握手中。
tcpdump flags 对应关系
由于tcpdump中tcp协议的flags不是直接以文件输出的,通常是以下方式出现,
1 | Flags [S.] |
下面直接指出了对应关系。
1 | Unskilled = URG = (Not Displayed in Flag Field, Displayed elsewhere) |
说明,在上表格中[.]
表示没有设置flag,但是经过我的测试,其实[.]
表示ACK标志
三次握手四次关闭
看完了上面的TCP的介绍,下面我们拿三次握手做一次实验
console1
1 | $ sleep 10 ;curl baidu.com |
console2
1 | $ tcpdump -nnn -S -vvv -i ens33 'port 80 and host baidu.com' |
1 | 02:55:40.348976 IP (tos 0x0, ttl 64, id 44247, offset 0, flags [DF], proto TCP (6), length 60) |
客户端发起建立连接请求,SYN标志为1,以及随机生成了一个值为39940599
seq的值。
1 | 02:55:40.381298 IP (tos 0x0, ttl 128, id 10245, offset 0, flags [none], proto TCP (6), length 44) |
服务端收到客户端发送的SYN包,返回带有SYN、ACK标志的包,包中还有一个为2621492164
的seq的值,以及一个ack值为39940600
,这里的ack的值就是上个包中,客户端发送的seq+1,下面的操作也是类似的。
1 | 02:55:40.381378 IP (tos 0x0, ttl 64, id 44248, offset 0, flags [DF], proto TCP (6), length 40) |
客户端收到服务端返回带有SYN、ACK标志的包,只后返回一个带有ACK标志的包,其中还有值为39940600
的seq值,也就是服务器返回的包里面的ack的值,还有一个值为ack值为2621492165
,这个也就是服务端返回的包的seq的值加一得来的。三次握手完毕。下面开始正常通讯。
1 | 02:55:40.381995 IP (tos 0x0, ttl 64, id 44249, offset 0, flags [DF], proto TCP (6), length 113) |
客户端发送带有HTTP协议的数据包,注意seq的值,,:
前面的是服务器端返回的数据包ack的值,后面的是前面的值+数据包包体的长度,也是就39940600 + 73 =39940673,其实这种格式: seq 39940600:39940673
并不是协议内是这样做的,这个只是tcpdump特意这样显示的,在协议中这次请求发送的seq的值还是39940600
。
1 | 02:55:40.382277 IP (tos 0x0, ttl 128, id 10246, offset 0, flags [none], proto TCP (6), length 40) |
服务端返回ack标志的数据包,告诉客户端,我收到了你发送的长度为73的数据包,注意一下ack的值变成了39940673=39940600+73(客户端发送数据包的长度)。
1 | 02:55:40.416335 IP (tos 0x0, ttl 128, id 10247, offset 0, flags [none], proto TCP (6), length 426) |
这里服务器端返回HTTP Response数据包,再次强调一下,seq目前还是2621492165
。
1 | 02:55:40.416398 IP (tos 0x0, ttl 64, id 44250, offset 0, flags [DF], proto TCP (6), length 40) |
客户端返回ACK标志的数据包,告诉服务端已经收到了长度为386
的数据包,ack为2621492165+386=2621492551
下面就是HTTP请求完毕,关闭连接。
1 | 02:55:40.416919 IP (tos 0x0, ttl 64, id 44251, offset 0, flags [DF], proto TCP (6), length 40) |
通讯完毕,客户端发送FIN标志的数据包请求关闭连接。
1 | 02:55:40.417278 IP (tos 0x0, ttl 128, id 10248, offset 0, flags [none], proto TCP (6), length 40) |
服务端发送数据包给客户端,表示收到客户端发来的FIN标志的数据包。seq的值为上一次ack的值+1,后面不再重复说明。
1 | 02:55:40.450349 IP (tos 0x0, ttl 128, id 10249, offset 0, flags [none], proto TCP (6), length 40) |
服务端再次发送FIN和PSH标志的数据包。
1 | 02:55:40.450403 IP (tos 0x0, ttl 64, id 63792, offset 0, flags [DF], proto TCP (6), length 40) |
客户端发送数据包给客户端表示已经收到你的FIN和PSH标志的数据包,此时客户端和服务端正式关闭连接。
抓取GET请求
1 | $ tcpdump 'tcp[(tcp[12]>>2):4] = 0x47455420' |
我第一次看到这个格式也是懵逼的,但是仔细的对照协议的数据之后就可以理解了(其实我看来很久,哈哈)
首先想一下,如果我们要过滤HTTP请求的’GET’参数,就需要定位到TCP包头的结束为止,从结束为止开始读取三个字节字符串,然后判断这三个字符串是不是为GET
,上面那个过滤语法大概就是这个意思,下面开始详解上面的语法。
0x47455420
表示字符串GET
后面有一个空格哦。
从最里开始看,开始,首先是tcp[12]
表示整个tcp数据包的从0开始数第12个byte(字节)(为了文章便于阅读,后面技术都是从0计数)然后我们对应上面TCP的图看。上图中都是以bit来计算的,1byte=8bit,把上面的协议图每一层分成4份,数一下从0开始数到第十二个正好就是TCP头部大小。总共8位,前四位为头部长度,后四位为余留的位,一般不使用。
下面的我以wireshark抓过来的GET请求的包,把tcp协议的头部信息带出拿下来,详细解释一下
1 | # 从wireshark抓包拷贝下来的hex dump |
根据上面的计算可以理解为TCP
所以我们只需要前四位,下面我们开始分析tcp[12]>>2
,刚说了我们只需要四位,但是为什么只向右移动了两位?上面也说了因为TCP头部这个值表示的单位是32bytes,这里他们就刷了一个小聪明,只去掉两位,加上多出来的两个比特位正好就是4的倍数, 那么这么就不用再写一个5 * 4
的运算了,所以010100 == 20(decimalism)
,这样直接可以得出正确的TCP头部的结束位置。
(tcp[12]>>2):4]
学过编程的都知道这是切片。根据上面的分析可以把:
前面的值理解为20
。也就是说是从TCP Body第开始的地方开始切(20确实是TCP头的长度,但是在tcpdump切片中是从0开始输的,所以20
对应的是tcp body第一个值),:
后面的4
表示切到第四位时停止,正好是4个字符,结果为GET
(注意GET
后面的空)的16进制的值0x47455420
所以到了最后的最后这个表达式以简单明了的方式写出来就是这个样子的
1 | $ python -c 'print "0x"+"GET ".encode("hex")' |
最终的结果如下图所示
1 | step: 0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 |
- 第一行标识的是长度,从0开始计数。
- 忽略掉第二行中间的
|
,那是分割线… - 第二行是从wireshark 复制下来tcp的hex dump的值。
抓取POST请求
1 | $ tcpdump 'tcp[(tcp[12]>>2):5] = 0x504f535420' |
上面都讲清楚了,没啥好说的了,POST
的hex值0x504f535420
可以用python来得到
1 | $ python -c 'print "0x"+"POST ".encode("hex")' |
记得把:
后面的数字改成对应的切割的长度。