共计 2266 个字符,预计需要花费 6 分钟才能阅读完成。
tcpdump -i any -A -s 0 -n 'port 80 and (tcp[((tcp[12:1] & 0xf0) >> 2):4] = 0x48545450) and (tcp[((tcp[12:1] & 0xf0) >> 2)+4:4] = 0x2f312e31) and (tcp[((tcp[12:1] & 0xf0) >> 2)+8:4] = 0x20323030)'
前面的参数比较简单,就不再详细说明了,我们关注下后面被单引号包裹起来的那串长长的让人有点懵逼的过滤表达式:
port 80 and (tcp[((tcp[12:1] & 0xf0) >> 2):4] = 0x48545450) and (tcp[((tcp[12:1] & 0xf0) >> 2)+4:4] = 0x2f312e31) and (tcp[((tcp[12:1] & 0xf0) >> 2)+8:4] = 0x20323030)
其中 and 起到的作用是逻辑运算里面的与操作符,我们把这个表达式分成四个部分:
port 80
这个比较好理解,意思就是说抓取发往 80 端口和从 80 端口回来的包;
tcp[((tcp[12:1] & 0xf0) >> 2):4] = 0x48545450
这段等式的前半段比较复杂,等式左边表达的意思是 tcp 数据包开头的四个字节,等式右边其实是 ascii 字符编码,查阅下 ascii 字符编码表(在 linux 系统可以直接通过 man ascii 查阅 ascii 字符编码表),0x48545450其实就是 HTTP
那么这个等式的意思就是:tcp 数据段的头四个字节 = HTTP,也就是说,我们抓取的是 HTTP 包;
tcp[((tcp[12:1] & 0xf0) >> 2)+4:4] = 0x2f312e31
类似于前面的表达式,表达的意思是:tcp 数据段的头四个字节之后出现的四个字节 = /1.1,即 http 的版本,上面解析出来的 HTTP 连起来就是 HTTP/1.1
tcp[((tcp[12:1] & 0xf0) >> 2)+8:4] = 0x20323030
这段表达式的意思是:tcp 数据段的头八个字节之后出现的四个字节 = ‘ 200’
那现在我们来总结下,前面那段垄长的表达式的意思其实比较简单:tcp 数据段的前 12 个字节 = HTTP/1.1 200
这其实就是 HTTP 响应报文的起始行。
那么 tcp[((tcp[12:1] & 0xf0) >> 2):4]
这种表达式该怎么理解呢?
我们对这段表达式做一个拆分:
tcp[12:1]
tcp[12:1] & 0xf0
(tcp[12:1] & 0xf0) >> 2
((tcp[12:1] & 0xf0) >> 2):4
第一,tcp[12:1]
,参考 tcp 表达式的语法解释,tcp[x:y]
表示的意思是:从 tcp 报文的第 x 字节(不包括第 x 字节)之后,读取 y 个字节,那么 tcp[12:1]
的意思就是说:读取 tcp 报文的第 12 个字节后的一个字节,也就是第 13 个字节,那么 tcp 报文的第 13 个字节是什么呢?来看下 tcp 报文的结构图:
上面的每一行是 32 位,也就是 4 个字节,第 13 个字节,也就是:4 位头部长度 + 6 位保留的前四位;
第二,tcp[12:1] & 0xf0
,其实就是把 tcp 报文的第 13 个字节读取出来,前四位也就是 tcp 头部里面的 4 位头部长度,后四位填充 4 个 0,也就是左移了四位的 tcp 报文的头部长度,因为我们多读取了后面四位;
第三,(tcp[12:1] & 0xf0) >> 2
,为什么要右移两位呢?如果我们要得到 tcp 报文头部的长度,应该要相应地右移四位对不对,因为前面我们多读了四位。但是,tcp 报文头部里面的 4 位头部长度其实是 tcp 有多少个 4 字节。那么,如果我们要得到 tcp 报文到底有多少个字节的话,要再左移两位
所以,先右移四位去掉向后多读的四位,再左移两位从有多少个 4 字节换算成有多少个字节,总共就是右移两位。
也就是,(tcp[12:1] & 0xf0) >> 2
tcp 头部有多少个字节
第四,((tcp[12:1] & 0xf0) >> 2):4
,结合 1 里面的表达式来理解,意思比较清楚了,读取 tcp 头部之后的数据段的前四个字节!
那么,tcp 数据段开头的四个字节是什么?我们来看下 tcp/ip 四层协议下的包结构:
在这里,我们记住一点就是,下层的协议数据是在上层的整个包体结构上加上自己的头部,类似于下面的等式关系:
tcp = http + tcp_header
ip = tcp + ip_header
ip = http + tcp_header + ip_header
如果最上层的应用层协议是 http 协议的话,那么参考 http 响应包报文:
前 12 个字节恰好就是 HTTP 的版本 + 状态码
下面是使用上面的 tcpdump 命令示例抓取到的测试机响应报文:
说了这么多,是不是觉得根据 http 的返回码来抓包还是有点复杂呢,要每次手撸那串表达式估计也是蛮蛋疼的。其实我们可以用 shell 脚本对 tcpdump 再进一步封装成 httpdump,输入参数就是你想抓取的返回码:
比如这个脚本文件名叫 dump.sh,执行
./dump.sh 200
脚本内容参考:
#!/bin/sh
#将输入的 http 返回码转换成 ascii 码
code=`echo 1 | hexdump -C | awk '{print234}' | grep -Ev "^"`
#抓包
sudo tcpdump -i any -A -s 0 -n "port 80 and (tcp[((tcp[12:1]&0xf0) >> 2):4] = 0x48545450) and (tcp[((tcp[12:1]&0xf0) >> 2)+4:4] = 0x2f312e31) and (tcp[((tcp[12:1]&0xf0) >> 2)+8:4] = 0x20code)"