《Violent Python》第四章Network Traffic Analysis with Py

  • A+
所属分类:WooYun-Zone

crown丶prince (我用双手成就你的梦想) 《Violent Python》第四章Network Traffic Analysis with Py | 2015-10-16 19:32

连载介绍信息:http://zone.wooyun.org/content/23138
原作者:Chris Katsaropoulos
第一译者:@草帽小子-DJ
第二译者:crown丶prince

第四章:网络流量分析
本章内容 :
1.网络协议流量定位地理位置
2.发现恶意的DDos工具
3.找到隐藏的网络扫描
4.分析Storm的Fast流量和Conficker蠕虫的Domain流量
5.理解TCP序列预测攻击
6.手工发包挫败入侵检测系统

比起被限制在单独的维度中,武术更应该成为我们的生活方式,我们的理念,我们对孩子的教育,我们投入的工作,我们建立的关系网,我们每天所做的选择的延伸。
                                                           —Daniele Bolelli 第四度卫冕黑带功夫秀

简介:极光行动以及如何明显的被避免

2010年1月14日,美国了解到一次针对Google,Adode和其他30多个全球100强公司的协调的,复杂的并且持久性的电脑攻击。这次攻击被称为极光行动,在受感染的机器上发现了一个文件夹,这次攻击使用了一个新的exploit,以前没有被发现。尽管微软知道这个漏洞的存在,但它错误的假定没有人知道这个漏洞,所以不存在这种攻击的检测机制。为了攻击他们的受害者,攻击者通过发送一封给受害者包含恶意的javascript脚本并连接到恶意网站的邮件发起攻击。当用户点击该链接他们就会下载一个恶意软件并返回一个控制命令行到中国的服务器上。在哪里,攻击者利用他们新获得权限的电脑寻找在受害者系统上存储的私人信息。

攻击很明显的出现了但是几个月未被发现,并成功的渗透了100强公司的代码库。甚至是基本的网络检测软件也能确认这次行为,为什么一个美国100强公司有几个用户连接到特定的台湾站点然后再次转到特定的中国服务器上?一个可视化的地图显示用户连接台湾和中国具有显著的频率可以允许网络管理员调查这次攻击,并在信息丢失前停止它。

在下面的章节中我们将研究利用Python分析不同的攻击,为了快速分析大量的不同的数据点。让我们开始通过建立一个脚本可视化分析流量来开始调查,那些受极光行动危害的100强管理员用过的方法。

IP流量头去哪了?---一个Python的回答
首先我们必须知道怎样将网络IP地址和物理位置相关联起来。为此,我们将依赖一个免费的数据库,MaxMind,MaxMind提供了一些精确的商业产品,他的开源GeoLiteCity数据库在http://www.maxmind.com/app/geolitecity可获得,为我们提供了足够的精确度从IP地址到物理地址。一旦数据库被下载,我们需要解压它并把它移动到其他位置,如/opt/Geoip/Gro.dat。

analyst# wget http://geolite.maxmind.com/download/geoip/database/
    GeoLiteCity.dat.gz
--2012-03-17 09:02:20-- http://geolite.maxmind.com/download/geoip/
    database/GeoLiteCity.dat.gz
Resolving geolite.maxmind.com... 174.36.207.186
Connecting to geolite.maxmind.com|174.36.207.186|:80... connected.
HTTP request sent, awaiting response... 200 OK
Length: 9866567 (9.4M) [text/plain]
Saving to: 'GeoLiteCity.dat.gz'
100%[================================================
====================================================
==================================================>]
    9,866,567 724K/s in 15s k
2012-03-17 09:02:36 (664 KB/s) – 'GeoLiteCity.dat.gz' saved
[9866567/9866567]
analyst#gunzip GeoLiteCity.dat.gz
analyst#mkdir /opt/GeoIP
analyst#mv GeoLiteCity.dat /opt/GeoIP/Geo.dat

利用我们的GeoIP数据库,我们可以关联一个IP地址到国家,邮政代码,城市名和一般的经纬度坐标。所有的这一切将在IP力量分析中用到。

使用PyGeoIP关联IP地址到物理地址

Jennifer Ennis制作了一个纯Python模块用来查询GeoLiteCity数据库。她的模块能从http://code.google.com/p/pygeoip/下载,安装并导入到我们的Python脚本中。注意,我们将首先实例化一个GeoIP类,用本地的GeoIP的位置。接下来我们将为特殊的记录指定IP地址查询数据库。它将返回一个记录包含城市(city),地区名(region_name),邮编(postal_code),国家(country_name),经纬度(latitude and longitude)以及其他的确认信息。

