1、IP流量将何去何从?——用Python回答:
使用PyGeoIP关联IP地址和物理地址:
需要下载安装pygeoip,可以pip install pygeoip
或者到Github上下载安装https://github.com/appliedsec/pygeoip
同时需要下载用pygeoip操作的GeoLiteCity数据库来解压获得GeoLiteCity.dat数据库文件:
http://dev.maxmind.com/geoip/legacy/geolite/
将GeoLiteCity.dat放在脚本的同一目录中直接调用即可。
#!/usr/bin/python #coding=utf-8 import pygeoip # 查询数据库相关的城市信息并输出 def printRecord(tgt): rec = gi.record_by_name(tgt) city = rec['city'] # 原来的代码为 region = rec['region_name'],已弃用'region_name' region = rec['region_code'] country = rec['country_name'] long = rec['longitude'] lat = rec['latitude'] print '[*] Target: ' + tgt + ' Geo-located. ' print '[+] '+str(city)+', '+str(region)+', '+str(country) print '[+] Latitude: '+str(lat)+ ', Longitude: '+ str(long) gi = pygeoip.GeoIP('GeoLiteCity.dat') tgt = '173.255.226.98' printRecord(tgt)
运行结果:
使用Dpkt解析包:
需要安装dpkt包:pip install dpkt
dpkt允许逐个分析抓包文件里的各个数据包,并检查数据包中的每个协议层。
#!/usr/bin/python #coding=utf-8 import dpkt import socket def printPcap(pcap): # 遍历[timestamp, packet]记录的数组 for (ts, buf) in pcap: try: # 获取以太网部分数据 eth = dpkt.ethernet.Ethernet(buf) # 获取IP层数据 ip = eth.data # 把存储在inet_ntoa中的IP地址转换成一个字符串 src = socket.inet_ntoa(ip.src) dst = socket.inet_ntoa(ip.dst) print '[+] Src: ' + src + ' --> Dst: ' + dst except: pass def main(): f = open('geotest.pcap') pcap = dpkt.pcap.Reader(f) printPcap(pcap) if __name__ == '__main__': main()
因为抓取的流量不多,直接使用书上的数据包来测试即可:
接着添加retGeoStr()函数,返回指定IP地址对应的物理位置,简单地解析出城市和三个字母组成的国家代码并输出到屏幕上。整合起来的代码如下:
#!/usr/bin/python #coding=utf-8 import dpkt import socket import pygeoip import optparse gi = pygeoip.GeoIP('GeoLiteCity.dat') # 查询数据库相关的城市信息并输出 def printRecord(tgt): rec = gi.record_by_name(tgt) city = rec['city'] # 原来的代码为 region = rec['region_name'],已弃用'region_name' region = rec['region_code'] country = rec['country_name'] long = rec['longitude'] lat = rec['latitude'] print '[*] Target: ' + tgt + ' Geo-located. ' print '[+] '+str(city)+', '+str(region)+', '+str(country) print '[+] Latitude: '+str(lat)+ ', Longitude: '+ str(long) def printPcap(pcap): # 遍历[timestamp, packet]记录的数组 for (ts, buf) in pcap: try: # 获取以太网部分数据 eth = dpkt.ethernet.Ethernet(buf) # 获取IP层数据 ip = eth.data # 把存储在inet_ntoa中的IP地址转换成一个字符串 src = socket.inet_ntoa(ip.src) dst = socket.inet_ntoa(ip.dst) print '[+] Src: ' + src + ' --> Dst: ' + dst print '[+] Src: ' + retGeoStr(src) + '--> Dst: ' + retGeoStr(dst) except: pass # 返回指定IP地址对应的物理位置 def retGeoStr(ip): try: rec = gi.record_by_name(ip) city = rec['city'] country = rec['country_code3'] if city != '': geoLoc = city + ', ' + country else: geoLoc = country return geoLoc except Exception, e: return 'Unregistered' def main(): parser = optparse.OptionParser('[*]Usage: python geoPrint.py -p <pcap file>') parser.add_option('-p', dest='pcapFile', type='string', help='specify pcap filename') (options, args) = parser.parse_args() if options.pcapFile == None: print parser.usage exit(0) pcapFile = options.pcapFile f = open(pcapFile) pcap = dpkt.pcap.Reader(f) printPcap(pcap) if __name__ == '__main__': main()
还是使用之前测试用的数据包:
使用Python画谷歌地图:
这里修改一下代码,将kml代码直接写入一个新文件中而不是直接输出到控制台。
#!/usr/bin/python #coding=utf-8 import dpkt import socket import pygeoip import optparse gi = pygeoip.GeoIP('GeoLiteCity.dat') # 通过IP地址的经纬度构建kml结构 def retKML(ip): rec = gi.record_by_name(ip) try: longitude = rec['longitude'] latitude = rec['latitude'] kml = ( '<Placemark>\n' '<name>%s</name>\n' '<Point>\n' '<coordinates>%6f,%6f</coordinates>\n' '</Point>\n' '</Placemark>\n' ) %(ip,longitude, latitude) return kml except: return ' ' def plotIPs(pcap): kmlPts = '' for (ts, buf) in pcap: try: eth = dpkt.ethernet.Ethernet(buf) ip = eth.data src = socket.inet_ntoa(ip.src) srcKML = retKML(src) dst = socket.inet_ntoa(ip.dst) dstKML = retKML(dst) kmlPts = kmlPts + srcKML + dstKML except: pass return kmlPts def main(): parser = optparse.OptionParser('[*]Usage: python googleearthPrint.py -p <pcap file>') parser.add_option('-p', dest='pcapFile', type='string', help='specify pcap filename') (options, args) = parser.parse_args() if options.pcapFile == None: print parser.usage exit(0) pcapFile = options.pcapFile f = open(pcapFile) pcap = dpkt.pcap.Reader(f) kmlheader = '<?xml version="1.0" encoding="UTF-8"?>\ \n<kml xmlns="http://www.opengis.net/kml/2.2">\n<Document>\n' kmlfooter = '</Document>\n</kml>\n' kmldoc = kmlheader + plotIPs(pcap) + kmlfooter # print kmldoc with open('googleearthPrint.kml', 'w') as f: f.write(kmldoc) print "[+]Created googleearthPrint.kml successfully" if __name__ == '__main__': main()
运行结果:
查看该kml文件:
接着访问谷歌地球:https://www.google.com/earth/
在左侧选项中导入kml文件:
导入后点击任一IP,可以看到该IP地址的定位地图:
2、“匿名者”真能匿名吗?分析LOIC流量:
LOIC,即Low Orbit Ion Cannon低轨道离子炮,是用于压力测试的工具,通常被攻击者用来实现DDoS攻击。
使用Dpkt发现下载LOIC的行为:
一个比较可靠的LOIC下载源:https://sourceforge.net/projects/loic/
由于下载源站点已从HTTP升级为HTTPS,即已经无法直接通过抓包来进行请求头的分析了。
#!/usr/bin/python #coding=utf-8 import dpkt import socket def findDownload(pcap): for (ts, buf) in pcap: try: eth = dpkt.ethernet.Ethernet(buf) ip = eth.data src = socket.inet_ntoa(ip.src) # 获取TCP数据 tcp = ip.data # 解析TCP中的上层协议HTTP的请求 http = dpkt.http.Request(tcp.data) # 若是GET方法,且请求行中包含“.zip”和“loic”字样则判断为下载LOIC if http.method == 'GET': uri = http.uri.lower() if '.zip' in uri and 'loic' in uri: print "[!] " + src + " Downloaded LOIC." except: pass f = open('download.pcap') pcap = dpkt.pcap.Reader(f) findDownload(pcap)
这里直接使用书上提供的数据包进行测试:
解析Hive服务器上的IRC命令:
下面的代码主要用于检测僵尸网络流量中的IRC命令:
#!/usr/bin/python #coding=utf-8 import dpkt import socket def findHivemind(pcap): for (ts, buf) in pcap: try: eth = dpkt.ethernet.Ethernet(buf) ip = eth.data src = socket.inet_ntoa(ip.src) dst = socket.inet_ntoa(ip.dst) tcp = ip.data dport = tcp.dport sport = tcp.sport # 若目标端口为6667且含有“!lazor”指令,则确定是某个成员提交一个攻击指令 if dport == 6667: if '!lazor' in tcp.data.lower(): print '[!] DDoS Hivemind issued by: '+src print '[+] Target CMD: ' + tcp.data # 若源端口为6667且含有“!lazor”指令,则确定是服务器在向HIVE中的成员发布攻击的消息 if sport == 6667: if '!lazor' in tcp.data.lower(): print '[!] DDoS Hivemind issued to: '+src print '[+] Target CMD: ' + tcp.data except: pass f = open('hivemind.pcap') pcap = dpkt.pcap.Reader(f) findHivemind(pcap)
同样直接用案例的数据包来测试:
实时检测DDoS攻击:
主要通过设置检测不正常数据包数量的阈值来判断是否存在DDoS攻击。
#!/usr/bin/python #coding=utf-8 import dpkt import socket # 默认设置检测不正常数据包的数量的阈值为1000 THRESH = 1000 def findAttack(pcap): pktCount = {} for (ts, buf) in pcap: try: eth = dpkt.ethernet.Ethernet(buf) ip = eth.data src = socket.inet_ntoa(ip.src) dst = socket.inet_ntoa(ip.dst) tcp = ip.data dport = tcp.dport # 累计各个src地址对目标地址80端口访问的次数 if dport == 80: stream = src + ':' + dst if pktCount.has_key(stream): pktCount[stream] = pktCount[stream] + 1 else: pktCount[stream] = 1 except: pass for stream in pktCount: pktsSent = pktCount[stream] # 若超过设置检测的阈值,则判断为进行DDoS攻击 if pktsSent > THRESH: src = stream.split(':')[0] dst = stream.split(':')[1] print '[+] ' + src + ' attacked ' + dst + ' with ' + str(pktsSent) + ' pkts.' f = open('attack.pcap') pcap = dpkt.pcap.Reader(f) findAttack(pcap)
同样直接用案例的数据包来测试:
然后将前面的代码整合到一起:
#!/usr/bin/python #coding=utf-8 import dpkt import socket import optparse # 默认设置检测不正常数据包的数量的阈值为1000 THRESH = 1000 def findDownload(pcap): for (ts, buf) in pcap: try: eth = dpkt.ethernet.Ethernet(buf) ip = eth.data src = socket.inet_ntoa(ip.src) # 获取TCP数据 tcp = ip.data # 解析TCP中的上层协议HTTP的请求 http = dpkt.http.Request(tcp.data) # 若是GET方法,且请求行中包含“.zip”和“loic”字样则判断为下载LOIC if http.method == 'GET': uri = http.uri.lower() if '.zip' in uri and 'loic' in uri: print "[!] " + src + " Downloaded LOIC." except: pass def findHivemind(pcap): for (ts, buf) in pcap: try: eth = dpkt.ethernet.Ethernet(buf) ip = eth.data src = socket.inet_ntoa(ip.src) dst = socket.inet_ntoa(ip.dst) tcp = ip.data dport = tcp.dport sport = tcp.sport # 若目标端口为6667且含有“!lazor”指令,则确定是某个成员提交一个攻击指令 if dport == 6667: if '!lazor' in tcp.data.lower(): print '[!] DDoS Hivemind issued by: '+src print '[+] Target CMD: ' + tcp.data # 若源端口为6667且含有“!lazor”指令,则确定是服务器在向HIVE中的成员发布攻击的消息 if sport == 6667: if '!lazor' in tcp.data.lower(): print '[!] DDoS Hivemind issued to: '+src print '[+] Target CMD: ' + tcp.data except: pass def findAttack(pcap): pktCount = {} for (ts, buf) in pcap: try: eth = dpkt.ethernet.Ethernet(buf) ip = eth.data src = socket.inet_ntoa(ip.src) dst = socket.inet_ntoa(ip.dst) tcp = ip.data dport = tcp.dport # 累计各个src地址对目标地址80端口访问的次数 if dport == 80: stream = src + ':' + dst if pktCount.has_key(stream): pktCount[stream] = pktCount[stream] + 1 else: pktCount[stream] = 1 except: pass for stream in pktCount: pktsSent = pktCount[stream] # 若超过设置检测的阈值,则判断为进行DDoS攻击 if pktsSent > THRESH: src = stream.split(':')[0] dst = stream.split(':')[1] print '[+] ' + src + ' attacked ' + dst + ' with ' + str(pktsSent) + ' pkts.' def main(): parser = optparse.OptionParser("[*]Usage python findDDoS.py -p <pcap file> -t <thresh>") parser.add_option('-p', dest='pcapFile', type='string', help='specify pcap filename') parser.add_option('-t', dest='thresh', type='int', help='specify threshold count ') (options, args) = parser.parse_args() if options.pcapFile == None: print parser.usage exit(0) if options.thresh != None: THRESH = options.thresh pcapFile = options.pcapFile # 这里的pcap文件解析只能调用一次,注释掉另行修改 # f = open(pcapFile) # pcap = dpkt.pcap.Reader(f) # findDownload(pcap) # findHivemind(pcap) # findAttack(pcap) with open(pcapFile, 'r') as f: pcap = dpkt.pcap.Reader(f) findDownload(pcap) with open(pcapFile, 'r') as f: pcap = dpkt.pcap.Reader(f) findHivemind(pcap) with open(pcapFile, 'r') as f: pcap = dpkt.pcap.Reader(f) findAttack(pcap) if __name__ == '__main__': main()
由于这部分作者没有给示例数据包,那就自己来合并上述几个pcap文件,使用命令:
mergecap -a -F pcap -w traffic.pcap download.pcap hivemind.pcap attack.pcap
-a参数指定按照命令顺序来合并各个pcap文件(不添加-a参数则默认按照时间的顺序合并),-F参数指定生成的文件类型,-w参数指定生成的pcap文件。
运行结果:
3、H.D.Moore是如何解决五角大楼的麻烦的:
理解TTL字段:
TTL即time-to-live,由8比特组成,可以用来确定在到达目的地之前数据包经过了几跳。当计算机发送一个IP数据包时会设置TTL字段为数据包在到达目的地之前所应经过的中继跳转的上限值,数据包每经过一个路由设备,TTL值就自减一,若减至0还未到目的地,路由器会丢弃该数据包以防止无限路由循环。
Nmap进行伪装扫描时,伪造数据包的TTL值是没有经过计算的,因而可以利用TTL值来分析所有来自Nmap扫描的数据包,对于每个被记录为Nmap扫描的源地址,发送一个ICMP数据包来确定源地址与目标机器之间隔了几跳,从而来辨别真正的扫描源。
Nmap的-D参数实现伪造源地址扫描:nmap 192.168.220.128 -D 8.8.8.8
Wireshark抓包分析,加上过滤器的条件“ip.addr==8.8.8.8”,发现确实是有用伪造源地址进行扫描:
点击各个数据包查看TTL值:
可以看到默认扫描的Nmap扫描,其ttl值是随机的。
添加-ttl参数指定值为13之后,可以看到发送的数据包的ttl值都为13:
用Scapy解析TTL字段的值:
这里使用Scapy库来获取源地址IP及其TTL值。
#!/usr/bin/python #coding=utf-8 from scapy.all import * # 检查数据包的IP层,提取出源IP和TTL字段的值 def testTTL(pkt): try: if pkt.haslayer(IP): ipsrc = pkt.getlayer(IP).src ttl = str(pkt.ttl) print "[+] Pkt Received From: " + ipsrc + " with TTL: " + ttl except: pass def main(): sniff(prn=testTTL, store=0) if __name__ == '__main__': main()
运行脚本监听后,启动Nmap伪造源地址扫描即可看到如下结果:
接着添加checkTTL()函数,主要实现对比TTL值进行源地址真伪判断:
#!/usr/bin/python #coding=utf-8 from scapy.all import * import time import optparse # 为避免IPy库中的IP类与Scapy库中的IP类冲突,重命名为IPTEST类 from IPy import IP as IPTEST ttlValues = {} THRESH = 5 # 检查数据包的IP层,提取出源IP和TTL字段的值 def testTTL(pkt): try: if pkt.haslayer(IP): ipsrc = pkt.getlayer(IP).src ttl = str(pkt.ttl) checkTTL(ipsrc, ttl) except: pass def checkTTL(ipsrc, ttl): # 判断是否是内网私有地址 if IPTEST(ipsrc).iptype() == 'PRIVATE': return # 判断是否出现过该源地址,若没有则构建一个发往源地址的ICMP包,并记录回应数据包中的TTL值 if not ttlValues.has_key(ipsrc): pkt = sr1(IP(dst=ipsrc) / ICMP(), retry=0, timeout=1, verbose=0) ttlValues[ipsrc] = pkt.ttl # 若两个TTL值之差大于阈值,则认为是伪造的源地址 if abs(int(ttl) - int(ttlValues[ipsrc])) > THRESH: print '\n[!] Detected Possible Spoofed Packet From: ' + ipsrc print '[!] TTL: ' + ttl + ', Actual TTL: ' + str(ttlValues[ipsrc]) def main(): parser = optparse.OptionParser("[*]Usage python spoofDetect.py -i <interface> -t <thresh>") parser.add_option('-i', dest='iface', type='string', help='specify network interface') parser.add_option('-t', dest='thresh', type='int', help='specify threshold count ') (options, args) = parser.parse_args() if options.iface == None: conf.iface = 'eth0' else: conf.iface = options.iface if options.thresh != None: THRESH = options.thresh else: THRESH = 5 sniff(prn=testTTL, store=0) if __name__ == '__main__': main()
运行脚本监听后,启动Nmap伪造源地址扫描即可看到如下结果:
4、“风暴”(Storm)的fast-flux和Conficker的domain-flux:
你的DNS知道一些不为你所知的吗?
下面用nslookup命令来进行一次域名查询:
Wireshark抓包如下:
可以看到客户端发送DNSQR请求包,服务器发送DNSRR响应包。
使用Scapy解析DNS流量:
一个DNSQR包含有查询的名称qname、查询的类型qtype、查询的类别qclass。
一个DNSRR包含有资源记录名名称rrname、类型type、资源记录类别rtype、TTL等等。
用Scapy找出fast-flux流量:
解析pcap文件中所有含DNSRR的数据包,提取分别含有查询的域名和对应的IP的rrname和rdata变量,然后建立一个索引字典并对字典中未出现的IP添加到数组中。
#!/usr/bin/python #coding=utf-8 from scapy.all import * dnsRecords = {} def handlePkt(pkt): # 判断是否含有DNSRR if pkt.haslayer(DNSRR): rrname = pkt.getlayer(DNSRR).rrname rdata = pkt.getlayer(DNSRR).rdata if dnsRecords.has_key(rrname): if rdata not in dnsRecords[rrname]: dnsRecords[rrname].append(rdata) else: dnsRecords[rrname] = [] dnsRecords[rrname].append(rdata) def main(): pkts = rdpcap('fastFlux.pcap') for pkt in pkts: handlePkt(pkt) for item in dnsRecords: print "[+] " + item + " has " + str(len(dnsRecords[item])) + " unique IPs." # for i in dnsRecords[item]: # print "[*] " + i # print if __name__ == '__main__': main()