tcpdump使用完整的英文文档https://www.tcpdump.org/tcpdump_man.html-A以ASCII格式打印出所有包,并最小化链路层的包头。-ctcpdump将在收到指定数量的数据包后停止。-C在将原始数据包写入文件之前,检查文件的当前大小是否超过参数file_size中指定的大小。如果超过指定的大小,则关闭当前文件并打开一个新文件。参数file_size的单位是兆字节(1,000,000字节,不是1,048,576字节)。-d在人类可读的汇编中给出匹配数据包的代码。-dd以C语言程序段的格式给出匹配包的代码。-ddd以十进制形式给出匹配数据包的代码。-D打印出系统中所有可以用tcpdump拦截的网络接口。-e在输出行打印出数据链路层头信息。-E使用spi@ipaddralgo:secret解密那些地址为addr且包含安全参数索引值spi的IPsecESP数据包。-f将外部Internet地址打印为数字。-F从指定文件中读取表达式,忽略命令行中给出的表达式。-i指定要侦听的网络接口。-l使标准输出成为缓冲行格式,可以将数据导出到文件中。-L列出网络接口的已知数据链路。-m从文件模块导入SMIMIB模块定义。可以多次使用该参数来导入多个MIB模块。-M如果tcp报文中有TCP-MD5选项,则需要使用secret作为共享验证码来验证TCP-MD5选项摘要(详见RFC2385)。-b选择数据链路层的协议,包括ip、arp、rarp、ipx。-n不要将网络地址转换为名称。-nn不转换端口名称。-N不输出主机名的域名部分。例如,“nic.ddn.mil”仅输出“nic”。-t不要在每行输出上打印时间戳。-O不运行数据包匹配代码优化器。-P不要将网络接口设置为混杂模式。-q快速输出。只输出较少的协议信息。-r从指定文件中读取包(这些包一般由-w选项生成)。-S以绝对值而不是相对值的形式输出tcp的序号。-s从每个数据包中读取第一个snaplen字节而不是默认的68字节。-T直接将监听的数据包解释为指定类型的数据包,常见的类型有rpc远程过程调用)和snmp(简单网络管理协议;)。-t不要在每一行打印时间戳。-tt在每一行输出未格式化的时间戳。-ttt打印本行与上一行之间的时间差。-tttt在每行上按日期处理的默认格式输出时间戳。-u输出未解码的NFS句柄。-v输出稍微详细一点的信息,比如可以在ip包中包含ttl和服务类型信息。-vv输出详细消息信息。-w将数据包直接写入文件而不是不解析就打印它们。拿消息获取干巴巴的讲这个东西会头晕,而且网上一大堆文章也没什么创新。我选择从另一个角度切入TCP/IP协议。首先通过tcpdump准备数据包。[1]我在192.168.1.22这台机器的10000端口启动了一个redis服务。[2]使用工具tcpdump抓包,命令如下:tcpdump-w/tmp/logs-ieth0port10000-s0[3]在192.168.1.26机器上访问redis实例192.168.1.22:10000,您可以使用redis-cli客户端或telnet发送ping并从对等方获得pong回复。【4】停止抓包,使用tcpdump读取这个数据包(-x以16进制显示,方便后面分析)tcpdump-r/tmp/logs-n-nn-A-x|vim-里面有个数据包是这样的,也就是本文要分析的:10:54:54.270967IP192.168.1.26.61096>192.168.1.22.10000:Flags[P.],seq1041414875:1041414889,ack658186233,wintions115,options[nop,nop,TSval2377448931ecr2741547141],length140x0000:[45600042756700003d066F3CC0A8011A0x0010:C0A80116]{eea827103e12badb273b1ff90x0020:8018007364b000000101080a8db4fde30x0030:a368b085}2a310d0a24340d0a70696e670x0040:0d0a注:[1]之前文章中介绍过常用shell中的抓包工具tcpdump,不知道的朋友可以先睹为快。[2]上述消息数据中的[、]、{、}是我为了方便区分数据而自行添加的。[]包围的部分是这条消息中的IP头,{}包围的部分是这条消息中的TCP头。数据包分析IP数据包整体结构如下,因为抓取的数据包是redis服务,所以传输层是TCP协议。在IP层解析解析数据包之前,先把IP协议拿出来,如下:可以看到,IP包头是由定长(20B)+变长组成的,后面的TCP头也是相同的。然后分析抓取到的数据包如下:[1]0x44bit,ip协议版本0x4表示IPv4。[2]0x54bit,ipheaderlength该字段表示单位是32bits(4字节),所以这个ip包的header有5*4=20B,可以推导出IPheader没有可选字段。4bit最大可以表示的数是0xF,因此IP头的最大长度是15*4=60B。我在消息中标记了消息的IP头。[3]0x608bit,服务类型TOS这一段数据由3bit优先级字段(现在忽略)+4bitTOS字段+1bit保留字段(必须为0)组成。4bitTOS字段分别代表最小延迟、最大吞吐量、最大可用性和最小成本。只能设置1位,全0表示一般服务。大多数当前的TCP/IP实现不支持TOS功能。可以看出这条报文的TOS域全为0。[4]0x004216bit,IP包的总长度,单位为字节。转换后的数据报长度为66字节。算一下上面的数据包,正好是66B。从占用位数来计算,最长的IP数据报是2^16=65535B,但是大部分网络的链路层MTU(MaximumTransmissionUnit)都没有那么大,有些上层协议或者主机不会接受这样的大的。因此,很长的IP数据报在传输过程中会被分片。[5]0x756716bit,唯一标识主机发送的每个数据报。通常每发送一条消息,它的值都是+1。当IP数据包被分片时,标识字段的值被复制到所有数据分片的标识字段中,这样当这些分片到达最终目的地时,可以根据标识字段的内容重构原始数据。【6】0x00003bitflag+13bitchipoffset3bitflag对应R,DF,MF。目前只有最后两位有效,DF位:1表示不分片,0表示分片。MF:1表示“更多切片”,0表示这是最后一个切片。13bitsliceoffset:这个slice相对于原始数据报文中第一个bit的偏移量。(需要乘以8)[7]0x3d8bittimetoliveTTL允许IP包通过的路由器的最大数量。每经过一个路由器,TTL就减1。当为0时,路由器丢弃该数据报。TTL字段是一个8位字段,最初由发送方设置。推荐的初始值由AssignedNumbersRFC指定。发送ICMP回应回复时,通常将TTL设置为最大值255。TTL防止数据报卡在路由循环中。在这个报文中,值为61。[8]0x068bitprotocol表示IP报文携带的数据使用哪种协议,以便目的主机的IP层可以知道将数据报交给哪个进程。TCP协议号为6,UDP协议号为17。ICMP的协议号为1,IGMP的协议号为2。IP报文携带的数据使用TCP协议进行校验。【9】0x6F3C16bitIPheaderchecksum由发送方填写。以这条消息为例,我先解释一下这个值是如何计算出来的。#擦除校验和字段的16位值变成'0x0000',然后加上前20个字节的值0x4560+0x0042+0x7567+0x0000+0x3d06+0x0000+0xC0A8+0x011A+0xC0A8+0x0116=0x27B95#以上结果进位2和低16bit相加0x7B95+0x2=0x7B97#0x7B97按位取反~(0x7B97)=0x8468结果0x8468就是该字段的值!在接收端校验时,进行如下计算#20B头值加法0x27B95+0x8468=0x2FFFD#将上述结果的进位2加到低16位0xFFFD+0x2=0xFFFF#0xFFFF按位取反~(0xFFFF)=0<--正确[10]0xC0A8011A32bit源地址可以通过python程序从hex转换为熟悉的点分IP表示法>>>importsocket>>>importstruct>>>int_ip=int("0xC0A8011A",16)>>>socket.inet_ntoa(struct.pack('I',socket.htonl(int_ip)))'192.168.1.26'这条消息中的srcaddr是192.168.1.26,正好是发起请求的IP。[11]0xC0A8011632bit的目的地址计算为192.168.1.22,刚好是启动redis服务的机器IP。由于消息头的长度是20B,所以没有变长部分。传输层分析这个报文携带的数据使用的TCP协议,所以下面开始分析TCP协议。和上面的IP报文一样,TCP报文头也是定长(20B)+变长的形式。首先看一下TCP协议的格式,从网上找了一张图,如下:注意:TCP的包头必须是4字节的倍数,大部分选项都不是4字节的倍数,并且不足的用NOP填充。[1]0xeea816bit,分析源端口得到61096,与tcpdump读包显示一致。16bit确定最大端口号为65535。[2]0x271016bit,目的端口解析为10000。[3]0x273b1ff932bit,分析序列号得到1041414875,与上面tcpdump显示的seq段一致。[4]0x273b1ff932bit,分析确认号得到658186233,与上面tcpdump显示的ack段一致。[5]0x84bit,TCP包头的长度也叫offset,其实就是数据开始的地方。8*4=32B,所以TCP报文可选部分的长度是32-20=12B,这个资源还是很紧张的!与IP头类似,最大长度为60B。【6】0b0000006bit,保留位为以后使用保留,但目前应设置为0。[7]0b0110006bit,TCPflag从上图可以看出,从左到右依次为紧急URG、确认ACK、推送PSH、复位RST、同步SYN、终止FIN。从抓包可以看出,报文中携带了ack,所以将ACK标志设置为1。关于标志位的知识这里不再展开。[8]0x007316bit,分析滑动窗口大小得到十进制115,与tcpdump分析的win字段一致。[9]0x64b016bit,校验和由发送端填充,接收端对TCP报文段执行CRC算法,检查TCP报文段在传输过程中是否损坏,损坏则丢弃。检查的范围包括头部和数据,这也是TCP可靠传输的重要保证。[10]0x000016bit,urgentpointer仅当URG=1时才有意义,表示本报文段中紧急数据的字节数。当URG=1时,发送方TCP在本段数据的最前面插入紧急数据,紧急数据后面的数据仍然是正常数据。以下是TCP选项,其格式如下:常用选项如下:[11]0x01NOPpadding,不带Length和Value字段,用于将TCPHeader的长度填充为32bit的倍数。[12]0x01同上。[13]0x080a的optiontype是timestamp,len是10B,value是0x8db40xfde30xa3680xb085,加上0x080a,正好是10B!启用TimestampOption后,该字段包含两个32位时间戳(TSval和TSecr)。[14]0x8db40xfde3解析后得到2377448931,正好和tcpdump解析出来的TS字段的val一致![15]0xa3680xb085解析得到2741547141,与tcpdump解析出的TS字段的ecr完全一致!数据部分分析以上分析可知,IP包长度为66B,IP包头长度为20B,TCP包头长度为32B,所以得到的数据长度为66-20-32=14B,这与len字段是一致的!我们来具体分析一下这个数据。这就涉及到redis协议了。不了解的可以查看本文档对redis协议的描述。客户端在抓包时向redis服务器发送ping命令,转换成redis协议如下:*1\r\n$4\r\nping\r\n我们来看抓包数据分析,需要对照ascii码表,linux下可以使用man7ascii命令获取,或者查看这里的ascii码表。0x2a31->*10x0d0a->\r\n0x2434->$40x0d0a->\r\n0x70690x6e67->ping0x0d0a->\r\ntcpdump补充由于我们详细讨论了TCP/IP协议,所以让我添加一些用法tcpdump过滤器。过滤器可以简单分为三类:type、dir和proto。type区分消息的类型,主要由host(主机)、net(网络,支持CIDR)和port(支持范围,如portrange21-23)组成。dir区分方向,主要由src和dst组成。proto区分协议支持tcp、udp、icmp等,下面说几个过滤表达式。proto[x:y]startatoffsetxintotheprotoheaderandreadybytes[x]abbreviationfor[x:1]注意:单位是字节,不是位!这里有几个例子:[1]打印端口80,带有数据的tcp数据包tcpdump'tcp端口80和(((ip[2:2]-((ip[0]&0xf)<<2))-((tcp[12]&0xf0)>>2))!=0)'ip[2:2]从ip包的第3字节开始读取2个字节,正好是ip包的总长度,单位是wordip[0]&0xf段取ip报文第一个字节的低4位,<<2(乘以4),即ip头的长度,单位为字节tcp[12]&0xf0取tcp报文的第13个字节的高4位,>>2其实相当于>>4再加上<<2,也就是tcp头的字节长度。所以((ip[2:2]-((ip[0]&0xf)<<2))-((tcp[12]&0xf0)>>2))表示数据长度。分解:1)获取tcp包头的长度:tcp[12:1]&0xf0>>2首先,因为tcp包的data-offset(数据偏移量)字段长度为4位,所以我们取字节data-offset所在位置,AND0xf0取dataoffset位,即tcp[12:1]&0xf0其次,因为data-offset字段位于高字节,所以真正的数据长度是移位后右移4位:tcp[12:1]&0xf0>>4最后,由于data-offset字段的单位是32位字(1个字长为4字节),结果需要乘以4(左移2位),所以tcp[12:1]&0xf0>>4<<2,最终结果为:tcp[12:1]&0xf0)>>22)获取tcp报文内容的前4个字节:'GET'=0x47455420'POST'=0x504f5354查看ASCII表:0x47:'G'0x45:'E'0x54:'T'0x20:'space'【2】打印80端口,长度超过576的ip包tcpdump'port80andip[2:2]>576'【3】打印特定TCPFlag的数据包tcpdump抓包中反映的TCPFlags:[S]:SYN(startconnection)[.]:NoFlag[P]:PSH(pushdata)[F]:FIN(endconnection)[R]:RST(resetconnection)[S.]SYN-ACK,即SYN报文的响应信息。tcpdump'tcp[13]&16!=0'#相当于tcpdump'tcp[tcpflags]==tcp-ack'打印出所有的ACK包。tcpdump'tcp[13]&4!=0'#相当于tcpdump'tcp[tcpflags]==tcp-rst'打印出所有RST包,即包含[R]标志的包。更多tcpdump过滤器,请查看PCAP-FILTER或mantcpdump!好了,这个IP包的分析到此结束。按照TCP/IP协议分析后,发现协议就是这么回事。没有想象中那么难。不要害怕协议![4]只抓取有数据的HTTP包内容tcpdump'tcpport80and(((ip[2:2]-((ip[0]&0xf)<<2))-((tcp[12]&0xf0)>>2))!=0)'1)从IP包中获取I包的总长度:ip[2:2]2)从IP包中获取IP包头的长度:(ip[0]&0xf)<<23)从TCP报文中获取TCP头长度:(tcp[12]&0xf0)>>24)计算数据包长度:报文总长度-IP长度header-TCPheader的长度5)获取非空长度的HTTP数据包:(((ip[2:2]-((ip[0]&0xf)<<2))-((tcp[12]&0xf0)>>2))!=0)'参考[1]常用TCP选项[2]IP包格式详解[3]TCP包结构