import pygeoip
gi = pygeoip.GeoIP('/opt/GeoIP/GeoIP.dat')
def printRecord(tgt):
    rec = gi.record_by_addr(tgt)
    city = rec['city']
    region = rec['region_name']
    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))
tgt = '173.255.226.98'
printRecord(tgt)

运行我们的脚本,我们可以看到它产生输出显示目标IP的物理位置。现在我们可以将IP地址和物理位置关联在一起,让我们开始编写我们的分析脚本。

analyst# python printGeo.py
[*] Target: 173.255.226.98 Geo-located.
[+] Jersey City, NJ, United States
[+] Latitude: 40.7245, Longitude: −74.0621

使用Dpkt解析数据包
在下面的章节中,我们将主要使用Scapy数据包操作工具来分析和制作数据包。Scapy提供了强大的功能,新手往往会发现在Windows或者Mac OS X系统上安装非常困难,相比之下,Dpkt则很简单,可以从http://code.google.com/p/dpkt/下载安装。两个都提供类似的功能,但是在工具集中它会比较有用。Dug Song最初创建Dpkt之后,Jon Oberheide增加了许多额外的功能用来解析不同的协议,如FTP,SCTP,BPG,IPv6,H.225。

例如,让我们假设一下我们捕获并记录了一个我们想要分析的网络数据包为pcap格式。Dpkt允许我们遍历每一个捕获的数据包并检查每一个协议层。在这个例子中,虽然我们只是简单的读取先前捕获的PCAP数据包,我们可以很容易的使用pypcap分析流量。可以从http://code.google.com/p/pypcap/下载。为了读取一个pcap文件,我们实例化文件,创建一个pcap.reader类对象,然后通过我们的对象函数printPcap()。这个对象pcap包含了一个数组,记录着时间戳和数据包,[timestamp, packet]。我们可以把每个数据包分为以太层和IP层。注意,这里要使用异常处理,因为我们可能捕获到第二层帧,不包含IP层,这有可能抛出一个异常。在这种情况下,我们使用异常处理捕获异常并继续下一个数据包。我们使用socket库解析IP地址。最后我们打印每个数据包的源地址和目标地址。

import dpkt
import socket

def printPcap(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)
            print('[+] Src: ' + src + ' --> Dst: ' + dst)
        except:
            pass

def main():
    f = open('data.pcap')
    pcap = dpkt.pcap.Reader(f)
printPcap(pcap)

if __name__ == '__main__':
    main()

运行该脚本,我们可以看到源地址和目标地址打印在屏幕上。这为我们提供了一定程度的分析,现在让我们使用我们先前的脚本关联IP地址和物理地址。

analyst# python printDirection.py
[+] Src: 110.8.88.36 --> Dst: 188.39.7.79
[+] Src: 28.38.166.8 --> Dst: 21.133.59.224
[+] Src: 153.117.22.211 --> Dst: 138.88.201.132
[+] Src: 1.103.102.104 --> Dst: 5.246.3.148
[+] Src: 166.123.95.157 --> Dst: 219.173.149.77
[+] Src: 8.155.194.116 --> Dst: 215.60.119.128
[+] Src: 133.115.139.226 --> Dst: 137.153.2.196
[+] Src: 217.30.118.1 --> Dst: 63.77.163.212
[+] Src: 57.70.59.157 --> Dst: 89.233.181.180

改善我们的脚本,让我们添加一个额外的函数retGeoStr(),通过IP地址返回物理地址。为此,我们将简单的分解城市和3位数的国家代码并将他们打印到屏幕上。如果函数抛出异常,我们将返回消息表示该地址未注册。这种异常是地址不在GeoIP数据库中或者是局域网IP地址,如192.168.1.3。

# coding=UTF-8
import dpkt
import socket
import pygeoip
import optparse

gi = pygeoip.GeoIP('/opt/GeoIP/GeoIP.dat')

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 as e:
        return 'Unregistered'

def printPcap(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)
            print('[+] Src: ' + src + ' --> Dst: ' + dst)
            print('[+] Src: ' + retGeoStr(src) + ' --> Dst: ' + retGeoStr(dst))
        except:
            pass

