20199105 实验:网络嗅探与协议分析
作业要求:
(1)根据教材参考代码,编写一个简单网络抓包工具。要求核心代码和运行结果截图1-2张。(3分)代码push到码云(1分)(2)找一个网站或者搭建一个本地网站,登录网站,并嗅探,分析出账号和密码,结果截图1-2张。(3分)可以用邮箱、各类博客、云班课,只能分析自己的账号,严禁做各类攻击,否则后果自负。
(3)加分项2分:抓取手机App的登录过程数据包,分析账号和密码。可以用邮箱、各类博客、云班课,只能分析自己的账号,严禁做各类攻击,否则后果自负。
(1)、编写一个简单网络抓包工具
1.1 编译环境与工具介绍
语言: python
编译环境: pycharm 2019.3.4
抓包库: scapy是python中一个可用于网络嗅探的非常强大的第三方库,可以用它来做 packet 嗅探和伪造 packet,还具有发送、捕获、匹配请求和响应这些 packet 以及更多的功能。scapy已经在内部实现了大量的网络协议,如DNS、ARP、IP、TCP、UDP等等,可以用它来编写非常灵活实用的工具
解包库: dpkt快速简单的包创建/解析工具
1.2 实验过程
抓包
- 使用scapy库里的sniff函数进行网络嗅探
参数说明
filter指定过滤语句,过滤规则使用(BPF)
prn指定回调函数,每当一个符合filter的报文被探测到时,就会执行回调函数
count指定最多嗅探多少个报文
iface用来指定要在哪个网络接口上进行抓包(通常不指定即所有网络接口)
timeout在给定的时间后停止嗅探,默认为None
代码如下:
from scapy.all import *
#sniff回调函数
def packet_callback(packet):
print(packet.show())
pkts = sniff(filter="ip.src == 192.168.1.104 ", prn=lambda x:x.summary(), count=20)
#将数据存储在demo.pcap文件中
wrpcap("demo.pcap", pkts)
-
pkts是一个PacketList对象,pkts.res是一个由packet组成的list,在后面解包对数据包组进行遍历时会用到
-
回调函数设置为lambda x:x.summary()时的输出,比较简洁
- 回调函数设置为packet_callback时的输出,信息较全面
- 关于sniff()函数,我简单的学习了一下接口源码: /python/site-packages/scapy/sendrecv.py
比如说下面这个函数,其实和外部的sniff函数很相似,用户接收数据包,传一些设置的参数。但是时间有限,忙着实现了,还没达到清晰的把sniff原理梳理的地步
- 在学习源码的过程中,我发现scapy的发包功能也很方便,源码中的函数定义
#发送网络层包
def send(x, inter=0, loop=0, count=None, verbose=None, realtime=None, return_packets=False, socket=None,*args,**kargs)
发送数据链路层包
def sendp(x, inter=0, loop=0, iface=None, iface_hint=None, count=None, verbose=None, realtime=None,return_packets=False,socket=None,*args,**kargs)
def sendpfast(x, pps=None, mbps=None, realtime=None, loop=0, file_cache=False, iface=None,*args,**kargs)
- 发包的包格式: 层的协议(参数)/上一层的协议(参数) /要发送的数据,尝试了一个简单的发包
from dpkt.ip import IP
from dpkt.tcp import TCP
from dpkt.udp import UDP
from scapy.all import *
from scapy.layers.l2 import Ether
send(IP(src='192.168.1.104',dst='192.168.1.101')/TCP(sport=80))
pkt=IP(dst="192.168.1.101")/UDP(dport=53)
send(pkt,inter=1,count=3) #每隔一秒发包,发3次
sendp(Ether()/IP(dst="192.168.1.101")/UDP(dport=53))
...有点扯远了,回归正题,继续实验...
解包
- 使用dpkt库进行数据包的解析,解包的过程其实就是基于OSI五层网络模型(物理层、数据链路层、网络层、传输层、应用层),按层逐步解析数据
以太网规定,一组电信号构成一个数据包,叫做"帧"(Frame),网络嗅探到的就是在数据链路层传输的信息,而每一帧分成两个部分:标头(Head)和数据(Data)
- 具体到dpkt库中,我们将数据链路层的数据存储在eth对象中,eth的data部分对应ip包(网络层),ip包的data部分对应tcp报文(传输层,也可以是udp等协议),tcp报文的data部分是应用层数据,就是这样一层层把head(标头)拿掉的过程
- 在主函数中,打开pcap文件,调用自己写的解析函数Analysis_Pcap()
代码如下:
def main():
#'rb'按照二进制位进行读取
f =open('demo.pcap','rb')
dpcap = dpkt.pcap.Reader(f)
Analysis_Pcap(dpcap)
f.close()
Analysis_Pcap()函数编写介绍:
- 传递数据包给dpkt的Ethernet类,解析和解码到eth对象。因为dpkt的Ethernet类同样包括一些额外功能去解析已知的高层次的协议,其实这一步IP层和TCP层的信息也被解码了
#迭代pcap对象
for timestamp, buf in pcap:
print('Arrival Time: ', str(datetime.datetime.utcfromtimestamp(timestamp))) # 包的抓取时间
eth = dpkt.ethernet.Ethernet(buf) # 解析以太网帧
- 首先,获取源IP地址与目标IP地址,使用自己写的mac_addr()函数将其转换为MAC地址形式并输出
#将MAC地址转换为可打印的字符串
def mac_addr(address):
return ':'.join('%02x' % compat_ord(b) for b in address)
# 获得MAC地址并输出
src_mac = mac_addr(eth.src)
dst_mac = mac_addr(eth.dst)
print('MAC: src( %s ) dst( %s ) Type( %s )' % (src_mac, dst_mac, hex(eth.type)))
- 解析网络层数据包(即IP数据包),使用socket.inet_ntoa()函数把4字节IP地址转化成点分十进制的可读形式并输出,提取MD\DF\OFFSET三个标志位信息并输出
- 在解析IP数据包时,我回顾了一下IP包的格式,选择了一些重要的信息进行输出(当年计算机网络课的知识突然涌现)
#解析ip包
ip = eth.data
# 把4字节IP地址转化成点分十进制的可读形式
src = socket.inet_ntoa(ip.src)
dst = socket.inet_ntoa(ip.dst)
# 提取标志信息
do_not_fragment = bool(ip.off & dpkt.ip.IP_DF) #不分片位
more_fragments = bool(ip.off & dpkt.ip.IP_MF) #更多分片位
fragment_offset = ip.off & dpkt.ip.IP_OFFMASK #该片偏移原始数据包起始位置
- 使用dpkt.http.Request()函数解析http数据信息,如运营商、版本、http与服务器交互方式(GET or POST)
if len(tcp.data):
request = dpkt.http.Request(tcp.data)
print("user-agent:" + request.headers['user-agent'])
print("request method:%s request version:%s" % (request.method, request.version))
- 最终最数据包的分析输出如下图,可以看到时间戳、MAC地址、IP类型(0x800表示IPv4)、IP地址及数据包流向、源端口、目标端口、传出曾协议等信息,参考了wireshark里的输出信息的模式。
完整代码如下:
import dpkt
import socket
import datetime
from dpkt.compat import compat_ord
#将MAC地址转换为可打印的字符串
def mac_addr(address):
return ':'.join('%02x' % compat_ord(b) for b in address)
#判断http
def checkIfHTTPRes(data):
if data[:4] == str.encode('GET'):
return True
else:
return False
def httpPacketParser(http):
if checkIfHTTPRes(http):
try:
request = dpkt.http.Request(http)
print(request.status)
except Exception as e:
pass
def tcpPacketParser(tcp):
http = tcp.data
if len(http):
httpPacketParser(http)
#数据包分析输出
def Analysis_Pcap(pcap):
try:
#迭代pcap对象
for timestamp, buf in pcap:
print('Arrival Time: ', str(datetime.datetime.utcfromtimestamp(timestamp))) # 包的抓取时间
eth = dpkt.ethernet.Ethernet(buf) # 解析以太网帧
# 获得MAC地址并输出
src_mac = mac_addr(eth.src)
dst_mac = mac_addr(eth.dst)
print('MAC: src( %s ) dst( %s ) Type( %s )' % (src_mac, dst_mac, hex(eth.type)))
#解析ip包
ip = eth.data
# 把4字节IP地址转化成点分十进制的可读形式
src = socket.inet_ntoa(ip.src)
dst = socket.inet_ntoa(ip.dst)
# 提取标志信息
do_not_fragment = bool(ip.off & dpkt.ip.IP_DF) #不分片位
more_fragments = bool(ip.off & dpkt.ip.IP_MF) #更多分片位
fragment_offset = ip.off & dpkt.ip.IP_OFFMASK #该片偏移原始数据包起始位置
print('IP: %s -> %s (len=%d ttl=%d DF=%d MF=%d offset=%d)' % (src, dst, ip.len, ip.ttl, do_not_fragment, more_fragments, fragment_offset))
print("sport= %d,dport= %d" % (eth.data.data.sport, eth.data.data.dport))
#传输层协议
if eth.data.data.__class__.__name__ == 'TCP':
# 解析tcp数据
tcp = ip.data
print("protocol:" + tcp.__class__.__name__)
# 解析tcp上层协议http
tcpPacketParser(tcp)
else:
print("protocol:" + eth.data.data.__class__.__name__)
request = dpkt.http.Request(tcp.data)
print("user-agent:" + request.headers['user-agent'])
print("request method:%s request version:%s" % (request.method, request.version))
#print("\n")
if not isinstance(eth.data, dpkt.ip.IP):
print('Non IP Packet type not supported %s' % ip.__class__.__name__)
continue
except:
pass
def main():
#'rb'按照二进制位进行读取
f =open('demo.pcap','rb')
dpcap = dpkt.pcap.Reader(f)
Analysis_Pcap(dpcap)
f.close()
if __name__ =='__main__':
main()
- 代码已经上传到码云上
Tips:创建github账号时我遇到一个小问题,最后一步邮箱认证失败。解决方法是:在邮箱中找到设置 ——> 反垃圾,将github.com添加到白名单里,就可以认证成功了 ~ ~ ~ /// (v) \\ ~ ~ ~
存在问题
dpkt进行数据包解析,要求只有在符合单个包的情况下才会正确解析,否则会报错,尝试在对应用层数据(http数据为例)分析时,利用if-else语句判断数据分了两个包发送,dpkt无法进行正确解析。
自己尝试编写了http解析过程,但未成功。
(2)、找一个网站,登录并嗅探,分析出账号和密码
http协议是明文传输的,所以在解析数据包过程中可以嗅探到登录操作的账号密码,实验使用的工具Wireshark也没有session key,不能解密https的数据流。
But!! 现在稍微正规点的网站都改成了https协议
一开始我选择了人人网,这个曾经辉煌一时网站,惊讶的发现它使用的是http
- 重新注册了一个账号,使用wireshark在注册和登录时进行抓包,结果如下图,账号就是手机号183********可以看到,但是密码password和验证码rkey都进行了加密(hash)
- 于是,我使用hash-identifier工具来确定上面的密码哈希值到底是什么类型的哈希,尝试解密。打开kali终端,然后输入“hash-identifier”并将上面的哈希值粘贴到终端,回车之后hash-identifier将会给出可能的匹配值。
- 可以看到推测可能是SHA256或Haval256,利用在线哈希解密网站,对SNA256进行解密,发现不对,Haval256没有找到解密方法,故只能放弃。等我回去在补补密码学知识。
于是我换了一个网站。
这里我拿鲜果网(http://www.xianguo.pro/)进行测试。
它的登陆界面长这个样子,买瓜果的主人应该叫“王大叔”
-
我将用户名设置为shuiguo(就是‘水果’啦),密码不能告诉你,注册时还要写手机号13051607197,这里我用了一个副号,专门抵抗各种信息骚扰的号。
-
使用wireshark在注册和登录时进行抓包。首先分析注册时抓的包,使用http.request.method=="POST"命令筛选,即客户机与服务器直接以post请求方式交互,其实就是我给网站传送信息的数据包。可以在下面信息栏看到用户名shuiguo和手机号,但是密码又被加密了。
- 于是我采用刚才的方法,在kali终端检测,发现使用的是MD5加密(这里忘记截图了),利用在线哈希解密获得了正确的密码
密文:708a9c84b47404c5524405e5Cbd910b8
密码:qazwsx123
- 在对登录操作抓包的数据包分析中,也可以看到账号和使用MD5加密后的密文,而且在wireshark中登录时数据包信息会显示“login”字段,便于大家找到登录传输的数据包
Tips:考虑到成本问题,并不是每个网站都会使用SSL来保证密码的安全。但是大部分为使用SSL的网站所有者通常在登录操作中进行哈希值加密密码的操作,这样至少在攻击者破解网站密码的时候不会那么轻而易举,能够多一道防护屏障。
(3)、加分项:抓取手机App的登录过程数据包,分析账号和密码
- 如果要使用wireshark抓取手机APP的数据包,那要将手机连接电脑的网络中,我通过手机连入电脑热点的方式,对手机数据进行抓包解析
具体步骤如下顺序,很简单。 - 首先,点击电脑左下角的微软按钮,点击设置,进入设置后,选择网络和Internet
- 跳转界面,在左侧栏选择移动热点,会看到如下图所示。右侧栏内打开移动热点,选择WLAN,会自动给出网络名称和网络密码
- 在手机上选择网络加入
至此,就完成了手机点解电脑热点的操作,可以开始抓包啦!!
- 使用wireshark嗅探手机访问浏览器进行账号登录操作,还是选择鲜果网,手机浏览器选择safari。(注意这个时候wireshark嗅探到的手机产生的数据包IP地址是之前电脑的IP地址,这里是192.168.1.104)
通过http.request.method=="POST"命令过滤,在信息栏可以看到访问的网址
找到传输账号密码的数据包,登录操作通常在“Info”栏后面会有“login”字段,还是可以看到明文的用户名,MD5加密的密码。
使用wireshark嗅探手机APP进行账号登录操作,没有成功分析,感觉wireshark也不好使,但是在分析数据包时看到好多TLS、SSL协议的包,因此查阅了一些资料,这里简单介绍一下我嗅探到的SSL/TLS握手过程。
SSL/TLS握手过程可以分成两种类型:
1)SSL/TLS 双向认证,双方都会互相认证,也就是两者之间将会交换证书。
2)SSL/TLS 单向认证,客户端会认证服务器端身份,而服务器端不会去对客户端身份进行验证。
握手阶段分成五步:
- 第一步,客户端给出协议版本号、生成的随机数(Client random),以及客户端支持的加密方法。在wireshark中显示为Client Hello
- 第二步,服务器确认双方使用的加密方法,并给出数字证书、以及一个服务器生成的随机数(Server random),在wireshark中分为两个包,你一个包显示为 Server Hello,第二个包显示为 certificate,server_key_exchange,server hello done
- 第三步,客户端确认数字证书有效,生成一个新的随机数(Premaster secret),并使用数字证书中的公钥,加密这个随机数,发给服务端。在wireshark中显示为client key exchange,change cypher spec
- 第四步,服务端使用自己的私钥,获取客户端发来的随机数(Premaster secret),在wireshark中显示为hange_cypher_spec
- 第五步,客户端和服务端根据约定的加密方法,使用前面的三个随机数,生成"对话密钥"(session key),用来加密接下来的整个对话过程。
-在wireshark中抓到的SSL/TLS握手过程
前两个随机数都是明文传送的,我们以第二步sever hello为例查看Server random
可以看到随机数和服务端支持的加密方法,例如RSA、AES等