def main():
    parser = optparse.OptionParser('usage%prog -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()

运行我们的脚本,我们可以看到我们的数据包有前往韩国,伦敦,日本甚至是澳大利亚的。这为我们提供了强大的分析工具。然而,Google地球可能会提供更好的方法来显示相同的信息。

analyst# python geoPrint.py -p geotest.pcap
[+] Src: 110.8.88.36 --> Dst: 188.39.7.79
[+] Src: KOR --> Dst: London, GBR
[+] Src: 28.38.166.8 --> Dst: 21.133.59.224
[+] Src: Columbus, USA --> Dst: Columbus, USA
[+] Src: 153.117.22.211 --> Dst: 138.88.201.132
[+] Src: Wichita, USA --> Dst: Hollywood, USA
[+] Src: 1.103.102.104 --> Dst: 5.246.3.148
[+] Src: KOR --> Dst: Unregistered
[+] Src: 166.123.95.157 --> Dst: 219.173.149.77
[+] Src: Washington, USA --> Dst: Kawabe, JPN
[+] Src: 8.155.194.116 --> Dst: 215.60.119.128
[+] Src: USA --> Dst: Columbus, USA
[+] Src: 133.115.139.226 --> Dst: 137.153.2.196
[+] Src: JPN --> Dst: Tokyo, JPN
[+] Src: 217.30.118.1 --> Dst: 63.77.163.212
[+] Src: Edinburgh, GBR --> Dst: USA
[+] Src: 57.70.59.157 --> Dst: 89.233.181.180
[+] Src: Endeavour Hills, AUS --> Dst: Prague, CZE

使用Python建立Google地图
Google地球提供了一个虚拟地球仪,地图,地理信息,显示在专门的视图上。虽然是专门的,但Google地球却可以很容易的集成定制或者在全球追踪。创建一个扩展名为KML的文本文件,允许用户整合各种地方标识到Google地球中。KML文件包含了一个特定的XML结构,就像下面我们展示的那样。在这里,我们展示了如何在地图上使用名字和具体坐标绘制具体的位置标记。我们已经有了IP地址,地点的经纬度,这应该很容易集成到我们现有的脚本中生成KML文件。

<?xml version="1.0" encoding="UTF-8"?>
<kml xmlns="http://www.opengis.net/kml/2.2">
<Document>
<Placemark>
<name>93.170.52.30</name>
<Point>
<coordinates>5.750000,52.500000</coordinates>
</Point>
</Placemark>
<Placemark>
<name>208.73.210.87</name>
<Point>
<coordinates>-122.393300,37.769700</coordinates>
</Point>
</Placemark>
</Document>
</kml>

让我们快速建立一个函数retKML(),将IP作为输入返回一个特殊的KML结构。请注意,首先我们要解决的是使用pygeoip获得IP地址的经纬度。然后我们可以为这个地方建立我们的KML标记。如果我们遇到异常,例如“location not found,”,将返回空字符串。

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 Exception, e:
        return ''

整合所有的功能到我们原始的脚本。我们现在添加特定的KML头和尾。对于每一个数据包,我们创建源地址和目标地址的KML标记,并在地图上绘制。这样就产生了一个美丽的网络流量可视化图。想想,所有扩展这些的方法都是有用的。你可能希望用不同的图片标记不同类型的流量,特定的源地址和目的地址TCP端口(比如说web80端口和25邮件端口)。可以参考Google的KML文档在网站:
https://developers.google.com/kml/documentation/并想想我们扩展我们可视化视图的目的。

# coding=UTF-8
import dpkt
import socket
import pygeoip
import optparse
gi = pygeoip.GeoIP('/opt/GeoIP/GeoIP.dat')

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 Exception, e:
        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%prog -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)

if __name__ == '__main__':
    main()

运行我们的脚本,我们将输出内容到KML文件中,用Google地球打开这个文件,我们可以看到我们数据包的源地址和目的地。在下一节中,我们将使用我们的分析技能侦查Anonymous组织在全球的威胁。

匿名真的是匿名了么?分析LOIC流量
2010年12月,荷兰警方逮捕了一名青少年参与分布式拒绝服务攻击一些反对维基解密的公司。不到一个月,FBI发处了40多份搜查令,警方逮捕了同样的五人。松散的黑客组织Anonymous下载并使用LOIC进行分布式拒绝服务攻击犯罪。

LOIC发送大量的TCP和UDP流量洪水攻击目标。一个单义的LOIC实例对目标消耗的资源很小,然而,当成千上万的人同时使用时他们有能力快速耗尽目标资源。

LOIC提供两种操作模式,第一中模式中,用户可以输入目标地址,第二种模式称为HIVEMIND,用户连接LOIC到一个目标的IRC服务将进行自动攻击。

使用Dpkt找到谁在下载LOIC

在进行操作时,Anonymous成员发布了一个问题文档,关于LOIC常见的问题的回答。常见为问题有:使用LOIC我们会被逮捕吗?可能性几乎为零。只要说是中了病毒或者干脆否认使用了他,在这一节中,让我们通过良好的分析数据包的知识并编写工具分析谁下载和使用了LOIC工具。

互联网上多个源提供LOIC的下载,一些更为可信。可以从sourceforge主机下载http://sourceforge.net/projects/loic/,让我们从这下载,下载前,打开tcpdump会话,过滤80端口,并打印结果,你可以看到一下结果。

analyst# tcpdump –i eth0 –A 'port 80'
17:36:06.442645 IP attack.61752 > downloads.sourceforge.net.http:
    Flags [P.], seq 1:828, ack 1, win 65535, options [nop,nop,TS val
    488571053 ecr 3676471943], length [email protected]@........".;.8.P.KC.T
.c................."
..GET /project/loic/loic/loic-1.0.7/LOIC 1.0.7.42binary.zip
    ?r=http%3A%2F%2Fsourceforge.net%2Fprojects%2Floic%2F&ts=1330821290
HTTP/1.1
Host: downloads.sourceforge.net
User-Agent: Mozilla/5.0 (Macintosh; Intel Mac OS X 10_7_3)
    AppleWebKit/534.53.11 (KHTML, like Gecko) Version/5.1.3
Safari/534.53.10

第一部分我们是发现LOIC工具,我们将编写一个Python脚本来解析HTTP流量,审查HTTP的GET头是否有LOIC的ZIP二进制。为此,我们将使用Dpkt库。为了检查HTTP流量,我们必须提取以太网协议,IP协议和TCP协议。最后是在TCP协议之上的HTTP协议。如果HTTP层用GET方法,我们解析特定的URL的GET请求。如果URl包含.zip和LOIC在名称中,我们打印消息在屏幕上,显示下载LOIC的IP。折可以帮助聪明的管理员证明用户在下载LOIC而不是因为病毒感染。接合第三章的下载法庭取证分析,我们可以确认用户下载了LOIC工具。

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 = ip.data
            http = dpkt.http.Request(tcp.data)
            if http.method == 'GET':
                uri = http.uri.lower()
                if '.zip' in uri and 'loic' in uri:
                    print('[!] ' + src + ' Downloaded LOIC.')
        except:
            pass

f = open('LOIC.pcap')
pcap = dpkt.pcap.Reader(f)
findDownload(pcap)

运行该脚本,我们可以看到已经有用户下载了LOIC工具。

analyst# python findDownload.py
[!] 192.168.1.3 Downloaded LOIC.
[!] 192.168.1.5 Downloaded LOIC.
[!] 192.168.1.7 Downloaded LOIC.
[!] 192.168.1.9 Downloaded LOIC.


解析HIVE模式的IRC命令

简单的下载LOIC工具不一定的覅违法的。然而,连接到Anonymous的HIVE并启动分布式拒绝服务攻击进行攻击确实违反了几个州的法律。因为Anonymous是一个松散的志同道合的人而不是由个人领导的黑客组织。任何人都可以建议对目标发起攻击。为了开始发动一次攻击,Anonymous成员登陆到一个特定的IRC服务器并发送攻击指令。例如!lazor targetip=66.211.169.66 message=test_test port=80 method=tcp wait=false random=true start。任何用LOIC的HIVEMIND模式连接到IRC的成员都能立即开始攻击目标。在这种情况下,可以指定任何攻击目标。

在tcpdump中检查具体的攻击信息流量,我们可以看到特定的用户anonOps发送了一个开始攻击命令。接下来,IRC服务器发送发送命令到连接的LOIC客户端上开始攻击。想像一下在一个很长的包含几个小时或者几天的网络流量的pcap文件中找到几个特定的数据包。

analyst# sudo tcpdump -i eth0 -A 'port 6667'
08:39:47.968991 IP anonOps.59092 > ircServer.ircd: Flags [P.], seq
    3112239490:3112239600, ack 110628, win 65535, options [nop,nop,TS
    val 437994780 ecr 246181], length 110
E...5<@[email protected]_..._............$....3......
..E.....TOPIC #LOIC:!lazor targetip=66.211.169.66 message=test_test
    port=80 method=tcp wait=false random=true start
08:39:47.970719 IP ircServer.ircd > loic-client.59092: Flags [P.],
    seq 1:139, ack 110, win 453, options [nop,nop,TS val 260262 ecr
    437994780], length 138
E....&@[email protected]_..._........$.........k.....
......E.:[email protected] TOPIC #loic:!lazor targetip=66.211.169.66
message=test_test port=80 method=tcp wait=false random=true start

在大多数情况下,IRC服务使用的是TCP 6667端口,消息到IRC服务器的目的地至是TCP的6667端口,从IRC返回的消息的源地址端口应该是TCP的6667端口。让我们利用这些知识来编写我们的HIVEMIND解析函数findHivemind()。这一次,我们提取以太网协议,IP协议和TCP协议。提取TCP协议后,我们在探究特定的源和目的端口。如果看到命令!lazor带有目的端口6667,我们就可以确认成员发送了攻击命令。如果我们看到!lazor带有源目的地端口6667,我们就可以确定服务器发送了成员攻击命令。

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
            if dport == 6667:
                if '!lazor' in tcp.data.lower():
                    print('[!] DDoS Hivemind issued by: '+src)
                    print('[+] Target CMD: ' + tcp.data)
            if sport == 6667:
                if '!lazor' in tcp.data.lower():
                    print('[!] DDoS Hivemind issued to: '+src)
                    print('[+] Target CMD: ' + tcp.data)
        except:
            pass

识别正在进行的DDos攻击
有了定位下载LOIC工具和发现HIVE命令的功能,最后一项任务是:识别正在进行的DDos攻击。当一个用户开始了一个LOIC攻击,它将发送大量的TCP数据包给目标主机。这些数据包,接合从HIVE来的集体的数据包基本耗尽了目标主机的资源。我们开始一个tcpdump会话看着每0.00005秒发送一个小的数据包。这种行为不断的重复直到攻击结束。注意,目标无法相应,每次只就收5个数据包。

analyst# tcpdump –i eth0 'port 80'
06:39:26.090870 IP loic-attacker.1182 >loic-target.www: Flags [P.], seq
336:348, ack 1, win
64240, length 12
06:39:26.090976 IP loic-attacker.1186 >loic-target.www: Flags [P.], seq
336:348, ack 1, win
64240, length 12
06:39:26.090981 IP loic-attacker.1185 >loic-target.www: Flags [P.], seq
301:313, ack 1, win
64240, length 12
06:39:26.091036 IP loic-target.www > loic-attacker.1185: Flags [.], ack
313, win 14600, lengt
h 0
06:39:26.091134 IP loic-attacker.1189 >loic-target.www: Flags [P.], seq
336:348, ack 1, win
64240, length 12
06:39:26.091140 IP loic-attacker.1181 >loic-target.www: Flags [P.], seq
336:348, ack 1, win
64240, length 12
06:39:26.091142 IP loic-attacker.1180 >loic-target.www: Flags [P.], seq
336:348, ack 1, win
64240, length 12
06:39:26.091225 IP loic-attacker.1184 >loic-target.www: Flags [P.], seq
336:348, ack 1, win
<.. REPEATS 1000x TIMES..>

让我们快速编写一个发现正在进行DDos攻击的函数。为了发现一个攻击,我们将设置一个数据包阀值。如果一个用户到特定地址的的数据包数量超过该阀值,这表明我们将把它当做一个攻击做进一步调查。但是,这并不能确定用户发起了攻击。然而,当用户下载了LOIC工具,随后接受了HIVE指令,然后是实际的攻击,这足以提供证据用户参与了一次匿名的DDos攻击。

import dpkt
import socket

THRESH = 10000
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
            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]
        if pktsSent > THRESH:
            src = stream.split(':')[0]
            dst = stream.split(':')[1]
            print('[+] '+src+' attacked '+dst+' with ' + str(pktsSent) + ' pkts.')

将我们的代码放在一起并加一些选项解析,我们的脚本现在可以检测下载,监听HIVE指令并检测攻击。

# coding=UTF-8
import dpkt
import socket
import optparse

def findDownload(pcap):
    for (ts, buf) in pcap:
        try:
            eth = dpkt.ethernet.Ethernet(buf)
            ip = eth.data
            src = socket.inet_ntoa(ip.src)
            tcp = ip.data
            http = dpkt.http.Request(tcp.data)
            if http.method == 'GET':
                uri = http.uri.lower()
                if '.zip' in uri and 'loic' in uri:
                    print('[!] ' + src + ' Downloaded LOIC.')
        except:
            pass

THRESH = 10000
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
            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]
        if pktsSent > THRESH:
            src = stream.split(':')[0]
            dst = stream.split(':')[1]
            print('[+] '+src+' attacked '+dst+' with ' + str(pktsSent) + ' pkts.')

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
            if dport == 6667:
                if '!lazor' in tcp.data.lower():
                    print('[!] DDoS Hivemind issued by: '+src)
                    print('[+] Target CMD: ' + tcp.data)
            if sport == 6667:
                if '!lazor' in tcp.data.lower():
                    print('[!] DDoS Hivemind issued to: '+src)
                    print('[+] Target CMD: ' + tcp.data)
        except:
            pass

def main():
    parser = optparse.OptionParser("usage%prog -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
    f = open(pcapFile)
    pcap = dpkt.pcap.Reader(f)
    findDownload(pcap)
    findHivemind(pcap)
    findAttack(pcap)

if __name__ == '__main__':
    main()

运行代码,我们可以看到结果。四个用户下载了工具。接着,不同的用户发送攻击命令给另外两个连接着的攻击者,最后,这两个攻击者实际参与了攻击。因此现在的脚本识别整个DDos攻击行动。虽然入侵检测系统可以检测类似的活动,但编写一个自定义脚本做的更好。在下面的章节中,我们看看一个自定义脚本,一个七岁小孩编写的用来保护五角大楼的脚本。

analyst# python findDDoS.py –p traffic.pcap
[!] 192.168.1.3 Downloaded LOIC.
[!] 192.168.1.5 Downloaded LOIC.
[!] 192.168.1.7 Downloaded LOIC.
[!] 192.168.1.9 Downloaded LOIC.
[!] DDoS Hivemind issued by: 192.168.1.2
[+] Target CMD: TOPIC #LOIC:!lazor targetip=192.168.95.141
    message=test_test port=80 method=tcp wait=false random=true start
[!] DDoS Hivemind issued to: 192.168.1.3
[+] Target CMD: TOPIC #LOIC:!lazor targetip=192.168.95.141
    message=test_test port=80 method=tcp wait=false random=true start
[!] DDoS Hivemind issued to: 192.168.1.5
[+] Target CMD: TOPIC #LOIC:!lazor targetip=192.168.95.141
    message=test_test port=80 method=tcp wait=false random=true start
[+] 192.168.1.3 attacked 192.168.95.141 with 1000337 pkts.
[+] 192.168.1.5 attacked 192.168.95.141 with 4133000 pkts.


H. D. Moore怎样解决五角大楼的困境

1999年末,美国五角大楼的计算机网络面临着严重的危机。美国国防总部,五角大楼宣布正遭受着一系列的组织协调的复杂的攻击。最新发布的工具Nmap,使得任何人扫描网络的服务和漏洞变得更容易了。五角大楼担心一些攻击者使用Nmap识别五角大楼庞大的计算机网络的漏洞地图。

检测Nmap扫描很容易,关联攻击者的地址,然后找到物理地址。然而,攻击者在Nmap中使用高级选项,而不是从特定的攻击者地址发动扫描,其中包括似乎来自世界各地的扫描的诱饵。五角大楼专家很难分清时间扫描和诱饵扫描之间的关系。

当专家研究了大量的理论方法分许数据的记录,最后7岁的H.D.Moore,传奇框架Metasploit框架的创造者,给出了一个可行的解决方案。他建议使用所有进来的数据包的TTL字段。生存时间(TTL)字段用来确认一个IP数据包多跳可以到达目的地。数据包没通过一个路由器,路由器就减少一个TTL字段的值。Moore意识到这可能是确认扫描数据包来源的极好的方法。对于记录的每一个Nmap扫描的源地址,他发送了一个ICMP数据包确认和扫描机器之间的条数。然后用这个信息来区分是攻击者还是诱饵。显然,只有攻击者才有正确的TTL值,而诱饵没有正确的TTL值。他的方案可行!五角大楼要求Moore在1999的SANS会议上展示自己的工具和研究。Moore称他的工具为Nlog,因为它记录了Nmap的各种扫描信息。

在下面的章节中,我们将使用Python重建Moore的分析过程和创建他的工具Nlog。你会希望了解一个7岁少年十多年前的想法:简单,优雅的解决检测攻击的方案。

理解TTL字段

在编写脚本之前,我们来接是一下IP数据包的TTL字段。TTL字段包含8个bit,有效值0到255。当计算机发送一个IP数据包时,它设置TTL字段为可以到达目的地的最大跳,每个路由设备改变数据包的TTL字段值。如果TTL字段为零,路由器抛弃这个数据包防止无限循环路由。比如说,如果我ping地址8.8.8.8,初始化TTL为64它将返回TTL的值为53我们可以看到数据包穿过了11个路由设备。

target# ping –m 64 8.8.8.8
PING 8.8.8.8 (8.8.8.8) 56(84) bytes of data.
64 bytes from 8.8.8.8: icmp_seq=1 ttl=53 time=48.0 ms
64 bytes from 8.8.8.8: icmp_seq=2 ttl=53 time=49.7 ms
64 bytes from 8.8.8.8: icmp_seq=3 ttl=53 time=59.4 ms

引进诱饵扫描的是1.6版本,诱饵数据包的TTL不是随机的也不是正确的。未能正确计算TTL允许Moore确认这些数据包。显然,Nmap的代码从1999年得到显著的增长和发展,在最近的版本中,Nmap使用下面的算法随机的设置TTL。该算法随机的产生一个TTL,用户也能自己指定TTL的值。

/* Time to live */
if (ttl == -1) {
    myttl = (get_random_uint()% 23) + 37;
} else {
myttl = ttl;
}

为了运行一个Nmap诱饵扫描,我们在IP地址后面加上参数-D,在这种情况下,我们将使用地址8.8.8.8作为诱饵地址,此外,我们自己指定TTL的值为13,因此,下面我们用TTL为13的诱饵地址为8.8.8.8扫描192.168.1.7。
attacker$ nmap 192.168.1.7 -D 8.8.8.8 -ttl 13
Starting Nmap 5.51 (http://nmap.org) at 2012-03-04 14:54 MST
Nmap scan report for 192.168.1.7
Host is up (0.015s latency).
<..SNIPPED..>

在目标192.168.1.7上,我们在详细模式下打开tcpdump(-v),禁用名称解析(-nn),过滤特定的地址8.8.8.8(‘host 8.8.8.8’),我们看到Nmap成功的用TTL为13,诱饵地址为8.8.8.8发送了数据包。

target# tcpdump –i eth0 –v –nn 'host 8.8.8.8'
8.8.8.8.42936 > 192.168.1.7.6: Flags [S], cksum 0xcae7 (correct), seq
    690560664, win 3072, options [mss 1460], length 0
14:56:41.289989 IP (tos 0x0, ttl 13, id 1625, offset 0, flags [none],
    proto TCP (6), length 44)
    8.8.8.8.42936 > 192.168.1.7.1009: Flags [S], cksum 0xc6fc (correct),
    seq 690560664, win 3072, options [mss 1460], length 0
14:56:41.289996 IP (tos 0x0, ttl 13, id 16857, offset 0, flags
    [none], proto TCP (6), length 44)
    8.8.8.8.42936 > 192.168.1.7.1110: Flags [S], cksum 0xc697 (correct),
    seq 690560664, win 3072, options [mss 1460], length 0
14:56:41.290003 IP (tos 0x0, ttl 13, id 41154, offset 0, flags [none],
    proto TCP (6), length 44)
    8.8.8.8.42936 > 192.168.1.7.2601: Flags [S], cksum 0xc0c4 (correct),
    seq 690560664, win 3072, options [mss 1460], length 0
14:56:41.307069 IP (tos 0x0, ttl 13, id 63795, offset 0, flags [none],
proto TCP (6), length 44)


用Scapy解析TTL字段

让我们开始编写我们的脚本来打印源地址和数据包里面的TTL值。这一点上,在本章的剩余部分我们会使用Scapy库,你也可以简单的使用Dpkt库来编写这个代码。我们将建立一个函数testTTL()来嗅探每一个经过的数据包,检查数据包的IP层,抽取IP地址和TTL字段并打印字段在屏幕上。

from scapy.all import *
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()

运行我们的代码,我们看到我们已经从不同的地址收到几个带有不同的TTL的数据包。这些结果也包括来自8.8.8.8的TTL为13的诱饵扫描。我们知道TTL应该是64-13=51跳,我们可以认为有人伪造数据包。应该注意一点,LInux/Unix系统上通常初始TTL值为64,而Windows系统初始TTL值为128。为了我们脚本的目的,我们假设我们只解析来自Linux扫描的数据包,所以让我们增加一个函数来检查实际接受的TTL。

analyst# python printTTL.py
[+] Pkt Received From: 192.168.1.7 with TTL: 64
[+] Pkt Received From: 173.255.226.98 with TTL: 52
[+] Pkt Received From: 8.8.8.8 with TTL: 13
[+] Pkt Received From: 8.8.8.8 with TTL: 13
[+] Pkt Received From: 192.168.1.7 with TTL: 64
[+] Pkt Received From: 173.255.226.98 with TTL: 52
[+] Pkt Received From: 8.8.8.8 with TTL: 13

我们的函数checkTTL()需要一个IP源地址和对应的TTL值作为输入并输出TTL是否有效的信息。首先,让我们用一个条件与语句快速的消除死人IP地址的数据包(10.0.0.0–10.255.255.255, 172.16.0.0–172.31.255.255, 和192.168.0.0–192.168.255.255)。

为此,我们导入IPy库,为了避免和Scapy的IP类冲突,我们把它作为IPTEST,如果IPTEST(ipsrc).iptype()返回‘PRIVATE‘,我们变忽略检查这个数据包。

我们从相同的源地址收到了不少的独特的数据包,我们只需要检查源地址一次。如果先前我们没看到源地址,让我们构建一个目的地址与源地址相同的IP数据包。此外,我们将制作一个ICMP数据包与目的地址向回应。一旦目标地址回应,我们将TTL值放在字典中,通过IP源地址索引,我们再来检查实际收到的TTL值和原始数据包里面的TTL值。数据包可能会走不同的路线来到达目的地,造成TTL不同,然而,如果跳数的距离相差五跳,我们可以假定,它可能是一个欺骗性的TTL,并打印警告信息在屏幕上。

from IPy import IP as IPTEST
ttlValues = {}
THRESH = 5

def checkTTL(ipsrc, ttl):
    if IPTEST(ipsrc).iptype() == 'PRIVATE':
        return
    if not ttlValues.has_key(ipsrc):
        pkt = sr1(IP(dst=ipsrc) / ICMP(), retry=0, timeout=1, verbose=0)
        ttlValues[ipsrc] = pkt.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]))

我们添加一些选项解析来指定要监听的地址,然后通过一个选项来设定阀值来产生最终的代码。少于50行的代码,我们拥有是数十年前Moore为五角大楼困境的解决方案。

# coding=UTF-8
import time
import optparse
from scapy.all import *
from IPy import IP as IPTEST

ttlValues = {}
THRESH = 5

def checkTTL(ipsrc, ttl):
    if IPTEST(ipsrc).iptype() == 'PRIVATE':
        return
    if not ttlValues.has_key(ipsrc):
        pkt = sr1(IP(dst=ipsrc) / ICMP(), retry=0, timeout=1, verbose=0)
        ttlValues[ipsrc] = pkt.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 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():
    parser = optparse.OptionParser("usage%prog -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扫描,来自8.8.8.8的扫描。需要注意的是,我们的值产生于一个默认的Linux初始TTL值64,尽管RFC 1700推荐的默认TTL值是64,但是Windows系统还是将128作为TTL的默认初始值。此外,其他一些Unix变种的系统有着不同的TTL值。现在我们假定产生数据包的系统为Linux。

analyst# python spoofDetect.py –i eth0 –t 5
[!] Detected Possible Spoofed Packet From: 8.8.8.8
[!] TTL: 13, Actual TTL: 53
[!] Detected Possible Spoofed Packet From: 8.8.8.8
[!] TTL: 13, Actual TTL: 53
[!] Detected Possible Spoofed Packet From: 8.8.8.8
[!] TTL: 13, Actual TTL: 53
[!] Detected Possible Spoofed Packet From: 8.8.8.8
[!] TTL: 13, Actual TTL: 53
<..SNIPPED..>

分享到: